采集、清洗与文档解析
正在读取学习记录…
数据摄取是把原始资料转成可检索的文档记录。采集负责取得资料,解析负责识别正文、标题等结构,清洗负责去掉重复空白等噪声。入库后还要能回答“这段文字来自哪份资料、哪次更新”。
用退款资料观察入库
退款政策会重复采集,也会从“三至五个工作日”更新为“二至四个工作日”。我们保留来源地址形成稳定文档编号;内容摘要用来区分重复采集与实际变更。
固定输入包含四次采集。source是逻辑来源地址,raw是取得的原文字;清洗后的text才进入正文。保存记录时增加digest内容摘要和version版本号。store是一份保存在内存中的字典,主程序重新运行会从空库开始,便于重复观察同一流程。
保存完整程序并运行
先在 PowerShell 创建并进入学习目录:
mkdir "F:\学习\l5-ingestion" -Force
cd "F:\学习\l5-ingestion"保存下面的文件后,在这个终端运行 dir 核对文件名,再执行本节给出的运行命令。
新建学习目录,例如 F:\学习\l5-ingestion。将下面代码完整保存为 ingestion_demo.py;代码包含固定资料和运行入口,保存后就能执行。
先在 PowerShell 创建并进入学习目录:
mkdir "F:\学习\l5-ingestion" -Force
cd "F:\学习\l5-ingestion"保存下面的文件后,在这个终端运行 dir 核对文件名,再执行本节给出的运行命令。
"""清洗退款资料,以稳定文档编号实现重复采集与更新。"""
import hashlib
import re
RAW = [
("policy://A/refund", "退款 通常三至五个工作日到账。"),
("policy://A/refund", "退款 通常三至五个工作日到账。"),
("policy://A/refund", "退款 通常二至四个工作日到账。"),
("policy://A/empty", " "),
]
def clean(text):
# 合并空白,保留业务期限、标点与正文顺序。
return re.sub(r"\s+", " ", text).strip()
def ingest(store, source, raw):
text = clean(raw)
if not text:
return "跳过空正文", None
# 来源标识决定文档身份;内容变化时文档编号仍保持一致。
doc_id = hashlib.sha256(source.encode("utf-8")).hexdigest()[:12]
digest = hashlib.sha256(text.encode("utf-8")).hexdigest()
previous = store.get(doc_id)
if previous and previous["digest"] == digest:
return "重复资料", doc_id
version = previous["version"] + 1 if previous else 1
store[doc_id] = {"source": source, "text": text,
"digest": digest, "version": version}
return ("更新" if previous else "新增"), doc_id
def main():
store = {}
for source, raw in RAW:
action, doc_id = ingest(store, source, raw)
print(f"{action}:{doc_id or '无文档编号'}")
for doc_id, doc in store.items():
print(f"库内 {doc_id} v{doc['version']} [{doc['source']}]:{doc['text']}")
if __name__ == "__main__":
main()在保存文件的文件夹地址栏输入 powershell 并回车,再执行:
macOS:先在 Python 篇的学习文件夹中打开终端,执行 source .venv/bin/activate。再输入 cd ,把当前例子的文件夹拖入终端并回车,运行下面的命令。
cd "F:\学习\l5-ingestion"
ls
python -X utf8 ingestion_demo.py使用其他目录时,把第一行换成自己的路径。dir 应能列出 ingestion_demo.py。终端输出如下:
新增:25fa200c0aa0
重复资料:25fa200c0aa0
更新:25fa200c0aa0
跳过空正文:无文档编号
库内 25fa200c0aa0 v2 [policy://A/refund]:退款 通常二至四个工作日到账。第一次采集创建v1;第二次只有空白差异,清洗后的正文一致,版本保持不变;第三次到账期限改变,同一编号更新为v2。空白文档没有进入库。最后库里只有一份退款政策,正文是最新采集的二至四个工作日。
从来源身份到内容版本
clean先合并空白,再去首尾空白。ingest把来源转换为固定编号,把清洗后的正文转换为内容摘要。摘要是用哈希函数得到的内容指纹:相同文字得到相同值,用来比较内容是否变化。来源身份与内容指纹承担不同任务,因此改正文不会生成第二份逻辑文档。
演示使用12位编号便于阅读;大规模入库应保留足够长度或数据库唯一约束,处理哈希碰撞。两个地址可能指向同一资料,要先做来源地址规范化或维护业务文档ID;两份资料正文相同但权限不同,也不能仅按内容删除其中一份。
真实PDF解析还需要处理页码、页眉、表格与扫描件。扫描页需要OCR,即从图片识别文字;表格应把列标题和数据行保留在一起,避免退款天数失去适用渠道。清洗规则应谨慎删除页眉,不能误删“生鲜除外”这样的条件。
增量采集应保留更新时间、解析器版本、来源版本和失败原因。此程序的更新顺序由固定输入决定;并发采集需要版本比较与事务,防止迟到的旧版本覆盖新版本。删除事件还要传给索引层,下一篇的分块记录也需要继承这些来源信息。
改一处输入,核对机制变化
只把RAW第三项的“二至四”改为“三至五”,运行 python -X utf8 ingestion_demo.py。第三次采集变为重复资料,最后仍是v1,编号保持25fa200c0aa0。恢复二至四后重跑,第三次才更新为v2。
运行配套检查
把下面测试保存为 test_ingestion_demo.py,与主程序放在同一目录。5项测试检查上面的实际处理结果,也覆盖重复采集、内容更新和空正文。
查看测试代码:test_ingestion_demo.py
"""核对采集、清洗与文档解析的正常结果与具体边界。"""
import unittest
import ingestion_demo as demo
class DemoTests(unittest.TestCase):
def test_whitespace_cleaning(self):
# 空白清洗后保留完整业务文字。
self.assertEqual(demo.clean(" 退款\n 到账 "), "退款 到账")
def test_repeated_content_does_not_increment_version(self):
# 同来源相同正文保持原版本。
store = {}
_, first = demo.ingest(store, "s", "退款 到账")
action, second = demo.ingest(store, "s", "退款 到账")
self.assertEqual((action, first), ("重复资料", second))
self.assertEqual(store[first]["version"], 1)
def test_changed_content_replaces_same_document(self):
# 期限更新替换同一文档,版本递增。
store = {}
_, key = demo.ingest(store, "s", "三至五日")
action, updated = demo.ingest(store, "s", "二至四日")
self.assertEqual((action, updated), ("更新", key))
self.assertEqual((len(store), store[key]["version"]), (1, 2))
def test_empty_document_never_enters_store(self):
# 空正文不创建任何库记录。
store = {}
self.assertEqual(demo.ingest(store, "s", " ")[0], "跳过空正文")
self.assertEqual(store, {})
def test_distinct_sources_keep_separate_identity(self):
# 不同来源的相同文字仍保留各自身份。
store = {}
demo.ingest(store, "a", "同一正文")
demo.ingest(store, "b", "同一正文")
self.assertEqual(len(store), 2)
if __name__ == "__main__":
unittest.main()python -X utf8 -m unittest -v test_ingestion_demo.py测试名称后应显示 ok,末尾是 Ran 5 tests 和 OK。
运行时怎样定位问题
| 看到的情况 | 检查与操作 |
|---|---|
| 找不到 ingestion_demo.py | 运行 dir,切换到保存主程序的目录 |
| 重复资料变成多个编号 | 确认source是否多了追踪参数,先规范化来源地址 |
| 更新后仍检索到旧期限 | 向索引同步新版本并删除旧块,核对更新任务 |
| 解析后表格条件丢失 | 保留列标题与行关系,先核对解析正文再分块 |
资料记录稳定后,分块需要继续保留文档身份、版本与权限。
参考阅读:LlamaIndex 源码与检索流程。阅读时可以沿文档、节点、索引和检索入口对照这里的数据流。
课后练习展开习题收起习题
正在读取本地记录…
不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。
选择题 · 先判断边界
关于“采集、清洗与文档解析”,哪种做法更稳妥?
对应知识点:L5-ingestion
简答题 · 解释失败
写出一个失败输入并说明记录字段。
对应知识点:L5-ingestion
代码题 · 边界测试
补充正常、边界、失败三组输入和预期结果。
对应知识点:L5-ingestion
场景题 · 结果未知
证据不足时如何设计状态和人工接管?
对应知识点:L5-ingestion
下一篇:分块、元数据与权限继承。
描述会发送给本站维护者,用于修订文章,保存90天。请只填写文章问题,省略个人信息、密钥和内部资料。