Skip to main content

html2md-clean

Чистий, керований конвертер HTML → Markdown з видаленням «шуму» (реклама, попапи, навігація), розрахований на web-scraping та підготовку тексту для LLM.

Python Ruff Tests License: MIT


Зміст


Огляд

html2md-clean перетворює «брудний» HTML реальних вебсторінок на чистий Markdown. На відміну від простих конвертерів, бібліотека спершу прибирає boilerplate — навігацію, футери, банери, cookie-попапи, рекламні блоки — а вже потім рендерить основний контент. Це робить її зручною для:

  • web-scraping та побудови датасетів;
  • підготовки тексту для RAG / LLM-пайплайнів (менше токенів шуму);
  • архівування статей у читабельному Markdown.

Бібліотека працює на двох парсерах: Selectolax (швидкий, за замовчуванням, якщо встановлений) та BeautifulSoup (fallback), схованих за єдиним Adapter-інтерфейсом.

Можливості

  • 🧹 Очищення шуму за тегами, CSS-класами, id, aria-hidden, inline display: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-function NoiseRemover).

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, pluggable token_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(...) та CLI html2md 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), CLI html2md 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)

Source distribution for html2md-clean 0.5.0
File Size Uploaded Provenance
html2md_clean-0.5.0.tar.gz 123.2 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for html2md-clean 0.5.0
File Interpreter ABI Platform
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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page