封面

Day09|大模型知识库建设前置之数据清洗——数据是 AI 的粮食,垃圾进则垃圾出

前言:教程里的语料是干净 txt,企业里是一锅粥

先甩一个可能反直觉的数字。

多年业界调研都指向同一件事:数据团队 / 数据科学家 60%~80% 的时间,花在"找数据、洗数据"上,而不是建模。AI 项目失败,根因也大多落在数据质量,而不是模型不够强。

这个数字我第一次看到时是不信的。直到我第一次给公司搭企业 RAG,自信满满上了线——结果用户一问,AI 一本正经地引用了一份 2018 年的过期表格,而那份表格,藏在一个扫描件 PDF 的第三页。

从那以后我学会一件事:先洗数据,再谈模型。

教程里的语料,永远是干干净净的 txt。企业里的语料呢?是 Word 批注、Excel 合并单元格、双栏 PDF、PPT 截图、扫描件 jpg 一锅粥。十几种格式,每种脏法都不一样。

这就引出 AI 领域最古老也最狠的一条铁律——Garbage In, Garbage Out(垃圾进,垃圾出)

很多人觉得大模型这么强,数据脏一点无所谓吧?恰恰相反。模型越强,对数据越敏感。 弱模型遇到脏数据,输出平庸;强模型遇到脏数据,输出的是"自信的胡说"——危害大十倍。

Day06 我讲过 RAG 检索。今天补上拼图里缺的那一块:为什么你 RAG 效果差,八成不是检索算法的锅,是语料没洗干净。

这篇文章按企业真实场景走一遍:

  1. 先建诊断框架——脏数据的"五宗罪"
  2. 按文件类型分类实战——纯文本、办公文档、PDF 与图片,各怎么洗
  3. 治本——如何从源头避免脏数据,而不是事后擦屁股

企业数据全景漏斗


PART 01:脏数据的五宗罪——先学会体检,再动刀

动手洗之前,先搞清楚"脏"有哪些种类。我把企业里最常见的脏数据归成五宗罪

  • ① 缺失与空值:字段为空、只有标题没有正文、只有模板没有内容
  • ② 重复数据:同一份文档被爬 10 次,严重污染检索结果和向量库
  • ③ 格式混乱:PDF / 网页提取后的换行错乱、表格散架、段落粘连
  • ④ 编码与噪音:乱码、HTML 标签残留、不可见字符、全角半角混用
  • ⑤ 隐私与合规:手机号、身份证、邮箱、地址直接进语料——最容易忽略,也最致命

记住这五条。后面不管你处理什么文件类型,都是在跟这五种脏打交道,只是每种格式的"重灾区"不一样。

一个心法:清洗前先体检,别上来就套模板。 统计一下缺失率、重复率,随机抽几条看看长什么样,再决定怎么洗。

脏数据五宗罪脑图


PART 02:纯文本与结构化数据(txt / markdown / html / json / csv)——别以为最简单就最干净

从最简单的说起。但最简单,不等于最干净。

txt:第一杀手是编码

txt 的头号问题是编码。同一份文件,可能是 GBK,可能是 UTF-8,还可能是带 BOM 的 UTF-8。读错编码,就是满屏乱码。

import re
import unicodedata
from charset_normalizer import from_path

def read_txt_clean(path: str) -> str:
    # 1. 自动探测编码,统一读成 utf-8
    raw = from_path(path).best().bytes.decode("utf-8", errors="ignore")
    # 2. NFKC 规范化:全角转半角、干掉零宽字符、统一兼容字符
    text = unicodedata.normalize("NFKC", raw)
    # 3. 折叠多余空白和连续空行
    text = re.sub(r"[ \t]+", " ", text)
    text = re.sub(r"\n{3,}", "\n\n", text)
    return text.strip()

这里有个坑我踩过无数次:全角逗号","和半角逗号",",看起来一样,其实是两个字符。 它们会让去重完全失效——你以为去重了,结果同一句话因为一个标点存了两份。

NFKC 规范化就是救场的。它会把全角字符统一转成半角,让"看起来一样"的东西"真的变一样"。

markdown / html:导航栏和广告是噪音重灾区

网页抓取下来的内容,正文往往混着导航栏、页脚、广告、JS 代码。直接喂给模型,等于让 AI 读一份贴满小广告的报纸。

别自己写正则去抠。用 trafilatura,专门做网页正文提取,一行搞定:

import trafilatura

def extract_main_content(html: str) -> str:
    # 一行干掉导航栏、广告、页脚,只留正文
    text = trafilatura.extract(
        html, include_comments=False, include_tables=True
    )
    return text or ""

json / csv:schema 不一致是慢性毒药

结构化数据看似规整,真正的杀手是字段对不上:这一批 title 是字符串,下一批变成了 null;这行的 date 是 2024-01-01,下一行是 2024年1月1日

别等数据进了向量库才发现问题。入库前就用 schema 校验卡住它,像代码 CI 一样:

import pandas as pd
import pandera as pa
from pandera import Column, DataFrameSchema, Check

schema = DataFrameSchema({
    "title":   Column(str, checks=Check.str_length(min_value=1)),    # 非空
    "content": Column(str, checks=Check.str_length(min_value=10)),   # 正文至少10字
    "date":    Column(pa.DateTime, nullable=True),
})

df = pd.read_csv("articles.csv")
try:
    schema.validate(df, lazy=True)
    print("✅ 数据合规")
except pa.errors.SchemaErrors as err:
    print(f"❌ 拦截 {len(err.failure_cases)} 行脏数据")

脏数据在门口就被拦下,总好过它溜进模型后再让你 debug 一整夜。


PART 03:办公文档三兄弟(Word / Excel / PPT)——企业语料的主战场

企业里的知识,八成躺在办公文档里。这一节是真正的实战。

Word Excel PPT 清洗对比

Word:批注和修订会"复活"脏内容

Word 文档最坑的地方,是修订和批注。你以为删掉的内容,其实还躺在文档里,只是被标记成"删除态"。如果你的提取代码不处理,已经删掉的脏内容会"复活",重新进语料。

from docx import Document

def extract_docx_text(path: str) -> str:
    doc = Document(path)
    paragraphs = []
    for p in doc.paragraphs:
        text = p.text.strip()
        if text:  # python-docx 的 .text 默认不含已删除修订,但仍建议先"接受所有修订"
            paragraphs.append(text)
    return "\n\n".join(paragraphs)

实操建议:批量处理前,先用脚本或 Word 自带功能"接受所有修订",再提取。别带着满篇红线就上。

Excel:合并单元格和公式是两个地雷

Excel 有两个经典地雷:

地雷一:公式 vs 公式结果。 一个单元格存的是 =A1+B1 这个公式字符串,还是它算出来的 42?读错就是灾难——下游拿到的全是公式,根本不是数据。

地雷二:合并单元格。 看起来一个值占了好几格,实际只有左上角有值,其它是空。直接读会丢一大片数据。

import openpyxl

def read_excel_values(path: str):
    # data_only=True:取公式计算结果,不取公式字符串
    wb = openpyxl.load_workbook(path, data_only=True)
    rows = []
    for ws in wb.worksheets:
        for row in ws.iter_rows(values_only=True):
            # 过滤整行全空的废行
            if any(c is not None and str(c).strip() for c in row):
                rows.append(row)
    return rows

data_only=True 这一个参数,能帮你省掉无数个"为什么我读出来全是 None"的深夜。

PPT:每页都碎,备注里藏着金子

PPT 的特点是碎片化——每页就那么几个字,单独看几乎没有信息量。但有个地方常被忽略:备注栏。演讲者把真正想说的、详细的解释,都写在备注里。丢掉备注,等于丢掉一半内容。

from pptx import Presentation

def extract_pptx(path: str):
    prs = Presentation(path)
    pages = []
    for i, slide in enumerate(prs.slides, 1):
        texts = []
        for shape in slide.shapes:
            if shape.has_text_frame:
                texts.append(shape.text_frame.text)
        note = slide.notes_slide.notes_text_frame.text if slide.has_notes_slide else ""
        pages.append({"page": i, "content": "\n".join(texts), "note": note})
    return pages

办公文档的"脏",往往不在字面,藏在格式结构里。所以你必须按结构去解析,而不是当纯文本一把梭。


PART 04:最硬的两块骨头——PDF 与图片(pdf / png / jpg / 扫描件)

终于来到企业最痛的地方。前面那些都算好洗的,PDF 和图片才是真正费时费力的。

PDF与图片处理决策流程图

PDF:第一步永远先判断"它有没有文本层"

PDF 分两种,清洗路线完全不同:

  • 文本层 PDF(数字原版导出的):可以直接提取文字
  • 扫描件 PDF(纸质扫描 / 拍照转的):没有文本层,必须走 OCR

怎么判断?提一下试试,提不出字,就是扫描件:

import pdfplumber

def is_scanned_pdf(path: str) -> bool:
    with pdfplumber.open(path) as pdf:
        text = pdf.pages[0].extract_text() or ""
    return len(text.strip()) < 20  # 提不出字 → 大概率扫描件

文本层 PDF 用 pdfplumber,还能把表格还原成结构化数据:

def extract_pdf(path: str):
    all_text, tables = [], []
    with pdfplumber.open(path) as pdf:
        for page in pdf.pages:
            all_text.append(page.extract_text() or "")
            tables.extend(page.extract_tables())  # 表格单独还原,保留行列结构
    return "\n".join(all_text), tables

PDF 的坑还有一堆:双栏排版(按行读会跨栏串台)、跨页断句(一句话被切成两半)、页眉页脚噪音。这些没有银弹,得针对你的文档类型调参。

复杂 PDF 别硬啃——上 MinerU

pdfplumber 提文本、自己再接 OCR,是"拼积木"。简单 PDF 够用,但企业里的 PDF 常常是双栏论文、嵌套表格、数学公式、图文混排——每个坑都得自己填,填到怀疑人生。

这种场景别硬拼,直接上 MinerU——上海人工智能实验室开源的文档解析工具,一个工具把 PDF(也支持 Word / PPT)变成结构化 Markdown,专治各种"硬骨头"。

它一个工具干完四件事:

  • 版面分析:自动识别标题、段落、图、表、页眉页脚,恢复正确阅读顺序——双栏不再串台
  • 表格还原:复杂表格直接输出 Markdown 表格,行列结构完整
  • 公式识别:数学公式转成 LaTeX——这是 pdfplumber 想都别想的能力
  • OCR:扫描件、图片也能解析,不用你手动分流文本层

输出是干干净净的 Markdown,直接喂 RAG。前面"文本层还是扫描件"的判断,MinerU 内部自动处理,你根本不用操心。

用法也很省心(参数以官方仓库 opendatalab/MinerU 为准):

pip install -U "mineru[core]"
mineru -p report.pdf -o output_dir/

那什么时候用 pdfplumber,什么时候上 MinerU?一句话:简单文本 PDF 用 pdfplumber(轻、快),复杂或扫描件 PDF 用 MinerU(重,但省心)。 当你手里有一堆合同、报告、论文 PDF 要清洗进 RAG,MinerU 基本是当下绕不开的选择。

图片 / 扫描件:OCR 的老路,和多模态大模型的新路

图片(png / jpg)和扫描件,本质都得把图变成字。有两条路:

老路:OCR。 pytesseract 是通用选择,中文场景 PaddleOCR 更准。

新路(2026 主流):直接丢给多模态大模型读图。 GPT-4o、Claude 这类模型能直接"看懂"图片,连图表、公式、手写都能读。省掉一整套 OCR 管线:

import base64
from anthropic import Anthropic

client = Anthropic()

def image_to_text(image_path: str) -> str:
    with open(image_path, "rb") as f:
        b64 = base64.b64encode(f.read()).decode()
    resp = client.messages.create(
        model="claude-opus-4-8",  # 替换成你在用的多模态模型
        max_tokens=2000,
        messages=[{
            "role": "user",
            "content": [
                {"type": "image",
                 "source": {"type": "base64", "media_type": "image/png", "data": b64}},
                {"type": "text",
                 "text": "把这张图里的文字、表格、公式结构化提取出来,保留原始顺序。"}
            ]
        }]
    )
    return resp.content[0].text

这条路还有一个隐形好处:多模态模型理解内容,而 OCR 只是"识别"字符。遇到模糊图、复杂表格,差距立现。

最后提一个隐私坑:图片的 EXIF 信息可能带 GPS 定位、拍摄设备、时间。上传图片给模型前,先把 EXIF 剥掉,别让你的训练数据泄漏公司坐标。


PART 05:治本——如何从源头避免脏数据

到这里你可能会想:清洗这么麻烦,能不能别让它脏?

能。清洗是治标,规范是治本。 源头少制造一份脏数据,下游就少洗十份。

脏数据预防闭环图

我总结成五件事:

① 采集就要立规矩。 统一文档模板、字段必填、定好 schema。别让每个人按自己习惯填,那是制造脏数据的流水线。

② 入库即校验(数据 CI)。 上一节的 Pandera 就是干这个的。让脏数据在进库那一刻就被拦下,像代码过 CI 一样——进不了库的脏数据,永远不会污染你的模型。

③ 隐私前置,别事后补救。 采集 / 上传那一刻就脱敏,而不是等数据堆成山了再全库扫一遍:

import re

PATTERNS = {
    "phone":   (re.compile(r"1[3-9]\d{9}"),          "[手机号]"),
    "id_card": (re.compile(r"\d{17}[\dXx]"),         "[身份证]"),
    "email":   (re.compile(r"[\w.-]+@[\w.-]+\.\w+"), "[邮箱]"),
}

def mask_pii(text: str) -> str:
    for name, (pat, rep) in PATTERNS.items():
        text = pat.sub(rep, text)
    return text

# mask_pii("联系我:13800138000,邮箱 abc@x.com")
# → "联系我:[手机号],邮箱 [邮箱]"

④ 数据血缘与版本管理。 每份数据能追溯来源、能回滚版本(工具如 DVCLakeFS)。哪天模型突然变蠢,你能定位到是哪一批脏数据混进来的。

⑤ 组织层面定 SLO、定 owner。 工具再全,没人对数据质量负责也白搭。脏数据有没有人兜底,比有没有工具更关键。

记住这句话:最好的清洗,是让数据从一开始就不脏。


结尾:模型决定上限,数据决定下限

回头盘点这篇的核心:

  • 脏数据有五宗罪:缺失、重复、格式、编码噪音、隐私泄漏
  • 按文件类型对症下药:纯文本治编码、办公文档治结构、PDF 与图片先判文本层
  • 清洗是治标,规范是治本——采集规范、入库校验、隐私前置、血缘版本、质量 SLO

我见过太多团队,模型换了三代,效果还是上不去。一查,语料还是三年前那堆没洗过的扫描件。

模型决定上限,数据决定下限——而大多数人卡在下限上。

你喂给 AI 什么,它就还你什么。粮食干净了,AI 才吃得香。

互动时间:你做 AI 项目时,被哪种格式的脏数据坑得最惨?是合并单元格、双栏 PDF,还是别的?来评论区聊聊,我挨个帮你拆。


下一篇 Day10 预告:大模型微调入门——数据洗干净了(Day09),下一步就是拿这批干净数据,训练一个属于你自己的模型。LoRA 和全参微调怎么选、什么场景才该微调。关注小刘檀木,不错过每一篇。

— END —

小刘檀木 · 帮普通人把 AI 学进简历

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐