Skill: haxck/mineru-agent (version 1.0.0)

--- name: mineru description: 用 MinerU Agent 轻量解析 API 将 PDF/Word/PPT/Excel/图片解析为 Markdown，无需 Token，IP 限频。适用于文档解析、表格提取、OCR 识别。 --- # 📄 MinerU - 文档解析 > PDF/Word/PPT/Excel/图片 → 结构化 Markdown ## 🎯 触发条件 当用户要求解析文档、提取文档内容、OCR 识别、将文件转为 Markdown 时使用。 ## 🔗 API 信息 - **Base URL:** `https://mineru.net/api/v1/agent` - **认证方式:** 无需 Token，IP 限频 - **文档:** https://mineru.net/apiManage/docs ## 📋 支持的文件类型 | 类型 | 格式 | |------|------| | 📕 PDF | 论文、书籍、扫描件 | | 📝 Word | .docx | | 📊 PPT | .pptx | | 📊 Excel | .xls, .xlsx | | 🖼️ 图片 | .png, .jpg, .jpeg, .jp2, .webp, .gif, .bmp | ## ⚠️ 限制 | 限制项 | 限制值 | |--------|--------| | 文件大小 | 10 MB | | 文件页数 | 20 页 | ## 🚀 使用方式 ### 方式一：URL 解析（文件有公开 URL 时） 直接调用解析脚本： ```bash python3 SKILL_DIR/scripts/mineru_parse.py --url "https://example.com/file.pdf" ``` 可选参数： - `--language ch|en` （默认 ch） - `--page_range 1-10`（仅 PDF 有效） - `--output /path/to/output.md`（指定输出文件） ### 方式二：文件上传解析（本地文件） ```bash python3 SKILL_DIR/scripts/mineru_parse.py --file /path/to/document.pdf ``` ### 方式三：在对话中直接使用 用户发送文件或提供文件路径/URL 时，调用脚本解析，将结果返回给用户。 ## 🔄 API 流程 ### URL 模式 1. `POST /parse/url` → 获取 task_id 2. `GET /parse/{task_id}` → 轮询直到 done 3. 下载 markdown_url 返回结果 ### 文件上传模式 1. `POST /parse/file` → 获取 task_id + file_url 2. `PUT file_url` → 上传文件到 OSS 3. `GET /parse/{task_id}` → 轮询直到 done 4. 下载 markdown_url 返回结果 ## ❌ 错误码 | 错误码 | 说明 | 应对策略 | |--------|------|----------| | -30001 | 文件大小超出限制（10MB） | 拆分文件或告知用户 | | -30002 | 不支持的文件类型 | 检查文件格式 | | -30003 | 页数超出限制 | 指定 page_range 拆分 | | -30004 | 请求参数错误 | 检查必填参数 | ## 💡 使用技巧 1. **中文文档用 `language: ch`** 2. **大文件指定 `page_range` 分段解析** 3. **Word/PPT 用 Office 原生 API 解析，速度最快** 4. **解析结果为 Markdown 格式，可直接用于后续处理** --- *轻量快速，无需 Token！📄*

path: scripts/mineru_parse.py
content: #!/usr/bin/env python3
"""MinerU Agent 轻量解析 API - 文档转 Markdown

用法:
  URL 模式:  python3 mineru_parse.py --url "https://example.com/file.pdf"
  文件模式:  python3 mineru_parse.py --file /path/to/document.pdf

可选参数:
  --language ch|en        解析语言，默认 ch
  --page_range 1-10       页码范围，仅 PDF 有效
  --output /path/to/out.md  指定输出文件路径
  --timeout 300           轮询超时秒数，默认 300
"""

import argparse
import json
import os
import sys
import time

import requests

BASE_URL = "https://mineru.net/api/v1/agent"

STATE_LABELS = {
    "uploading": "文件下载中",
    "pending": "排队中",
    "running": "解析中",
    "waiting-file": "等待文件上传",
}


def poll_result(task_id, timeout=300, interval=3):
    """轮询查询解析结果，返回 Markdown 文本或 None。"""
    start = time.time()
    while time.time() - start < timeout:
        resp = requests.get(f"{BASE_URL}/parse/{task_id}")
        result = resp.json()
        data = result.get("data", {})
        state = data.get("state", "unknown")
        elapsed = int(time.time() - start)

        if state == "done":
            md_url = data["markdown_url"]
            print(f"[{elapsed}s] 解析完成", file=sys.stderr)
            md_resp = requests.get(md_url)
            md_resp.encoding = "utf-8"
            return md_resp.text

        if state == "failed":
            err_msg = data.get("err_msg", "未知错误")
            err_code = data.get("err_code", "")
            print(f"[{elapsed}s] 解析失败 [{err_code}]: {err_msg}", file=sys.stderr)
            return None

        label = STATE_LABELS.get(state, state)
        print(f"[{elapsed}s] {label}...", file=sys.stderr)
        time.sleep(interval)

    print(f"轮询超时 ({timeout}s)，task_id: {task_id}", file=sys.stderr)
    return None


def parse_by_url(url, language="ch", page_range=None, timeout=300):
    """通过 URL 提交文档解析任务并等待结果。"""
    data = {"url": url, "language": language}
    if page_range:
        data["page_range"] = page_range

    resp = requests.post(f"{BASE_URL}/parse/url", json=data)
    result = resp.json()
    if result.get("code") != 0:
        print(f"提交失败: {result.get('msg')}", file=sys.stderr)
        return None

    task_id = result["data"]["task_id"]
    print(f"任务已提交, task_id: {task_id}", file=sys.stderr)
    return poll_result(task_id, timeout=timeout)


def parse_by_file(file_path, language="ch", page_range=None, timeout=300):
    """通过文件上传提交文档解析任务并等待结果。"""
    if not os.path.exists(file_path):
        print(f"文件不存在: {file_path}", file=sys.stderr)
        return None

    file_name = os.path.basename(file_path)
    file_size = os.path.getsize(file_path)
    if file_size > 10 * 1024 * 1024:
        print(f"文件大小 {file_size/1024/1024:.1f}MB 超出 10MB 限制", file=sys.stderr)
        return None

    # 1. 获取签名上传 URL
    data = {"file_name": file_name, "language": language}
    if page_range:
        data["page_range"] = page_range

    resp = requests.post(f"{BASE_URL}/parse/file", json=data)
    result = resp.json()
    if result.get("code") != 0:
        print(f"获取上传链接失败: {result.get('msg')}", file=sys.stderr)
        return None

    task_id = result["data"]["task_id"]
    file_url = result["data"]["file_url"]
    print(f"任务已创建, task_id: {task_id}", file=sys.stderr)

    # 2. PUT 上传文件到 OSS
    with open(file_path, "rb") as f:
        put_resp = requests.put(file_url, data=f)
        if put_resp.status_code not in (200, 201):
            print(f"文件上传失败, HTTP {put_resp.status_code}", file=sys.stderr)
            return None
    print("文件上传成功，等待解析...", file=sys.stderr)

    # 3. 轮询等待结果
    return poll_result(task_id, timeout=timeout)


def main():
    parser = argparse.ArgumentParser(description="MinerU 文档解析")
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument("--url", help="远程文件 URL")
    group.add_argument("--file", help="本地文件路径")
    parser.add_argument("--language", default="ch", help="解析语言 (默认 ch)")
    parser.add_argument("--page_range", help="页码范围，如 1-10（仅 PDF）")
    parser.add_argument("--output", "-o", help="输出文件路径（默认 stdout）")
    parser.add_argument("--timeout", type=int, default=300, help="轮询超时秒数（默认 300）")
    args = parser.parse_args()

    if args.url:
        content = parse_by_url(args.url, language=args.language, page_range=args.page_range, timeout=args.timeout)
    else:
        content = parse_by_file(args.file, language=args.language, page_range=args.page_range, timeout=args.timeout)

    if content is None:
        sys.exit(1)

    if args.output:
        with open(args.output, "w", encoding="utf-8") as f:
            f.write(content)
        print(f"结果已保存到: {args.output}", file=sys.stderr)
    else:
        print(content)


if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)