html2md-clean
Чистий, керований конвертер HTML → Markdown з видаленням «шуму» (реклама, попапи, навігація), розрахований на web-scraping та підготовку тексту для LLM.
Зміст
- Огляд
- Можливості
- Встановлення
- Швидкий старт
- CLI
- Приклади використання
- Публічний API
- Опції конвертації
- Опційні залежності
- Продуктивність
- Розробка
- Тестування
- Документація
- Ліцензія
Огляд
html2md-clean перетворює «брудний» HTML реальних вебсторінок на чистий Markdown.
На відміну від простих конвертерів, бібліотека спершу прибирає boilerplate —
навігацію, футери, банери, cookie-попапи, рекламні блоки — а вже потім рендерить
основний контент. Це робить її зручною для:
- web-scraping та побудови датасетів;
- підготовки тексту для RAG / LLM-пайплайнів (менше токенів шуму);
- архівування статей у читабельному Markdown.
Бібліотека працює на двох парсерах: Selectolax (швидкий, за замовчуванням, якщо
встановлений) та BeautifulSoup (fallback), схованих за єдиним Adapter-інтерфейсом.
Можливості
- 🧹 Очищення шуму за тегами, CSS-класами,
id,aria-hidden, inlinedisplay:none. - 🧠 Виділення основного контенту через link-density та content-density евристики.
- 🔗 Посилання: inline
[text](url)або reference-style[text][N], опційне очищення UTM/tracker-хвостів (courlan). - 📚 Цитати: варіант Markdown із виносками
[1],[2]+ список джерел. - 🧾 Метадані:
title, першийh1,description, лічильники слів/символів/заголовків/посилань. - 📅 Дати публікації/оновлення (опційно, через
htmldate). - 🩹 Лікування mojibake (
ftfy), NFKC-нормалізація Unicode, HTML-санітизація (nh3). - ⚡ Синхронний та асинхронний API, ліниво-генерований
raw_markdown. - 🎛️ Immutable-опції (
MarkdownOptions.with_overrides(...)) — безпечні для потоків.
Structural noise (v0.2)
- 🏷️ ARIA-landmarks як шум:
role=banner|navigation|contentinfo|complementary|search|dialog,<dialog>,<search>,[popover],[inert]— не залежить від назв класів (CSS-modules / Tailwind). - 🧩 Declarative Shadow DOM:
<template shadowrootmode>розгортається, а не видаляється. - 🖼️ Lazy-images:
src → data-src → data-lazy-src → data-original → srcset,<picture>,<noscript><img>; base64-плейсхолдери пропускаються. - 🎯 CSS-селектори як правила:
exclude_selectors,include_selectors(захист),unwrap_selectors. - 🔍 Explain-режим:
result.removed— що видалено, яким правилом, скільки символів (html2md --explain). - ⚖️ Паритет парсерів: Selectolax і BeautifulSoup дають однаковий
fit_markdown(єдина decision-functionNoiseRemover).
Agent-ready output (v0.3)
- 📇 YAML-frontmatter (
frontmatter=True):title, description, canonical_url, lang, author, image, site_name, published, updated, source_url, word_count, tokens_estimate, page_type, content_signal. - 🧬 JSON-LD / Microdata:
result.structured_data;append_json_ld_block→ Cloudflare-сумісний fenced ```json блок. - 🔢 Токени:
token_estimate/original_token_estimate, pluggabletoken_counter(tiktoken тощо),max_tokensзамістьmax_length. - ✂️ Heading-aware chunking:
result.chunks(max_tokens=…, overlap=…)→Chunk(text, breadcrumb "H1 > H2 > H3", level, tokens); fenced-код не ріжеться. - 🌐 ASGI-middleware
MarkdownNegotiationдля FastAPI/Starlette:Accept: text/markdownз q-values,Vary: Accept,x-markdown-tokens/x-original-tokens, проносContent-Signal, RFC 9457 Markdown-помилки (4xx/5xx іapplication/problem+json). - 🛡️ Політика:
respect_nosnippet(data-nosnippet),<link rel=alternate type=text/markdown>→result.markdown_alternate_url,Content-Signal→result.content_signal. - 📜
llms.txt:build_llms_txt(...)та CLIhtml2md llms-txt.
Page types & site templates (v0.4)
- 🗂️ Page-type класифікатор без ML (URL + HTML-сигнали: JSON-LD
@type,og:type, картки з цінами, thread-структури, code-блоки, форми) →result.page_type,page_type_confidence,page_type_signals; профілі екстракціїarticle / forum / product / listing / docs / service(page_type="auto", або явний). - 🏗️ Site-level template detection:
SiteProfile.learn(pages, threshold=0.6)— спільні блоки на N сторінках сайту за структурно-текстовими відбитками (без назв класів), JSON-серіалізація,options.with_site_profile(profile), CLIhtml2md learn-site. - 🎨 Output-профілі:
output_profile = gfm | commonmark | obsidian | mdx-safe;heading_style(atx/setext),bullet_marker,fence_char. - 🧮 Math: MathML / KaTeX / MathJax →
$…$/$$…$$(annotation encoding="application/x-tex"має пріоритет; базовий Presentation MathML → LaTeX без залежностей). - 🔢 Чистий код: зняття gutter-номерів рядків (
.line-numbers,td.linenos,.hljs-ln-numbers, префікси1 2 3). - 🦶 Футноти:
<sup><a href="#fn1">→[^1],<li id="fn1">→[^1]: …(GFM); дляcommonmark—[1].
Встановлення
Потрібен Python 3.11+.
pip install html2md-clean
Разом з опційними «батарейками» (дати, санітизація, encoding-fix, очищення URL):
pip install "html2md-clean[full]"
Базова збірка тягне лише selectolax (+pydantic). BeautifulSoup-бекенд
(force_beautifulsoup=True, CLI --force-bs4) — окремий extra:
pip install "html2md-clean[bs4]"
Якщо у вас уже є спарсене selectolax-дерево (Modest або Lexbor) — не парсьте двічі:
from selectolax.lexbor import LexborHTMLParser
from html2md_clean import MarkdownGenerator
tree = LexborHTMLParser(html)
result = MarkdownGenerator().generate_from_tree(tree, source_html=html) # copy=True: дерево не мутується
Встановлення з GitLab-репозиторію:
pip install git+https://gitlab.com/demoprogrammer/html2md-clean.git
Швидкий старт
0.2.0: пакет імпортується як
html2md_clean(старийimport markdownпрацює зDeprecationWarningдо 0.3.0).
from html2md_clean import MarkdownGenerator
html = """
<html><body>
<nav>меню сайту</nav>
<article>
<h1>Заголовок статті</h1>
<p>Це основний абзац із корисним текстом.</p>
</article>
<footer>© 2026</footer>
</body></html>
"""
generator = MarkdownGenerator()
result = generator.generate_from_html(html)
print(result.fit_markdown)
# # Заголовок статті
#
# Це основний абзац із корисним текстом.
print(result.word_count, result.h1)
Найкоротший варіант:
from html2md_clean import MarkdownGenerator
result = MarkdownGenerator.quick_generate(html)
print(result.markdown) # аліас для fit_markdown
CLI
Після встановлення доступна команда html2md:
# з файлу у stdout
html2md page.html
# зі stdin у файл
cat page.html | html2md -o page.md
# зберегти посилання (за замовчуванням вимкнено у CLI-профілі)
html2md page.html --include-links
# форсувати BeautifulSoup замість Selectolax
html2md page.html --force-bs4
# agent-ready: frontmatter + JSON-LD блок, обрізати до ~2000 токенів
html2md page.html --frontmatter --json-ld --max-tokens 2000 --source-url https://ex.com/page
# heading-aware chunks (JSON) по ~512 токенів
html2md page.html --chunks 512
# повний MarkdownResult як JSON (metadata, tokens, page_type, removed…)
html2md page.html --json --explain
# тип сторінки / діалект виводу
html2md page.html --page-type forum --profile obsidian
# шаблон сайту: навчити на кількох сторінках і застосувати
html2md learn-site pages/*.html -o site.json
html2md page.html --site-profile site.json
# llms.txt з набору конвертованих сторінок
html2md llms-txt --site https://ex.com --name "Example" out/*.md > llms.txt
Middleware Accept: text/markdown (FastAPI / Starlette)
from fastapi import FastAPI
from html2md_clean.middleware import MarkdownNegotiation
app = FastAPI()
app.add_middleware(MarkdownNegotiation, include_frontmatter=True, include_json_ld=True)
# curl -H "Accept: text/markdown" https://your.site/page → text/markdown; variant=CommonMark
Page types, chunks і site templates у коді
from html2md_clean import MarkdownGenerator, MarkdownOptions, SiteProfile
opts = MarkdownOptions(frontmatter=True, page_type="auto", output_profile="gfm")
result = MarkdownGenerator(opts).generate_from_html(html)
print(result.page_type, result.page_type_confidence) # 'forum' 0.81
print(result.render()) # frontmatter + markdown
for chunk in result.chunks(max_tokens=512, overlap=50):
print(chunk.context, chunk.tokens) # 'Title > Section', 480
profile = SiteProfile.learn(html_pages_of_same_site, threshold=0.6)
clean = MarkdownGenerator(opts.with_site_profile(profile)).generate_from_html(html)
Аналогічно можна запускати як модуль без встановлення:
python main_service.py page.html -o page.md
Приклади використання
Кастомні опції
from html2md_clean import MarkdownGenerator, MarkdownOptions
options = MarkdownOptions(
include_links=True,
include_images=True,
generate_citations=True,
max_length=50_000,
clean_urls=True, # прибрати utm_*, fbclid, gclid
base_url="https://example.com",
)
generator = MarkdownGenerator(options)
result = generator.generate_from_html(html)
print(result.markdown_with_citations)
for ref in result.references:
print(ref)
Reference-style посилання
from html2md_clean import MarkdownGenerator, MarkdownOptions
from html2md_clean.options import LinkStyle
options = MarkdownOptions(link_style=LinkStyle.REFERENCE)
result = MarkdownGenerator(options).generate_from_html(html)
Асинхронний виклик
import asyncio
from html2md_clean import generate_markdown_async
async def run():
result = await generate_markdown_async(html)
print(result.fit_markdown)
asyncio.run(run())
Витяг дат публікації (опційно)
from html2md_clean import MarkdownGenerator, MarkdownOptions
options = MarkdownOptions(extract_dates=True)
result = MarkdownGenerator(options).generate_from_html(html)
print(result.published_date, result.updated_date)
Фасад під web-scraping
from main_service import HTMLToMarkdownConverter
converter = HTMLToMarkdownConverter() # агресивніші дефолти під scraping
markdown = converter.convert(html)
Реальний приклад: налаштування MarkdownOptions під scraping блогу
Нижче — як це роблять «по-справжньому»: беремо HTML новинної/блогової сторінки, свідомо вмикаємо/вимикаємо потрібні опції та пояснюємо, навіщо кожна з них.
from html2md_clean import MarkdownGenerator, MarkdownOptions
from html2md_clean.options import LinkStyle, AGGRESSIVE_NOISE_CLASSES, COMMON_NOISE_PATTERNS
options = MarkdownOptions(
# --- виділення основного контенту ---
use_content_density=True, # шукати <article>/<main>, а не весь <body>
link_density_threshold=0.4, # жорсткіше відкидати меню/футери (більше посилань = шум)
min_content_words=40, # короткі блоки не вважати основним контентом
# --- очищення шуму ---
remove_ads=True,
noise_classes=AGGRESSIVE_NOISE_CLASSES, # + sidebar/related/comments/carousel
remove_display_none=True, # прибрати приховані inline-блоки
dynamic_noise_patterns=COMMON_NOISE_PATTERNS, # "5 хв тому", "Apply now", лічильники
# --- що лишаємо в Markdown ---
include_links=True,
include_images=True,
include_tables=True,
generate_citations=True, # зробити варіант із виносками [1], [2]
# --- посилання та URL ---
link_style=LinkStyle.REFERENCE, # у довгих статтях чистіше, ніж inline
normalize_urls=True,
base_url="https://example-blog.com", # відносні /article → абсолютні
clean_urls=True, # прибрати utm_*, fbclid, gclid
# --- нормалізація тексту ---
fix_text_encoding=True, # полікувати mojibake (потрібен ftfy)
normalize_unicode=True, # fullwidth/лігатури → ASCII (NFKC)
max_length=200_000, # довгі лонгріди не обрізати завчасно
)
generator = MarkdownGenerator(options)
result = generator.generate_from_html(html)
print(result.markdown_with_citations) # текст із [1], [2]
print("слів:", result.word_count, "| посилань:", result.rendered_link_count)
for ref in result.references:
print(ref) # {'id': 1, 'text': '...', 'url': '...'}
💡 Порада. Конфіг незмінний, тож зберігай один «базовий» профіль і роби точкові варіації через
with_overrides— без дублювання всього набору:BASE = MarkdownOptions(use_content_density=True, include_links=True) # для e-commerce-карток: коротший поріг довжини кандидата ecommerce = BASE.with_overrides(main_content_min_length=100, include_images=True) # для «сирого» дампу без фільтрації посилань raw_dump = BASE.with_overrides(include_links=False, use_content_density=False)
Публічний API
Імпорт: from html2md_clean import ...
| Об'єкт | Призначення |
|---|---|
MarkdownGenerator |
Головний генератор. generate_from_html(html) -> MarkdownResult, quick_generate(html), generate(source). |
MarkdownOptions |
Immutable-конфіг конвертації. Використовуй with_overrides(**kwargs). |
MarkdownResult |
Результат: fit_markdown, raw_markdown (лениво), markdown_with_citations, метадані та лічильники. |
generate_markdown_async(html, options=None) |
Асинхронна обгортка (thread executor). |
configure_logging(level=WARNING) |
Налаштування логера пакета. |
COMMON_NOISE_PATTERNS |
Готовий набір regex для dynamic_noise_patterns. |
Ключові поля MarkdownResult:
| Поле | Тип | Опис |
|---|---|---|
fit_markdown / markdown |
str |
Основний очищений Markdown. |
raw_markdown |
str |
Повний Markdown без агресивної фільтрації (ленива генерація). |
markdown_with_citations |
str |
Варіант із виносками [1], [2]. |
references |
list[dict] |
Список джерел для цитат. |
title, h1, description |
str |
Метадані сторінки. |
word_count, char_count, heading_count, link_count, image_count |
int |
Статистика. |
published_date, updated_date |
str | None |
Дати (лише з extract_dates=True). |
is_truncated |
bool |
Чи текст обрізаний до max_length. |
Опції конвертації
MarkdownOptions — єдине джерело правди для дефолтів. Найчастіші:
| Опція | Дефолт | Опис |
|---|---|---|
remove_nav / remove_header / remove_footer / remove_aside |
True |
Видалення відповідних семантичних блоків. |
remove_ads |
True |
Видалення за noise_classes / noise_ids. |
remove_hidden |
True |
Прибирати aria-hidden, HTML5 hidden, hidden-класи. |
include_links |
True |
Рендерити посилання. |
include_images |
False |
Рендерити зображення. |
include_tables / include_lists / include_code_blocks / include_blockquotes |
True |
Підтримка відповідних елементів. |
max_length |
100000 |
Максимальна довжина тексту. |
min_paragraph_length |
3 |
Мінімальна довжина абзацу. |
generate_citations |
False |
Генерувати markdown_with_citations. |
link_style |
INLINE |
INLINE або REFERENCE. |
clean_urls |
False |
Прибирати UTM/tracker-параметри (courlan). |
normalize_urls / base_url |
True / "" |
Абсолютизація відносних URL. |
use_content_density |
False |
Евристика виділення основного контенту. |
link_density_threshold |
0.5 |
Поріг link-density для відкидання меню/футерів. |
force_beautifulsoup |
False |
Форсувати BS4 замість Selectolax. |
Повний перелік — у html2md_clean/options.py та в документації.
Опції незмінні: створюй нові варіанти через with_overrides:
base = MarkdownOptions()
scraping = base.with_overrides(include_links=False, max_length=200_000)
Опційні залежності
Деякі можливості вмикаються встановленням додаткових пакетів (бібліотека gracefully деградує, якщо їх немає):
| Можливість | Опція | Пакет |
|---|---|---|
| Витяг дат | extract_dates |
htmldate |
| Санітизація HTML | sanitize_html |
nh3 |
| Лікування mojibake | fix_text_encoding |
ftfy |
| Очищення URL | clean_urls |
courlan |
| Boilerplate-екстракція | use_trafilatura_extraction |
trafilatura |
| Швидкий парсер | — (авто) | selectolax |
Продуктивність
- Selectolax використовується автоматично, якщо встановлений, і дає суттєвий приріст швидкості проти BeautifulSoup.
raw_markdownгенерується лениво — платиш за нього лише при доступі.MarkdownOptions— immutable, тож інстанси генератора безпечно шарити між потоками.
Бенчмарки — у теці test/ (benchmark.py, benchmark_html2md.py, test_benchmarks.py).
Розробка
git clone https://gitlab.com/demoprogrammer/html2md-clean.git
cd html2md-clean
python -m venv .venv && source .venv/bin/activate
pip install -e ".[full,dev]"
Лінтер (конфіг у ruff.toml):
ruff check .
ruff format --diff .
Тестування
pytest # весь набір
pytest -q test/test_generator.py
Конфіг у pytest.ini (asyncio_mode = auto). Набір містить unit-,
integration-, property- (Hypothesis) та stress-тести.
Документація
Документація збирається через MkDocs (Material theme):
pip install mkdocs mkdocs-material
mkdocs serve # локальний перегляд на http://127.0.0.1:8000
mkdocs build # статична збірка у site/
Джерела — у теці docs/, конфіг — mkdocs.yml.
Ліцензія
Розповсюджується за ліцензією MIT. Деталі — у файлі LICENSE.
Metadata
Release files for html2md-clean 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| html2md_clean-0.5.0.tar.gz | 123.2 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| html2md_clean-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 225.5 kB
Release files / html2md_clean-0.5.0.tar.gz
| Download URL | html2md_clean-0.5.0.tar.gz |
|---|---|
| Size | 123.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c0aefd10f026e05a0619e7e708731b3b6638f551c6ad18d4a2033aff120cb682
|
|
BLAKE2b-256 checksum How to use checksums |
d82f69a8ef5600f164b32a72ff2124b43e2dcbdd27d2e8d22fa3ab08917a7712
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.16
|
Release files / html2md_clean-0.5.0-py3-none-any.whl
| Download URL | html2md_clean-0.5.0-py3-none-any.whl |
|---|---|
| Size | 102.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b67f36d913ed4d4a5645ba2d40e5cb48d099e9b0682515a1d026de32d40a0b78
|
|
BLAKE2b-256 checksum How to use checksums |
f9177abb0721d25f61ec55cd02dd1fac61a3d99b942b6224940b6900b08379e5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.16
|
Security for html2md-clean 0.5.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.