1. 项目背景与核心需求
这个Python脚本process_pdf.py看起来是一个处理PDF文件的工具,结合PostgreSQL数据库和FastAPI框架使用。从标题中的"必须修改的部分"可以推断,这个脚本存在某些关键问题或功能缺陷,需要进行必要的调整才能正常工作或满足新的需求。
在实际开发中,PDF处理是一个常见但容易出问题的领域。PDF文件格式复杂,不同生成工具创建的PDF结构差异很大,这给解析和处理带来了挑战。同时,当PDF处理需要与数据库交互或通过API提供服务时,代码的健壮性和效率就变得尤为重要。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 脚本功能分析与问题定位
2.1 主要功能推测
基于关键词和常见场景,process_pdf.py可能包含以下功能:
- PDF文本内容提取
- PDF元数据读取/修改
- PDF页面分割/合并
- PDF转图像或其他格式
- 处理结果存储到PostgreSQL
- 通过FastAPI提供Web服务接口
2.2 必须修改的部分分析
标题特别强调"必须修改的部分",这表明脚本中存在一些关键问题,可能包括:
-
PDF解析库的选择与配置:
- 可能使用了不合适的PDF处理库(如PyPDF2对某些PDF兼容性差)
- 缺少必要的错误处理和格式验证
-
数据库交互问题:
- PostgreSQL连接参数配置不当
- 大数据量插入的性能问题
- 事务处理不完整
-
API接口设计缺陷:
- FastAPI端点设计不符合RESTful规范
- 缺少必要的请求验证
- 文件上传处理不完善
-
性能瓶颈:
- 大PDF文件处理时内存占用过高
- 缺少异步处理机制
- 没有实现合理的缓存策略
3. 关键修改方案与实现
3.1 PDF处理模块优化
python复制# 原代码可能存在的问题示例
def extract_text(pdf_path):
with open(pdf_path, 'rb') as f:
reader = PyPDF2.PdfFileReader(f)
text = ""
for page in range(reader.numPages):
text += reader.getPage(page).extractText()
return text
# 改进后的版本
def extract_text(pdf_path):
try:
# 使用更现代的pdfplumber库,对复杂PDF兼容性更好
with pdfplumber.open(pdf_path) as pdf:
text = "\n".join(page.extract_text() for page in pdf.pages)
return text
except Exception as e:
logger.error(f"PDF解析失败: {str(e)}")
raise ValueError("无法处理该PDF文件") from e
改进点说明:
- 从PyPDF2切换到pdfplumber,后者对复杂布局的PDF处理更好
- 添加了完整的错误处理
- 使用生成器表达式减少内存占用
- 添加了日志记录
3.2 数据库交互优化
python复制# 原代码可能存在的问题示例
def save_to_db(text):
conn = psycopg2.connect("dbname=test user=postgres")
cur = conn.cursor()
cur.execute("INSERT INTO pdf_data (content) VALUES (%s)", (text,))
conn.commit()
cur.close()
conn.close()
# 改进后的版本
from contextlib import contextmanager
@contextmanager
def db_connection():
conn = psycopg2.connect(
host=DB_HOST,
database=DB_NAME,
user=DB_USER,
password=DB_PASS,
connect_timeout=5
)
try:
yield conn
finally:
conn.close()
def save_to_db(text):
try:
with db_connection() as conn:
with conn.cursor() as cur:
cur.execute(
"INSERT INTO pdf_data (content, created_at) VALUES (%s, NOW())",
(text,)
)
conn.commit()
except OperationalError as e:
logger.error(f"数据库操作失败: {str(e)}")
raise
改进点说明:
- 使用上下文管理器确保数据库连接正确关闭
- 连接参数从代码中分离,便于配置管理
- 添加了连接超时设置
- 记录了创建时间戳
- 完善了错误处理
3.3 FastAPI接口改进
python复制# 原代码可能存在的问题示例
@app.post("/upload")
async def upload(file: UploadFile = File(...)):
contents = await file.read()
text = extract_text(contents)
save_to_db(text)
return {"message": "File processed"}
# 改进后的版本
from fastapi import HTTPException
@app.post("/pdf/extract-text",
response_model=TextExtractionResult,
status_code=status.HTTP_201_CREATED)
async def extract_text_from_pdf(
file: UploadFile = File(..., description="PDF文件"),
background_tasks: BackgroundTasks
):
# 验证文件类型
if not file.filename.lower().endswith('.pdf'):
raise HTTPException(
status_code=400,
detail="仅支持PDF文件"
)
try:
# 使用临时文件处理大PDF
with tempfile.NamedTemporaryFile(delete=False) as tmp:
contents = await file.read()
tmp.write(contents)
tmp_path = tmp.name
# 使用后台任务处理耗时操作
background_tasks.add_task(process_pdf_task, tmp_path)
return TextExtractionResult(
message="PDF已接收,正在处理",
task_id=str(uuid.uuid4())
)
except Exception as e:
logger.error(f"文件处理错误: {str(e)}")
raise HTTPException(
status_code=500,
detail="处理PDF时发生错误"
) from e
改进点说明:
- 添加了文件类型验证
- 使用临时文件处理大文件,避免内存问题
- 使用后台任务处理耗时操作
- 定义了清晰的响应模型
- 完善了错误处理和HTTP状态码
- 添加了任务ID跟踪机制
4. 性能优化与高级功能
4.1 异步处理实现
对于PDF处理这种可能耗时的操作,应该实现异步处理模式:
python复制from celery import Celery
app = Celery('pdf_tasks', broker='redis://localhost:6379/0')
@app.task(bind=True)
def process_pdf_task(self, file_path):
try:
text = extract_text(file_path)
save_to_db(text)
os.unlink(file_path) # 删除临时文件
return {"status": "success"}
except Exception as e:
os.unlink(file_path)
raise self.retry(exc=e, countdown=60)
4.2 数据库批量插入优化
当需要处理大量PDF时,应该使用批量插入:
python复制def batch_insert_pdf_data(records):
with db_connection() as conn:
with conn.cursor() as cur:
# 使用execute_values高效批量插入
execute_values(
cur,
"""INSERT INTO pdf_data
(content, file_name, file_size, created_at)
VALUES %s""",
records,
page_size=100 # 每批100条
)
conn.commit()
4.3 PDF处理增强功能
可以考虑添加以下实用功能:
-
OCR支持:对于扫描版PDF,集成Tesseract OCR
python复制def extract_text_with_ocr(pdf_path): images = convert_from_path(pdf_path) text = "" for img in images: text += pytesseract.image_to_string(img, lang='chi_sim') return text -
PDF/A合规性检查:
python复制def check_pdfa_compliance(pdf_path): result = subprocess.run( ['veraPDF', '--format', 'text', pdf_path], capture_output=True, text=True ) return "isCompliant=True" in result.stdout -
敏感信息检测:
python复制def detect_sensitive_info(text): patterns = { '身份证号': r'\b\d{17}[\dXx]\b', '手机号': r'\b1[3-9]\d{9}\b', '银行卡': r'\b\d{16,19}\b' } findings = {} for name, pattern in patterns.items(): if re.search(pattern, text): findings[name] = True return findings
5. 部署与监控建议
5.1 容器化部署
使用Docker部署可以简化环境配置:
dockerfile复制FROM python:3.9-slim
WORKDIR /app
RUN apt-get update && apt-get install -y \
poppler-utils \
tesseract-ocr \
tesseract-ocr-chi-sim \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
5.2 监控配置
添加Prometheus监控指标:
python复制from prometheus_fastapi_instrumentator import Instrumentator
Instrumentator().instrument(app).expose(app)
5.3 日志配置
结构化日志配置示例:
python复制import structlog
structlog.configure(
processors=[
structlog.processors.JSONRenderer()
],
wrapper_class=structlog.BoundLogger,
context_class=dict,
logger_factory=structlog.PrintLoggerFactory()
)
logger = structlog.get_logger()
6. 测试策略与质量保证
6.1 单元测试示例
python复制import pytest
from unittest.mock import patch, MagicMock
@pytest.mark.asyncio
async def test_pdf_upload_success():
mock_file = MagicMock()
mock_file.filename = "test.pdf"
mock_file.read.return_value = b"%PDF-sample-content"
with patch("process_pdf.extract_text") as mock_extract:
mock_extract.return_value = "sample text"
response = await extract_text_from_pdf(mock_file)
assert response["message"] == "PDF已接收,正在处理"
mock_extract.assert_called_once()
6.2 性能测试
使用locust进行负载测试:
python复制from locust import HttpUser, task, between
class PdfProcessingUser(HttpUser):
wait_time = between(1, 5)
@task
def upload_pdf(self):
with open("sample.pdf", "rb") as f:
self.client.post(
"/pdf/extract-text",
files={"file": f},
timeout=30
)
6.3 安全测试
检查常见安全漏洞:
-
文件上传漏洞:
- 测试上传非PDF文件
- 测试上传恶意构造的PDF
-
注入攻击:
- SQL注入测试
- 命令注入测试
-
敏感数据暴露:
- 检查API是否返回过多信息
- 验证错误处理是否泄露系统细节
7. 实际应用中的经验分享
在真实项目中处理PDF时,有几个容易忽视但很重要的问题:
-
字体处理:
- 中文PDF常常遇到字体嵌入问题
- 解决方案是确保系统中安装了常用中文字体
python复制# 检查PDF是否嵌入所需字体 def check_fonts(pdf_path): with pdfplumber.open(pdf_path) as pdf: for page in pdf.pages: fonts = page.objects.get("font", []) for font in fonts: if font["name"] == "Unsupported": raise ValueError("PDF包含不支持的字体") -
内存管理:
- 大PDF文件可能导致内存溢出
- 解决方案是使用流式处理和临时文件
python复制def process_large_pdf(pdf_path, chunk_size=10): with pdfplumber.open(pdf_path) as pdf: for i in range(0, len(pdf.pages), chunk_size): chunk = pdf.pages[i:i+chunk_size] text = "\n".join(page.extract_text() for page in chunk) yield text -
编码问题:
- PDF中的文本可能有多种编码
- 需要动态检测和转换
python复制def normalize_text(text): encodings = ['utf-8', 'gbk', 'gb2312', 'big5'] for enc in encodings: try: return text.encode(enc).decode('utf-8') except UnicodeError: continue return text # 回退到原始文本 -
性能调优:
- 使用多进程处理多个PDF
python复制from concurrent.futures import ProcessPoolExecutor def batch_process_pdfs(pdf_paths, workers=4): with ProcessPoolExecutor(max_workers=workers) as executor: results = list(executor.map(process_pdf, pdf_paths)) return results -
PostgreSQL特定优化:
- 对于大文本字段,考虑使用TOAST存储
- 添加适当的索引
sql复制CREATE INDEX idx_pdf_content_search ON pdf_data USING gin(to_tsvector('simple', content)); -
FastAPI最佳实践:
- 使用依赖注入管理数据库连接
- 实现认证中间件
python复制async def get_db(): with db_connection() as conn: yield conn @app.post("/secure/upload") async def secure_upload( file: UploadFile = File(...), user: User = Depends(get_current_user), conn: Connection = Depends(get_db) ): # 实现带认证的上传逻辑
这个process_pdf.py脚本的修改需要全面考虑PDF处理的特殊性、数据库交互的可靠性以及API设计的合理性。通过上述改进,可以构建一个健壮、高效的PDF处理服务。在实际项目中,还需要根据具体需求进行适当调整,比如添加更细粒度的权限控制、实现更复杂的PDF操作功能等。
