章节导航 第 3 章 / 共 20 章
数据采集与解析
从Common Crawl的WARC字节到干净纯文本——主流数据源盘点、HTML正文抽取(trafilatura/resiliparse)、PDF解析(MinerU/Nougat/Marker)、采集架构与解析时序
预训练数据工程的入口是从原始字节到纯文本的转换。
这一步的产出质量直接决定了后续所有环节的”地基”是否稳固。
3.1 主流数据源盘点
预训练数据的主流来源可以归纳为”七大源”:
| 数据源 | 内容类型 | 规模(tokens级别) | 质量 | 关键挑战 |
|---|---|---|---|---|
| Common Crawl (CC) | 网页快照 | 100T+ | 参差不齐 | HTML清洗、去重、低质过滤 |
| GitHub | 代码仓库 | 1T+ | 中-高 | 许可证、低质代码过滤 |
| arXiv | 学术论文 | 100B+ | 高 | PDF公式解析 |
| Wikipedia | 百科 | 4B+ | 高 | 多语种平衡 |
| Books (Books3/Gutenberg/PBLLM) | 书籍 | 200B+ | 高-中 | 版权、PDF解析 |
| StackExchange | 问答 | 100B+ | 中-高 | 答案质量、投票排序 |
| OpenWebMath | 数学网页 | 10B+ | 高 | LaTeX公式还原 |
3.1.1 数据源贡献排序
以Llama3的15T tokens为例,典型配比:
Common Crawl清洗后: 60-70% (10T+)
GitHub代码: 10-15% (1.5T)
Books: 5-10% (1T)
arXiv: 3-5% (500B)
Wikipedia: 1-2% (200B)
StackExchange: 1-2% (200B)
其他(数学/多语/合成): 5-10% (1T)
Common Crawl是绝对主力,但代码、论文、书籍贡献了”高密度知识”的绝大部分。
3.2 Common Crawl实战:WARC/WAT/WET
Common Crawl(CC)是最大的开源网页爬虫数据集,每月发布一次快照,单快照约 100TB 压缩后数据。CC提供三种格式:
3.2.1 三种格式对比
| 格式 | 内容 | 大小(单快照) | 用途 |
|---|---|---|---|
| WARC | 完整响应(含HTTP头+HTML+元数据) | ~100TB | 主流选择,信息最完整 |
| WAT | 提取的链接和元数据 | ~30TB | 链接分析、PageRank |
| WET | 预提取的纯文本 | ~20TB | 简化使用,但解析质量一般 |
工业级流水线首选 WARC——自己控制解析质量,不依赖CC的预提取。WET虽然方便,但CC的trafilatura版本可能落后,且无法做精细的正文/导航/页脚切分。
3.2.2 WARC文件结构
WARC是行式存储的容器格式,每条记录由header+payload组成:
WARC/1.0
WARC-Type: response
WARC-Record-ID: <urn:uuid:...>
WARC-Date: 2024-12-15T08:30:00Z
WARC-Target-URI: https://example.com/article
Content-Type: application/http; msgtype=response
Content-Length: 8456
HTTP/1.1 200 OK
Content-Type: text/html
...
<html><head>...</head><body>...</body></html>
WARC/1.0
WARC-Type: response
...
3.2.3 WARC下载与解析代码
# CC的WARC文件按月发布在AWS S3上
# 路径:s3://commoncrawl/crawl-data/CC-MAIN-< YYYY >-< WW >/segments/.../warc/CC-MAIN-< timestamp >-< index >.warc.gz
# 示例快照:CC-MAIN-2026-26 (2026-06采集,2.10B网页,354.59 TiB) <!-- edit: 2026-07-24 更新至 2026-06 快照,CC 已通过 HuggingFace Storage Bucket 分发(2026-04 快照起) -->
# CC-MAIN-2024-51 仍可用作示例,生产环境建议跟踪最新快照
import boto3
import gzip
import io
from warcio import ArchiveIterator
def download_warc_list(snapshot="CC-MAIN-2026-26"):
"""获取某个月CC快照的WARC文件路径列表"""
s3 = boto3.client('s3')
paginator = s3.get_paginator('list_objects_v2')
paths = []
for page in paginator.paginate(
Bucket='commoncrawl',
Prefix=f'cc-index/collections/{snapshot}/indexes/'
):
for obj in page.get('Contents', []):
paths.append(obj['Key'])
return paths
def stream_warc_and_extract(warc_path, output_queue):
"""
流式读取WARC.gz,逐条记录解析
采用流式是为了避免单文件解压占用过多内存
"""
s3 = boto3.client('s3')
response = s3.get_object(Bucket='commoncrawl', Key=warc_path)
# 流式解压 + WARC迭代器
for record in ArchiveIterator(response['Body']):
if record.rec_type != 'response':
continue
uri = record.rec_headers.get_header('WARC-Target-URI')
content_type = record.http_headers.get_header('Content-Type', '')
# 只处理HTML响应
if 'text/html' not in content_type:
continue
html_bytes = record.content_stream().read()
# 推入下游解析队列
output_queue.put({
'url': uri,
'html': html_bytes,
'warc_date': record.rec_headers.get_header('WARC-Date')
})
3.2.3.1 工业方案:NeMo Curator
以上手动WARC解析提供了原理理解,但工业级场景推荐使用 NVIDIA NeMo Curator 的 CommonCrawlDownloadExtractStage(NeMo Curator 26.04,基于 Nemotron-CC 流水线开源):
三阶段流水线(26.04 架构更新):
- URL 生成:根据CC快照索引自动生成WARC文件URL列表
- 下载:支持HTTPS和S3双模式(
use_s3=True可直接从 S3 传输,避免 HTTPS 限速),可自动 fallback - 迭代抽取:流式迭代WARC记录 → 内置抽取,内存友好;内置三种抽取器——
jusText(通用)、resiliparse(高速)、trafilatura(高质量),可按需切换
26.04 新增能力:
- 自动语种识别(LID),无需单独调用fastText
- PDF 流水线(Nemotron-Parse Xenna):四阶段 PDF 解析,基于 pypdfium2
- vLLM 嵌入去重:语义去重默认启用,模型从 all-MiniLM-L6-v2 切换为 google/embeddinggemma-300m
- 与NeMo Curator后续质量过滤流水线无缝集成
- 分布式处理支持(Dask/Spark)
# NeMo Curator Common Crawl 流水线示例
from nemo_curator import Sequential
from nemo_curator.download import download_common_crawl
from nemo_curator.datasets import DocumentDataset
# 单行启动CC下载+抽取+语种识别
pipeline = Sequential([
download_common_crawl(
snapshot="CC-MAIN-2026-26",
output_dir="./cc_output",
extractor="trafilatura", # 或 "jusText" / "resiliparse"
download_method="s3", # 或 "https";use_s3=True 直接S3传输,避免限速
language="en,zh", # 自动LID过滤
seed=42
),
])
dataset = pipeline.run()
print(f"下载完成:{len(dataset.df)} 篇文档")
对于从头搭建流水线的团队,直接使用NeMo Curator可以跳过大部分手动编码,将精力聚焦于数据策略而非工程实现。
3.2.4 工程参数:CC下载的并发控制
CC单快照有约 90,000 个 WARC 文件(每个约 1GB 压缩)。下载时需要注意:
# 推荐的S3并发下载配置
S3_CONFIG = {
'max_pool_connections': 50, # S3连接池
'multipart_threshold': 8 * 1024**2, # 8MB以上启用分片
'multipart_chunksize': 8 * 1024**2,
}
# 千万别用requests直接下载CC——AWS S3限速会让你怀疑人生
# 必须用boto3 + s3 transfer manager
踩坑经验:CC的WARC.gz是单文件gzip,不能seek。如果想并行处理单文件,需要先解压再分块,或者用 warcio 的流式接口。
3.3 HTML正文抽取
HTML解析的目标:从一坨带导航/广告/页脚的HTML中提取真正的正文内容,去除模板化噪音。
3.3.1 主流工具对比
| 工具 | 准确率 | 速度 | 多语种 | 公式/LaTeX | 推荐场景 |
|---|---|---|---|---|---|
| trafilatura | 高 | 快(500 docs/s) | 支持 | 弱 | 通用网页正文 |
| resiliparse | 高 | 极快(2000 docs/s) | 支持 | 弱 | 大规模批处理 |
| boilerpy3 | 中 | 快 | 一般 | 无 | 简单网页 |
| readability-lxml | 中 | 快 | 一般 | 无 | 新闻类文章 |
| html2text | 低 | 极快 | 支持 | 无 | 粗提取 |
| MinerU-HTML v1.1 | 极高 (0.9001 ROUGE-N F1) | 快(VLLM GPU) | 支持 | 强 | 高质量正文抽取(SLM驱动) |
工业级首选 trafilatura(HuggingFace FineWeb同款),双路fallback可用resiliparse。
3.3.1.1 多抽取器 Union:2026 年新共识
Apple、Stanford 和 UW 联合团队在 2026 年 2 月发表的研究(arXiv:2602.19548,“Beyond a Single Extractor”)颠覆了”选一个最好的抽取器就够了”的假设。核心发现:
- 不同抽取器覆盖的网页子集高度互补——仅 39% 的网页能被多个抽取器同时保留
- trafilatura、resiliparse、jusText 各有”擅长”的网页类型:resiliparse 在 WikiTableQuestions 上比 trafilatura 高 10.3pp;代码块抽取器选择导致 HumanEval 差 3pp
- 取 Union(trafilatura ∪ resiliparse ∪ jusText) 在 DCLM-Baseline 流水线下 token yield 提升 71%(去重后仍提升 58%),且不牺牲 benchmark 性能
工业最佳实践更新:
| 场景 | 推荐策略 |
|---|---|
| GPU 可用 | trafilatura ∪ MinerU-HTML ∪ resiliparse(三抽取器 Union) |
| CPU 场景 | trafilatura ∪ resiliparse(双抽取器 Union) |
| 资源受限 | trafilatura(首选)+ resiliparse(fallback,仅当 trafilatura 产出 < 200 字符时启用) |
多抽取器 Union 的核心代价是引入更多重复内容(需在后续去重环节统一处理),但 token yield 提升远超去重损失。
3.3.2 trafilatura 实战
from trafilatura import extract
from trafilatura.settings import use_config
# 必须调整配置,否则会过度删除"看起来像导航"的正文段落
config = use_config()
config.set('DEFAULT', 'EXTRACTION_TIMEOUT', '0') # 不超时
config.set('DEFAULT', 'MIN_EXTRACTED_SIZE', '200') # 至少200字符
config.set('DEFAULT', 'MIN_OUTPUT_SIZE', '100') # 至少100字符输出
config.set('DEFAULT', 'MAX_FILE_SIZE', '10000000') # 10MB上限
def extract_html(html_text, url=''):
"""
带fallback的HTML正文抽取
"""
# 第一路:trafilatura(推荐)
text = extract(
html_text,
url=url,
config=config,
output_format='txt', # 纯文本输出
include_comments=False, # 去掉评论
include_tables=True, # 保留表格
favor_recall=True, # 偏向召回(宁可多留也不漏)
deduplicate=True # 模板去重
)
if text and len(text) > 100:
return text, 'trafilatura'
# 第二路:resiliparse fallback
from resiliparse.extract_html2text import extract_preserve_structure
text_fallback = extract_preserve_structure(html_text)
if text_fallback and len(text_fallback) > 100:
return text_fallback, 'resiliparse'
return None, 'rejected'
3.3.3 解析质量检查
抽取后的文本必须经过质量检查,否则会留下大量”半解析”的脏数据:
def check_extraction_quality(text):
"""
检查抽取质量,返回 True/False
"""
if not text or len(text) < 200:
return False
# 1. HTML标签残留检查(解析失败的标志)
html_tag_ratio = count_html_tags(text) / len(text)
if html_tag_ratio > 0.05: # 5%以上HTML残留
return False
# 2. JS代码残留检查
if 'function(' in text or 'var ' in text[:500]:
return False
# 3. 模板化检查(导航菜单类)
lines = text.split('\n')
short_lines = sum(1 for l in lines if len(l) < 30)
if short_lines / len(lines) > 0.7: # 70%短行=导航
return False
# 4. 字符分布异常(乱码检查)
ascii_ratio = sum(c.isascii() for c in text) / len(text)
if ascii_ratio < 0.6 and ascii_ratio > 0.3: # 既不是纯英文也不是纯中文,可能是乱码
return False
return True
3.4 PDF解析:论文与书籍的核心挑战
PDF是arXiv论文和Books的主要来源。PDF解析的难点在于:PDF是为打印设计的,不是为文本提取设计的。
3.4.1 主流工具对比
| 工具 | 公式还原 | 表格 | 速度 | 多栏 | 适用 |
|---|---|---|---|---|---|
| PyPDF2/pdfminer | 无 | 差 | 快 | 差 | 简单PDF |
| Grobid | 部分 | 中 | 慢 | 好 | 学术论文 |
| Nougat (Meta) | 强(LaTeX) | 中 | 慢(GPU) | 好 | arXiv论文 |
| Marker | 强 | 好 | 中(GPU) | 好 | 通用PDF |
| MinerU | 强(LaTeX) | 强 | 中(GPU) | 好 | 论文+书籍+复杂版式 |
| docling | 中 | 好 | 快 | 好 | 工业文档 |
论文PDF首选 Nougat 或 MinerU;书籍+复杂版式首选 MinerU。
3.4.2 MinerU 实战
MinerU(OpenDataLab, 2024)是当前最强的开源PDF解析器。 2025-06 的 2.0 版本完成架构重构:输入范围从 PDF/图片扩展到 DOCX/PPTX/XLSX/网页,新增化学论文解析(分子结构识别、化学反应提取,与 A4Chemistry 合作),综合解析准确率较前代提升约 22%,核心模型约 0.9B 参数(decoupled 视觉-语言双编码器),GitHub 星标截至 2026-08 已破 81K。支持:
- 公式 → LaTeX 还原
- 表格 → HTML/Markdown 还原
- 多栏阅读顺序识别
- 图表与正文分离
# MinerU 通过命令行调用,最简用法(2.0 起 CLI 由 magic-pdf 更名为 mineru)
# mineru -p input.pdf -o output/
# 工业级批量处理脚本
import subprocess
from pathlib import Path
def batch_parse_pdfs(pdf_dir, output_dir, gpu_id=0):
"""
批量解析PDF为Markdown
MinerU 2.0 三引擎:pipeline(纯 CPU 可跑)/ vlm-engine(vLLM/LMDeploy)/ hybrid-engine(默认推荐)
"""
pdfs = list(Path(pdf_dir).glob('*.pdf'))
for pdf in pdfs:
cmd = [
'mineru',
'-p', str(pdf),
'-o', f'{output_dir}/{pdf.stem}',
'-m', 'auto', # 自动选择模式
'-d', str(gpu_id), # GPU设备
'--lang', 'en,zh' # 多语种
]
try:
result = subprocess.run(
cmd, capture_output=True, timeout=300, text=True
)
if result.returncode != 0:
log_error(pdf, result.stderr)
except subprocess.TimeoutExpired:
log_error(pdf, 'Timeout after 300s')
3.4.3 Nougat 与公式还原
Nougat 是 Meta 开发的基于 ViT 的 PDF 解析器,特别擅长公式还原:
# Nougat 直接将PDF转为Markdown+LaTeX
# 输入:paper.pdf
# 输出:paper.mmd(Mathpix Markdown)
# 命令行用法
# nougat path/to/file.pdf -o output_dir
# 一个解析后的样例:
"""
# Attention Is All You Need
## Abstract
We propose a new simple network architecture, the Transformer,
based solely on attention mechanisms, dispensing with recurrence
and convolutions entirely.
$$\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$
where $Q \in \mathbb{R}^{n \times d_k}$ ...
"""
踩坑经验:Nougat 对扫描版PDF(图片型)效果差,需要先用 OCR 处理。MinerU 的 auto 模式可以自动切换。
3.5 采集架构图
工业级数据采集系统的典型架构:
┌─────────────────────────────────────────────────────────────┐
│ 采集调度层 (Airflow) │
│ 定时任务 → CC快照监控 → GitHub爬取 → arXiv同步 → ... │
└──────────────────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 下载队列层 (Kafka) │
│ topic: cc.warc.url, github.repo.url, arxiv.id, ... │
└──────────────────────────────┬──────────────────────────────┘
│
┌────────────────┼────────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Worker 1 │ │ Worker 2 │ │ Worker N │
│ (S3下载) │ │ (S3下载) │ │ (S3下载) │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
└───────────────┼───────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ 解析集群 (Ray/Spark) │
│ WARC → HTML抽取 → 纯文本 │
│ PDF → MinerU/Nougat → Markdown+LaTeX │
└──────────────────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 原始文本存储 (S3/HDFS) │
│ parquet 格式,按 source/date/hash 分区 │
│ schema: source, url, html, text, lang, parsed_at, ... │
└─────────────────────────────────────────────────────────────┘
3.5.1 关键设计原则
- 解耦:下载、解析、存储三层独立扩缩容
- 幂等:每个URL/文件可重试,避免重复处理
- 可追溯:保留WARC原始字节,方便重新解析
- 背压:下游处理慢时,上游自动暂停下载
3.6 解析时序图
单条数据从下载到产出的时序:
时间轴 →
下载层: [S3 GET WARC.gz]──────────[stream 1MB chunks]
│
解析层: ▼
[warcio迭代器]──[record 1]──[record 2]──...
│
▼
[trafilatura]
│
▼
[质量检查]
│
▼
[语种识别 fastText]
│
┌───────────┴───────────┐
▼ ▼
[zh文本] [en文本]
│ │
▼ ▼
[S3: zh/raw] [S3: en/raw]
parquet分区 parquet分区
3.6.1 性能基准
在 32 vCPU + 64GB 内存节点上:
| 阶段 | 吞吐 | 单节点日处理量 |
|---|---|---|
| WARC 下载 | 200 MB/s | ~17 TB |
| HTML 抽取 | 500 docs/s | ~43M 文档 |
| PDF 解析(MinerU GPU) | 5 docs/s | ~430K 文档 |
| 完整流水线 | 300 docs/s | ~26M 文档 |
PDF 解析是瓶颈—— MinerU 单GPU 5 docs/s,处理100万篇arXiv论文需要约 50 GPU·天。
3.7 GitHub 代码采集
代码数据的特殊处理:
3.7.1 采集策略
# GitHub API 限制:5000 req/h,所以要用 GH Archive + 仓库快照
# 推荐用 Google BigQuery 的 GH Archive 数据集
# 高质量代码仓库筛选标准
REPO_FILTERS = {
'min_stars': 10, # 至少10 star
'min_commits': 5, # 至少5次提交
'exclude_fork': True, # 排除纯fork
'exclude_template': True, # 排除模板仓库
'exclude_size_kb': 1, # 排除小于1KB的
'exclude_size_mb': 10, # 排除大于10MB的(避免单文件巨无霸)
'languages': ['Python', 'Java', 'C++', 'JavaScript', 'Go', 'Rust']
}
# 文件级过滤
FILE_FILTERS = {
'exclude_paths': [
'node_modules/', 'vendor/', '.git/',
'test/', 'tests/', '__pycache__/',
'docs/', 'examples/' # 这些通常质量低
],
'exclude_extensions': ['.min.js', '.lock', '.svg', '.png'],
'exclude_licenses': ['GPL-3.0', 'AGPL-3.0'] # 视团队合规而定
}
3.7.2 代码数据许可证
| 许可证 | 是否可用(参考) |
|---|---|
| MIT | ✅ |
| Apache-2.0 | ✅ |
| BSD-3-Clause | ✅ |
| GPL-3.0 | ❌(传染性) |
| AGPL-3.0 | ❌(网络传染) |
| 无 LICENSE 文件 | ❌(默认”All rights reserved”) |
3.8 arXiv 与学术数据
3.8.1 arXiv 采集
arXiv 提供 bulk access(S3上公开):
# arXiv S3 bucket
aws s3 ls s3://arxiv/pdf/ --recursive | head
# 完整下载约 2.5 TB,含 240万+ 篇论文
# 推荐方式:用 arxiv-oai-mcp 工具或 kaggle 的 arxiv dataset
3.8.2 LaTeX 源码处理
arXiv 大部分论文有 LaTeX 源码,比 PDF 解析更准确:
def parse_arxiv_latex(source_tarball):
"""
从arXiv tarball提取LaTeX源码,转纯文本
比从PDF解析质量高10倍以上
"""
with tarfile.open(source_tarball) as tar:
# 找到主tex文件
tex_files = [f for f in tar.getnames() if f.endswith('.tex')]
main_tex = find_main_tex(tex_files)
# 提取内容
content = tar.extractfile(main_tex).read().decode('utf-8')
# 去除LaTeX命令,保留文本
# 推荐用 pylatexenc 或 panflute
from pylatexenc.latex2text import LatexNodes2Text
text = LatexNodes2Text().latex_to_text(content)
return text
踩坑经验:arXiv 的 LaTeX 源码格式极其混乱(每个作者风格不同)。建议先尝试 LaTeX 源码,失败再回退到 PDF(Nougat/MinerU)。
3.9 踩坑经验总结
坑1:CC单快照不够,必须跨快照去重
某团队用1个快照(100TB)训练,发现大量数据是同一个网站的不同时间快照。必须跨多个快照做去重,且时间戳靠后的优先。
坑2:HTML抽取把广告当正文
某团队用 html2text 抽取,结果模型生成的内容里全是”点击这里”、“立即购买”。必须用 trafilatura 或 resiliparse 做严格的正文/模板分离。
坑3:PDF表格被解析成乱序文本
某团队用 pdfminer 解析财报PDF,表格被拆成一坨。复杂版式PDF必须用 MinerU/Marker,简单文本PDF才用 pdfminer。
坑4:GitHub代码含测试数据
某团队从 GitHub 抓代码,没过滤 test/ 目录,结果训练数据里混入了大量 JSON/CSV 测试数据。模型生成代码时经常吐出一坨测试数据。代码采集必须做路径过滤。
坑5:解析后的文本长度过滤太激进
某团队过滤掉了所有<500字符的文本,结果丢失了大量列表页、表格、定义。长度过滤推荐 <200 字符,而不是<500。
3.10 本课总结
- 七大主流数据源:CC/GitHub/arXiv/Wiki/Books/StackExchange/OpenWebMath
- CC 三种格式:WARC(首选)、WAT(链接分析)、WET(简化但解析差)
- HTML抽取:trafilatura(首选)+ resiliparse(fallback)
- PDF解析:MinerU(最强通用)/ Nougat(论文公式)/ Marker(综合)
- 架构原则:下载-解析-存储三层解耦,可独立扩缩容
- 性能瓶颈:PDF解析是最大开销,单GPU只能处理5 docs/s
课后练习
- 用
warcio流式解析1个CC的WARC.gz文件,统计其中的语种分布 - 对比 trafilatura 和 resiliparse 在100篇中文网页上的抽取质量
- 用 MinerU 解析1篇 arXiv 论文,检查公式还原准确率
- 设计你团队的代码采集流水线,包含许可证过滤
下一章我们将进入语种识别与质量过滤——CommonLingua、fastText、Gopher规则、FineWeb-Edu模型打分,把”垃圾数据”过滤掉的核心环节。