☕ 用智普 OCR + 阿里云大模型,优雅提取 PDF 数据(Java 版)

151 阅读7分钟

"没有什么问题是一行代码解决不了的,如果有,那就再加一行。" ------某位在 PDF 地狱里摸爬滚打的开发者


🧩 一、PDF 的"阴间属性"

PDF 是一个神奇的存在, 看起来是文本,其实是图片; 看起来整齐,其实结构乱七八糟。

尤其当客户甩给你几百份扫描件时,你才体会到: "这哪是文件,这是数字版的迷宫。"

所以,我决定不再手抄。 我要召唤两位 AI 战神出场: - 📖 智普 OCR:看得懂 PDF; - 🤖 阿里云大模型:读得懂内容。


🧠 二、为什么选择智普 OCR?

说实话,我一开始是想用阿里云的 OCR 的。 结果一试,才发现------ 它对 PDF 的支持并不理想,准确率也不高。

阿里云 OCR 对图片识别还行,但不支持直接解析 PDF; 而我主要处理的是 PDF 格式。

这时候智普 OCR 给了我惊喜:

  • Expert 模式 支持直接上传 PDF;
  • 💰按页计费:限时 6 折优惠,仅 0.012 元/页
  • 🧠能高精度识别表格、公式、复杂版面
  • 🧾在科研、教辅、财报、标准等多场景表现稳定。

一句话:准确、稳定、便宜,还支持 PDF! 这还选啥?当然是智普。

详细的智普ocr类型如下:

服务类型支持格式最大文件大小解析结果计费方式核心优势推荐场景
Primepdf, docx, doc, xls, xlsx, ppt, pptx, png, jpg, jpeg, csv, txt, md, html, epub, bmp, gif, webp, heic, eps, icns, im, pcx, ppm, tiff, xbm, heif, jp2PDF/DOC/DOCX/PPT ≤100MB XLS/XLSX/CSV ≤10MB PNG/JPG/JPEG ≤20MB图片 + Markdown 文件 + 包含布局信息的 JSON 文件按解析页数消耗后付费 优惠后 0.12 元/页- 支持多种复杂版式(双栏、混排、三栏等) - 高精度解析图文、公式、段落、表格、页眉页脚等 - 多模态模型,适配复杂排版 - 精度行业领先,适合高要求场景- 科研出版:学术论文、技术书籍、会议资料 - 教育考试:试卷、教材、讲义 - 行业文档:合同、行业报告、白皮书
Expertpdf≤100MB图片 + Markdown 文件按页数计费 限时 6 折优惠后 0.012 元/页- PDF、图片适配能力突出 - 高精度识别 PDF 表格与公式 - 在科研、教辅、企业、财报、标准等多领域表现稳定 - 性价比高,适合大规模解析- 学术研究:论文、学术报告、专利 - 教育出版:教辅书籍、教育资料 - 商业金融:年报、财报、研究报告、国家标准
Litepdf, docx, doc, xls, xlsx, ppt, pptx, png, jpg, jpeg, csv, txt, md≤50MB纯文本(无图片)按调用次数计费 当前免费(2025-10-08 起 0.01 元/次)- 全格式支持,覆盖常见办公文档 - 提供基本结构化解析,速度快 - 成本低,适合对版面还原要求不高的任务- 办公场景:标准合同、规章制度、公告 - 批量解析:资料归档、文本抽取、快速预处理

💡 三、用 Java 调智普 OCR(Unirest版)

既然确定了 OCR 工具,那就上代码!

下面这段 Java 代码用 kong.unirest.Unirest 上传 PDF文件与获取解析结果的代码 👇

import kong.unirest.HttpResponse;
import kong.unirest.Unirest;

import java.io.File;

import static com.scm.ai.AiConstaints.Z_API_KEY;

public class OrderParserOcr {
    /**
     * 上传需要解析的pdf文件
     * @return
     */
    public static ParserFileResponse upload(String filePath){
        File file = new File(filePath);
        HttpResponse<ParserFileResponse> response = Unirest.post("https://open.bigmodel.cn/api/paas/v4/files/parser/create")
                .header("Authorization", "Bearer " + Z_API_KEY)
                .field("tool_type", "expert")
                .field("file_type", "PDF")
                .field("file", file)
                .asObject(ParserFileResponse.class);

        return response.getBody();
    }
    /**
     * 获取解析结果
     * @param taskId
     * @return
     */
    public static ParserResultResponse getParseResult(String taskId){
        HttpResponse<ParserResultResponse> response = Unirest.get("https://open.bigmodel.cn/api/paas/v4/files/parser/result/"+taskId+"/text")
                .header("Authorization", "Bearer " + Z_API_KEY)
                .asObject(ParserResultResponse.class);
        return response.getBody();
    }
}

其中Z_API_KEY是智普API的key。

智普是分两步实现

  • 把需要解析的文件上传

    📦 响应结果:

    {
      "success": true,
      "message": "任务创建成功",
      "task_id": "task_123456789"
    }
    
  • 获取解析的结果

    📦 示例响应:

    {
      "status": "succeeded",
      "message": "结果获取成功",
      "content": "这是解析后的文本内容...",
      "task_id": "task_123456789",
      "parsing_result_url": "https://example.com/download/result.zip"
    }
    

⚙️ 四、为什么不直接用智普的大模型?

其实智普也有自己的大模型,体验也不错。 但我最终选择了 阿里云通义千问,原因很现实 👇

智普的 Java SDK 目前还不太完善, 无法设置 请求超时时间,而且在处理多页 PDF 时速度偏慢。

比如我识别一份 17 页的 PDF, 整整花了 300 多秒 才返回结果 🕐。

而阿里云的 Java API 体验就好得多:

  • 支持自定义超时;
  • 返回速度快;
  • Java SDK 相对成熟;
  • qwen-plus模型提取结构化数据的能力非常稳。

所以,我决定------ 识别用智普,理解用阿里云。 这波组合拳,刚柔并济,效率拉满 💪。


☁️ 五、阿里云大模型:让文字变得聪明

OCR 出来是纯文本内容,我们希望它变成结构化数据。 这就要靠通义千问(Qwen)来理解内容。

💬 Java 调用示例

  • BasicAliyunChat

    import java.time.Duration;
    import java.util.Arrays;
    import java.lang.System;
    import com.alibaba.dashscope.aigc.generation.Generation;
    import com.alibaba.dashscope.aigc.generation.GenerationParam;
    import com.alibaba.dashscope.aigc.generation.GenerationResult;
    import com.alibaba.dashscope.common.Message;
    import com.alibaba.dashscope.common.Role;
    import com.alibaba.dashscope.exception.ApiException;
    import com.alibaba.dashscope.exception.InputRequiredException;
    import com.alibaba.dashscope.exception.NoApiKeyException;
    import com.alibaba.dashscope.protocol.ConnectionConfigurations;
    import com.alibaba.dashscope.utils.Constants;
    import lombok.Data;
    
    import static com.scm.ai.AiConstaints.ALIYUN_API_KEY;
    
    @Data
    public class BasicAliyunChat {
    
    
        private String systemContent = "You are a helpful assistant.";
        private String userContent = "你是谁?";
        private String model = "qwen-plus";
    
    
    
        //  若使用新加坡地域的模型,请释放下列注释
        //  static {Constants.baseHttpApiUrl="https://dashscope-intl.aliyuncs.com/api/v1";}
        public static GenerationResult callWithMessage(String systemContent, String userContent,String model,String apiKey) throws ApiException, NoApiKeyException, InputRequiredException {
            Generation gen = new Generation();
            Message systemMsg = Message.builder()
                    .role(Role.SYSTEM.getValue())
                    .content(systemContent)
                    .build();
            Message userMsg = Message.builder()
                    .role(Role.USER.getValue())
                    .content(userContent)
                    .build();
            GenerationParam param = GenerationParam.builder()
                    // 若没有配置环境变量,请用阿里云百炼API Key将下行替换为:.apiKey("sk-xxx")
    //                .apiKey(System.getenv("DASHSCOPE_API_KEY"))
                    .apiKey(apiKey)
                    // 模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models
                    .model(model)
                    .messages(Arrays.asList(systemMsg, userMsg))
                    .resultFormat(GenerationParam.ResultFormat.MESSAGE)
                    .build();
            return gen.call(param);
        }
    }
    
  • OrderParserChat

    import com.alibaba.dashscope.aigc.generation.GenerationResult;
    import com.alibaba.dashscope.exception.ApiException;
    import com.alibaba.dashscope.exception.InputRequiredException;
    import com.alibaba.dashscope.exception.NoApiKeyException;
    import com.alibaba.dashscope.protocol.ConnectionConfigurations;
    import com.alibaba.dashscope.utils.Constants;
    import com.scm.ai.ocr.OrderParserOcr;
    import com.scm.ai.ocr.ParserResultResponse;
    import lombok.Data;
    import lombok.extern.slf4j.Slf4j;
    
    import java.time.Duration;
    
    import static com.scm.ai.AiConstaints.ALIYUN_API_KEY;
    
    @Slf4j
    @Data
    public class OrderParserChat {
        public static final String QWEN_PLUS = "qwen-plus";
        private String systemPrompt = "作为一名订单解析专家,提供的文本是ocr识别的文本内容,要求返回的格式json。";
        private String prompt="帮忙识别采购订单的客户、归属部门,并识别列表中的订单编码(订单编码在项目中7位数字串)、数量、单价";
        public String createPrompt(String order){
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append(this.systemPrompt)
                    .append(prompt)
                    .append("下面是需要解析的内容"+"\n\n")
                    .append(order);
            return stringBuilder.toString();
        }
        public String parse(String taskId){
            //如果数量大了,容易超时,需要设置readTimeout的时间
            Constants.connectionConfigurations = ConnectionConfigurations.builder()
    //                .connectTimeout(Duration.ofSeconds(10))  // 建立连接的超时时间, 默认 120s
                    .readTimeout(Duration.ofSeconds(600)) // 读取数据的超时时间, 默认 300s
    //                .writeTimeout(Duration.ofSeconds(60)) // 写入数据的超时时间, 默认 60s
    //                .connectionIdleTimeout(Duration.ofSeconds(300)) // 连接池中空闲连接的超时时间, 默认 300s
    //                .connectionPoolSize(256) // 连接池中的最大连接数, 默认 32
    //                .maximumAsyncRequests(256)  // 最大并发请求数, 默认 32
    //                .maximumAsyncRequestsPerHost(256) // 单个主机的最大并发请求数, 默认 32
                    .build();
    
            OrderParserChat chat = new OrderParserChat();
            ParserResultResponse response = OrderParserOcr.getParseResult(taskId);
            try {
                GenerationResult generationResult = BasicAliyunChat.callWithMessage(chat.systemPrompt, chat.createPrompt(response.getContent()), QWEN_PLUS, ALIYUN_API_KEY);
                return generationResult.getOutput().getChoices().get(0).getMessage().getContent();
            } catch (ApiException | NoApiKeyException | InputRequiredException e) {
                log.info("错误信息:"+e.getMessage());
                return null;
            }
        }
    
        public static void main(String[] args) {
            String result = new OrderParserChat().parse("dccd5ead5fc94b83bbdf35a5290f9963");
            log.info(result);
        }
    }
    

🧠 输出结果:

{ "订单编码明细": [
        {
          "物品号": "F040900008CP",
          "数量": 2.00,
          "单价": 10.00
        },
        {
          "物品号": "F049900087CP",
          "数量": 40.00,
          "单价": 7.00
        },
        {
          "物品号": "F050100042CP",
          "数量": 3.90,
          "单价": 7.50
        },
        {
          "物品号": "F050100067CP",
          "数量": 50.00,
          "单价": 7.20
        },
        {
          "物品号": "F050100077CP",
          "数量": 20.00,
          "单价": 6.00
        },
        {
          "物品号": "F059900038CP",
          "数量": 7.50,
          "单价": 8.50
        },
        {
          "物品号": "F083600039CP",
          "数量": 2.50,
          "单价": 4.60
        }
      ]}

🔗 六、组合拳:OCR + LLM 一条龙

import com.scm.ai.chat.OrderParserChat;
import com.scm.ai.ocr.OrderParserOcr;
import com.scm.ai.ocr.ParserFileResponse;
import com.scm.ai.ocr.ParserResultResponse;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class OrderParserHandler {
    public static void parser(String filePath) {
        ParserFileResponse upload = OrderParserOcr.upload(filePath);
        if(upload.getSuccess()){
            String taskId = upload.getTaskId();
            ParserResultResponse parseResult = OrderParserOcr.getParseResult(taskId);
            String status = parseResult.getStatus();
            //等待处理完毕
            while ("processing".equals(status)){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                parseResult = OrderParserOcr.getParseResult(taskId);
                status = parseResult.getStatus();
            }
            if ("succeeded".equals(status)) {
                String content = parseResult.getContent();
                OrderParserChat chat = new OrderParserChat();
                //返回json格式
                String result = chat.parse(taskId);
                log.info(result);
            }
        }
    }
}


💡 七、实战经验分享

  • ✅ 智普 OCR Expert 模式对 PDF 特别友好,能识别表格和公式

  • ⚡ 阿里云 qwen-plus性能稳定,支持超时控制

  • 🧱 建议 OCR 后先清洗文本,去除多余换行或噪音

  • 🧵 批量处理时可使用线程池提升效率

  • 🧾 如果文本中有公式或表格,可保留原格式,方便后续处理


🌈 八、结语:从痛苦到优雅的跃迁

过去: 🕸我盯着 PDF,怀疑人生。

现在: 🍿我敲一下 Enter,看着 AI 自动提取。

智普 OCR 负责"看懂", 阿里云大模型负责"理解"。

AI 不只是替我干活, 更是让我------少熬夜多喝咖啡。 ☕😎


📦 九、项目结构参考

pdf-smart-extractor/
├── src/
│   ├── main/java/
│   │   ├── BasicAliyunChat.java
│   │   ├── OrderParserChat.java
│   │   ├── OrderParserOcr.java
│   │   └── OrderParserHandler.java
├── resources/
│   └── order.pdf
└── pom.xml

Maven 依赖

		<dependency>
			<groupId>com.konghq</groupId>
			<artifactId>unirest-java</artifactId>
			<version>3.14.1</version> <!-- 请注意版本更新 -->
		</dependency>
		<!-- alibaba的ai依赖 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>dashscope-sdk-java</artifactId>
			<!-- 请将 'the-latest-version' 替换为最新版本号:https://mvnrepository.com/artifact/com.alibaba/dashscope-sdk-java -->
			<version>2.21.11</version>
		</dependency>
		<dependency>
			<groupId>ai.z.openapi</groupId>
			<artifactId>zai-sdk</artifactId>
			<version>0.0.6</version>
		</dependency>