mcpField
P
paper-fetch

paper-fetch

github.com/agents365-ai生产力973.4k 次安装2 周前更新
代码仓库

关于 paper-fetch

paper-fetch 是一个命令行工具,可根据 DOI 或论文标题查找并下载论文 PDF,适合自动化文献获取。它按来源优先级尝试解析和下载,并提供批量处理、JSON 输出及可供 Agent 读取的 CLI schema。

主要功能

  1. 01通过 DOI 或标题获取 PDF
  2. 02按顺序尝试多个论文来源
  3. 03支持 DOI 批量下载
  4. 04提供 JSON 结果与进度事件
  5. 05输出机器可读 CLI schema

适用场景

  1. 01按 DOI 下载单篇论文
  2. 02通过标题解析并获取论文
  3. 03批量处理 DOI 列表
  4. 04让自动化程序读取下载结果

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/agents365-ai/365-skills --skill paper-fetch

安装后在 Claude Code 中输入/paper-fetch确认已加载。

附带文件scripts/cloak_pdf.pyscripts/fetch.py

详细介绍

paper-fetch

根据 DOI(或标题)获取论文的 PDF。按优先级依次尝试多个来源,并在找到第一个结果后停止。

解析顺序

  1. Unpaywall — https://api.unpaywall.org/v2/{doi}?email=$UNPAYWALL_EMAIL,读取 best_oa_location.url_for_pdf(若未设置 UNPAYWALL_EMAIL,则跳过)
  2. Semantic Scholar — https://api.semanticscholar.org/graph/v1/paper/DOI:{doi}?fields=openAccessPdf,externalIds
  3. arXiv — 若存在 externalIds.ArXiv,则使用 https://arxiv.org/pdf/{arxiv_id}.pdf
  4. PubMed Central OA — 若存在 PMCID,则使用 https://www.ncbi.nlm.nih.gov/pmc/articles/{pmcid}/pdf/
  5. bioRxiv / medRxiv — 若 DOI 前缀为 10.1101,则查询 https://api.biorxiv.org/details/{server}/{doi} 获取最新版本的 PDF URL
  6. 出版商直连 (仅限机构模式 — PAPER_FETCH_INSTITUTIONAL=1) — 将 DOI 前缀映射到出版商 PDF 模板(Nature / Science / Wiley / Springer / ACS / PNAS / NEJM / Sage / T&F / Elsevier)。授权下载的是调用方自己的订阅 IP / Cookie / EZproxy;未授权的响应无法通过 %PDF 检查,因此会继续尝试第 7 步。
  7. Sci-Hub 镜像 (默认启用;使用 PAPER_FETCH_NO_SCIHUB=1 禁用) — 最后的备用方案。按顺序尝试 PAPER_FETCH_SCIHUB_MIRRORS 中的镜像列表(或内置默认列表 sci-hub.ru、sci-hub.st、sci-hub.su、sci-hub.box、sci-hub.red、sci-hub.al、sci-hub.mk、sci-hub.ee);若全部失败,则在每个进程中抓取一次 https://www.sci-hub.pub/,获取最新镜像。验证码 / 缺少论文的页面没有 PDF iframe,因此会静默跳过。
  8. 否则 → 报告失败,并附上标题 / 作者信息,以便用户通过馆际互借(ILL)申请

CloakBrowser 备用方案(下载层,可选启用 — PAPER_FETCH_CLOAK=1)。 这不是单独的数据源:它位于下载瓶颈处,因此适用于上述任意来源。当 Cloudflare 阻止已解析出的 PDF URL 时——HTTP 403/429,或返回“Just a moment…” HTML 中间页而非文件——且操作员已选择启用时,会通过 cloak_pdf.py 辅助程序使用 CloakBrowser(能够通过 JS 验证的隐身 Chromium)重试该 URL。返回的字节会通过相同的 %PDF 魔数 + 50 MB 检查进行重新验证;成功后,结果会包含 via: "cloak"。默认关闭,且失败时会安全关闭(缺少 CloakBrowser 时静默跳过);Agent 无法选择启用——参见下文的 CloakBrowser access。

如果只提供标题,请通过 --title "<title>" 直接传入。解析链如下:

  1. Crossref query.title — 首选;涵盖所有主要期刊 / 会议 DOI
  2. Semantic Scholar /paper/search/match — 当 Crossref 的首选匹配置信度较低(match_score < 40)或与次选结果的分差小于 < 3 时,作为备用方案。关键在于,S2 能覆盖仅有 arXiv 的预印本(没有 Crossref DOI)。当 S2 找到的论文只有 arXiv id 时,会合成规范的 10.48550/arXiv.<id>,以保持下载链统一。
  3. Crossref 的最佳猜测(低置信度) — 仅当两个解析器都难以确定时使用。结果封套会设置 meta.title_resolution.low_confidence: true,并包含 low_confidence_reason(score_below_threshold / ambiguous_runner_up),以便 Agent 决定放弃或通过 --dry-run 确认。

无论哪种情况,解析出的 DOI、胜出的解析器、完整的 resolvers_tried 列表以及排名靠前的候选匹配都会显示在 meta.title_resolution 下。

如果已注册 semanticscholar-skill,它可以作为更丰富的标题 → DOI 解析前置步骤——适用于还需要相关性排序、摘要搜索或引文上下文,而不仅仅是 DOI 的情况。Agent 编写 Python 脚本,使用该技能的 match_title() 读取 externalIds.DOI,然后运行 paper-fetch <doi>。如果结果只有 ArXiv id(没有 DOI),则合成 10.48550/arXiv.<ArXiv> 并传给 paper-fetch。

如果只需要 DOI,--title 是单命令路径——paper-fetch 内置的 Crossref → S2 链可以处理大多数情况。

用法

python scripts/fetch.py <DOI> [options]
python scripts/fetch.py --title "<paper title>" [options]
python scripts/fetch.py --batch <FILE|-> [options]
python scripts/fetch.py schema           # machine-readable self-description
标志

以下标志是 Agent 在日常使用中会组合使用的标志。完整规范——包括 --dry-run、--pretty、--stream、--overwrite、--timeout、--version,以及参数类型和退出码映射——请运行 python scripts/fetch.py schema(机器可读,并通过 schema_version 检查漂移)。

标志默认值说明
doi—要获取的 DOI(位置参数)。使用 - 从标准输入读取单个 DOI
--title TITLE—论文标题;下载前会通过 Crossref 将其解析为 DOI。不能与位置参数 DOI 或 --batch 同时使用
--batch FILE—每行一个 DOI 的批量下载文件。使用 - 从标准输入读取
--out DIRpdfs输出目录
--formatauto面向 Agent 的 json,面向用户的 text。自动检测:stdout 不是 TTY 时使用 json,是 TTY 时使用 text
--idempotency-key KEY—安全重试键。使用相同的键重新运行时,会从 <out>/.paper-fetch-idem/ 重放原始信封,不进行网络 I/O
Agent 发现:schema 子命令
python scripts/fetch.py schema

在 stdout 上输出 CLI 的完整机器可读描述(不进行网络访问)。其中包括 cli_version、schema_version、参数类型、退出码、错误码、信封结构和环境变量。Agent 应读取一次,根据 schema_version 缓存,并在缓存版本发生变化时重新读取。

输出规范

stdout 输出单个 JSON 信封。每个信封都包含一个 meta 槽位。

成功(所有 DOI 均已解析):

{
  "ok": true,
  "data": {
    "results": [
      {
        "doi": "10.1038/s41586-021-03819-2",
        "success": true,
        "source": "unpaywall",
        "pdf_url": "https://www.nature.com/articles/s41586-021-03819-2.pdf",
        "file": "pdfs/Jumper_2021_Highly_accurate_protein_structure_predic.pdf",
        "meta": {"title": "Highly accurate protein structure prediction with AlphaFold", "year": 2021, "author": "Jumper"},
        "sources_tried": ["unpaywall"]
      }
    ],
    "summary": {"total": 1, "succeeded": 1, "failed": 0},
    "next": []
  },
  "meta": {
    "request_id": "req_a908f5156fc1",
    "latency_ms": 2036,
    "schema_version": "1.9.0",
    "cli_version": "0.13.1",
    "sources_tried": ["unpaywall"]
  }
}

部分成功(批量模式——部分 DOI 获取失败,退出码反映失败类别):

{
  "ok": "partial",
  "data": {
    "results": [
      { "doi": "10.1038/s41586-021-03819-2", "success": true, "source": "unpaywall", ... },
      {
        "doi": "10.1234/nonexistent",
        "success": false,
        "source": null,
        "pdf_url": null,
        "file": null,
        "meta": {},
        "sources_tried": ["unpaywall", "semantic_scholar"],
        "error": {
          "code": "not_found",
          "message": "No open-access PDF found",
          "retryable": true,
          "retry_after_hours": 168,
          "reason": "OA availability changes over time; retry after embargo lifts or preprint appears"
        }
      }
    ],
    "summary": {"total": 2, "succeeded": 1, "failed": 1},
    "next": ["paper-fetch 10.1234/nonexistent --out pdfs"]
  },
  "meta": { ... }
}

next 槽位是建议后续执行的命令数组:重新调用这些命令只会重试失败的部分。与 --idempotency-key 配合使用,可安全重试整个批次,而不会重新下载已成功的项目。

失败(参数错误,退出码为 3):

{
  "ok": false,
  "error": {
    "code": "validation_error",
    "message": "Provide a DOI or --batch file",
    "retryable": false
  },
  "meta": { ... }
}

单项跳过(目标文件已存在,且未指定 --overwrite):

{
  "doi": "10.1038/s41586-021-03819-2",
  "success": true,
  "source": "unpaywall",
  "pdf_url": "https://...",
  "file": "pdfs/Jumper_2021_...pdf",
  "skipped": true,
  "skip_reason": "file_exists",
  "sources_tried": ["unpaywall"]
}

幂等性重放(使用相同的 --idempotency-key 重新运行):

缓存的信封会原样返回,但 meta.request_id 和 meta.latency_ms 会更新为当前调用的值,并会设置 meta.replayed_from_idempotency_key。不会进行网络 I/O。

Stderr 进度(NDJSON)

使用 --format json 时,stderr 会为保持运行状态每行输出一个 JSON 对象:

{"event": "session",     "request_id": "req_...", "elapsed_ms": 0,    "cli_version": "0.13.1", "schema_version": "1.9.0"}
{"event": "start",       "request_id": "req_...", "elapsed_ms": 2,    "doi": "10.1038/..."}
{"event": "source_try",  "request_id": "req_...", "elapsed_ms": 2,    "doi": "...", "source": "unpaywall"}
{"event": "source_hit",  "request_id": "req_...", "elapsed_ms": 2036, "doi": "...", "source": "unpaywall", "pdf_url": "..."}
{"event": "download_ok", "request_id": "req_...", "elapsed_ms": 4120, "doi": "...", "file": "..."}

事件类型:session、start、source_try、source_hit、source_miss、source_skip、source_enrich、source_enrich_failed、download_ok、download_error、download_skip、dry_run、not_found、resolve_error。所有事件都包含 request_id 和 elapsed_ms,使编排器能够关联 stderr 中的进度与最终的 stdout 信封。每次调用只会触发一次 session 事件,且会在任何 DOI 处理或网络 I/O 开始前触发;该事件包含 cli_version / schema_version,让 Agent 无需等待最终信封即可检测缓存副本的模式漂移。

当 Semantic Scholar 仅用于在其他来源已提供 PDF URL 后补充缺失的 author / title 时,会触发 source_enrich;其 fields 数组会准确列出已补充的字段。补充调用失败时会触发 source_enrich_failed——仍会使用 Unpaywall 的 PDF URL,文件名则回退为 unknown_<year>_…。

使用 --format text 时,stderr 会输出人类可读的文本。

退出代码
Code含义可重试类别
0所有 DOI 均已解析/预览—
1未解析 — 一个或多个 DOI 没有开放获取副本;没有传输故障暂不重试(在 retry_after_hours 后重试)
2保留用于身份验证错误(目前未使用)—
3验证错误(参数错误、缺少输入)否
4传输错误(网络/下载/IO 故障)是

此分类法让编排器能够以确定的方式处理故障:值得立即重试退出代码 4,退出代码 1 不值得,而退出代码 3 则是调用方的错误。

JSON 中的错误代码

每个可重试错误都会在错误对象中包含 retry_after_hours 提示,让编排器无需猜测即可安排重试。

Code含义可重试retry_after_hours
validation_error参数错误或输入为空否—
title_resolve_failedCrossref 对给定的 --title 查询未返回任何条目(请尝试更长/更准确的标题,或直接传入 DOI)否—
not_found未找到开放获取 PDF是168(一周 — 开放获取内容通常会在禁运期/预印本的时间尺度内上线)
resolve_network_error元数据解析器发生传输错误(超时/5xx/403);开放获取情况未知 — 应重试,而不是将其视为 not_found。对应退出代码 4。是1
download_network_error下载期间发生网络故障是1
download_not_a_pdf响应不是 PDF(HTML 落地页)否—
download_host_not_allowedPDF URL 未通过 SSRF 安全检查(私有 IP/非 http(s)/非 80,443/被屏蔽的元数据主机/解析到私有网段的主机名/不安全的重定向跳转)否—
download_size_exceeded响应超过 50 MB 限制是24
download_io_error本地文件系统写入失败是1
internal_error意外错误否—

规范映射定义在 scripts/fetch.py 中的 RETRY_AFTER_HOURS,并显示在 schema.error_codes 中。

示例
# Single DOI (JSON output when piped; text when in a terminal)
python scripts/fetch.py 10.1038/s41586-020-2649-2

# Single title (resolved to DOI via Crossref, then downloaded)
python scripts/fetch.py --title "Highly accurate protein structure prediction with AlphaFold"

# Dry-run preview (resolve without downloading)
python scripts/fetch.py 10.1038/s41586-020-2649-2 --dry-run

# Title + dry-run — preview the resolved DOI and candidate matches
python scripts/fetch.py --title "Attention Is All You Need" --dry-run

# Force JSON (for agents even inside a terminal)
python scripts/fetch.py 10.1038/s41586-020-2649-2 --format json

# Human-readable with pretty colors in a pipeline
python scripts/fetch.py 10.1038/s41586-020-2649-2 --format text

# Batch download, safely retriable
python scripts/fetch.py --batch dois.txt --out ./papers \
    --idempotency-key monday-review-batch

# Pipe DOIs from another tool
zot -F ids.json query ... | jq -r '.[].doi' | python scripts/fetch.py --batch -

# Agent discovery
python scripts/fetch.py schema --pretty

# Streaming mode — one result per line as each DOI resolves
python scripts/fetch.py --batch dois.txt --stream

# Works without UNPAYWALL_EMAIL (skips Unpaywall, uses remaining 4 sources)
python scripts/fetch.py 10.1038/s41586-020-2649-2

环境

变量默认值用途
UNPAYWALL_EMAILunset用于 Unpaywall API 的联系邮箱。可选,但建议设置。不设置时将跳过 Unpaywall(其余来源仍可使用)。
PAPER_FETCH_INSTITUTIONALunset设置为任意值(例如 1)以启用机构模式 — 激活每秒 1 个请求的速率限制器和出版商直连回退。见下文。
PAPER_FETCH_NO_SCIHUBunset设置为任意值以禁用 Sci-Hub 回退(步骤 7)。
PAPER_FETCH_SCIHUB_MIRRORSunset按优先级顺序尝试的逗号分隔镜像主机名(例如 sci-hub.ru,sci-hub.st,sci-hub.su)。覆盖内置默认值。
PAPER_FETCH_CLOAKunset设置为任意值以启用 CloakBrowser 回退 — 通过隐身 Chromium 重试被 Cloudflare 拦截的 PDF(HTTP 403/429 或非 PDF 中间页)。见下文的 CloakBrowser 访问。
CLOAKBROWSER_PYTHONauto可用于 import cloakbrowser 的 Python 路径,供 cloak 回退使用。自动检测顺序:此变量 → ~/github/CloakBrowser/.venv/bin/python → 当前解释器。
PAPER_FETCH_CLOAK_HEADEDunset设置为任意值以启动有头(可见)浏览器,而非无头浏览器。较难应对的 Cloudflare 验证(例如 science.org)会让无头模式失效,且只能在真实窗口中通过 — 如果 cloak 回退持续返回 HTTP 403 / "Just a moment…",请设置此项。需要显示器。

CloakBrowser 访问(选择启用)

部分出版商(例如 science.org)使用 Cloudflare 防护,纯 HTTP 客户端会收到 403/429,或看到“Just a moment…” JS 挑战页面,而不是 PDF。因此,即使浏览器可以正常访问该 URL,默认的 urllib 下载也无法通过。CloakBrowser 是一款能够通过此类挑战的隐蔽版 Chromium。此技能借鉴了 cloakFetch 的实现方式。

选择启用: export PAPER_FETCH_CLOAK=1(还需要可导入 cloakbrowser 的 Python,参见 CLOAKBROWSER_PYTHON)。

工作原理: 此回退机制位于下载层,而不是作为新的来源,因此适用于任何解析出的 URL(Unpaywall、出版商直链、Sci-Hub,等等)。遇到 Cloudflare 拦截时,fetch.py 会通过指定的 Python 调用配套脚本 cloak_pdf.py;CloakBrowser 加载 PDF 主机的源站以通过 JS 挑战,然后通过页面内的 fetch() 获取 PDF(这样请求会携带浏览器的真实指纹和 cf_clearance Cookie),并将字节内容写入标准输出。fetch.py 本身仅使用标准库,不会导入 cloakbrowser。

无头与有头模式。 配套脚本默认以无头模式运行。某些挑战(例如 science.org)会让无头 Chromium 无法通过,并一直停留在“Just a moment…”页面——设置 PAPER_FETCH_CLOAK_HEADED=1 可使用可见窗口,从而通过这些挑战。已验证:纯客户端请求返回 403 的 www.science.org/doi/pdf/… PDF,可通过有头 CloakBrowser 回退机制成功下载。

仅限同源。 页面内的 fetch 为同源请求,因此当解析出的 URL 是被拦截主机上的 PDF 直链时(例如 www.science.org/doi/pdf/…),此回退机制才有效。若 URL 会跨源重定向(例如不带路径的 doi.org/… 链接),或旧版主机上的挑战始终无法通过,则会安全地失败,并继续尝试下一个来源。

以下行为保持不变:

  • 返回的字节内容仍会经过相同的 %PDF 魔数检查和 50 MB 大小上限验证。SSRF 防护仍会在启动浏览器前检查 URL。
  • 不解决 CAPTCHA。 CloakBrowser 可通过自动化 JS 挑战,但不能通过交互式挑战;遇到交互式 Turnstile 时仍会安全地失败,并继续尝试下一个来源。
  • 安全失败。 没有可导入 cloakbrowser 的 Python、缺少辅助脚本或发生任何错误时,都会静默地继续尝试下一个来源。Agent 绝不会被告知被拦截的下载已成功。
  • Agent 无法自行选择启用——必须由人工操作员设置 PAPER_FETCH_CLOAK。这与机构模式具有相同的信任边界。
  • 成功时,每个结果对象都会包含 via: "cloak",以便编排器识别是否使用了回退机制。

成本: 触发回退机制时会启动真实浏览器(耗时约 20–40 秒;首次下载 Chromium 约占用 200 MB)。它仅在常规下载被拦截后才会触发,因此不影响正常下载。

机构访问(主动选择启用)

许多研究人员通过所在机构的 IP 范围(在校园内或通过 VPN)拥有合法的订阅访问权限。Paper-fetch 可以利用这种访问权限,由出版商自身的身份验证机制(你的 IP、会话 Cookie)决定是否提供 PDF。

不同模式下的主机可达性没有区别——公共模式已信任开放获取 API(Unpaywall、Semantic Scholar、bioRxiv、PMC)返回的 URL,并会获取任何通过 SSRF 防护的 HTTPS 主机。机构模式增加了两项功能:(1)直连出版商回退(上文第 6 步):当所有开放获取来源均未命中时,根据 DOI 前缀构造出版商侧的 PDF URL,以便你的机构 IP/Cookie 授权获取;(2)1 req/s 限速器,防止批量任务导致你的 IP 因“系统性下载”而被限流或封禁。

主动选择启用: export PAPER_FETCH_INSTITUTIONAL=1

机构模式下的变化:

方面公共模式(默认)机构模式
主机可达性通过 SSRF 防护的任何公共 HTTPS 主机相同
SSRF 防护已启用(屏蔽私有 IP / 非 http(s) / 非 80、443 端口 / 云元数据)已启用——规则相同
直连出版商回退关闭开启——根据 DOI 前缀 → 出版商 PDF URL,在所有开放获取来源均未命中后作为最后手段
限速无1 req/s 令牌桶(所有出站请求)
meta.auth_mode"public""institutional"

保持不变的内容:

  • %PDF 魔数检查和 50 MB 大小上限(防止 HTML 落地页和超大响应蒙混过关)
  • 绝不破解 CAPTCHA。如果出版商显示验证挑战,响应内容将不会以 %PDF 开头,paper-fetch 会转而尝试下一个来源。
  • 机构模式本身不使用浏览器自动化、Playwright 或隐匿技术——它通过直连出版商 URL 发起普通 HTTP 获取。(浏览器自动化是单独选择启用的功能:上文的 CloakBrowser 回退,由 PAPER_FETCH_CLOAK 控制。)
  • Agent 不能自行选择启用——必须由人工操作员在 Shell 环境中设置 PAPER_FETCH_INSTITUTIONAL。这就是信任边界。

当 paper-fetch 找不到开放获取副本,且你处于公共模式时,错误封装中会包含 suggest_institutional: true,并提示用户设置环境变量。Agent 可以原样呈现该提示,而不是悄无声息地失败。

服务条款提示: 几乎所有出版商的订阅都禁止“系统性下载”。1 req/s 限速以及现有的单文件幂等机制,旨在让个人研究用途保持在可接受范围内。运行多个并行的 paper-fetch 进程或取消限速,可能触发出版商封禁整个机构的 IP。请勿这样做。

说明

  • 身份验证由外部负责。 Agent 绝不会运行登录子命令。人工操作员或编排器会在环境中设置 UNPAYWALL_EMAIL;Agent 会继承该变量。未设置邮箱时,会妥善降级为使用其余 4 个来源。
  • 信任具有方向性。 CLI 参数只在入口点验证一次。SSRF 防护、%PDF 魔数检查和 50 MB 大小上限由环境层强制执行,而非由 Agent 请求决定。Agent 无法通过传递标志放宽安全限制——选择启用机构模式(及其限速风险配置)是操作员通过环境变量执行的操作。
  • 下载天然具备幂等性。 对同一 --out 重复运行时,会跳过已存在的文件(确定性文件名:{first_author}_{year}_{journal_abbrev}_{short_title}.pdf;如果元数据缺少期刊/发表平台,则省略期刊部分)。同时使用 --idempotency-key,还可以在不进行任何网络 I/O 的情况下重放完全相同的封装。
  • 默认输出目录: ./pdfs/。

评论

支持 Markdown