☰
MinerU 4.x API 适配:修复文档解析 404 导致的上传失败
2026/10/20 2:23:57 网站建设 项目流程

MinerU 4.x API 适配:修复文档解析 404 导致的上传失败

适用场景:自建知识库 / RAG 项目调用本地 MinerU 解析服务,上传 PDF/Word 报错,服务端日志出现POST /file_parse HTTP/1.1" 404 Not Found。根因是 MinerU 4.0 重写了 API 契约,本文给出定位方法和适配代码。

一、问题现象

上传 Word 文档,接口返回 500,应用日志报“文件上传失败”。同时 MinerU 服务终端出现:

INFO: 127.0.0.1:xxxxx - "POST /file_parse HTTP/1.1" 404 Not Found

请求发出去了,但服务端没有这个路由。

二、根因:API 契约被大版本更换

MinerU 4.0 对 API 做了整体重写,旧版 2.x 的同步解析契约已下线:

项旧版 2.x(应用按此编写)新版 4.x(服务实际提供)
路由POST /file_parsePOST /v1/file_parse
参数形式multipart 表单参数query 参数 + JSON/multipart
ZIP 输出表单参数response_format_zip=truequery 参数response_format=zip
解析档位表单参数backend=pipelinequery 参数tier(flash/basic/standard/advanced)
其他参数return_images/return_model_output/return_middle_json已废弃,不再支持
认证无业务接口声明 Bearer(本地部署通常未强制)

注意:这不是装错了包。官方mineru 4.0.12自身就是这套新契约,把服务换成官方新版同样没有/file_parse。

三、自查:确认服务端的真实契约

不要凭记忆猜接口,有 Swagger 的服务直接抓规范:

curl-shttp://localhost:8000/openapi.json|jq'.paths | keys'

输出中能看到/v1/file_parse、/v1/parse/jobs、/v1/uploads等路由,没有/file_parse,即可确认契约版本。

四、修复代码

文件:src/main/java/know/engine/document/service/impl/MinerUProcessBaseServiceImpl.java
方法:parseDocumentToZip(String fileName, InputStream fileStream)

修改前(2.x 契约):

Stringurl=fileParseApiUrl+"/file_parse";HttpPosthttpPost=newHttpPost(url);httpPost.setHeader("Accept","application/json");HttpEntitymultipartEntity=MultipartEntityBuilder.create().setCharset(StandardCharsets.UTF_8).addBinaryBody("files",fileStream,ContentType.APPLICATION_OCTET_STREAM,fileName).addTextBody("backend","pipeline").addTextBody("response_format_zip","true").addTextBody("return_images","true").addTextBody("return_model_output","false").addTextBody("return_middle_json","false").build();

修改后(4.x 契约):

// MinerU 4.x 新契约:路由为 /v1/file_parse(旧版 2.x 的 /file_parse 已下线)// ZIP 输出通过 query 参数控制:response_format=zip 返回 ZIP 字节流// wait_timeout 设为 280s,略小于客户端响应超时(默认 300s),// 避免服务端还在等待而客户端已断开;超时服务端会返回 202,按失败处理Stringurl=fileParseApiUrl+"/v1/file_parse?response_format=zip&wait_timeout=280";HttpPosthttpPost=newHttpPost(url);httpPost.setHeader("Accept","application/zip");// 4.x 的 multipart 只需 files 二进制字段;// 旧版的 backend / response_format_zip / return_images 等表单参数已废弃HttpEntitymultipartEntity=MultipartEntityBuilder.create().setCharset(StandardCharsets.UTF_8).addBinaryBody("files",fileStream,ContentType.APPLICATION_OCTET_STREAM,fileName).build();

改动点共五处:

  1. 路由/file_parse→/v1/file_parse
  2. ZIP 输出控制:表单参数response_format_zip=true→ query 参数response_format=zip
  3. 新增wait_timeout=280:服务端默认等待 300s,与客户端超时相同会导致两边同时断开;280s 让服务端先超时并返回明确状态
  4. 删除backend/return_images/return_model_output/return_middle_json四个废弃参数
  5. Accept头application/json→application/zip

响应处理逻辑无需改动:仍判断statusCode == 200后读取响应体为 ZIP 字节流,后续解压、提取 markdown、上传对象存储的流程不变。

服务端地址配置(application.yml,无需改动,列出供对照):

file:parse:api:url:http://localhost:8000

五、验证

重启应用后上传一个 PDF/Word,观察两侧日志:

  • MinerU 终端应出现POST /v1/file_parse HTTP/1.1" 200 OK
  • 应用日志出现“文件解析接口调用成功,ZIP 文件大小: xxxxx bytes”,文档状态进入后续分块、向量化流程

六、注意事项

  1. 首次解析会下载模型权重(数 GB),服务端可能长时间无输出,属正常现象,不要中断
  2. 新契约业务接口声明了 Bearer 认证,本地部署通常未强制;若返回 401/403,需在请求头加Authorization: Bearer <key>
  3. 解析耗时超过wait_timeout时服务端返回 202,此时需改用异步轮询(POST /v1/parse/jobs+ 查询任务状态),本文的同步方案适用于可在一个超时周期内完成的文档
  4. 排查同类问题的通用方法:抓openapi.json对账,不凭记忆猜接口——大版本升级换路由是高频事故

七、 完整方法(直接替换即可)

文件:src/main/java/know/engine/document/service/impl/MinerUProcessBaseServiceImpl.java
方法:parseDocumentToZip(String fileName, InputStream fileStream)

/** * 调用文件解析接口,获取 ZIP 格式响应 * 使用 Apache HttpClient 5,支持流式下载大文件 * * @param fileName 文件名 * @param fileStream 文件输入流 * @return ZIP 文件字节数组 */privatebyte[]parseDocumentToZip(StringfileName,InputStreamfileStream){// MinerU 4.x 新契约:路由为 /v1/file_parse(旧版 2.x 的 /file_parse 已下线)// ZIP 输出通过 query 参数控制:response_format=zip 返回 ZIP 字节流// wait_timeout 设为 280s,略小于客户端响应超时(默认 300s),// 避免服务端还在等待而客户端已断开;超时服务端会返回 202,下方按失败处理并提示Stringurl=fileParseApiUrl+"/v1/file_parse?response_format=zip&wait_timeout=280";// 配置请求超时RequestConfigrequestConfig=RequestConfig.custom().setConnectionRequestTimeout(Timeout.ofMilliseconds(connectTimeout)).setResponseTimeout(Timeout.ofMilliseconds(responseTimeout)).build();try(CloseableHttpClienthttpClient=HttpClients.custom().setDefaultRequestConfig(requestConfig).build()){HttpPosthttpPost=newHttpPost(url);httpPost.setHeader("Accept","application/zip");// 4.x 的 multipart 只需 files 二进制字段;// 旧版的 backend / response_format_zip / return_images 等表单参数已废弃,传了也会被忽略HttpEntitymultipartEntity=MultipartEntityBuilder.create().setCharset(StandardCharsets.UTF_8).addBinaryBody("files",fileStream,org.apache.hc.core5.http.ContentType.APPLICATION_OCTET_STREAM,fileName).build();httpPost.setEntity(multipartEntity);log.info("开始调用文件解析接口(ZIP 模式): {}",url);try(CloseableHttpResponseresponse=httpClient.execute(httpPost)){intstatusCode=response.getCode();log.info("文件解析接口响应状态码: {}",statusCode);HttpEntityresponseEntity=response.getEntity();if(statusCode==200&&responseEntity!=null){// 读取响应体为字节数组(ZIP 文件)byte[]zipBytes=EntityUtils.toByteArray(responseEntity);log.info("文件解析接口调用成功,ZIP 文件大小: {} bytes",zipBytes.length);returnzipBytes;}else{StringresponseBody=responseEntity!=null?EntityUtils.toString(responseEntity,"UTF-8"):"";log.error("文件解析接口调用失败,状态码: {}, 响应: {}",statusCode,responseBody);thrownewRuntimeException("文件解析接口调用失败: HTTP "+statusCode+", "+responseBody);}}}catch(Exceptione){log.error("调用文件解析接口异常",e);thrownewRuntimeException("调用文件解析接口失败: "+e.getMessage(),e);}finally{closeQuietly(fileStream);}}

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询