那个......RAG 处理 PDF 和 Excel 具体是怎么弄的呀?
听到面试官这个问题时,你的大脑是不是瞬间死机,然后开始疯狂输出:「呃......就是......解析......然后......切片?」
停停停!隔着屏幕我都替你抠出三室一厅了。
别担心,救星来了。今天这篇博客,就是专门为你量身定制的「反懵圈指南」。咱们不背死板的八股文,直接把各种文档格式的解析套路摸得透透的------从 .txt 的编码鬼故事,到扫描件 PDF 的 OCR 流水线,从嵌套 JSON 怎么拍扁,到视频怎么抽出关键帧。保证你看完之后,不仅能把面试官问服,还能让他忍不住点头说------
「这小伙子/小姑娘有点东西!」
目录
- [〇、为什么数据解析才是 RAG 的「水下冰山」](#〇、为什么数据解析才是 RAG 的「水下冰山」)
- 一、基础文本与文档
- [1.1 纯文本 (.txt)](#1.1 纯文本 (.txt))
- [1.2 Word 文档 (.docx)](#1.2 Word 文档 (.docx))
- [1.3 PowerPoint (.pptx)](#1.3 PowerPoint (.pptx))
- [1.4 Excel / CSV (.xlsx .csv)](#1.4 Excel / CSV (.xlsx .csv))
- [1.5 PDF(三种类型,三种策略)](#1.5 PDF(三种类型,三种策略))
- [1.6 Markdown (.md)](#1.6 Markdown (.md))
- [1.7 邮件 (.eml)](#1.7 邮件 (.eml))
- [1.8 EPUB (.epub)](#1.8 EPUB (.epub))
- [二、多媒体数据(多模态 RAG)](#二、多媒体数据(多模态 RAG))
- [2.1 图像:三重提取](#2.1 图像:三重提取)
- [2.2 音频:STT 语音识别](#2.2 音频:STT 语音识别)
- [2.3 视频:关键帧 + 语音轨](#2.3 视频:关键帧 + 语音轨)
- 三、结构化数据(数据库)
- [3.1 关系型数据库:Text2SQL](#3.1 关系型数据库:Text2SQL)
- [3.2 知识图谱:GraphRAG](#3.2 知识图谱:GraphRAG)
- 四、半结构化数据
- [4.1 JSON:嵌套结构的扁平化](#4.1 JSON:嵌套结构的扁平化)
- [4.2 日志:去噪 + 聚合](#4.2 日志:去噪 + 聚合)
- 五、实时动态数据
- 六、企业私有数据
- 七、公开权威数据
- 八、一表总结
阅读导航
┌──────────────────────────────┐
│ ① 基础文本与文档 │
│ .txt .docx .pptx .xlsx .pdf │
│ .md .epub .eml │
└──────────────────────────────┘
│
┌──────────────────────────────┐ ┌────────┴─────────┐ ┌──────────────────────────────┐
│ ② 多媒体(多模态 RAG) │ │ │ │ ③ 结构化数据 │
│ .jpg .mp3 .mp4 │ │ RAG 数据源全景 │ │ MySQL · 知识图谱 │
└──────────────────────────────┘ └────────┬─────────┘ └──────────────────────────────┘
│
┌──────────────────────────────┐ ┌────────┴─────────┐ ┌──────────────────────────────┐
│ ④ 半结构化数据 │ │ │ │ ⑤ 实时动态数据 │
│ JSON .xml 日志 │ │ ⑥ 企业私有数据 │ │ 天气/股票/新闻 API │
└──────────────────────────────┘ │ Confluence Notion │ └──────────────────────────────┘
│ ⑦ 公开权威数据 │
│ Wikipedia arXiv │
└───────────────────┘
〇、为什么数据解析才是 RAG 的「水下冰山」
大多数 RAG 教程教你:装一个 PDF loader → 调 text_splitter → 调 embedding → 调 LLM,完事。
生产环境中的真实情况是:
客户丢过来 500 页扫描件 + 嵌套 8 层合并单元格的 Excel + 一段没有字幕的会议录音,
你的 pipeline 在第一步就「哑火」了。

数据解析占 55%,而「Prompt 与生成」只占 10%。这就是冰山------海面上是优雅的向量检索,海面下是编码检测、版面分析、OCR 纠错、表格语义化、语音转文字......
一、基础文本与文档
核心要点 :基础文档占了 RAG 数据源的 60% 以上。记住三条铁律------① 永远先做编码检测(chardet);②
.docx/.pptx本质是 ZIP 包,解压后按 XML 解析;③ PDF 有三种人格(原生/扫描/混合),先判断类型再选方案。
这是 RAG 的「主食」。看似简单,每种格式都有自己的暗坑。
1.1 纯文本 (.txt)

.txt 解析的核心问题只有一个:编码。中文环境尤其严重------同一个文件可能是 UTF-8、GBK、GB2312、Big5。
python
import chardet
with open("unknown.txt", "rb") as f:
raw = f.read()
result = chardet.detect(raw)
text = raw.decode(result["encoding"] or "utf-8", errors="replace")
# 清洗控制字符、统一换行符
import re
text = re.sub(r"[\x00-\x08\x0b\x0c\x0e-\x1f]", "", text)
text = text.replace("\r\n", "\n").replace("\r", "\n")
| 常见坑 | 表现 | 解决 |
|---|---|---|
| 编码检测失败 | 中文变 "é��误" | chardet.detect_all() 候选逐个试 |
| BOM 头残留 | 开头有 |
text.lstrip("") |
| 超大文件 | OOM | 流式读取 f.read(65536) 循环 |
1.2 Word 文档 (.docx)
.docx 本质上是一个 ZIP 压缩包,内部是一组 XML 文件:

python
from docx import Document
doc = Document("report.docx")
for para in doc.paragraphs:
if para.text.strip():
level = 0
if para.style.name.startswith("Heading"):
level = int(para.style.name.replace("Heading ", ""))
# level=1 是 H1,level=2 是 H2,0 是正文
# 切片时以 H2 为边界,H1 作为文档标题元数据
.docx vs .doc |
新格式 (.docx) | 旧格式 (.doc) |
|---|---|---|
| 内部结构 | ZIP + XML | OLE 二进制 |
| 推荐工具 | python-docx | antiword / libreoffice 转换 |
| 建议 | 直接解析 | 先转 .docx 再解析 |
1.3 PowerPoint (.pptx)
PPT 的文本不是线性的------它散布在文本框、表格和备注中,有绝对坐标。

关键洞察 :PPT 的演讲者备注往往是正文的精华浓缩,对 RAG 的价值远超幻灯片上的要点文字。解析时务必带上。
python
from pptx import Presentation
prs = Presentation("slides.pptx")
for i, slide in enumerate(prs.slides, 1):
# 收集文本框,按 top 坐标(y)排序
shapes = [(s.top, s.text_frame.text)
for s in slide.shapes if s.has_text_frame]
shapes.sort(key=lambda x: x[0])
title = shapes[0][1] if shapes else ""
body = "\n".join(t for _, t in shapes[1:] if t.strip())
notes = ""
if slide.has_notes_slide:
notes = slide.notes_slide.notes_text_frame.text
1.4 Excel / CSV (.xlsx .csv)
表格绝对不能当纯文本切片。一张表被切成碎片后,语义全部丢失。

python
import pandas as pd
df = pd.read_excel("data.xlsx")
# 策略 A:Markdown 化
md = df.to_markdown(index=False)
# 策略 B:行级展开
for _, row in df.iterrows():
parts = [f"{col}是{row[col]}" for col in df.columns]
sentence = ",".join(parts) # "姓名是张三,部门是技术,工资是15000"
| 坑 | 解法 |
|---|---|
| 合并单元格 | df.fillna(method='ffill') 向下填充 |
| CSV 分隔符不一定是逗号 | csv.Sniffer().sniff(sample) 自动检测 |
| 数字丢失前导零 | 读入时指定 dtype=str |
1.5 PDF(三种类型,三种策略)
PDF 是 RAG 数据解析的「终极 Boss」。它有三种截然不同的人格:

三种解析引擎对比:
| 引擎 | 原生 PDF | 扫描 PDF | 表格提取 | 中文 OCR |
|---|---|---|---|---|
| PyMuPDF (fitz) | ★★★★★ | ✗ | ★★★☆ | ★★★★ |
| pdfplumber | ★★★★ | ✗ | ★★★★★ | ★★★☆ |
| PaddleOCR | ✗ | ★★★★★ | ✗ | ★★★★★ |
| Tesseract | ✗ | ★★★☆ | ✗ | ★★☆ |
| LlamaParse | ★★★★ | ★★★☆ | ★★★★ | ★★★☆ |
原生 PDF 代码:
python
import fitz
doc = fitz.open("native.pdf")
blocks = []
for page in doc:
text_dict = page.get_text("dict")
for block in text_dict["blocks"]:
if block["type"] == 0: # 文本块
text = "".join(s["text"] for line in block["lines"]
for s in line["spans"])
if text.strip():
blocks.append({
"page": page.number + 1,
"text": text.strip(),
"y": block["bbox"][1] # 垂直位置,用于排序
})
# 按页面+垂直位置排序 → 阅读顺序
blocks.sort(key=lambda b: (b["page"], b["y"]))
扫描件 PDF 代码:
python
from pdf2image import convert_from_path
from paddleocr import PaddleOCR
images = convert_from_path("scanned.pdf", dpi=300)
ocr = PaddleOCR(use_angle_cls=True, lang="ch", show_log=False)
full_text = []
for i, img in enumerate(images, 1):
result = ocr.ocr(f"/tmp/page_{i}.png", cls=True)
if result and result[0]:
page_text = "\n".join(line[1][0] for line in result[0])
full_text.append(f"--- Page {i} ---\n{page_text}")
1.6 Markdown (.md)
Markdown 是对 RAG 最友好的格式------标题天然就是切片边界,代码块有语言标注,链接有 URL 上下文。
切片策略:
# H1 → 文档级元数据(不参与切片,作为 document title)
## H2 → 切片边界(每个 H2 区块独立成一个 chunk)
### H3 → 作为 H2 chunk 的子结构保留
```代码块 → 保留语言标注(如 "python"),内容不参与切片分割
python
import re
with open("doc.md") as f:
content = f.read()
# 按 ## 二级标题切分
sections = re.split(r"\n(?=## )", content)
for sec in sections:
m = re.match(r"^#{1,6}\s+(.+)", sec)
title = m.group(1) if m else ""
body = sec[m.end():].strip() if m else sec.strip()
# 每个 (title, body) 是一个 chunk
1.7 邮件 (.eml)
邮件的独特价值:元数据极其丰富------发件人、收件人、时间、主题、正文、附件------且在企业知识库中,大量隐性知识只存在于邮件中。
python
import email
from email import policy
from bs4 import BeautifulSoup
with open("message.eml", "rb") as f:
msg = email.message_from_binary_file(f, policy=policy.default)
# 元数据:检索时可作为过滤条件
meta = {k: msg[k] for k in ["From", "To", "Subject", "Date"]}
# 正文:优先 text/plain,降级 text/html
body = ""
for part in msg.walk():
if part.get_content_type() == "text/plain":
body = part.get_content(); break
elif part.get_content_type() == "text/html" and not body:
body = BeautifulSoup(part.get_content(), "lxml").get_text()
1.8 EPUB (.epub)
EPUB 也是 ZIP 包,内部是 XHTML 章节。用 ebooklib 按章节提取即可。
python
from ebooklib import epub
from bs4 import BeautifulSoup
book = epub.read_epub("book.epub")
for item in book.get_items():
if item.get_type() == 9: # ITEM_DOCUMENT
text = BeautifulSoup(item.get_content(), "lxml").get_text()
二、多媒体数据(多模态 RAG)
核心要点:多模态 RAG 的统一范式是「先转文字,再进 RAG」。图像用 OCR+视觉描述+EXIF 三重提取,音频用 Whisper STT,视频是两者的组合,关键是按时间轴对齐。
核心范式:先转为文本,再进入 RAG 流水线。

2.1 图像:三重提取
一张图片可以提取三种互补文本,拼接后形成完整语义:
| 提取层 | 工具 | 输出示例 |
|---|---|---|
| ① OCR 文字 | PaddleOCR | "欢迎光临 · 营业时间 9:00-22:00" |
| ② 视觉描述 | GPT-4V / Qwen-VL | "一张街边店铺的照片,玻璃门上贴着营业时间告示......" |
| ③ EXIF 元数据 | Pillow | "拍摄时间: 2026-07-01, GPS: 39.9N 116.4E" |
三者拼接 → 一个包含「画面里有什么 + 画面上写了什么 + 何时何地」的完整 chunk。
python
from paddleocr import PaddleOCR
from PIL import Image
import base64
ocr = PaddleOCR(use_angle_cls=True, lang="ch", show_log=False)
result = ocr.ocr("photo.jpg", cls=True)
ocr_text = "\n".join(line[1][0] for line in result[0]) if result[0] else ""
# 视觉描述 → 调用多模态 LLM API
img_b64 = base64.b64encode(open("photo.jpg", "rb").read()).decode()
description = call_vision_llm(img_b64) # 返回自然语言描述
# EXIF
exif = Image.open("photo.jpg")._getexif() or {}
2.2 音频:STT 语音识别

python
import whisper
model = whisper.load_model("medium") # 中文用 medium,英文可用 small
result = model.transcribe("audio.wav", language="zh", task="transcribe")
# 每段带时间戳
for seg in result["segments"]:
print(f"[{seg['start']:.1f}s - {seg['end']:.1f}s] {seg['text']}")
2.3 视频:关键帧 + 语音轨
视频 = 图像序列 + 音频轨。解析策略是双通道并行,时间轴对齐:

python
import cv2, subprocess
# 提取音频
subprocess.run(["ffmpeg", "-i", "video.mp4", "-vn", "-ar", "16000",
"-ac", "1", "audio.wav", "-y"], capture_output=True)
audio_text = transcribe("audio.wav") # 复用 2.2 的 STT
# 关键帧:每 5 秒取一帧
cap = cv2.VideoCapture("video.mp4")
fps = cap.get(cv2.CAP_PROP_FPS)
for t in range(0, int(cap.get(cv2.CAP_PROP_FRAME_COUNT)), int(fps * 5)):
cap.set(cv2.CAP_PROP_POS_FRAMES, t)
_, frame = cap.read()
cv2.imwrite(f"/tmp/frame_{t/fps:.1f}s.jpg", frame)
三、结构化数据(数据库)
核心要点:结构化数据不要 dump 成文本。关系型数据库走 Text2SQL------把 Schema 注入 Prompt,让 LLM 写 SQL 实时查。知识图谱走 GraphRAG------实体关系 + 向量语义混合检索。
3.1 关系型数据库:Text2SQL
对于 MySQL / PostgreSQL,不要把数据 dump 成文本。用 Text2SQL 让 LLM 生成查询语句实时检索:

python
from sqlalchemy import create_engine, inspect, text
def get_schema(engine):
"""提取所有表的列信息作为 LLM 上下文"""
insp = inspect(engine)
return "\n".join(
f"TABLE {t}:\n " + "\n ".join(
f"{c['name']} ({c['type']})" for c in insp.get_columns(t)
)
for t in insp.get_table_names()
)
# Prompt = Schema + 用户问题 → LLM → SQL → execute → 结果
3.2 知识图谱:GraphRAG
传统 RAG 找「相似文本」,GraphRAG 还能找「实体关系」:

GraphRAG 三阶段:离线构建 (文本 → 实体抽取 → 图谱入库)→ 在线检索 (向量 + 图谱混合查询)→ 生成(双源上下文注入 LLM)。
四、半结构化数据
核心要点:JSON 的核心问题是深层嵌套------LLM 读嵌套 JSON 会「迷失」。解决方法是扁平化:对象列表转 Markdown 表格。日志的核心问题是噪声太大------先过滤 DEBUG/INFO 级别,再聚合相同错误。
4.1 JSON:嵌套结构的扁平化
JSON 有深层嵌套。直接当纯文本给 LLM → 嵌套深了 LLM 会「迷失」。正确做法是扁平化:
原始 JSON: 扁平化后:
{ | company | name | dept | salary |
"company": "Apple", |---------|------|------|--------|
"employees": [ | Apple | 张三 | 技术 | 15000 |
{"name":"张三","dept":"技术"}, | Apple | 李四 | 销售 | 12000 |
{"name":"李四","dept":"销售"} |
] LLM 对表格的理解准确度 >> 嵌套 JSON
}
python
def flatten_json(data, max_depth=3):
if isinstance(data, list) and data and isinstance(data[0], dict):
# 对象列表 → Markdown 表格
keys = list(data[0].keys())[:10]
lines = ["| " + " | ".join(keys) + " |",
"| " + " | ".join(["---"] * len(keys)) + " |"]
for item in data[:100]:
lines.append("| " + " | ".join(str(item.get(k,""))[:50] for k in keys) + " |")
return "\n".join(lines)
return str(data)
4.2 日志:去噪 + 聚合
日志的核心问题不是解析,是噪声太大。Info 级别的常规日志没有检索价值,需要过滤:
原始日志 (10 万行) → 正则提取级别 → 过滤 DEBUG/INFO
→ 保留 WARN/ERROR + 异常堆栈 → 相同错误聚合计数
→ "Connection timeout (×42)" → 干净的 chunks
五、实时动态数据
核心要点:时效性数据不要提前索引。用 Agent 的 Function Calling 机制在查询时实时拉取------这是「RAG + Agent」融合架构的典型场景。
对于天气、股票、新闻等时效性数据,不提前索引,查询时实时拉取。这是 Agent 的 Function Calling 机制,不是传统 RAG 的向量检索。

六、企业私有数据
核心要点:企业 RAG 和公网 RAG 最大的区别是权限。Confluence/Notion 解析时要保留页面→用户组的权限映射,检索时做权限过滤。业务系统数据走 Text2SQL,不要全量 dump。
| 平台 | 特有挑战 | 策略 |
|---|---|---|
| Confluence / Notion | 层级结构 + 富文本 + 权限 | API 递归拉取 → 保留空间/页面层级元数据 → 权限映射到 RAG 过滤 |
| 飞书 / 钉钉文档 | 国内 API 限频 | 增量同步 + Webhook 监听变更 → 只处理 diff |
| CRM / ERP | 结构化 + 自然语言混查 | 静态知识(手册)→ 向量检索;动态数据(客户列表)→ Text2SQL |
关键点:企业数据的 RAG 必须带权限过滤------CEO 能搜到的内容,实习生不应该搜到。这是企业 RAG 和公网 RAG 最大的区别。
七、公开权威数据
核心要点:公开数据优先用预清洗数据集(HuggingFace / arXiv API),不要自己爬。学术和法律法规要按章节/条款号切片,保留元数据用于引用溯源。
- 维基百科 :直接用 HuggingFace 预清洗数据集
wikimedia/wikipedia,不要自己爬 - arXiv :API 免费、返回结构化元数据(标题/作者/摘要/分类)。保留元数据用于引用溯源
- 政府/法律数据 :格式高度规范但冗长。建议按法条编号切片,不要按字数硬切
八、一表总结
| 数据源 | 核心工具 | 核心挑战 | RAG 策略 | 难度 |
|---|---|---|---|---|
| .txt | chardet | 编码鬼故事 | 直接切片 | ★ |
| .docx | python-docx | 表格+图片混杂 | 结构感知切片 | ★★ |
| .pptx | python-pptx | 文本框阅读顺序 | 按幻灯片切片 | ★★ |
| .xlsx | pandas | 合并单元格 | Markdown 化 / Text2SQL | ★★ |
| 原生 PDF | PyMuPDF | 版面分析 | 坐标排序+按章节切片 | ★★★ |
| 扫描 PDF | PaddleOCR | OCR 识别率 | OCR→纠错→切片 | ★★★★ |
| .md | 标准库 | 几乎没有 | 按标题层级切片 | ★ |
| .epub | ebooklib | XHTML 清洗 | 按章节切片 | ★★ |
| .eml | email 模块 | 附件递归 | 元数据+正文 | ★★ |
| 图片 | PaddleOCR+GPT-4V | 三合一融合 | 多模态向量 | ★★★★ |
| 音频 | Whisper | 多人说话人分离 | STT→分段 | ★★★ |
| 视频 | cv2+Whisper | 帧去重+时间轴对齐 | 双通道并行 | ★★★★★ |
| 数据库 | SQLAlchemy | Schema 注入设计 | Text2SQL | ★★★ |
| 知识图谱 | Neo4j | 实体消歧 | GraphRAG | ★★★★★ |
| JSON | json 模块 | 深层嵌套 | 扁平化→表格 | ★★ |
| 日志 | 正则+聚合 | 噪声 | 过滤+聚合 | ★★ |
| 实时 API | requests | 时效性 | Agent Function Call | ★★★ |
| Confluence | REST API | 权限映射 | 层级感知+权限过滤 | ★★★★ |
| Wikipedia | datasets | 更新频率 | 预训练数据集 | ★ |
| arXiv | arxiv API | 公式/图表 | 元数据增强 | ★★★ |
One more thing :一个好的 RAG 系统,70% 的代码在数据管道上,20% 在检索策略上,只有 10% 在 Prompt 上。但大多数教程把比例反过来教。如果你在面试中能把上面这个表的内容讲出来------面试官会知道你在生产环境做过 RAG,而不只是跑过 demo。
写在最后
如果这篇博客帮你搞清楚了 RAG 数据解析的全貌,不妨:
- 👍 点赞------让更多人看到这份「反懵圈指南」
- ⭐ 收藏------面试前翻出来看一遍,底气至少 +50%
- 💬 评论区聊聊------你遇到过最头疼的格式是什么?扫描件 PDF 还是嵌套 8 层的 JSON?我赌扫描件 PDF 赢
关注我,持续输出「能直接写到简历上」的 AI 实战内容。
编程星辰海· 2026-07 · MIT License