cc_rag_tutorial

第 14 章:用 Common Crawl 做外挂语料——从海量网页到可检索知识库

1. 开篇段落

在第 13 章中,我们学会了如何“搬运” Common Crawl 的数据。现在,你的硬盘里可能已经躺着数百 GB 的 WET 文件。但请注意:原始的 Common Crawl 数据是“有毒”的。

如果你直接将这些数据喂给 Embedding 模型并存入向量库,你将面临“垃圾进,垃圾出”(GIGO)的灾难:

  1. 检索中毒:用户问“Python 教程”,结果检索出 SEO 内容农场的垃圾页面。
  2. 上下文浪费:Claude Code (CC) 的上下文窗口是昂贵的。如果检索到的片段充满了“点击这里”、“Cookie 政策”或乱码,你实际上是在浪费 token 并降低模型智商。
  3. 甚至安全风险:包含恶意 Prompt 注入的网页可能会诱导 CC 执行危险操作。

本章将详细讲解如何构建一个工业级的数据清洗漏斗(Data Processing Funnel)。我们将深入探讨从域名过滤、文本去噪、质量评分到大规模去重的全流程。这一章的内容是将“公开数据”转化为“私有资产”的核心壁垒。


2. 数据处理漏斗:架构总览

处理 Common Crawl 数据的核心哲学是 “分层过滤,激进丢弃”。我们不需要索引整个互联网,只需要索引对目标领域最有价值的 1%。

以下是一个工业级的数据处理流水线架构:

[ 原始数据流 (WET Stream) ]  <-- 吞吐量: ~100 MB/s (单节点)
         |
         v
+-----------------------------+
| Layer 1: 粗筛 (Metadata)    | <-- 成本极低
| - 域名黑/白名单 (TLD/Domain)| 
| - URL 模式过滤 (Regex)       | <-- 过滤掉 60% 无用数据
| - 语言检测 (Language ID)    |
+-----------------------------+
         |
         v
+-----------------------------+
| Layer 2: 细洗 (Structure)   | <-- 基于规则
| - 样板代码移除 (Boilerplate)| 
| - 短文本/低密度文本丢弃     | <-- 过滤掉 20% 噪声
| - 个人隐私信息 (PII) 擦除   |
+-----------------------------+
         |
         v
+-----------------------------+
| Layer 3: 质检 (Semantics)   | <-- 计算密集
| - 困惑度 (Perplexity) 阈值  | 
| - 敏感/有害内容分类         | <-- 确保语料"高智商"且"安全"
+-----------------------------+
         |
         v
+-----------------------------+
| Layer 4: 去重 (Global)      | <-- 内存密集
| - MinHash + LSH (模糊去重)  | 
| - 权威性重排                | <-- 消除 40%+ 的重复内容
+-----------------------------+
         |
         v
[  黄金 RAG 语料库 (JsonL)  ]

3. Layer 1: 守门员——域名与 URL 策略

这是性价比最高的一层。在解压 WET 内容之前,甚至在下载 WARC 之前,就应该通过 URL 决定去留。

3.1 域名策略 (The Trust Spectrum)

在 RAG 场景下,我们通常采用 “白名单优先,黑名单兜底” 的混合策略。

3.2 URL 模式过滤 (Regex Is Your Friend)

很多页面虽然是 HTML,但对 RAG 毫无价值。使用正则表达式快速剔除。

过滤类型 典型特征 (Regex Pattern) 理由
功能页 /login, /cart, /signup, /search, /forgot-password 动态交互页,无持久知识
索引页 /tag/, /category/, /author/, /page/\d+ 仅仅是链接列表,正文密度低
非内容后缀 \.xml$, \.json$, \.rss$, \.php$ (带参数) 往往是配置或接口,而非文档
特定参数 \?share=, \?lang=, \?print= 重复页面的变体

3.3 语言检测 (Language Identification)

Common Crawl 的元数据中提供了语言标签,但并不完全准确。必须进行二次校验。


4. Layer 2: 结构化清洗——去除“样板噪音”

WET 格式虽然去除了 HTML 标签,但保留了网页的视觉结构(换行符)。这导致页眉、页脚、侧边栏、广告词依然存在。这些被称为 Boilerplate(样板文本)

4.1 基于行的启发式规则 (Line-Based Heuristics)

由于 WET 是纯文本,我们无法使用 DOM 树去噪,必须依赖文本特征。遍历每一行,应用以下规则:

  1. 页脚/版权特征
    • 正则:(?i)(copyright|©|all rights reserved|隐私政策|联系我们)
    • 动作:删除该行及后续所有行(通常页脚在最后)。
  2. 导航/菜单特征
    • 特征:短文本,包含常见导航词,或者大量管道符 |>
    • 示例:首页 > 产品 > 软件Login | Sign Up | Help
    • 规则:如果 len(words) < 5 且包含导航符号,删除。
  3. 社交媒体噪音
    • 特征:Share on Facebook, Tweet this, 点赞, 评论
    • 动作:删除。

4.2 文本密度与块过滤 (Block Filtering)

将文本按空行分割成“段落块(Paragraph Blocks)”。


5. Layer 3: 质量评估——让数据“高智商”

仅仅是“干净的文本”还不够,我们需要“有意义的文本”。

5.1 困惑度过滤 (Perplexity Filtering)

这是 OpenAI 和 DeepMind 处理预训练数据的标准法。

5.2 停用词比率 (Stopword Ratio)


6. Layer 4: 大规模去重 (Deduplication)

互联网上 50% 的内容是重复的。如果不去重,你的 RAG 系统会变成“复读机”。

6.1 为什么 MD5/SHA256 不够用?

传统的哈希算法是“雪崩效应”的:改动一个标点符号,哈希值全变。 网页常见情况:

6.2 MinHash + LSH (局部敏感哈希)

这是工业界处理海量文本去重的标准方案。

  1. Shingling:将文本切分为 N-gram 集合(例如 5-grams)。
  2. MinHash:通过多个哈希函数生成文档的“签名”(Signature)。两个文档的签名相似度近似于它们的 Jaccard 相似度。
  3. LSH (Locality Sensitive Hashing):将签名分段索引。只有在同一个桶(Bucket)里的文档才需要进行具体的相似度对比。

6.3 权威性重排策略

当发现 10 个重复文档时,保留哪一个?


7. Layer 5: 元数据封装与输出

清洗后的数据需要封装为标准 JSONL 格式,供 Ingest 脚本(第 5 章)使用。

关键字段规范:

{
  "id": "CC-MAIN-2024-10-...",
  "text": "清洗后的纯净正文...",
  "metadata": {
    "source": "common_crawl",
    "url": "https://example.com/docs/api",
    "domain": "example.com",
    "title": "API Documentation - Example",
    "date_crawled": "2024-03-15T10:00:00Z", 
    "date_published": "2023-12-01", // (可选) 如果能从正文提取到
    "language": "en",
    "quality_score": 0.87, // 根据 PPL 或规则打分
    "original_warc_id": "<urn:uuid:...>" // 用于溯源
  }
}

Rule of Thumb:务必保留 date_crawled。在 RAG 检索时,可以将其作为“时效性”特征,告诉 LLM:“这是 2021 年的信息,可能已过时。”


8. 本章小结


9. 练习题

基础题

Q1: 域名黑白名单策略 假设你要构建一个“云原生技术 (Kubernetes/Docker)”的 RAG 知识库。

  1. 请给出 3 个你会优先加入白名单的域名(或子域名模式)。
  2. 请给出 2 个你绝对要加入黑名单的网站类型或特征。
点击查看提示与参考答案 **提示**: 思考云原生技术的权威源头在哪里(官方文档、基金会)。反之,思考当你搜索报错信息时,哪些网站总是给你复制粘贴的无用废话。 **参考答案**: 1. **白名单**: * `kubernetes.io` (及 `*.kubernetes.io`) * `docs.docker.com` * `cncf.io` (云原生计算基金会博客/报告) * (备选) `medium.com` 的特定组织账号(如 Netflix TechBlog),而不是全站。 2. **黑名单**: * **代码聚合站**:`coderanch.com`, `programcreek.com` (往往是无上下文的代码片段堆砌)。 * **SEO 问答镜像**:任何将 StackOverflow 内容机器翻译或直接抓取并插入大量广告的站点。

Q2: 正则表达式实战 你需要编写一个正则表达式,从 CC 的 URL 列表中剔除不适合做知识库的页面。 要求剔除:

点击查看提示与参考答案 **提示**: 关注 URL 的后缀(extension)和路径中的关键词。 **参考答案**: ```regex # Python re 风格 pattern = r".*\.(jpg|jpeg|png|gif|bmp|zip|tar|gz|rar|exe|iso)$|.*(search\?|/search/|/login|/signin|/signup|/cart).*" ``` **解释**: - `.*\.(...)$` 匹配以特定后缀结尾的 URL。 - `.*(...).*` 匹配路径中包含特定关键词的 URL。

Q3: 样板代码(Boilerplate)识别 以下是从 WET 提取的三段文本块,请判断哪些应该被清洗掉,并说明理由。 A. “2023-10-12 | By Admin | Comments (0)” B. “Pod 的生命周期包含 Pending, Running, Succeeded, Failed, Unknown 五种状态。” C. “主页 产品 解决方案 价格 关于我们”

点击查看提示与参考答案 **提示**: 分析文本的信息密度、语法结构和是否具有导航属性。 **参考答案**: * **A -> 清洗**:这是典型的文章元数据行(Meta Line),虽然有日期,但本身不是知识。 * **B -> 保留**:这是高质量的定义性陈述,包含完整的主谓宾结构。 * **C -> 清洗**:这是导航栏(Menu),缺乏语义连接词,全是名词堆砌。

挑战题

Q4: 对抗“关键词填充” (SEO Spam) Common Crawl 中经常混入 SEO 垃圾页,这些页面为了骗取流量,会在正文中隐藏大量热门关键词(如 “Python tutorial free download bitcoin casino”)。 仅靠正则很难发现它们。请设计一个基于统计特征的算法来识别并剔除这类页面。

点击查看提示与参考答案 **提示**: 正常的语言具有一定的词频分布(Zipf 定律)。SEO 垃圾通常会有异常的词对共现(Word Co-occurrence)或极高的困惑度。 **参考答案**: 1. **困惑度 (Perplexity, PPL)**:使用 KenLM 模型计算文本 PPL。这类关键词堆砌的句子在语法上是不通顺的,PPL 会显著高于正常文本。设定阈值(如 PPL > 1000)直接丢弃。 2. **独特词比率 (Vocabulary Richness)**:计算 `Unique Words / Total Words`。如果比率极低(一直在重复几个词),或者比率异常高(全是随机词,没有功能词连接),都是可疑的。 3. **顶级高频词检查**:统计该文档出现频率最高的 5 个词。如果其中包含 `casino`, `loan`, `sex`, `buy` 等垃圾词,且该文档并未被分类为相关主题,则判定为 Spam。

Q5: 时效性与冲突处理 你的 RAG 库中通过 CC 索引了关于 “React Router” 的文档。

点击查看提示与参考答案 **提示**: 不仅要存数据还要存元数据。在检索时,要么过滤旧的,要么让 LLM 知道时间。 **参考答案**: * **存储层**: * 必须在 Metadata 中存储 `crawl_date` 或从文中提取的 `publish_date`。 * 在去重阶段,如果发现 URL 相同但内容不同,**Upsert(覆盖)** 旧数据,只保留最新版。 * **检索层 (Retrieve & Pack)**: * **时间衰减重排 (Time-Decay Reranking)**:在计算相关性分数时,加入时间因子,新文档加分。 * **Prompt 注入上下文**:在组装 Prompt 时,显式带上时间:“[Reference 1 (Date: 2020-01)]: ... [Reference 2 (Date: 2024-03)]: ...”。并系统提示 CC:“如果存在冲突,优先采纳日期较新的信息”。

Q6: 构建代码感知的去重策略 你正在做一个面向代码库的 RAG。你发现 Common Crawl 抓取了大量 GitHub 的镜像站或 Fork 仓库,导致同一个 utils.py 出现了 1000 次。 使用标准的 MinHash 对代码去重效果佳,因为代码中只要改一个变量名或加一行注释,MinHash 签名就可能变化。请设计一种针对代码的更鲁棒的去重指纹算法。

点击查看提示与参考答案 **提示**: 代码的本质是抽象语法树(AST)。去重应忽略变量名、注释和空白,只关注结构。 **参考答案**: 1. **AST 提取**:使用 `tree-sitter` 或 `ast` 模块将代码解析为抽象语法树 (AST)。 2. **归一化 (Normalization)**: * 移除所有注释。 * 将所有变量名替换为占位符(如 `VAR_1`, `VAR_2`)。 * 将所有字符串字面量替换为 `STR_LITERAL`。 3. **结构指纹**:对归一化后的代码序列计算哈希(如 SHA-256)。 4. **效果**:这样,即使开发者把 `def calculate_sum(a, b)` 改名为 `def add(x, y)`,只要逻辑结构没变,指纹就是一样的,从而实现高效去重。

10. 常见陷阱与错误 (Gotchas)

  1. 编码灾难 (Encoding Hell)
    • 现象:提取出的中文变成 é�
    • 原因:Common Crawl 的 WET 默认假设 UTF-8,但很多老旧中文网站是 GBK 或 Big5,且 HTTP Header 声明错误。
    • 解决:不要盲目信任 WET。如果乱码率高,建议使用 ftfy (Fix Text For You) 库自动修复,或者回退到 WARC 格式,使用 cchardet 检测原始二进制流的编码。
  2. PDF 解析的陷阱
    • 现象:WET 文件中包含 PDF 转换来的文本,但全是断行,单词被切断(如 Pyt hon)。
    • 原因:CC 的自动 PDF 转文本工具对排版复杂的文档处理不佳。
    • Rule of Thumb:对于外挂 RAG,尽量过滤掉 WET 中的 PDF 内容(通过 content-type 判断)。PDF 通常需要专门的 Pipeline(如使用 OCR 或专门的 PDF 解析器)才能得到高质量文本,直接用 CC 的转换结果效果很差。
  3. 内存溢出 (OOM) 就在一瞬间
    • 错误all_hashes = set()。试图把 10 亿个指纹放在 Python 的 Set 集合里。
    • 解决
      • 使用 RedisKeyDB 存储去重指纹。
      • 使用 Bloom Filter (布隆过滤器) 做第一层快速去重(极省内存,但有误判率)。
      • 或者使用 磁盘上的键值存储 (如 RocksDB)。
  4. Robots.txt 的“事后诸葛亮”
    • 风险:CC 抓取时网站允许抓取,但现在网站更新了 robots.txt 禁止 AI 使用。
    • 建议:虽然 CC 数据在法律上通常被认为是合规的(Fair Use),但在构建企业级 RAG 时,如果你想规避风险,可以定期(如每月)检查源域名的 robots.txt,如果发现对方禁止了 CCBotGPTBot,主动从你的索引中删除该域名的数据。