跳转至

HTML 解析器选型

更新日期:2026-04-26

时效性

本页 F1 / 速度数字基于截至 2024 末的公开 benchmark(trafilatura / DCLM / FineWeb 论文报告)。Library 版本号 / 最近 release 日期 sanity-check 一下 PyPI / GitHub releases。

爬完 HTML 不等于拿到训练数据。HTML → 干净正文 这一步决定 30-50% 的数据可用性 —— Common Crawl 自带的 WET 提取因为质量太差被普遍弃用,frontier lab 都在 WARC 上重提。


一、对比一览

工具 速度(CPU 单核 docs/s) 提取 F1 多语言 维护 主要用户
trafilatura(adbar) 40-80 0.85-0.91 强(CN/JP/AR/RTL 都好) 活跃,单维护人 FineWeb(部分)、CommonPool 论文 baseline、众多学术
resiliparse(chatnoir-eu) 400-2000(5-25× trafilatura) 0.75-0.82 活跃 DCLM-Pool、FineWeb 主提取、CC-net 后期
magic-html(opendatalab) ~50 0.85+ on CN CN 站最强 活跃 OpenDataLab / Shanghai AI Lab,中文预训练
goose3 ~100 0.74 半死 已被淘汰
boilerpy3 ~150 0.72 半死 trafilatura 的 fallback
jusText ~80 0.78 半死 OSCAR-V1 用过,现在被 trafilatura 包了当 fallback
readability-lxml ~50 0.75 半死 reader-mode 风格,不适合预训练
dragnet 死了(Python 2 时代,现代 lxml 跑不了)

二、选型决策

flowchart LR
    Q{"主语言?"}
    Q -- "英语为主<br/>追求极速" --> R[resiliparse]
    Q -- "英语为主<br/>追求质量" --> T[trafilatura]
    Q -- "中文为主" --> M[magic-html]
    Q -- "多语言均衡" --> T2[trafilatura<br/>+ fasttext lid 分流]

    classDef tool fill:#fff,stroke:#cc785c;
    class Q,R,T,M,T2 tool

经验法则

  • trillion-token 级(30B+ 参数模型)→ resiliparse 优先(速度差距决定成本)。质量差 5-10 个 F1 在大量 dedup + filter 后被 wash 掉
  • 百亿 token 级(7B-13B) → trafilatura 完全够用,质量更重要
  • 中文为主magic-html + 把英文部分扔给 trafilatura,分语言流水线
  • 混合最佳:trafilatura(作为 quality gate)+ resiliparse(作为速度引擎)—— FineWeb 类似策略

三、Trafilatura 深度

主流 PyPI 选择,单维护人 Adrien Barbaresi(柏林勃兰登堡科学院 BBAW)。

架构

HTML → lxml 解析
 主路径: 启发式(meta tag、article tag、structured data)
 fallback 1: justText(boilerplate removal)
 fallback 2: readability-lxml
 fallback 3: trafilatura 自家规则
 后处理: 去 boilerplate / 标点修复 / 语言检测

典型用法

import trafilatura

# 从 URL 抓 + 提取
downloaded = trafilatura.fetch_url("https://example.com/article")
text = trafilatura.extract(
    downloaded,
    include_tables=True,        # 表格转 markdown
    include_comments=False,     # 评论区扔掉
    include_links=False,        # 链接保留 anchor text,丢 URL
    deduplicate=True,           # 内部去重
    output_format='txt',        # 'txt' / 'json' / 'xml' / 'markdown'
)

# 从 WARC 处理(CC 用法)
from warcio.archiveiterator import ArchiveIterator
with open('CC-MAIN-...warc.gz', 'rb') as fh:
    for record in ArchiveIterator(fh):
        if record.rec_type != 'response':
            continue
        html = record.content_stream().read()
        text = trafilatura.extract(html)
        if text and len(text) > 200:
            yield text

性能调优

# trafilatura 默认设置已经 OK,但大批量时可关掉一些 fallback 加速
text = trafilatura.extract(
    html,
    no_fallback=True,           # 关 readability/justText fallback,提速 ~2x,损失 ~3-5 F1
    favor_precision=True,       # 偏 precision(漏抓多但更干净)
)

四、Resiliparse 深度

Cython 写的高性能提取器,来自 chatnoir-eu/chatnoir-resiliparse

为什么快

  • C++ HTML parser(lexbor)替换 lxml/BeautifulSoup
  • Cython binding 减少 Python overhead
  • 流式 WARC 处理,不全文 load

典型用法

from resiliparse.extract.html2text import extract_plain_text
from resiliparse.parse.html import HTMLTree
from fastwarc.warc import ArchiveIterator

with open('CC-MAIN-...warc.gz', 'rb') as fh:
    for record in ArchiveIterator(fh):
        if record.headers.get('Content-Type', '').startswith('text/html'):
            html = record.reader.read().decode('utf-8', errors='ignore')
            tree = HTMLTree.parse(html)
            text = extract_plain_text(
                tree,
                main_content=True,          # 试图找主内容
                preserve_formatting=False,  # 输出纯文本
            )
            if len(text) > 200:
                yield text

用在 FineWeb / DCLM 的原因:单 EC2 c5.24xlarge 一天能跑完一个 CC snapshot 的提取,trafilatura 要 10-30 倍机器小时。


五、magic-html(中文场景)

opendatalab/magic-html,上海 AI 实验室 OpenDataLab 出品(和 Tencent 没关系),跟 MinerU(PDF 解析)是兄弟项目。

为什么对中文好

  • 中文站点常用模板(CSDN / 知乎 / 简书 / 微信公众号)的 layout 单独适配
  • 输出 markdown 风格保留段落结构(trafilatura 默认输出 plain text)
  • 内置 forum / wechat / article 三种 layout 识别

用法

from magic_html import GeneralExtractor

extractor = GeneralExtractor()
result = extractor.extract(html_content, base_url="https://csdn.net/...")
# result.html_text 是清洗后的 HTML
# result.html_markdown 是 markdown
# result.layout_type 是识别出的 layout("article" / "forum" / "wechat")

六、Agentic / LLM-based parsers(≠ 预训练规模)

2024-2026 年涌现一批 LLM-driven parser:unstructured.io / docling(IBM)/ Crawl4AI LLM mode / llmware / marker-pdf 等。

核心问题:成本。

工具 速度(docs/s) 成本(per 1B docs) 用途
resiliparse 400-2000 ~$50-500 计算 预训练(trillion token 级)
trafilatura 40-80 ~$500-2000 计算 预训练(百亿 token 级)
unstructured.io(带 vision LM) 1-5 ~$50K-200K RAG ingest
docling(IBM, vision optional) 1-5 同上 企业 RAG
Crawl4AI + LLM extraction <1 \(200K-\)5M 不可能用在预训练

结论:agentic parser 适合高价值小批量(论文 / 数学 / 法律 / 医学)—— 这些场景每 doc 提取成本 $0.001-0.01 还能接受。Phi 系列、Cosmopedia 等合成 / 高质量小数据集才会用,做万亿级 web 提取的话完全不划算


七、Per-domain 自定义解析器

frontier lab 不会对全部 CC 写自定义。但高价值结构化源会绕过 HTML 直接用官方 dump:

不抓 HTML,用 ↓
Stack Overflow / Stack Exchange data.stackexchange.com 的 XML/Parquet dump
Wikipedia 官方 XML dump + mwparserfromhellWikiExtractor
Reddit Pushshift dump(2022 前)/ 官方 API(2023 后,licensed to Google for Gemini)
GitHub GHArchive + git clone(不 scrape HTML)
arXiv 官方 LaTeX source bulk(S3 requester-pays),用 pylatexenc 解析
PubMed Central NCBI bulk XML
Wikidata 官方 RDF dump
Wikinews / Wikibooks / Wikiversity Wikimedia bulk dumps

公开 pipeline 文档 per-domain 写得最详细的:

  • The Pile (EleutherAI 2020) — 22 个 source,每个独立 extractor
  • RedPajama V1 — 7 source 各自处理
  • Dolma (AI2 2024) —— 全开源 toolkit,每 source 一套

Pattern:高价值结构化源 = bypass HTML;CC 长尾 = 一个通用提取器(resiliparse / trafilatura)。


八、并行 + 错误处理

# 万级 WARC 的并行处理:multiprocessing + 队列
from multiprocessing import Pool

def process_warc(warc_path):
    results = []
    try:
        with open(warc_path, 'rb') as fh:
            for record in ArchiveIterator(fh):
                if record.rec_type != 'response':
                    continue
                html = record.content_stream().read()
                # 防 mem 爆: 跳过超大 HTML
                if len(html) > 10_000_000:
                    continue
                try:
                    text = trafilatura.extract(html)
                    if text and len(text) > 200:
                        results.append({
                            'url': record.rec_headers['WARC-Target-URI'],
                            'text': text,
                        })
                except Exception as e:
                    # 单个 doc 解析失败不要 crash 整个 worker
                    log.warning(f"extract failed for {url}: {e}")
    except Exception as e:
        log.error(f"WARC {warc_path} corrupted: {e}")
    return results

# n_workers = CPU * 0.8(提取是 CPU-bound)
with Pool(n_workers) as p:
    for batch in p.imap_unordered(process_warc, warc_paths):
        write_to_parquet(batch)

易踩的坑

  • HTML 编码混乱 — Common Crawl 里 ~10% 文档 charset 标注错(说自己是 utf-8 实际 GBK)。trafilatura 内部用 cchardet 自动检测,但仍有 ~1% fail
  • GZIP 嵌套 / WARC 损坏 — 大批量必有 0.1-0.5% WARC 文件传输损坏,fastwarcwarcio 抗损坏更强
  • 巨型 HTML(>10MB) — 数据库 dump / 论坛归档页等。直接跳过,不值得花内存解析
  • JS-rendered SPA — trafilatura/resiliparse 都解析不了 React/Vue 渲染前的 HTML,需要 headless 浏览器再抓一遍(详见 crawler

九、追问延伸

问题 方向
提取质量怎么 eval? CleanEval / Web2Text 公开 benchmark;自己也可以拿 100 个手动标注样本 cross-check
提取后还要清洗吗? 必须。trafilatura/resiliparse 只去 boilerplate,剩下还要做 dedup(dedup)+ 质量分类(quality)+ PII(pipeline index.md §五)
长文档(>50KB)保留 vs 截断? 大多数 frontier pipeline 保留全文,下游训练时按 sequence length 切块。截断会丢上下文
表格 / 代码块怎么处理? trafilatura include_tables=True 转 markdown 表;代码块用 <pre><code> 标签识别,保留

参考链接


上级 · B1. 预训练数据 Pipeline:爬取→清洗→去重→过滤→配比