Nitish kumar
Upload folder using huggingface_hub
c20f20c verified

PDF 解析系统

提供统一接口支持多种 PDF 解析提供商。

支持的提供商

1. unpdf (内置)

  • 成本: 免费,内置
  • 特性: 基础文本提取、图片提取
  • 要求: 无
  • 使用: 直接上传 PDF 文件

2. MinerU (本地部署)

  • 成本: 免费(需要自己部署)
  • 特性:
    • 高级文本提取(保留 Markdown 布局)
    • 表格识别
    • 公式提取(LaTeX)
    • 更好的 OCR 支持
    • 多种输出格式(markdown, JSON, docx, html, latex)
  • 要求:
    • 部署 MinerU 服务(Docker 或源码)
    • 配置服务器地址
  • 优势: 数据隐私、无文件大小限制

快速开始

部署 MinerU(可选)

# 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(文件上传)

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(本地服务)

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

响应格式

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 解析器与内容生成流程无缝集成:

// 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. 转换: ![alt](images/img_1.png)![alt](img_1)
  3. 映射: 创建 { "img_1": "data:image/png;base64,..." }
  4. 生成: AI 使用 img_1 引用生成幻灯片
  5. 解析: resolveImageIds() 替换为实际 URL
  6. 渲染: 幻灯片显示图片

配置

全局设置

import { useSettingsStore } from '@/lib/store/settings';

useSettingsStore.setState({
  pdfProviderId: 'mineru',
  pdfProvidersConfig: {
    mineru: {
      baseUrl: 'http://localhost:8080',
      apiKey: 'optional-if-needed',
    },
  },
});

请求级配置

// 在 API 调用时覆盖全局设置
formData.append('providerId', 'mineru');
formData.append('baseUrl', 'http://your-server:8080');
formData.append('apiKey', 'optional');

添加新的提供商

1. 定义提供商

lib/pdf/constants.ts:

export const PDF_PROVIDERS = {
  myProvider: {
    id: 'myProvider',
    name: 'My Provider',
    requiresApiKey: true,
    features: ['text', 'images'],
  },
};

2. 实现解析器

lib/pdf/pdf-providers.ts:

async function parseWithMyProvider(
  config: PDFParserConfig,
  pdfBuffer: Buffer
): Promise<ParsedPdfContent> {
  // 实现解析逻辑
  return {
    text: '...',
    images: [...],
    metadata: {
      pageCount: 0,
      parser: 'myProvider',
    },
  };
}

3. 添加到路由

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: 检查:

# 服务状态
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: 优化:

# 增加 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 并发处理

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());
  }),
);

结果缓存

// 考虑缓存解析结果
const cacheKey = `pdf_${fileHash}`;
const cached = localStorage.getItem(cacheKey);
if (cached) {
  return JSON.parse(cached);
}

参考资源


最后更新: 2026-02-11 模式: 本地自托管 状态: 生产就绪