共计 2789 个字符,预计需要花费 7 分钟才能阅读完成。
痛点分析
在处理 PDF 文档时,开发者常常会遇到几个棘手的问题:

-
PDF 格式复杂性:PDF 本身是为打印设计的格式,其内部结构复杂,包含文字、图片、表格等多种元素混合排布,导致简单的文本提取方法往往无法完整保留原始文档的结构和内容。
-
大文档处理时的 token 限制:像 ChatGPT 这样的模型有 token 限制(通常是几千个),而很多 PDF 文档的文本量远超这个限制,需要有效的分块策略来分割文本。
-
特殊元素处理:表格、数学公式、图表等特殊元素在 PDF 中通常以非文本形式存在,常规的文本提取方法无法正确处理这些内容。
技术方案对比
PDF 解析库比较
- PyPDF2:轻量级,基本文本提取功能,但对复杂格式支持有限
- pdfplumber:提供精确的文本定位和表格提取,保持原始布局
- pdfminer:强大的文本提取能力,支持复杂布局分析,但 API 较复杂
集成方式选择
- 直接 API 调用:灵活但需要自行处理分块、上下文管理等
- LangChain 框架:提供预构建的文档加载、分块和问答链,开发效率高但灵活性稍低
文本分块策略
- 固定窗口分块:简单按字符 / 字数分割,可能切断语义
- 语义分割:基于句子或段落边界分割,保持语义完整性但实现复杂
核心实现
使用 pdfplumber 提取文本
import pdfplumber
def extract_text_with_coords(pdf_path: str) -> list[dict]:
"""
提取 PDF 文本及其坐标信息
:param pdf_path: PDF 文件路径
:return: 包含文本和坐标的字典列表
"""
try:
with pdfplumber.open(pdf_path) as pdf:
pages = []
for page in pdf.pages:
text = page.extract_text()
words = page.extract_words()
pages.append({
'text': text,
'words': words,
'page_number': page.page_number
})
return pages
except Exception as e:
print(f"Error extracting text: {e}")
return []
递归式文本分块算法
from typing import List
def recursive_text_split(text: str,
chunk_size: int = 2000,
overlap: int = 200) -> List[str]:
"""
递归式文本分块,保持段落完整
:param text: 输入文本
:param chunk_size: 目标分块大小
:param overlap: 块间重叠量
:return: 文本块列表
"""
if len(text) <= chunk_size:
return [text]
# 尝试在段落边界分割
split_pos = text.rfind('\n\n', 0, chunk_size)
if split_pos == -1:
split_pos = text.rfind('\n', 0, chunk_size)
if split_pos == -1:
split_pos = chunk_size
current_chunk = text[:split_pos].strip()
remaining_text = text[max(0, split_pos - overlap):]
return [current_chunk] + recursive_text_split(remaining_text, chunk_size, overlap)
构建问答链
from langchain.chains import RetrievalQA
from langchain.llms import OpenAI
from langchain.vectorstores import FAISS
from langchain.embeddings import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 初始化组件
llm = OpenAI(temperature=0)
embeddings = OpenAIEmbeddings()
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=2000,
chunk_overlap=200,
)
# 处理 PDF 文本
def build_qa_system(pdf_text: str):
"""
构建 PDF 问答系统
:param pdf_text: 提取的 PDF 文本
:return: 问答链实例
"""
try:
# 文本分块
chunks = text_splitter.split_text(pdf_text)
# 创建向量存储
vectorstore = FAISS.from_texts(chunks, embeddings)
# 构建问答链
qa = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=vectorstore.as_retriever(),
return_source_documents=True
)
return qa
except Exception as e:
print(f"Error building QA system: {e}")
return None
生产级优化
- OCR 集成:对于扫描件 PDF,可以使用 Tesseract OCR 进行文本识别
import pytesseract
from pdf2image import convert_from_path
def ocr_pdf(pdf_path: str) -> str:
"""
对扫描件 PDF 进行 OCR 识别
:param pdf_path: PDF 文件路径
:return: 识别出的文本
"""
try:
images = convert_from_path(pdf_path)
text = ""
for img in images:
text += pytesseract.image_to_string(img)
return text
except Exception as e:
print(f"OCR error: {e}")
return ""
-
异步化改造:使用异步框架如 FastAPI 或 Sanic 提高并发处理能力
-
敏感信息过滤:实现基于正则表达式或 NER 的敏感信息检测
避坑指南
- 编码问题:PDF 可能使用 GB18030 等编码,需检测并正确解码
- 字体缺失:嵌入字体缺失时考虑备用字体或 OCR 方案
- 成本监控:记录 API 调用次数和 token 使用量,设置预警阈值
延伸思考
- 如何评估不同分块策略对问答准确性的影响?
- 对于专业领域 PDF(如法律、医学文档),如何提升问答的专业性?
- 在多文档问答场景中,如何实现跨文档的关联问答?
完整可执行代码可在 Colab Notebook 中获取。
正文完
发表至: 未分类
近三天内
