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 + mwparserfromhell 或 WikiExtractor |
| 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 文件传输损坏,
fastwarc比warcio抗损坏更强 - 巨型 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> 标签识别,保留 |
参考链接¶
- trafilatura repo + 文档
- resiliparse 论文 SIGIR 2018
- magic-html
- FineWeb 论文(提取细节)
- DCLM 论文(resiliparse 在 pretrain 的实战)
- CleanEval benchmark 数据集