PMC / OA 文獻 PDF 獲取
適用前提
只用於開放獲取 (OA) 文獻(CC BY / CC BY-NC / CC BY-NC-ND 等)。不要用於繞過付費牆獲取訂閱制論文——那屬於規避版權保護。判斷 OA 狀態:Unpaywall 回傳 is_oa: true,或 EuropePMC 的 isOpenAccess: Y。
機構訂閱場景(重要補充)
使用者所在學校/單位訂閱了出版商資料庫時,人工單篇下載在授權範圍內完全合規,但需注意:
- 校園網 / 機構 IP 段內:出版商認 IP 不認人,降級鏈第 5 級(出版社直鏈)通常直接回傳 200,Cloudflare 也不會攔截——此時本 skill 的現有管線可以直接拿到訂閱內容,無需任何登入邏輯。
- 校外存取:優先使用學校提供的系統級 VPN(對腳本透明,設定一次即可);WebVPN(URL 改寫型代理)因各校改寫規則不同、且需攜帶 Session Cookie,本 skill 不做支援。
- 不要寫 Shibboleth / OpenAthens 自動登入:需要本地儲存統一身份認證憑證,屬安全隱患;且各校 IdP 流程差異大,通用實作不可行。
- 批次下載紅線:幾乎所有出版商的機構許可都區分「人工單篇下載」與 systematic/bulk downloading。即使有訂閱,腳本批次抓取也違反許可條款,可導致全校 IP 段被封禁。確有大規模全文挖掘需求時,應申請出版商的 TDM(文本挖掘)API(如 Elsevier TDM API、Springer Nature Meta API),走官方配額通道,而不是用本 skill 批次循環。
降級鏈(嚴格按順序)
| 優先級 | 途徑 | 命令 / 端點 | 說明 |
|---|---|---|---|
| 1 | EuropePMC 結構化全文 | https://www.ebi.ac.uk/europepmc/webservices/rest/{PMCID}/fullTextXML | 最穩定。用於提取精確到二級標題的全文結構。回傳 JATS XML |
| 2 | Unpaywall 定位 OA 位置 | https://api.unpaywall.org/v2/{DOI}[email protected] | 讀取 oa_locations[].url_for_pdf,直接給出 PDF 直鏈 |
| 3 | PMC PoW 求解下載 | scripts/pmc_pdf.py <PMCID> <out.pdf> | 見下文。獲取正文 PDF 的主力方案 |
| 4 | EuropePMC 補充材料 | https://www.ebi.ac.uk/europepmc/webservices/rest/{PMCID}/supplementaryFiles | 回傳 ZIP:原始圖片(gr1…=Figure 1…)+ 補充材料 PDF(mmc1.pdf) |
| 5 | 出版社直鏈 | 各出版社 .../article-pdf/... | 通常 403(Cloudflare)。最後才試 |
已確認失效的端點(不要浪費時間)
…/rest/{PMCID}/fullTextPDF→ 404(已廢棄)…/rest/{PMCID}/fulltextRepo→ 403https://europepmc.org/articles/{PMCID}?pdf=render→ 403https://europepmc.org/backend/ptpmcrender.fcgi?accid=…&blobtype=pdf→ 403https://www.ncbi.nlm.nih.gov/pmc/utils/oa/oa.fcgi?id=…→ 404(OA service 已遷移)ashpublications.org文章頁在無頭瀏覽器中停留於 Cloudflare「請稍候…」挑戰頁,不可用
★ 先決條件:PMC 的 PDF 檔案名稱不固定,必須先解析
https://pmc.ncbi.nlm.nih.gov/articles/{PMCID}/pdf/<檔案名稱>.pdf 中的檔案名稱隨期刊與年份變化,硬編碼 main.pdf 在多數情況下會 404。
| 實測期刊 | 真實檔案名稱 |
|---|---|
| Am J Hematol | main.pdf |
| Blood Advances | main.pdf |
| HemaSphere | HEM3-10-e70338.pdf |
| Am J Hematol | AJH-101-1790.pdf |
| Liver International | LIV-46-0.pdf |
| Haematologica | 111300.pdf(卷 111 + 起始頁 300,省略期刊縮寫) |
| Blood(NIH 作者稿) | nihms-2192199.pdf |
解析方法(按優先級;實測多例全部命中第 1 條)
- 文章頁
<meta name="citation_pdf_url" content="https://pmc.ncbi.nlm.nih.gov/articles/{PMCID}/pdf/XXX.pdf">← 最可靠 grep -oE 'href="[^"]*?/pdf/[^"]+?\.pdf"'取第一個命中- 兜底
pdf/main.pdf
命名規律參考(不可依賴,僅用於人工判斷是否解析正確):Wiley 系 OA 常見 <期刊縮寫>-<印刷卷>-0.pdf(如 LIV-46-0.pdf);帶文章號時為 <期刊縮寫>-<卷>-<文章號>.pdf(如 HEM3-10-e70338.pdf);同刊常為 <縮寫>-<卷>-<起始頁>.pdf(如 AJH-101-1790.pdf);亦有「卷+起始頁、無期刊縮寫」變體(如 Haematologica 的 111300.pdf = 卷 111 + 起始頁 300)。
抓文章頁時務必丟棄 Cookie:用一個獨立的、不保留 Cookie 的請求去讀文章頁,PDF 請求仍必須從全新 Cookie 會話發起。若把文章頁產生的
ncbi_sid/ncbi_pinger帶到 PDF 請求上,會被直接升級為 reCAPTCHA(實測)。scripts/pmc_pdf.py已按此實作。
核心方案:離線求解 PMC Proof-of-Work
原理
https://pmc.ncbi.nlm.nih.gov/articles/{PMCID}/pdf/<檔案名稱>.pdf 會按風險策略在兩種挑戰頁之間切換:
- PoW 頁(1817 位元組,標題
Preparing to download ...)→ 可離線求解,這是我們要的通道; - reCAPTCHA 頁(約 20 KB,標題含
reCAPTCHA)→ 無法離線求解。
觸發 PoW 而非 reCAPTCHA 的關鍵:使用簡約 UA,且不要在請求 PDF 之前存取文章頁。
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)
反例(會被升級為 reCAPTCHA,實測):
- 完整 Chrome UA(
…AppleWebKit/537.36 … Chrome/1xx.0.0.0 Safari/537.36) - 先
GET文章頁(攜帶ncbi_sid/ncbi_pinger)再請求 PDF
PoW 演算法(從 pow-*.js 反解,已確認)
PoW 頁內嵌四個常數:POW_CHALLENGE / POW_DIFFICULTY / POW_COOKIE_NAME / POW_COOKIE_PATH。
- 求最小非負整數
nonce,使得sha256(challenge + str(nonce)).hexdigest()的前綴等於'0' * difficulty; - 設定 Cookie:
<POW_COOKIE_NAME> = challenge + "," + nonce(路徑為POW_COOKIE_PATH); - 攜帶該 Cookie 與
Referer: https://pmc.ncbi.nlm.nih.gov/articles/{PMCID}/重新請求 PDF 直鏈 → 應回傳200與Content-Type: application/pdf。
典型 difficulty = 4(期望約 65536 次哈希,本地亞秒級完成)。
一鍵腳本
# 推薦:自動解析真實 PDF 檔案名稱(走 citation_pdf_url),再離線求解 PoW
python3 scripts/pmc_pdf.py PMC13575452 ./out.pdf
# 已自行解析出檔案名稱時可直接指定,跳過文章頁請求
python3 scripts/pmc_pdf.py PMC13575452 ./out.pdf --pdf-name LIV-46-0.pdf
# 靜默模式(只輸出結果行)
python3 scripts/pmc_pdf.py PMC13575452 ./out.pdf --quiet
腳本行為:先以簡約 UA、丟棄 Cookie 地請求文章頁解析真實 PDF 直鏈 → 再以全新 Cookie 會話冷請求該 PDF → 若直接得到 PDF 則儲存;否則解析 PoW 常數、求解、回填 Cookie 重試。 如需強制重取 challenge,重跑即可(腳本不落盤 Cookie,每次都是全新會話)。
★★ 兩條必須記住的硬規則(實測血淚教訓)
1. 第二個位置參數是輸出檔案完整路徑,不是目錄。 傳目錄會在已經拿到 PDF 之後拋
IsADirectoryError: [Errno 21] Is a directory,前面那次 PoW 白解、那份 PDF 白拿。 ❌pmc_pdf.py PMC13482240 --outdir /path/to/dir✅pmc_pdf.py PMC13482240 /path/to/dir/xxx.pdf2. PMC 的 PDF 通道有「一次機會」特性。 首次冷請求基本都能走通 PoW 並拿到真 PDF; 緊接著對同一 PDF 再請求一次,幾乎必然升級為 reCAPTCHA(不是「換 UA 就好」的普通限流)。 因此第一次就要把輸出路徑寫對,絕對不要「先試跑看看」。 實測:首輪
status=200 type=application/pdf bytes=23970819,數秒後重跑同一 URL 即變為type=text/html bytes=21329(reCAPTCHA 挑戰頁)。 一旦浪費掉這次機會,直接跳到下一節的「出版社官網降級路線」,不要在 PMC 上反覆重試。
故障排查
| 現象 | 原因 | 處置 |
|---|---|---|
回傳 HTML 且含 POW_CHALLENGE | 進入了 PoW 通道(正常) | 交給腳本求解 |
回傳 HTML 且含 recaptcha | 被升級為 reCAPTCHA | 換簡約 UA、清會話 Cookie、稍等重試;仍失敗則改用途徑 4/5 |
| 404 / 「Article not found」 | PDF 檔案名稱猜錯了 | 回到文章頁解析 citation_pdf_url,不要硬編碼 main.pdf |
nonce 長時間不收斂 | difficulty 被調高 | 列印 difficulty 判斷;>5 時考慮非同步或多進程求解 |
| 求解成功但 PDF 仍是 HTML | Cookie 域 / 路徑不匹配 | 確認 Cookie 域為 pmc.ncbi.nlm.nih.gov、路徑取 POW_COOKIE_PATH |
IsADirectoryError: Is a directory | 把輸出參數當成了目錄 | 第二參數是輸出檔案完整路徑。此時 PDF 其實已經拿到了,只是沒落盤——不要立刻重跑(會觸發 reCAPTCHA),先改對路徑,若已升級則直接走出版社官網路線 |
| 首次成功、第二次立刻 reCAPTCHA | PMC 對同一 PDF 路徑的「一次機會」限流 | 見上文硬規則 2;放棄 PMC,走出版社官網降級路線 |
citation_pdf_url 回傳 -mmc1.pdf | 該文 PDF 未入庫,解析到的是補充材料 | Blood 等刊常見「有 JATS 全文但正文 PDF 未入庫」。立刻放棄 PMC 路線,不要拿補充材料冒充正文(實測) |
★ 存在 IP 級限流(實測) 短時間內對 PMC 重複請求 PDF(例如連續下載多篇)後,即使使用正確的簡約 UA,也會被直接升級為 reCAPTCHA, 且等待 45 秒仍不恢復。表現為「首次成功、隨後全部失敗」。 處置: ① 批次下載時在兩次請求之間留出足夠間隔;② 一旦進入 reCAPTCHA 狀態,不要反覆重試(只會延長封禁), 改為先完成其他工作,或改用途徑 4(
supplementaryFiles)與出版社直鏈;③ 若確有多篇需求,優先按需下載而非批次預熱。
★★ 降級路線:PMC 被 reCAPTCHA 卡死時改走出版社官網(實測有效)
當 PMC 直鏈兩次均被升級為 reCAPTCHA(IP 級限流,等待後仍不恢復)時,若目標刊是全 OA 刊,走出版社官網比繼續重試 PMC 更可靠:
UA="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"
# 1) DOI 解析 → 觀察最終落到哪個出版社文章頁
curl -s -o /dev/null -w "final: %{url_effective}\nHTTP: %{http_code}\n" -A "$UA" -L "https://doi.org/<DOI>"
# 2) 在文章頁解析真實 PDF 直鏈
curl -s -A "$UA" "https://<publisher-article-page>" | grep -oiE '<meta name="citation_pdf_url"[^>]*>|href="[^"]*\.pdf[^"]*"' | sort -u | head
# 3) 帶 Referer 直取(出版社幾乎不校驗 Referer,但帶上更穩)
curl -sL --retry 3 -A "$UA" -e "$ARTICLE_PAGE" -o "$OUT" "$PDF_URL"
★★ 出版社頁被 Cloudflare 完全攔死時:用瀏覽器「列印為 PDF」(實測有效)
ashpublications.org(ASH 系期刊)本輪出現 Cloudflare 完全攔截,
此前「等 10 秒再 open」的經驗未能復現。有效替代路徑:
- 打開 PMC 全文頁(非 PDF),繞過出版社站點
- 用瀏覽器工具列印該頁為 PDF:
agent-browser pdf - reCAPTCHA 首次彈出時再執行一次 open 即可通過
判定要點:目標刊屬於 ASH / ScienceDirect 一類強 Cloudflare 防護時, 不要在 curl 上反覆重試——成功率低且會加深封禁,直接切瀏覽器通道。
- 已驗證可行:Haematologica / Ferrata Storti ——
doi.org/10.3324/haematol.2025.287698→haematologica.org/article/view/12187→haematologica.org/article/download/12187/78653→ 2.91 MB / 14 頁。 - 不可行:仍受 Cloudflare 類挑戰攔截的出版社(ASH
ashpublications.org、Wileyonlinelibrary.wiley.com/doi/pdfdirect/...、De Gruyter、Thieme)——這些只能回 PMC 通道或瀏覽器會話。 - 前置判斷:只有全 OA / Gold OA 刊才值得試這條路線(訂閱刊會撞付費牆,禁止繞過)。
★ 完整性校驗:大檔案下載被中斷會導致 xref 損壞
curl 取 PDF 時若被 SIGTERM / 超時中斷,檔案頭仍是正常的 %PDF-1.6、體積也看似合理,但 pdfinfo 會報 Invalid XRef entry。因此下載後不能只看檔案頭與檔案大小,必須同時滿足:
head -c 8 "$OUT" # %PDF-…
tail -c 32 "$OUT" | od -c | tail -3 # 應能看到 startxref … %%EOF
pdfinfo "$OUT" | grep -E "Pages|Page size" # 頁數應與「卷期頁」推算一致
三者齊全才算完整;缺失則重新完整下載(加 --retry 3),不要拿損壞檔案交差。
補充材料與圖片
正文 PDF 受阻時,supplementaryFiles 端點仍通常可用,且內容往往正是隨文圖片與補充方法:
curl -sL -o sup.zip "https://www.ebi.ac.uk/europepmc/webservices/rest/PMC13049628/supplementaryFiles"
unzip -o sup.zip -d sup/
命名約定(Wiley / ASH 系):*-gr1…*-grN 對應 Figure 1…N,*-ga1 為圖形摘要,*-mmc1.pdf 為補充材料。
使用瀏覽器工具時
只有在需要點擊、登入或渲染 JS 頁面時才動用 agent-browser。純下載場景不要啟動瀏覽器——
PoW 可離線求解,啟動 Chromium 只增加數百 MB 開銷與失敗面。若確需瀏覽器:
- 必須先把可用的 node 加入 PATH(否則 daemon 啟動逾時);
- 任務結束務必
agent-browser close,避免殭屍 daemon。
複核清單
- 下載前確認目標文獻為 OA(或確認當前處於機構訂閱 IP 段內——此情形下出版社直鏈可直接用, 但仍僅限人工量的單篇下載,禁止批次)
- 從文章頁
citation_pdf_url解析出真實 PDF 檔案名稱(不要硬編碼main.pdf) - 儲存後校驗檔案頭為
%PDF - 尾部校驗
startxref/%%EOF(大檔案被中斷會留下頭正常、xref 損壞的半成品;pdfinfo能識別) - 核驗頁數:用
re.findall(rb'/Count\s+(\d+)', data)或/Type /Page計數, 不要相信file out.pdf的頁數(macOS 上常誤報為 "1 pages");也可用mdls -name kMDItemNumberOfPages - 抽首末頁核對內容(
pdftotext -f 1 -l 1/-f N -l N),確認不是補充材料或其它文章 - 記錄
PMCID/DOI/ 落盤路徑,便於後續複核 - 未成功時如實說明失敗原因,不要以補充材料或圖片冒充正文 PDF