当前位置: 首页 > news >正文

LangChain-Chatchat 开发与应用(七) 自定义文档解析器-搞定那些不听话的PDF

自定义文档解析器:搞定那些"不听话"的 PDF

标签:文档解析 | PDF | OCR | 自定义 Loader | 二次开发


一、文档解析的"噩梦"

做知识库的同学,最头疼的往往不是系统配置,而是文档本身

你遇到过这些情况吗?

  • 上传了一个 PDF,解析出来是空的——因为它其实是扫描件(图片)
  • 一个表格 PDF,解析后数据全乱了,列对不上行
  • 图文混排的文档,文字顺序是乱的,读起来前言不搭后语
  • 几百页的 PDF,解析到一半内存溢出,进程挂了

Chatchat 默认的文档解析能处理大部分情况,但遇到"硬骨头"就不够用了。

今天这篇,咱们就深入文档解析,看看怎么搞定这些"不听话"的文档。


二、Chatchat 的文档加载体系

2.1 默认的加载流程

用户上传文件 ↓ 根据文件后缀选择 Loader ↓ Loader 解析文件 → Document 对象列表 ↓ 每个 Document 包含:page_content(文本)+ metadata(元数据) ↓ 交给 TextSplitter 分块 ↓ 向量化入库

2.2 默认 Loader 映射

Chatchat 内置的 Loader 选择逻辑:

文件后缀使用的 Loader依赖库
.txtTextLoader内置
.mdUnstructuredMarkdownLoaderunstructured
.pdfPyPDFLoader / UnstructuredPDFLoaderpypdf / unstructured
.docxDocx2txtLoader / UnstructuredWordLoaderdocx2txt
.csvCSVLoader内置
.htmlBSHTMLLoaderbeautifulsoup4

2.3 默认 Loader 的局限

# PyPDFLoader 的问题示例fromlangchain.document_loadersimportPyPDFLoader loader=PyPDFLoader("scan_document.pdf")# 扫描件docs=loader.load()print(docs[0].page_content)# 输出:"" ← 空的!因为 PyPDF 只能提取文本层,不能 OCR

三、扫描件 PDF 的 OCR 处理

3.1 问题本质

扫描件 PDF 的每一页都是图片,没有文本层。需要 OCR(光学字符识别)把图片里的文字提取出来。

3.2 方案一:基于 pytesseract(开源免费)

# 自定义 OCR PDF Loaderimportpytesseractfrompdf2imageimportconvert_from_pathfromlangchain.schemaimportDocumentclassOCRPDFLoader:"""支持 OCR 的 PDF 加载器"""def__init__(self,file_path,language="chi_sim+eng"):self.file_path=file_path self.language=language# 中文+英文defload(self):# 1. PDF 转图片images=convert_from_path(self.file_path,dpi=300)documents=[]fori,imageinenumerate(images):# 2. OCR 识别文字text=pytesseract.image_to_string(image,lang=self.language)# 3. 创建 Documentdoc=Document(page_content=text,metadata={"source":self.file_path,"page":i+1})documents.append(doc)returndocuments# 使用loader=OCRPDFLoader("scan_document.pdf")docs=loader.load()print(docs[0].page_content)# 现在有内容了!

优点:完全免费,本地运行
缺点:速度慢(逐页 OCR),中文识别准确率一般

3.3 方案二:基于 Marker(推荐)

Marker 是一个专门做 PDF 解析的开源工具,对扫描件和复杂排版支持很好。

# 安装pipinstallmarker-pdf# 使用marker_single input.pdf output.md--langsChinese
# 集成到 Chatchatimportsubprocessfromlangchain.schemaimportDocumentclassMarkerPDFLoader:"""基于 Marker 的 PDF 加载器"""def__init__(self,file_path):self.file_path=file_pathdefload(self):# 调用 Marker 转换output_dir="/tmp/marker_output"subprocess.run(["marker_single",self.file_path,output_dir,"--langs","Chinese"],check=True)# 读取转换后的 Markdownmd_file=f"{output_dir}/input.md"withopen(md_file,"r",encoding="utf-8")asf:text=f.read()return[Document(page_content=text,metadata={"source":self.file_path})]

优点:排版还原好,支持表格、公式
缺点:需要额外安装,首次运行会下载模型

3.4 方案三:基于 MinerU(阿里出品,效果最佳)

MinerU 是阿里开源的 PDF 解析工具,对中文文档支持非常好。

# 安装pipinstallmagic-pdf# 下载模型(首次)wgethttps://github.com/opendatalab/MinerU/raw/master/scripts/download_models.py python download_models.py
frommagic_pdf.data.data_reader_writerimportFileBasedDataWriterfrommagic_pdf.data.datasetimportPymuDocDatasetfrommagic_pdf.model.doc_analyze_by_custom_modelimportdoc_analyzefrommagic_pdf.config.enumsimportSupportedPdfParseMethodclassMinerUPDFLoader:"""基于 MinerU 的 PDF 加载器"""def__init__(self,file_path):self.file_path=file_pathdefload(self):# 读取 PDFwithopen(self.file_path,"rb")asf:pdf_bytes=f.read()# 解析dataset=PymuDocDataset(pdf_bytes)# 判断是文本型还是扫描型ifdataset.classify()==SupportedPdfParseMethod.OCR:result=dataset.apply(doc_analyze,ocr=True)else:result=dataset.apply(doc_analyze,ocr=False)# 提取 Markdownmd_text=result.get_markdown()return[Document(page_content=md_text,metadata={"source":self.file_path})]

优点:中文效果最好,支持 OCR,排版还原优秀
缺点:模型较大,首次加载慢


四、表格数据的特殊处理

4.1 表格解析的难点

PDF 里的表格解析出来往往是这样的:

产品名称 价格 库存 iPhone 15 5999 100 iPad Pro 8999 50 MacBook 12999 30

看起来是表格,但其实是纯文本,丢失了表格结构。后续分块和检索时,行与行之间的关系就断了。

4.2 方案:保留表格结构

# 使用 tabula-py 或 camelot 提取表格importcamelot# 提取 PDF 中的所有表格tables=camelot.read_pdf("document.pdf",pages="all")fori,tableinenumerate(tables):# 转成 DataFramedf=table.df# 转成 Markdown 表格格式(保留结构)md_table=df.to_markdown(index=False)print(f"表格{i+1}:")print(md_table)

输出:

| 产品名称 | 价格 | 库存 | |---------|------|------| | iPhone 15 | 5999 | 100 | | iPad Pro | 8999 | 50 | | MacBook | 12999 | 30 |

Markdown 表格格式的好处:

  • 保留了行列结构
  • TextSplitter 按行分割时,每行包含完整的表头信息
  • LLM 理解 Markdown 表格很容易

4.3 集成到自定义 Loader

classTableAwarePDFLoader:"""能识别并保留表格结构的 PDF 加载器"""def__init__(self,file_path):self.file_path=file_pathdefload(self):# 1. 先用 Marker / MinerU 提取文本和表格# 2. 表格部分保留 Markdown 格式# 3. 普通文本正常处理# 4. 合并输出full_text=self._extract_with_tables()return[Document(page_content=full_text,metadata={"source":self.file_path})]def_extract_with_tables(self):# 具体实现...pass

五、自定义 Loader 接入 Chatchat

5.1 找到扩展点

Chatchat 的文档加载在server/knowledge_base/utils.py中:

# 原始逻辑(简化)defget_loader(file_path):ext=os.path.splitext(file_path)[1].lower()loaders={".txt":TextLoader,".pdf":PyPDFLoader,# ← 这里可以替换".docx":Docx2txtLoader,# ...}returnloaders.get(ext,TextLoader)(file_path)

5.2 修改 Loader 映射

# 在 server/knowledge_base/utils.py 中修改fromchatchat.document_loadersimport(# 自定义的 loaderMinerUPDFLoader,TableAwarePDFLoader,OCRPDFLoader)defget_loader(file_path,loader_type="default"):ext=os.path.splitext(file_path)[1].lower()# 根据配置或文件特征选择 loaderifext==".pdf":ifloader_type=="mineru":returnMinerUPDFLoader(file_path)elifloader_type=="table":returnTableAwarePDFLoader(file_path)elifloader_type=="ocr":returnOCRPDFLoader(file_path)else:returnPyPDFLoader(file_path)# 其他文件类型...

5.3 在配置中指定 Loader

# kb_settings.yamlDOCUMENT_LOADERS:pdf:default:"PyPDFLoader"# 默认scan:"OCRPDFLoader"# 扫描件complex:"MinerUPDFLoader"# 复杂排版table:"TableAwarePDFLoader"# 表格密集

5.4 WebUI 中选择解析方式

可以在上传文档时,让用户选择解析方式:

# 前端增加选择框parser_type=st.selectbox("文档解析方式",["默认","扫描件 OCR","复杂排版","表格优化"])# 传给后端upload_file(file,parser_type=parser_type)

六、性能优化:大文档处理

6.1 问题:大 PDF 内存溢出

几百页的 PDF,一次性加载到内存,很容易 OOM。

6.2 方案:分页流式处理

classStreamingPDFLoader:"""流式处理大 PDF,避免内存溢出"""def__init__(self,file_path,batch_size=10):self.file_path=file_path self.batch_size=batch_size# 每批处理 10 页defload(self):# 获取总页数total_pages=self._get_total_pages()forstartinrange(0,total_pages,self.batch_size):end=min(start+self.batch_size,total_pages)# 分批处理batch_docs=self._process_pages(start,end)# 立即分块入库,不保留在内存fordocinbatch_docs:yielddocdef_process_pages(self,start,end):# 只加载 start 到 end 页# 处理并返回 Document 列表pass

6.3 异步处理 + 进度反馈

# 上传大文档时,异步处理并返回进度asyncdefprocess_large_document(file_path):total=get_total_pages(file_path)processed=0fordocinStreamingPDFLoader(file_path).load():# 分块、向量化、入库awaitprocess_and_index(doc)processed+=1yield{"progress":processed/total,"status":f"已处理{processed}/{total}页"}

七、小结

这篇咱们深入文档解析,解决了几个核心问题:

✅ 扫描件 PDF:OCR 方案对比(pytesseract / Marker / MinerU)
✅ 复杂排版:MinerU 和 Marker 的排版还原能力
✅ 表格数据:保留 Markdown 表格结构的方法
✅ 自定义 Loader:从开发到接入 Chatchat 的完整流程
✅ 大文档处理:流式处理避免内存溢出

文档解析是 RAG 的"地基",地基不牢,上面再花哨也白搭。

建议根据你的文档类型选择合适的方案:

文档类型推荐方案
普通文本 PDFPyPDFLoader(默认)
扫描件MinerU 或 Marker
图文混排MinerU
表格密集TableAwarePDFLoader
超大文档StreamingPDFLoader


你在处理文档时遇到过什么"奇葩" PDF?扫描件、表格错乱、还是超大文件?用了什么方案解决的?欢迎分享!

http://www.jsqmd.com/news/863641/

相关文章:

  • 2026年普通家庭选白色十字门冰箱,大白405成首选之选! - 品牌企业推荐师(官方)
  • 3步掌握抖音批量下载:免费高效的抖音下载器完整指南
  • 五寨县黄金回收哪家强?铭润稳居第一 - 亦辰小黄鸭
  • 在不同网络环境下体验taotoken api服务的连接成功率
  • 余生黄金回收金凤区全攻略|6街道2镇全覆盖 黄金回收哪家更靠谱 - 润富黄金珠宝行
  • 0x.Tools高级技巧:如何利用动态查询系统进行多维性能分析
  • 蘑菇博客持续集成实践:Github Actions自动化部署完整流程
  • 武宁县黄金回收哪家强?铭润稳居第一 - 亦辰小黄鸭
  • 可观测体系最佳实践:Prometheus+Grafana+Loki+Jaeger
  • 如何高效下载抖音无水印视频:douyin-downloader的5个核心技巧
  • 2026年天津黄金回收平台,福昌夏凭实力脱颖而出 - 黄金上门回收
  • QKeyMapper:Windows跨设备按键映射解决方案,提升游戏与办公操作效率
  • CANN asc-devkit int4x2转half函数
  • 终极指南:如何用Python脚本免费实现百度网盘高速下载
  • 从传统推荐系统到AI原生架构:Gorse推荐引擎的技术演进与架构哲学
  • 08-前后端分离改造-把Chatchat嵌入你的业务系统
  • 武平县黄金回收哪家强?铭润稳居第一 - 亦辰小黄鸭
  • 泗洪县黄金回收哪家强?铭润稳居第一 - 亦辰小黄鸭
  • 镇江本地黄金回收六家老店服务周到诚信经营值得信赖 六大品牌 优选长悦 - 专业黄金回收
  • 第九篇:《软件测试中的常见误区与事实》
  • 泗水县黄金回收哪家强?铭润稳居第一 - 亦辰小黄鸭
  • 2026洗发水排行榜:不同发质都爱的5款修护洗发水 - 速递信息
  • 从数据到图形:ElGrapho数据模型与布局算法深度解析
  • 天赐范式第50天:当生活成为你每天必须照的镜子,实际上就是同行评议的反向蓝图——同时触发自审视
  • 实时API数据集成:从Yelp API到Postgres数据库的完整ETL流程
  • 从创意到分镜:用DeepSeek打造短视频一气呵成的秘密
  • REFramework游戏启动崩溃:如何高效解决注入冲突的实用解决方案
  • C# Mat对象 VS JaCoCo Win32_类:3个致命坑,谁才是代码维护的“真香“选手?
  • 泗县黄金回收哪家强?铭润稳居第一 - 亦辰小黄鸭
  • 5分钟掌握PlantUML Editor:代码驱动UML设计的终极解决方案