PDF 论文处理器 --- 技术报告文档
适用项目:
数据处理脚本 / Dify 知识库数据生成工具文档性质:技术剖析与归档
1. 文档说明
本文档覆盖以下内容:
- 系统总体架构与分层设计
- 核心模块职责、关键类与关键函数
- 数据处理全流程与数据形态演变
- 数据输入 / 输出规范与元数据结构
- 外部依赖与运行环境要求
- 关键算法原理说明
- 部署与运行指南
2. 项目概述
2.1 项目定位
本工具是一个面向网络安全(网安)科研论文的领域专用 PDF 文本预处理流水线。其目标是将分散存储的英文论文 PDF,转化为 Dify 知识库可直接导入的高质量"数据块(chunk)",服务于 RAG(检索增强生成)场景下的论文检索、研究现状总结与创新点挖掘。
2.2 设计理念
工具在"提取 → 清洗 → 语义分块 → 元数据增强 → Dify 兼容输出"的流水线中,刻意融入了两类领域特征:
- 结构感知:依据论文标准章节(Abstract / Introduction / Method ...)进行分块,而非无差别固定长度切分,保留章节语义边界。
- 元数据驱动:从"目录结构 + 文件名"自动推断论文类别(漏洞挖掘 / 漏洞修复 / LLM 安全 / 其他)与发表年份,并将章节、字数、来源等字段随块输出,提升检索可过滤性与可追溯性。
2.3 技术栈概览
| 维度 | 选型 |
|---|---|
| 语言 | Python 3(源码兼容 3.8+,详见 7.3) |
| PDF 抽取 | PyMuPDF1.24.5 |
| 进度显示 | tqdm 4.66.1 |
| 落盘格式 | 标准库 csv / json(声明了 pandas 但未使用) |
| 命令行 | 标准库 argparse |
3. 系统总体架构
3.1 分层结构
系统自上而下分为四层,依赖方向单一(上层依赖下层,配置层被各层共享):
┌─────────────────────────────────────────────────────────────┐ │ ① 命令行入口层 process_papers.py(argparse 解析与配置改写) │ └───────────────────────────────┬─────────────────────────────┘ │ 创建并驱动 ┌───────────────────────────────▼─────────────────────────────┐ │ ② 流程编排层 PDFProcessor(processor.py) │ │ process_file / process_directory / save_results │ └───┬───────────────┬───────────────┬───────────────┬──────────┘ │ │ │ │ ┌───▼───┐ ┌──────▼─────┐ ┌──────▼─────┐ ┌──────▼─────┐ │PDFEx- │ │Semantic- │ │Metadata- │ │Output- │ │tractor│ │Chunker │ │Manager │ │Manager │ └───────┘ └────────────┘ └────────────┘ └────────────┘ │ │ │ │ └───────────────────────┬───────────────────────────────┘ ┌───────▼────────┐ │ ③ 配置层 config.py │ │ CHUNK / CLEAN / │ │ OUTPUT / CATEGORY│ │ / SECTION_HEADERS│ └─────────────────┘
3.2 组件清单与职责
| 文件 | 核心类 / 函数 | 职责 |
|---|---|---|
process_papers.py |
main() |
CLI 入口;解析参数、改写全局配置、打印运行提示、驱动 PDFProcessor |
src/pdf_processor/__init__.py |
__all__ |
包导出,统一对外暴露 5 个公共类 |
src/pdf_processor/config.py |
CHUNK_CONFIG / CLEAN_CONFIG / OUTPUT_CONFIG / CATEGORY_MAPPING / SECTION_HEADERS |
集中式配置字典 |
src/pdf_processor/pdf_extractor.py |
PDFExtractor |
PDF 文本抽取、清洗、基础元数据提取 |
src/pdf_processor/chunker.py |
SemanticChunker |
章节识别 + 滑动窗口分块 + 句末对齐 |
src/pdf_processor/metadata_manager.py |
MetadataManager |
路径元数据提取、元数据精简、Dify 格式化 |
src/pdf_processor/output_manager.py |
OutputManager |
CSV/JSON 落盘、统计生成与保存 |
src/pdf_processor/processor.py |
PDFProcessor |
串联上述四模块,提供文件 / 目录级处理接口 |
4. 核心模块详解
4.1 PDFExtractor(pdf_extractor.py)
职责:打开 PDF、逐页抽取纯文本、执行清洗规则、提取基础元数据,对单页异常做容错。
关键方法:
-
extract_text(pdf_path) -> (cleaned_text, metadata)(L28) 主入口。先调用_extract_metadata在文档打开态取元数据;随后逐页page.get_text("text")抽取,并在finally块中doc.close()确保句柄释放(L70-75)。单页异常被try/except捕获后跳过该页(L65-68),避免整篇失败。 -
_remove_page_numbers(text, page_num)(L85) 用\b{page_num}\b、Page\s*{n}、page\s*{n}三种正则移除页码。 -
_remove_headers_footers(text)(L99) 移除孤立数字行(^\d+\s*$),并丢弃短于min_paragraph_length且不以大写字母开头的行。 -
_clean_text(text)(L117) 标准化空白(+→单空格、\n{3,}→\n\n、去行首尾空格);移除引用标记[\d+]与年份(YYYY)。 -
_extract_metadata(doc, pdf_path)(L134) 记录filename / file_path / total_pages;若 PDF 元信息含title则取之;否则以第一页首行(长度 10--200)作为标题。
4.2 SemanticChunker(chunker.py)
职责:将长文本按章节切分,再对每个章节做带重叠的滑动窗口分块,并尽量对齐到句子边界。
关键方法:
-
chunk_text(text, metadata)(L19) 对外主接口。调用_identify_sections得到(section_name, section_text)列表,逐章节_sliding_window_chunk,为每个块补充section / chunk_id / char_count元数据。 -
_identify_sections(text)(L58) 将SECTION_HEADERS中所有模式拼为(?m)^<escaped>\s*$的多选正则,定位章节标题行;按匹配位置切片,得到各章节文本。若无匹配则返回单段("Full Text", text)。 -
_sliding_window_chunk(text, section_name)(L125) 若整段 ≤max_chunk_size直接整体输出;否则以chunk_size为步长、回退chunk_overlap作为下一段起点,循环切片。 -
_find_sentence_boundary(text, target_pos)(L163) 在目标位置 ±100 字符范围内,按优先级(.\n/!/?/./;\n/:\n)向后搜索句末标点,将切分点对齐到句子结束处。
4.3 MetadataManager(metadata_manager.py)
职责:元数据全生命周期管理------从路径提取、合并精简、到 Dify 格式输出。
关键方法:
-
extract_from_path(file_path)(L24) 按/规范化路径,遍历层级:若某层命中CATEGORY_MAPPING则记录category / category_cn,并取下一层若为合法年份(2000--2030)记为year;目录中未取到年份时回退到文件名年份正则(20\d{2}/'24)。 -
enrich_metadata(chunk, pdf_metadata)(L113) 合并 PDF 元数据后,精简为 6 个核心字段(category / category_cn / year / section / char_count / source_file),存在title时追加。 -
format_for_dify(chunk, output_config)(L151) 按csv_mode分发到_format_standard或_format_merged,并在末尾追加 Dify 分隔符。 -
_format_standard(L179):三列独立,metadata为 JSON 串。 -
_format_merged(L198):将metadata与source拼接到content末尾(带前缀[Metadata:、[Source:),再追加分隔符;CSV 的metadata / source列置空。 -
_generate_chunk_marker(L231):基于模板生成标记字符串(主流程当前未启用)。
4.4 OutputManager(output_manager.py)
职责:落盘与统计。
关键方法:
save_chunks(chunks, base_filename)(L30):按self.format(csv/json)与时间戳命名写入。_save_csv(L60):以utf-8-sig编码写content,metadata,source三列(BOM 头便于 Excel 打开)。_save_json(L85):ensure_ascii=False美化输出。generate_statistics(chunks)(L99):统计总量 / 字数 / 均值 / 极值,并按category / year / section三维聚合(merged 模式下从content内正则回抽元数据)。print_statistics/save_statistics:控制台打印与 JSON 落盘。
4.5 PDFProcessor(processor.py)
职责:组装四模块,提供统一处理接口。
关键方法:
process_file(pdf_path)(L38):单文件流水线------extract_from_path→extract_text→ 元数据合并 →chunk_text→ 逐块enrich_metadata+format_for_dify。process_directory(root_dir, recursive)(L70):os.walk收集 PDF,用tqdm显示进度,逐文件处理并收集失败清单,最后汇总成功 / 失败 / 总块数。_find_pdf_files(L117):递归或非递归收集.pdf,结果排序保证可复现。save_results(chunks, output_filename)(L144):落盘主数据 + 生成 / 打印 / 保存统计。
5. 数据处理流程
5.1 端到端流程
输入目录/文件 │ ▼ [CLI] 解析参数 → 改写全局 CHUNK_CONFIG / OUTPUT_CONFIG │ ▼ PDFProcessor.process_directory / process_file │ ├─① MetadataManager.extract_from_path │ 产出: {category, category_cn, year, source_file} (来自路径/文件名) │ ├─② PDFExtractor.extract_text │ 产出: (cleaned_text, {title, total_pages, ...}) (来自 PDF 元信息/首页) │ ├─③ 元数据合并 (base_metadata.update(pdf_metadata)) │ ├─④ SemanticChunker.chunk_text │ 产出: [{content, metadata{...section,chunk_id,char_count}}] │ ├─⑤ 逐块 MetadataManager.enrich_metadata │ 产出: 精简后 metadata (6+ 字段) │ ├─⑥ 逐块 MetadataManager.format_for_dify │ 产出: {content, metadata, source} (按 csv_mode 组织 + 分隔符) │ ▼ PDFProcessor.save_results ├─ OutputManager.save_chunks → *.csv / *.json └─ OutputManager.generate/save_statistics → *_statistics.json
5.2 数据形态演变
| 阶段 | 数据形态 | 关键字段 |
|---|---|---|
| 原始 | 磁盘 PDF | 二进制 |
| ① 路径解析 | dict | category, category_cn, year, source_file |
| ② 抽取清洗 | (str, dict) |
cleaned_text, title, total_pages |
| ④ 分块 | List[{content, metadata}] |
追加 section, chunk_id, char_count |
| ⑥ 格式化 | List[{content, metadata, source}] |
merged 模式 metadata/source 为空串 |
| 落盘 | CSV / JSON 文件 | 见第 6 节 |
6. 数据输入 / 输出规范
6.1 输入规范
目录模式(推荐):
papers/ ├── 漏洞挖掘/ │ ├── 2024/ paper1.pdf, paper2.pdf │ └── 2023/ paper3.pdf ├── 漏洞修复/ │ └── 2024/ paper4.pdf ├── LLM安全/ │ └── 2024/ paper5.pdf └── 其他/ └── 2024/ paper6.pdf
- 类别文件夹须使用约定中文名(
漏洞挖掘 / 漏洞修复 / LLM安全 / 其他),用于自动映射英文标识。 - 年份文件夹格式为 4 位年份;缺失时回退文件名年份识别。
- 单文件模式:直接传
.pdf路径并加--single-file。
6.2 输出规范
主数据文件:
| 列 | 含义 | merged 模式 | standard 模式 |
|---|---|---|---|
content |
文本 +(merged)元数据/来源 + 分隔符 | 完整拼接 | 纯文本 + 分隔符 |
metadata |
元数据 JSON 串 | 空 | {"category":...} |
source |
来源文件名 | 空 | paper1.pdf |
统计文件 (*_statistics_*.json):
{ "total_chunks": 123, "total_chars": 86000, "avg_chunk_size": 699, "min_chunk_size": 105, "max_chunk_size": 1198, "by_category": {"vulnerability_mining": 60, "llm_security": 40, "other": 23}, "by_year": {"2024": 90, "2023": 33}, "by_section": {"Abstract": 12, "Method": 40, "Conclusion": 18} }
6.3 元数据结构(精简后)
| 字段 | 类型 | 说明 |
|---|---|---|
category |
str | 英文类别标识(vulnerability_mining 等) |
category_cn |
str | 中文类别名 |
year |
str | 发表年份 |
section |
str | 所属章节(Abstract / Method ...) |
char_count |
int | 块字符数 |
source_file |
str | 原始 PDF 文件名 |
title |
str(可选) | 论文标题(命中时存在) |
7. 外部依赖与运行环境
7.1 运行环境要求
| 项目 | 要求 |
|---|---|
| 操作系统 | Windows / Linux / macOS(路径分隔符已兼容) |
| Python | 源码语法兼容 3.8+;注意 :requirements.txt 中 pandas==2.2.0 实际需要 Python ≥ 3.9(见 7.3) |
| 内存 | 随 PDF 总量线性增长,建议 ≥ 4 GB |
| 磁盘 | 输出目录需可写 |
7.2 第三方依赖
| 包 | 版本 | 用途 | 实际调用位置 |
|---|---|---|---|
| PyMuPDF | 1.24.5 | PDF 文本抽取 | pdf_extractor.py |
| tqdm | 4.66.1 | 目录处理进度条 | processor.py |
| pandas | 2.2.0 | 声明但全项目未 import(冗余依赖) | 无 |
安装:
pip install -r requirements.txt
7.3 版本一致性说明
README 标注"Python 3.8+",但 pandas==2.2.0 官方要求 Python ≥ 3.9。由于 pandas 实际未被使用,若移除该依赖,则代码在 3.8 即可运行;若保留则应同步将最低版本声明修正为 3.9+。
8. 关键算法说明
8.1 章节识别算法
基于配置 SECTION_HEADERS(章节名 → 多别名列表),构造多选正则 (?m)^(别名1|别名2|...)\s*$,以整行精确匹配 定位章节标题。匹配点按位置排序后,相邻匹配点之间的文本即一个章节。该方案零依赖、开销低,但对"编号章节(如 2. Related Work)、内联标题、非英文标题"识别有限。
8.2 滑动窗口与重叠
对每个章节独立分块:
- 若
len(section) ≤ max_chunk_size:整段作为单个块(不再细分)。 - 否则:
end = start + chunk_size,下一段起点start = end - chunk_overlap。 - 重叠区(
overlap)使块边界处的上下文在相邻块中重复,缓解语义割裂。
8.3 句末对齐
在理想切分点 chunk_size 处,不强制切断,而是在其 ±100 字符范围内按优先级寻找句末标点(.\n 最优先),将 end 后移至句末,使切块落在句子边界,提升文本完整度。min_chunk_size 用于防止回退后块过短。
8.4 路径元数据提取
将路径 \\ 统一为 / 后按层遍历:命中类别映射层即记录类别,并取紧邻下层若为合法年份则记为年份;该机制无需读取 PDF 内容即可获得领域标签,是"零成本元数据"的关键设计。
9. 部署与运行指南
9.1 安装
pip install -r requirements.txt
9.2 基本运行
# 处理整个目录(默认输出到 ./output,CSV merged 模式) python process_papers.py --input ./papers # 指定输出、块大小、格式 python process_papers.py \ --input ./papers \ --output ./dify_data \ --chunk-size 800 --overlap 200 \ --format csv --csv-mode merged \ --output-name cyber_security_papers # 单文件 python process_papers.py --input ./papers/漏洞挖掘/2024/paper.pdf --single-file
9.3 主要命令行参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--input / -i |
必填 | 输入目录或 PDF 路径 |
--output / -o |
output |
输出目录 |
--chunk-size |
700 | 块大小(字符) |
--overlap |
150 | 块重叠(字符) |
--format |
csv | csv / json |
--csv-mode |
merged | merged(推荐)/ standard |
--single-file |
False | 单文件模式 |
--output-name |
dify_knowledge_base |
输出文件名 |
--add-separator / --no-separator |
启用 | Dify 分隔符开关 |
--separator |
见 config | 自定义分隔符 |
--csv-mode |
merged | CSV 列组织方式 |
9.4 Dify 导入要点
- merged 模式:上传 CSV 时在 Dify 设置"分段标识符"为上述分隔符,可实现精准分段并保留元数据于
content。 - 推荐索引模式"高质量",Embedding 选支持中英文的模型。