Hive 项目 PDF 高级处理完全指南:pypdfium2、pdf-lib、pdfjs-dist 与命令行工具实战
【免费下载链接】hive Multi-Agent Harness for Production AI 项目地址: https://gitcode.com/gh_mirrors/hive48/hive
导读
本文是 Hive 项目 内置 Agent Skill hive.pdf(SKILL.md)的进阶配套参考,以仓库中的 REFERENCE.md 为主体展开。它面向需要在 Agent 工作流中处理复杂 PDF 任务的开发者,覆盖 Python 侧的高性能渲染库 pypdfium2、JavaScript 侧的 pdf-lib 与 pdfjs-dist、poppler-utils 与 qpdf 的命令行高级用法,以及批量处理、性能优化与故障排查的完整方案。读完本文,你将掌握从"快速文本抽取"到"复杂报表生成、合并拆分、加密解密、图像提取、扫描件 OCR"的全栈 PDF 处理能力,并了解这些能力如何与 Hive 框架中的 pdf_read 工具及大型 PDF 的 agentic 处理路径相互配合。
1. 定位:REFERENCE.md 在 Hive Skill 体系中的角色
在 Hive 的框架中,PDF 处理能力以 Agent Skill 的形式内置于框架,由 DefaultSkillManager 注册为 hive.pdf,其目录下包含两个文档:
- SKILL.md:主技能文档,覆盖 pypdf、pdfplumber、reportlab 等基础操作,以及 OCR、水印、加密等常见任务;
- REFERENCE.md:进阶参考文档(本文主体),覆盖主文档未展开的高级特性、更多三方库与复杂工作流。
从源码结构看,hive.pdf 被设计为"按需加载"的参考型 Skill——其正文不会常驻系统提示词,而是等 Agent 依据描述触发渐进式披露后才注入(见 defaults.py 中关于 pdf 是 reference skill 的注释)。因此 REFERENCE.md 实际承担着"专家级备查手册"的职责:当基础技能不足以应对高性能渲染、浏览器端渲染、复杂页面操作或加密 PDF 时,Agent 与开发者可以翻到这里获取完整的进阶方案。
在实际运行时,Hive 的 pdf_read 工具(pdf_read_tool.py)已内置对 pdfplumber 的调用,并对扫描页输出 needs_vision 标记;上传路径上,小 PDF 会内联为多模态 file 块,超过 10 MB 阈值(LARGE_PDF_THRESHOLD_BYTES = 10 * 1024 * 1024,见 routes_execution.py)的大 PDF 则进入"仅路径"的 agentic 模式,提示 Agent 用 pdf_read(file_path=..., pages='1-N') 分段加载。本文的进阶工具恰好可以补足这些内置能力之外的场景:渲染、创建、合并、加密与故障恢复。
2. pypdfium2:高性能 PDF 渲染与图像生成
2.1 为什么选择 pypdfium2
pypdfium2 是 Chromium 的 PDF 引擎 PDFium 的 Python 绑定,许可证为 Apache/BSD(宽松可商用)。它最突出的能力是快速渲染与图像生成,常被用作 PyMuPDF(fitz)的替代方案,适合需要把 PDF 页面转成位图供视觉模型、OCR 或缩略图流水线使用的场景。其底层直接复用 Chromium 的排版渲染管线,因此渲染质量与 Chromium 内置 PDF 阅读器一致。
2.2 渲染 PDF 页面为图像
基础用法非常直接:打开 PdfDocument 后按索引取页,调用 page.render() 得到位图,再经 bitmap.to_pil() 转换为 PIL Image 保存:
import pypdfium2 as pdfium
from PIL import Image
# Load PDF
pdf = pdfium.PdfDocument("document.pdf")
# Render page to image
page = pdf[0] # First page
bitmap = page.render(
scale=2.0, # Higher resolution
rotation=0 # No rotation
)
# Convert to PIL Image
img = bitmap.to_pil()
img.save("page_1.png", "PNG")
# Process multiple pages
for i, page in enumerate(pdf):
bitmap = page.render(scale=1.5)
img = bitmap.to_pil()
img.save(f"page_{i+1}.jpg", "JPEG", quality=90)
关键参数说明:
scale:渲染倍率,1.0为页面原始尺寸(72 DPI 基准),2.0/3.0常用于需要放大细节、表格识别或喂给视觉模型的高清场景。倍率越高内存与耗时越大,预览用1.0~1.5、最终输出用2.0~3.0是比较均衡的实践(REFERENCE.md 的性能建议也明确"low resolution for previews, high resolution for final output")。rotation:旋转角度(0/90/180/270),用于修正扫描件方向。- 输出格式由保存时的扩展名决定,JPEG 可附加
quality参数控制压缩质量。
2.3 用 pypdfium2 抽取文本
渲染之外,pypdfium2 也提供基础的文本抽取 API page.get_text(),适合快速获取整页文本长度或做概览:
import pypdfium2 as pdfium
pdf = pdfium.PdfDocument("document.pdf")
for i, page in enumerate(pdf):
text = page.get_text()
print(f"Page {i+1} text length: {len(text)} chars")
需要说明的是,pypdfium2 的文本抽取属于"够用"级别;对于带坐标的结构化文本或复杂表格,REFERENCE.md 与 Hive 内置的 pdf_read 工具都更推荐 pdfplumber(其底层基于 pdfminer,表格与版式还原更好,见 pdf_read_tool.py 的说明)。
3. JavaScript 库:pdf-lib 与 pdfjs-dist
3.1 pdf-lib:创建与修改 PDF(MIT 许可证)
pdf-lib 是一个可在任意 JavaScript 环境(Node、浏览器、Deno 等)中创建和修改 PDF 的库,纯 JS 实现、无原生依赖,适合在 Agent 的 Node 工具链或前端中直接使用。
加载并操作已有 PDF:读取字节流 → PDFDocument.load → 查询页数、新增页面、绘制文本 → save() 落盘:
import { PDFDocument } from 'pdf-lib';
import fs from 'fs';
async function manipulatePDF() {
// Load existing PDF
const existingPdfBytes = fs.readFileSync('input.pdf');
const pdfDoc = await PDFDocument.load(existingPdfBytes);
// Get page count
const pageCount = pdfDoc.getPageCount();
console.log(`Document has ${pageCount} pages`);
// Add new page
const newPage = pdfDoc.addPage([600, 400]);
newPage.drawText('Added by pdf-lib', {
x: 100,
y: 300,
size: 16
});
// Save modified PDF
const pdfBytes = await pdfDoc.save();
fs.writeFileSync('modified.pdf', pdfBytes);
}
从零创建复杂 PDF:通过 embedFont 嵌入标准字体(StandardFonts.Helvetica 等),用 drawText 控制样式,用 drawRectangle 绘制色块,再以坐标循环拼出表格效果,可快速生成发票、报表等结构简单但样式多样的文档:
import { PDFDocument, rgb, StandardFonts } from 'pdf-lib';
import fs from 'fs';
async function createPDF() {
const pdfDoc = await PDFDocument.create();
// Add fonts
const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica);
const helveticaBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
// Add page
const page = pdfDoc.addPage([595, 842]); // A4 size
const { width, height } = page.getSize();
// Add text with styling
page.drawText('Invoice #12345', {
x: 50,
y: height - 50,
size: 18,
font: helveticaBold,
color: rgb(0.2, 0.2, 0.8)
});
// Add rectangle (header background)
page.drawRectangle({
x: 40,
y: height - 100,
width: width - 80,
height: 30,
color: rgb(0.9, 0.9, 0.9)
});
// Add table-like content
const items = [
['Item', 'Qty', 'Price', 'Total'],
['Widget', '2', '$50', '$100'],
['Gadget', '1', '$75', '$75']
];
let yPos = height - 150;
items.forEach(row => {
let xPos = 50;
row.forEach(cell => {
page.drawText(cell, {
x: xPos,
y: yPos,
size: 12,
font: helveticaFont
});
xPos += 120;
});
yPos -= 25;
});
const pdfBytes = await pdfDoc.save();
fs.writeFileSync('created.pdf', pdfBytes);
}
A4 页面尺寸为 595×842 点(pt);所有坐标以页面左下角为原点,这与 PDF 规范一致,也是新手最易踩坑之处。
高级合并与拆分:copyPages 支持把源文档的任意页面(含指定索引数组)复制进新文档,配合 getPageIndices() 可实现整本合并或选择性抽取:
import { PDFDocument } from 'pdf-lib';
import fs from 'fs';
async function mergePDFs() {
// Create new document
const mergedPdf = await PDFDocument.create();
// Load source PDFs
const pdf1Bytes = fs.readFileSync('doc1.pdf');
const pdf2Bytes = fs.readFileSync('doc2.pdf');
const pdf1 = await PDFDocument.load(pdf1Bytes);
const pdf2 = await PDFDocument.load(pdf2Bytes);
// Copy pages from first PDF
const pdf1Pages = await mergedPdf.copyPages(pdf1, pdf1.getPageIndices());
pdf1Pages.forEach(page => mergedPdf.addPage(page));
// Copy specific pages from second PDF (pages 0, 2, 4)
const pdf2Pages = await mergedPdf.copyPages(pdf2, [0, 2, 4]);
pdf2Pages.forEach(page => mergedPdf.addPage(page));
const mergedPdfBytes = await mergedPdf.save();
fs.writeFileSync('merged.pdf', mergedPdfBytes);
}
此外,REFERENCE.md 的性能建议指出:pdf-lib 在表单(Form)结构的保留上优于多数替代方案,处理带表单的 PDF 时优先考虑它,并在处理前先校验表单字段。
3.2 pdfjs-dist:浏览器端渲染与解析(Apache 许可证)
pdfjs-dist 即 Mozilla 的 PDF.js,是浏览器端渲染 PDF 的事实标准库。其核心是多线程架构——渲染任务交给 Web Worker,避免阻塞主线程。
基础加载与渲染:关键一步是配置 workerSrc(指向 pdf.worker.js),然后 getDocument 异步加载,getPage + getViewport({ scale }) 得到视口尺寸,最后在 canvas 上执行渲染:
import * as pdfjsLib from 'pdfjs-dist';
// Configure worker (important for performance)
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdf.worker.js';
async function renderPDF() {
// Load PDF
const loadingTask = pdfjsLib.getDocument('document.pdf');
const pdf = await loadingTask.promise;
console.log(`Loaded PDF with ${pdf.numPages} pages`);
// Get first page
const page = await pdf.getPage(1);
const viewport = page.getViewport({ scale: 1.5 });
// Render to canvas
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
const renderContext = {
canvasContext: context,
viewport: viewport
};
await page.render(renderContext).promise;
document.body.appendChild(canvas);
}
注意 getPage(1) 的索引从 1 开始,与 pypdfium2 的 pdf[0](从 0 开始)不同。
带坐标的文本抽取:getTextContent() 返回的 items 中,每个文本项带有 transform 矩阵(transform[4]、transform[5] 即 x、y 平移量)与宽高信息,可据此重建带坐标的文本层,用于高亮、检索、点击定位等高级交互:
import * as pdfjsLib from 'pdfjs-dist';
async function extractText() {
const loadingTask = pdfjsLib.getDocument('document.pdf');
const pdf = await loadingTask.promise;
let fullText = '';
// Extract text from all pages
for (let i = 1; i <= pdf.numPages; i++) {
const page = await pdf.getPage(i);
const textContent = await page.getTextContent();
const pageText = textContent.items
.map(item => item.str)
.join(' ');
fullText += `\n--- Page ${i} ---\n${pageText}`;
// Get text with coordinates for advanced processing
const textWithCoords = textContent.items.map(item => ({
text: item.str,
x: item.transform[4],
y: item.transform[5],
width: item.width,
height: item.height
}));
}
console.log(fullText);
return fullText;
}
注释与表单抽取:page.getAnnotations() 返回注释对象数组,subtype 标识类型(Link、Text、Widget 等),contents 为注释内容,rect 为坐标矩形,可用于实现批注清单、表单字段盘点等能力:
import * as pdfjsLib from 'pdfjs-dist';
async function extractAnnotations() {
const loadingTask = pdfjsLib.getDocument('annotated.pdf');
const pdf = await loadingTask.promise;
for (let i = 1; i <= pdf.numPages; i++) {
const page = await pdf.getPage(i);
const annotations = await page.getAnnotations();
annotations.forEach(annotation => {
console.log(`Annotation type: ${annotation.subtype}`);
console.log(`Content: ${annotation.contents}`);
console.log(`Coordinates: ${JSON.stringify(annotation.rect)}`);
});
}
}
4. 命令行工具的高级操作
4.1 poppler-utils:文本、图像与渲染三件套
poppler-utils 是 Linux 桌面与 CI 环境中最常用的 PDF 工具集,Hive 桌面运行时的 pdf_read 失败兜底方案(见 pdf_read_tool.py)也明确依赖其中的 pdftotext、pdfinfo、pdfimages、pdfgrep。
带包围盒坐标的文本抽取——结构化数据提取的关键:
# Extract text with bounding box coordinates (essential for structured data)
pdftotext -bbox-layout document.pdf output.xml
# The XML output contains precise coordinates for each text element
-bbox-layout 输出的 XML 为每个文本元素标注精确坐标,是解析双栏论文、发票、简历等版式化文档的基础;对纯文本需求,-layout(保留版式)与 -f/-l(页码范围)则更快。
高级图像转换:
# Convert to PNG images with specific resolution
pdftoppm -png -r 300 document.pdf output_prefix
# Convert specific page range with high resolution
pdftoppm -png -r 600 -f 1 -l 3 document.pdf high_res_pages
# Convert to JPEG with quality setting
pdftoppm -jpeg -jpegopt quality=85 -r 200 document.pdf jpeg_output
-r 指定 DPI(300 为印刷级,600 为超高清扫描),-f/-l 限定页范围,-jpegopt 控制 JPEG 质量。
提取内嵌图像:
# Extract all embedded images with metadata
pdfimages -j -p document.pdf page_images
# List image info without extracting
pdfimages -list document.pdf
# Extract images in their original format
pdfimages -all document.pdf images/img
-j:按 JPEG 原始格式写出(避免重编码损失);-p:文件名携带页码信息;-list:仅打印图像清单(位置、尺寸、色彩空间),用于先盘点再决定是否提取;-all:按原始格式提取所有图像。
4.2 qpdf:页面操作、优化修复与加密
qpdf 以结构级操作见长,是 PDF 修复与页面操纵的瑞士军刀(Apache 许可证)。
复杂页面操作:
# Split PDF into groups of pages
qpdf --split-pages=3 input.pdf output_group_%02d.pdf
# Extract specific pages with complex ranges
qpdf input.pdf --pages input.pdf 1,3-5,8,10-end -- extracted.pdf
# Merge specific pages from multiple PDFs
qpdf --empty --pages doc1.pdf 1-3 doc2.pdf 5-7 doc3.pdf 2,4 -- combined.pdf
--pages 支持逗号、连字符、end 关键字的混合范围语法,甚至可在一次命令中跨多个源文件选页合并,%02d 为拆分输出文件名提供补零序号。
优化与修复:
# Optimize PDF for web (linearize for streaming)
qpdf --linearize input.pdf optimized.pdf
# Remove unused objects and compress
qpdf --optimize-level=all input.pdf compressed.pdf
# Attempt to repair corrupted PDF structure
qpdf --check input.pdf
qpdf --fix-qdf damaged.pdf repaired.pdf
# Show detailed PDF structure for debugging
qpdf --show-all-pages input.pdf > structure.txt
--linearize:生成线性化(Web 优化)PDF,支持边下载边显示;--optimize-level=all:清理无用对象并压缩,显著减小体积;--check:只读体检,报告结构问题;--fix-qdf:修复损坏结构(通常配合--qdf先规范化再修复)。
高级加密:
# Add password protection with specific permissions
qpdf --encrypt user_pass owner_pass 256 --print=none --modify=none -- input.pdf encrypted.pdf
# Check encryption status
qpdf --show-encryption encrypted.pdf
# Remove password protection (requires password)
qpdf --password=secret123 --decrypt encrypted.pdf decrypted.pdf
--encrypt 第一个参数为用户密码(打开密码),第二个为所有者密码(权限密码),256 指定 AES-256 加密强度,--print=none --modify=none 限制打印与修改权限。-- 用于分隔选项与输入文件。
5. Python 高级技巧:pdfplumber 与 reportlab
5.1 pdfplumber:坐标级文本与复杂表格
pdfplumber 基于 pdfminer.six,提供字符级(chars)坐标访问。REFERENCE.md 演示了三种进阶能力:
带坐标的文本抽取:逐字符输出 text 及左下角坐标 x0/y0;within_bbox((left, top, right, bottom)) 可按包围盒截取指定区域文本,适合处理固定版式的表单或合同条款区域:
import pdfplumber
with pdfplumber.open("document.pdf") as pdf:
page = pdf.pages[0]
# Extract all text with coordinates
chars = page.chars
for char in chars[:10]: # First 10 characters
print(f"Char: '{char['text']}' at x:{char['x0']:.1f} y:{char['y0']:.1f}")
# Extract text by bounding box (left, top, right, bottom)
bbox_text = page.within_bbox((100, 100, 400, 200)).extract_text()
自定义表格提取设置:对于无边框、不规则或压线表格,默认策略可能失效。通过 table_settings 可指定垂直/水平线检测策略(lines/text/explicit)、吸附容差与交叉点容差:
import pdfplumber
import pandas as pd
with pdfplumber.open("complex_table.pdf") as pdf:
page = pdf.pages[0]
# Extract tables with custom settings for complex layouts
table_settings = {
"vertical_strategy": "lines",
"horizontal_strategy": "lines",
"snap_tolerance": 3,
"intersection_tolerance": 15
}
tables = page.extract_tables(table_settings)
# Visual debugging for table extraction
img = page.to_image(resolution=150)
img.save("debug_layout.png")
snap_tolerance 控制相近线条被吸附为同一条线的距离阈值,intersection_tolerance 控制交点判定容差;调整这两个参数通常是解决"表格断裂/错位"的第一步。page.to_image() 可输出版式调试图,用于肉眼核对表格识别结果——这与 Hive 中扫描页需要视觉兜底的思路一致(needs_vision 标记,见 pdf_read_tool.py)。
5.2 reportlab:带表格的专业报表生成
reportlab 的 Platypus 框架(SimpleDocTemplate + Flowable 对象)适合生成多页结构化报表。其 Table 配合 TableStyle 可实现表头着色、居中对齐、加粗、底纹与网格线等专业排版:
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib import colors
# Sample data
data = [
['Product', 'Q1', 'Q2', 'Q3', 'Q4'],
['Widgets', '120', '135', '142', '158'],
['Gadgets', '85', '92', '98', '105']
]
# Create PDF with table
doc = SimpleDocTemplate("report.pdf")
elements = []
# Add title
styles = getSampleStyleSheet()
title = Paragraph("Quarterly Sales Report", styles['Title'])
elements.append(title)
# Add table with advanced styling
table = Table(data)
table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 14),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
('GRID', (0, 0), (-1, -1), 1, colors.black)
]))
elements.append(table)
doc.build(elements)
TableStyle 命令采用 (操作, 起始单元格, 结束单元格, 参数...) 四元组形式,(0, 0), (-1, -1) 表示从左上角到右下角的全表范围。
6. 复杂工作流实战
6.1 从 PDF 中提取图片/图形
- 方法一:pdfimages(最快)——直接抽取内嵌位图,无重编码,速度远超渲染方案:
# Extract all images with original quality
pdfimages -all document.pdf images/img
- 方法二:pypdfium2 + 图像处理——当 PDF 中的"图"是矢量绘制或由文本组成、无内嵌位图时,只能渲染页面再做检测。REFERENCE.md 给出了思路骨架:高倍率渲染 → 转 numpy 数组 → 以非白区域构建掩码 → 找轮廓取包围盒:
import pypdfium2 as pdfium
from PIL import Image
import numpy as np
def extract_figures(pdf_path, output_dir):
pdf = pdfium.PdfDocument(pdf_path)
for page_num, page in enumerate(pdf):
# Render high-resolution page
bitmap = page.render(scale=3.0)
img = bitmap.to_pil()
# Convert to numpy for processing
img_array = np.array(img)
# Simple figure detection (non-white regions)
mask = np.any(img_array != [255, 255, 255], axis=2)
# Find contours and extract bounding boxes
# (This is simplified - real implementation would need more sophisticated detection)
# Save detected figures
# ... implementation depends on specific needs
注意 REFERENCE.md 明确提示:真实场景需更精细的检测(如结合版面分析区分图表与文本块),上述代码是起点而非成品。
6.2 批量 PDF 处理(含错误处理)
使用 pypdf 的 PdfReader/PdfWriter,配合 glob 遍历与逐文件 try/except,实现单文件失败不中断全批的健壮流水线——合并或批量抽文本两个操作共用一套框架:
import os
import glob
from pypdf import PdfReader, PdfWriter
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def batch_process_pdfs(input_dir, operation='merge'):
pdf_files = glob.glob(os.path.join(input_dir, "*.pdf"))
if operation == 'merge':
writer = PdfWriter()
for pdf_file in pdf_files:
try:
reader = PdfReader(pdf_file)
for page in reader.pages:
writer.add_page(page)
logger.info(f"Processed: {pdf_file}")
except Exception as e:
logger.error(f"Failed to process {pdf_file}: {e}")
continue
with open("batch_merged.pdf", "wb") as output:
writer.write(output)
elif operation == 'extract_text':
for pdf_file in pdf_files:
try:
reader = PdfReader(pdf_file)
text = ""
for page in reader.pages:
text += page.extract_text()
output_file = pdf_file.replace('.pdf', '.txt')
with open(output_file, 'w', encoding='utf-8') as f:
f.write(text)
logger.info(f"Extracted text from: {pdf_file}")
except Exception as e:
logger.error(f"Failed to extract text from {pdf_file}: {e}")
continue
6.3 高级裁剪(Mediabox)
通过直接修改页面的 mediabox 四边坐标(单位:点),可裁剪掉页眉页脚或广告区域:
from pypdf import PdfWriter, PdfReader
reader = PdfReader("input.pdf")
writer = PdfWriter()
# Crop page (left, bottom, right, top in points)
page = reader.pages[0]
page.mediabox.left = 50
page.mediabox.bottom = 50
page.mediabox.right = 550
page.mediabox.top = 750
writer.add_page(page)
with open("cropped.pdf", "wb") as output:
writer.write(output)
7. 性能优化要点
REFERENCE.md 针对不同场景给出了明确的选型建议,整理如下:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 大文件处理 | 流式/分块,避免整本载入内存;qpdf --split-pages 拆分;pypdfium2 逐页处理 | 大文件整载易 OOM,拆分后可并行 |
| 纯文本抽取 | pdftotext -bbox-layout 最快;结构化数据与表格用 pdfplumber;避免对超大文档用 pypdf.extract_text() | 文本模式快于渲染;pypdf 逐页抽取超慢 |
| 图像提取 | pdfimages 远快于页面渲染;预览用低分辨率、最终输出用高分辨率 | 直接取内嵌位图免渲染 |
| 表单填充 | pdf-lib 保留表单结构优于多数替代 | 结构保留好,处理前先校验字段 |
| 内存管理 | 分块处理,如每次 10 页写入一个 chunk 文件 | 控制峰值内存与上下文占用 |
其中"分块处理大 PDF"给出了完整可运行示例:按 chunk_size 把大文件切成若干小块逐个写出,后续可对每块单独抽文本或喂给 Agent(这正好与 Hive 中大 PDF 的 agentic 路径——pdf_read(file_path=..., pages='1-N') 分段加载——形成呼应,见 routes_execution.py):
# Process PDFs in chunks
def process_large_pdf(pdf_path, chunk_size=10):
reader = PdfReader(pdf_path)
total_pages = len(reader.pages)
for start_idx in range(0, total_pages, chunk_size):
end_idx = min(start_idx + chunk_size, total_pages)
writer = PdfWriter()
for i in range(start_idx, end_idx):
writer.add_page(reader.pages[i])
# Process chunk
with open(f"chunk_{start_idx//chunk_size}.pdf", "wb") as output:
writer.write(output)
8. 常见问题排查
8.1 加密 PDF
用 pypdf 的 is_encrypted 探测 + decrypt(password) 解锁;注意解密失败时抛异常,需捕获处理。这与 Hive pdf_read 工具对加密 PDF 的兜底建议一致(qpdf --decrypt --password=<pw> 后重试,见 pdf_read_tool.py):
# Handle password-protected PDFs
from pypdf import PdfReader
try:
reader = PdfReader("encrypted.pdf")
if reader.is_encrypted:
reader.decrypt("password")
except Exception as e:
print(f"Failed to decrypt: {e}")
8.2 损坏 PDF
先用 qpdf --check 体检定位问题,再尝试 --replace-input 原地修复(比输出新文件更省磁盘操作):
# Use qpdf to repair
qpdf --check corrupted.pdf
qpdf --replace-input corrupted.pdf
8.3 文本抽取异常(扫描件兜底 OCR)
当页面无文本层(扫描件)时,pypdf/pdfplumber 抽取结果为空或极短。REFERENCE.md 给出 pytesseract + pdf2image 的 OCR 兜底方案;Hive 中 pdf_read 工具则通过 _VISION_TEXT_THRESHOLD = 200 字符阈值将低文本页标记为 needs_vision,由视觉模型或 attach_file 直接读图(见 pdf_read_tool.py):
# Fallback to OCR for scanned PDFs
import pytesseract
from pdf2image import convert_from_path
def extract_text_with_ocr(pdf_path):
images = convert_from_path(pdf_path)
text = ""
for i, image in enumerate(images):
text += pytesseract.image_to_string(image)
return text
9. 与 Hive 运行时集成的落地建议
结合仓库源码,以上进阶能力在 Hive 中有几条明确的落地路径:
- 文本抽取优先走内置
pdf_read:工具已封装 pdfplumber 的文本/表格抽取、页范围过滤、多文件批处理与解密支持,并对扫描页输出needs_vision信号,无需重复造轮子;其完整参数行为(pages、max_pages、include_metadata等)有 test_pdf_read_tool.py 覆盖验证(如max_pages上下限钳制、pages="1-20"超限截断并返回 truncation 元数据)。 - 大 PDF 走 agentic 路径:超过 10 MB 的 PDF 不上传字节,而是以路径形式提示 Agent 用
pdf_read(file_path=..., pages='1-N')分段加载(初始建议页数按min(50, page_count)计算),避免巨型文档撑爆上下文——routes_execution.py 的注释形象地称之为"calculus-textbook 场景"。 - 渲染、生成、加密、修复等场景按需选用本文工具:渲染用 pypdfium2,浏览器端展示用 pdfjs-dist,生成报表用 reportlab/pdf-lib,结构级操作(拆分/合并/加密/修复)用 qpdf,位图提取用 pdfimages,扫描件 OCR 用 pdf2image + pytesseract。
附:各库许可证速查
REFERENCE.md 结尾汇总了全部涉及库的许可证,便于合规选用:
- pypdf:BSD License
- pdfplumber:MIT License
- pypdfium2:Apache/BSD License
- reportlab:BSD License
- poppler-utils:GPL-2 License
- qpdf:Apache License
- pdf-lib:MIT License
- pdfjs-dist:Apache License
其中 GPL-2 的 poppler-utils 与其余宽松许可库需分开评估分发场景;若在商业闭源产品中分发二进制,优先选择 MIT/Apache/BSD 的纯库方案(pdf-lib、pdfjs-dist、pypdfium2 等)。
【免费下载链接】hive Multi-Agent Harness for Production AI 项目地址: https://gitcode.com/gh_mirrors/hive48/hive
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



