| # PDF 解析系统 |
|
|
| 提供统一接口支持多种 PDF 解析提供商。 |
|
|
| ## 支持的提供商 |
|
|
| ### 1. unpdf (内置) |
|
|
| - **成本**: 免费,内置 |
| - **特性**: 基础文本提取、图片提取 |
| - **要求**: 无 |
| - **使用**: 直接上传 PDF 文件 |
|
|
| ### 2. MinerU (本地部署) |
|
|
| - **成本**: 免费(需要自己部署) |
| - **特性**: |
| - 高级文本提取(保留 Markdown 布局) |
| - 表格识别 |
| - 公式提取(LaTeX) |
| - 更好的 OCR 支持 |
| - 多种输出格式(markdown, JSON, docx, html, latex) |
| - **要求**: |
| - 部署 MinerU 服务(Docker 或源码) |
| - 配置服务器地址 |
| - **优势**: 数据隐私、无文件大小限制 |
|
|
| ## 快速开始 |
|
|
| ### 部署 MinerU(可选) |
|
|
| ```bash |
| # Docker 部署(推荐) |
| docker pull opendatalab/mineru:latest |
| docker run -d --name mineru -p 8080:8080 opendatalab/mineru:latest |
| |
| # 验证 |
| curl http://localhost:8080/api/health |
| ``` |
|
|
| ### API 使用 |
|
|
| #### 使用 unpdf(文件上传) |
|
|
| ```typescript |
| const formData = new FormData(); |
| formData.append('pdf', pdfFile); |
| formData.append('providerId', 'unpdf'); |
| |
| const response = await fetch('/api/parse-pdf', { |
| method: 'POST', |
| body: formData, |
| }); |
| |
| const result = await response.json(); |
| // result.data: ParsedPdfContent |
| ``` |
|
|
| #### 使用 MinerU(本地服务) |
|
|
| ```typescript |
| const formData = new FormData(); |
| formData.append('pdf', pdfFile); |
| formData.append('providerId', 'mineru'); |
| formData.append('baseUrl', 'http://localhost:8080'); |
| |
| const response = await fetch('/api/parse-pdf', { |
| method: 'POST', |
| body: formData, |
| }); |
| |
| const result = await response.json(); |
| // result.data: ParsedPdfContent with imageMapping |
| ``` |
|
|
| ## 响应格式 |
|
|
| ```typescript |
| interface ParsedPdfContent { |
| text: string; // 提取的文本(MinerU 为 Markdown) |
| images: string[]; // Base64 图片数组 |
| |
| // 扩展特性(MinerU) |
| tables?: Array<{ |
| page: number; |
| data: string[][]; |
| caption?: string; |
| }>; |
| |
| formulas?: Array<{ |
| page: number; |
| latex: string; |
| position?: { x: number; y: number; width: number; height: number }; |
| }>; |
| |
| layout?: Array<{ |
| page: number; |
| type: 'title' | 'text' | 'image' | 'table' | 'formula'; |
| content: string; |
| position?: { x: number; y: number; width: number; height: number }; |
| }>; |
| |
| metadata?: { |
| pageCount: number; |
| parser: 'unpdf' | 'mineru'; |
| fileName?: string; |
| fileSize?: number; |
| processingTime?: number; |
| |
| // 用于内容生成流程(MinerU) |
| imageMapping?: Record<string, string>; // img_1 -> base64 URL |
| pdfImages?: Array<{ |
| id: string; // img_1, img_2, etc. |
| src: string; // base64 data URL |
| pageNumber: number; // PDF 页码 |
| description?: string; // 图片描述 |
| }>; |
| }; |
| } |
| ``` |
|
|
| ## 与内容生成集成 |
|
|
| MinerU 解析器与内容生成流程无缝集成: |
|
|
| ```typescript |
| // 1. 解析 PDF |
| const parseResult = await parsePDF( |
| { |
| providerId: 'mineru', |
| baseUrl: 'http://localhost:8080', |
| }, |
| buffer, |
| ); |
| |
| // 2. 提取数据 |
| const pdfText = parseResult.text; // Markdown(含 img_1 引用) |
| const pdfImages = parseResult.metadata.pdfImages; // 图片数组 |
| const imageMapping = parseResult.metadata.imageMapping; // 图片映射 |
| |
| // 3. 生成场景大纲 |
| await generateSceneOutlinesFromRequirements( |
| requirements, |
| pdfText, // Markdown 内容 |
| pdfImages, // 带页码的图片 |
| aiCall, |
| ); |
| |
| // 4. 生成场景(含图片) |
| await buildSceneFromOutline( |
| outline, |
| aiCall, |
| stageId, |
| assignedImages, // 从 pdfImages 筛选 |
| imageMapping, // 用于解析 img_1 到实际 URL |
| ); |
| ``` |
|
|
| ## 图片处理流程 |
|
|
| MinerU 的图片处理: |
|
|
| 1. **提取**: PDF → MinerU → Markdown + 图片 |
| 2. **转换**: `` → `` |
| 3. **映射**: 创建 `{ "img_1": "data:image/png;base64,..." }` |
| 4. **生成**: AI 使用 `img_1` 引用生成幻灯片 |
| 5. **解析**: `resolveImageIds()` 替换为实际 URL |
| 6. **渲染**: 幻灯片显示图片 |
|
|
| ## 配置 |
|
|
| ### 全局设置 |
|
|
| ```typescript |
| import { useSettingsStore } from '@/lib/store/settings'; |
| |
| useSettingsStore.setState({ |
| pdfProviderId: 'mineru', |
| pdfProvidersConfig: { |
| mineru: { |
| baseUrl: 'http://localhost:8080', |
| apiKey: 'optional-if-needed', |
| }, |
| }, |
| }); |
| ``` |
|
|
| ### 请求级配置 |
|
|
| ```typescript |
| // 在 API 调用时覆盖全局设置 |
| formData.append('providerId', 'mineru'); |
| formData.append('baseUrl', 'http://your-server:8080'); |
| formData.append('apiKey', 'optional'); |
| ``` |
|
|
| ## 添加新的提供商 |
|
|
| ### 1. 定义提供商 |
|
|
| `lib/pdf/constants.ts`: |
|
|
| ```typescript |
| export const PDF_PROVIDERS = { |
| myProvider: { |
| id: 'myProvider', |
| name: 'My Provider', |
| requiresApiKey: true, |
| features: ['text', 'images'], |
| }, |
| }; |
| ``` |
|
|
| ### 2. 实现解析器 |
|
|
| `lib/pdf/pdf-providers.ts`: |
|
|
| ```typescript |
| async function parseWithMyProvider( |
| config: PDFParserConfig, |
| pdfBuffer: Buffer |
| ): Promise<ParsedPdfContent> { |
| // 实现解析逻辑 |
| return { |
| text: '...', |
| images: [...], |
| metadata: { |
| pageCount: 0, |
| parser: 'myProvider', |
| }, |
| }; |
| } |
| ``` |
|
|
| ### 3. 添加到路由 |
|
|
| ```typescript |
| switch (config.providerId) { |
| case 'unpdf': |
| result = await parseWithUnpdf(pdfBuffer); |
| break; |
| case 'mineru': |
| result = await parseWithMinerU(config, pdfBuffer); |
| break; |
| case 'myProvider': |
| result = await parseWithMyProvider(config, pdfBuffer); |
| break; |
| } |
| ``` |
|
|
| ## 调试工具 |
|
|
| 访问 http://localhost:3000/debug/pdf-parser 测试解析功能: |
|
|
| - 切换提供商(unpdf/MinerU) |
| - 上传 PDF 文件 |
| - 配置服务器地址 |
| - 查看解析结果 |
| - 检查图片映射 |
|
|
| ## 常见问题 |
|
|
| ### Q: MinerU 服务无法连接? |
|
|
| **A**: 检查: |
|
|
| ```bash |
| # 服务状态 |
| docker ps | grep mineru |
| |
| # 网络连通性 |
| curl http://localhost:8080/api/health |
| |
| # 日志 |
| docker logs mineru |
| ``` |
|
|
| ### Q: 图片不显示? |
|
|
| **A**: 确保: |
|
|
| 1. `imageMapping` 正确传递到 scene-stream API |
| 2. 图片 ID 格式正确(img_1, img_2) |
| 3. Base64 编码完整 |
|
|
| ### Q: 解析速度慢? |
|
|
| **A**: 优化: |
|
|
| ```bash |
| # 增加 Docker 资源 |
| docker run -d \ |
| --name mineru \ |
| -p 8080:8080 \ |
| --memory=4g \ |
| --cpus=2 \ |
| opendatalab/mineru:latest |
| ``` |
|
|
| ### Q: unpdf vs MinerU 如何选择? |
|
|
| **A**: 选择建议: |
|
|
| | 场景 | 推荐 | |
| | ------------------ | ------ | |
| | 简单 PDF(纯文本) | unpdf | |
| | 包含表格、公式 | MinerU | |
| | 需要保留布局 | MinerU | |
| | 快速测试 | unpdf | |
| | 生产环境 | MinerU | |
| | 无法部署服务 | unpdf | |
|
|
| ## 性能建议 |
|
|
| ### MinerU 并发处理 |
|
|
| ```typescript |
| const files = [file1, file2, file3]; |
| |
| const results = await Promise.all( |
| files.map((file) => { |
| const formData = new FormData(); |
| formData.append('pdf', file); |
| formData.append('providerId', 'mineru'); |
| return fetch('/api/parse-pdf', { |
| method: 'POST', |
| body: formData, |
| }).then((r) => r.json()); |
| }), |
| ); |
| ``` |
|
|
| ### 结果缓存 |
|
|
| ```typescript |
| // 考虑缓存解析结果 |
| const cacheKey = `pdf_${fileHash}`; |
| const cached = localStorage.getItem(cacheKey); |
| if (cached) { |
| return JSON.parse(cached); |
| } |
| ``` |
|
|
| ## 参考资源 |
|
|
| - **MinerU GitHub**: https://github.com/opendatalab/MinerU |
| - **快速开始**: `/MINERU_QUICKSTART.md` |
| - **变更说明**: `/MINERU_LOCAL_DEPLOYMENT.md` |
| - **调试工具**: http://localhost:3000/debug/pdf-parser |
|
|
| --- |
|
|
| **最后更新**: 2026-02-11 |
| **模式**: 本地自托管 |
| **状态**: 生产就绪 |
|
|