pyPaperFlow 0.3.0__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. pypaperflow-0.6.0/.claude/settings.local.json +8 -0
  2. pypaperflow-0.6.0/.github/workflows/docs.yml +35 -0
  3. pypaperflow-0.6.0/.gitignore +24 -0
  4. {pypaperflow-0.3.0 → pypaperflow-0.6.0}/PKG-INFO +203 -39
  5. {pypaperflow-0.3.0 → pypaperflow-0.6.0}/README.md +202 -38
  6. {pypaperflow-0.3.0 → pypaperflow-0.6.0}/README_zh.md +764 -75
  7. pypaperflow-0.6.0/mkdoc_site/index.md +61 -0
  8. pypaperflow-0.6.0/mkdocs.yml +53 -0
  9. pypaperflow-0.6.0/requirements-docs.txt +1 -0
  10. pypaperflow-0.6.0/scripts/sync_docs.py +228 -0
  11. pypaperflow-0.6.0/src/pyPaperFlow/__init__.py +1 -0
  12. {pypaperflow-0.3.0 → pypaperflow-0.6.0}/src/pyPaperFlow/cli.py +309 -33
  13. pypaperflow-0.6.0/src/pyPaperFlow/integrations/cloak_fallback.py +72 -0
  14. pypaperflow-0.6.0/src/pyPaperFlow/integrations/cloak_pdf.py +221 -0
  15. {pypaperflow-0.3.0 → pypaperflow-0.6.0}/src/pyPaperFlow/integrations/pdf_fetch.py +514 -51
  16. pypaperflow-0.6.0/src/pyPaperFlow/integrations/undetected_fallback.py +77 -0
  17. pypaperflow-0.6.0/src/pyPaperFlow/integrations/undetected_pdf.py +362 -0
  18. {pypaperflow-0.3.0 → pypaperflow-0.6.0}/src/pyPaperFlow/preprint/arxiv_fetcher.py +153 -58
  19. pypaperflow-0.6.0/src/pyPaperFlow/preprint/biorxiv_fetcher.py +699 -0
  20. pypaperflow-0.3.0/src/pyPaperFlow/preprint/biorxiv_fetcher.py → pypaperflow-0.6.0/src/pyPaperFlow/preprint/chemrxiv_fetcher.py +180 -137
  21. pypaperflow-0.6.0/src/pyPaperFlow/preprint/europepmc_fetcher.py +204 -0
  22. pypaperflow-0.6.0/src/pyPaperFlow/preprint/source_merge.py +52 -0
  23. {pypaperflow-0.3.0 → pypaperflow-0.6.0}/src/pyPaperFlow/preprint/source_utils.py +23 -2
  24. pypaperflow-0.6.0/tests/test_arxiv_fulltext.py +47 -0
  25. pypaperflow-0.6.0/tests/test_biorxiv_fulltext.py +101 -0
  26. pypaperflow-0.3.0/.gitignore +0 -12
  27. pypaperflow-0.3.0/src/pyPaperFlow/__init__.py +0 -1
  28. {pypaperflow-0.3.0 → pypaperflow-0.6.0}/LICENSE +0 -0
  29. {pypaperflow-0.3.0 → pypaperflow-0.6.0}/pyproject.toml +0 -0
  30. {pypaperflow-0.3.0 → pypaperflow-0.6.0}/src/pyPaperFlow/integrations/github_export.py +0 -0
  31. {pypaperflow-0.3.0 → pypaperflow-0.6.0}/src/pyPaperFlow/integrations/mineru_parser.py +0 -0
  32. {pypaperflow-0.3.0 → pypaperflow-0.6.0}/src/pyPaperFlow/preprint/source_models.py +0 -0
  33. {pypaperflow-0.3.0 → pypaperflow-0.6.0}/src/pyPaperFlow/pubmed/__init__.py +0 -0
  34. {pypaperflow-0.3.0 → pypaperflow-0.6.0}/src/pyPaperFlow/pubmed/pubmed_fetcher.py +0 -0
  35. {pypaperflow-0.3.0 → pypaperflow-0.6.0}/src/pyPaperFlow/pubmed/pubmed_merger.py +0 -0
  36. {pypaperflow-0.3.0 → pypaperflow-0.6.0}/src/pyPaperFlow/utils.py +0 -0
@@ -0,0 +1,8 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(curl -s -m 40 --noproxy '*' \"https://www.ebi.ac.uk/europepmc/webservices/rest/search?query=SRC:PPR%20AND%20zinc%20AND%20finger%20AND%20263%20AND%20FIRST_PDATE:%5B2026-08-01%20TO%202026-12-31%5D&format=json&pageSize=25&cursorMark=*&resultType=core\" -o /tmp/epmc_date.json -w \"HTTP=%{http_code}\\\\n\")",
5
+ "Bash(python3 -c ' *)"
6
+ ]
7
+ }
8
+ }
@@ -0,0 +1,35 @@
1
+ name: docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - "README_zh.md"
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+ pages: write
13
+ id-token: write
14
+
15
+ concurrency:
16
+ group: pages
17
+ cancel-in-progress: true
18
+
19
+ jobs:
20
+ deploy:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v7
24
+ - uses: actions/setup-python@v7
25
+ with:
26
+ python-version: "3.13"
27
+ - run: pip install -r requirements-docs.txt
28
+ - run: python scripts/sync_docs.py
29
+ - run: mkdocs build
30
+ - uses: actions/configure-pages@v6
31
+ - uses: actions/upload-pages-artifact@v5
32
+ with:
33
+ path: site
34
+ - id: deployment
35
+ uses: actions/deploy-pages@v5
@@ -0,0 +1,24 @@
1
+ # build product
2
+ dist/
3
+ build/
4
+ *.egg-info/
5
+
6
+ # Python cache
7
+ __pycache__/
8
+ *.py[cod]
9
+
10
+ # environment
11
+ .env
12
+ venv/
13
+ CLAUDE.md
14
+ pdfs/
15
+ refers/
16
+
17
+ # mkdocs build output
18
+ site/
19
+ .cache/
20
+
21
+ # generated docs (from scripts/sync_docs.py)
22
+ # ignore everything under mkdoc_site/ except the hand-written homepage
23
+ mkdoc_site/*
24
+ !mkdoc_site/index.md
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pyPaperFlow
3
- Version: 0.3.0
3
+ Version: 0.6.0
4
4
  Summary: Automated paper fetching and analysis platform.
5
5
  Project-URL: Homepage, https://github.com/MaybeBio/pyPaperFlow
6
6
  Project-URL: Issues, https://github.com/MaybeBio/pyPaperFlow/issues
@@ -35,7 +35,7 @@ Description-Content-Type: text/markdown
35
35
 
36
36
  <p><strong>An automated literature processing platform for scientific researchers.</strong></p>
37
37
 
38
- <p>Batch retrieve, fetch, parse, and structure papers from PubMed, arXiv, bioRxiv, and DOI-based sources.</p>
38
+ <p>Batch retrieve, fetch, parse, and structure papers from PubMed, arXiv, bioRxiv, ChemRxiv, and DOI-based sources.</p>
39
39
 
40
40
  <p>From paper retrieval to knowledge internalization, automate the heavy lifting and keep the judgment human.</p>
41
41
 
@@ -44,17 +44,20 @@ Description-Content-Type: text/markdown
44
44
  [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
45
45
  [![PR's Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat)](http://makeapullrequest.com)
46
46
  [![Workflow](https://img.shields.io/badge/Workflow-7%20Stages-0366d6)](docs/Design.md)
47
- [![Sources](https://img.shields.io/badge/Sources-PubMed%20%2F%20arXiv%20%2F%20bioRxiv-f59e0b)](#features)
47
+ [![Sources](https://img.shields.io/badge/Sources-PubMed%20%2F%20arXiv%20%2F%20bioRxiv%20%2F%20medRxiv%20%2F%20chemRxiv-f59e0b)](#features)
48
48
  [![PyPI version](https://img.shields.io/pypi/v/pyPaperFlow.svg?logo=pypi&logoColor=white)](https://pypi.org/project/pyPaperFlow/)
49
49
  [![Python Versions](https://img.shields.io/pypi/pyversions/pyPaperFlow.svg?logo=python&logoColor=white)](https://pypi.org/project/pyPaperFlow/)
50
50
  [![Downloads](https://static.pepy.tech/badge/pyPaperFlow)](https://pepy.tech/project/pyPaperFlow)
51
+ [![Docs](https://img.shields.io/badge/Docs-online_docs-2ea44f)](https://maybebio.github.io/pyPaperFlow/)
51
52
 
52
53
  <p>
53
- Document here 👉
54
+ 文档阅读👉
54
55
  <a href="README.md">English</a> |
55
- <a href="README_zh.md">中文</a>
56
+ <a href="README_zh.md">中文</a> |
57
+ <a href="https://maybebio.github.io/pyPaperFlow/" target="_blank">中文在线文档</a>
56
58
  </p>
57
59
 
60
+
58
61
  <p>
59
62
  <a href="./docs/Design.md">Design</a> |
60
63
  <a href="./docs/Cases.md">Cases</a>
@@ -179,6 +182,8 @@ flowchart TD
179
182
 
180
183
  ## 📦 Installation
181
184
 
185
+ > ⚠️ For typical usage, you only need to install our tool, seen below:
186
+
182
187
  ```bash
183
188
  # 1. install our tool
184
189
  ## ✏️1️⃣ option1: Install via pip (Recommended)
@@ -188,9 +193,11 @@ pip install pyPaperFlow
188
193
  git clone https://github.com/MaybeBio/pyPaperFlow.git
189
194
  cd pyPaperFlow
190
195
  pip install -e .
196
+ ```
191
197
 
192
- --------------------------------------------------------
198
+ > Some optional dependencies, if you do not need to use the corresponding functional modules, you can ignore the installation. seen below:
193
199
 
200
+ ```bash
194
201
  # 2. install MinerU
195
202
  # follow the official installation guide: https://github.com/opendatalab/MinerU
196
203
  # verify installation: mineru --help
@@ -210,10 +217,11 @@ pip install openai anthropic
210
217
  pip install paperscraper
211
218
  ```
212
219
 
213
- > ⚠️ For typical usage, you only need to install the repository from source and MinerU, which are steps 1 and 2.
214
220
 
215
221
  ## 🛠️ Usage
216
222
 
223
+ > 📌 **Tip**: If you want to get started directly, please refer to the usage examples in [Cases.md](./docs/Cases.md). The content below is theoretical process analysis and can be skipped.
224
+
217
225
  We designed pyPaperFlow as a versatile academic research tool built strictly around the `real‑world workflow of researchers conducting literature investigation, paper reading, literature comprehension and analysis, and corpus utilization`.
218
226
 
219
227
  Therefore, please follow our step‑by‑step operations, which mirror your full literature research process. Through this hands‑on experience, you will fully grasp the design philosophy and usage of this tool.
@@ -227,33 +235,39 @@ Current available modules include (`will be continuously updated`):
227
235
 
228
236
  ```python
229
237
  ❯ paperflow --help
230
-
231
- Usage: paperflow [OPTIONS] COMMAND [ARGS]...
232
-
233
- pyPaperFlow CLI
234
-
235
- ╭─ Options ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
236
- │ --install-completion Install completion for the current shell. │
237
- │ --show-completion Show completion for the current shell, to copy it or customize the installation. │
238
- │ --help Show this message and exit. │
239
- ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
240
- ╭─ Commands ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
241
- │ pubmed-search Search PubMed using Your customized query and return PMIDs. │
242
- │ pubmed-meta Fetch paper metadata from PubMed using Your customized query, pmid list file and save to storage. │
243
- │ pubmed-content Download full text (PMC) for given PMIDs if the paper has a PMC ID. │
244
- │ pubmed-all Fetch BOTH metadata and full text (if available) for papers. │
245
- │ Also extracts URLs from full text and updates metadata links. │
246
- │ pubmed-merge-json Create a merged JSON (or JSONL) file from PubMed paper directories. │
247
- │ pubmed-export-md Export a single Markdown view from a merged JSON file using optional YAML config. │
248
- │ arxiv-search Search arXiv and write matching IDs to a text file. │
249
- │ arxiv-fetch Fetch arXiv metadata and attempt to download PDFs. │
250
- │ biorxiv-search Search bioRxiv and write matching IDs to a text file. │
251
- │ biorxiv-fetch Fetch bioRxiv metadata and attempt to download PDFs. │
252
- │ paper-fetch Fetch PDFs by DOI — passes through to the paper-fetch engine. │
253
- │ pdf-parse Parse a PDF file using MinerU engine, and clean up the output directory. │
254
- │ mineru-parse Parse mineru output content_list_v2.json into canonical sectioned JSON. │
255
- │ mineru-export-md Export structured mineru JSON to a clean Markdown file for LLM processing. │
256
- ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
238
+
239
+ Usage: paperflow [OPTIONS] COMMAND [ARGS]...
240
+
241
+ pyPaperFlow CLI
242
+
243
+ ╭─ Options ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
244
+ │ --install-completion Install completion for the current shell. │
245
+ │ --show-completion Show completion for the current shell, to copy it or customize the installation. │
246
+ │ --help Show this message and exit. │
247
+ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
248
+ ╭─ Commands ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
249
+ │ pubmed-search Search PubMed using Your customized query and return PMIDs. │
250
+ │ pubmed-meta Fetch paper metadata from PubMed using Your customized query, pmid list file and save to storage. │
251
+ │ pubmed-content Download full text (PMC) for given PMIDs if the paper has a PMC ID. │
252
+ │ pubmed-all Fetch BOTH metadata and full text (if available) for papers. │
253
+ │ Also extracts URLs from full text and updates metadata links. │
254
+ │ pubmed-merge-json Create a merged JSON (or JSONL) file from PubMed paper directories. │
255
+ │ pubmed-export-md Export a single Markdown view from a merged JSON file using optional YAML config. │
256
+ │ arxiv-search Search arXiv and write matching IDs to a text file. │
257
+ │ arxiv-fetch Fetch arXiv metadata and attempt to download PDFs. │
258
+ │ biorxiv-search Search bioRxiv and write matching IDs to a text file. │
259
+ │ biorxiv-fetch Fetch bioRxiv metadata and attempt to download PDFs. │
260
+ │ medrxiv-search Search medRxiv and write matching IDs to a text file. │
261
+ │ medrxiv-fetch Fetch medRxiv metadata and attempt to download PDFs. │
262
+ │ chemrxiv-search Search ChemRxiv and write matching IDs to a text file. │
263
+ │ chemrxiv-fetch Fetch ChemRxiv metadata and attempt to download PDFs. │
264
+ │ paper-fetch Fetch PDFs by DOI — passes through to the paper-fetch engine. │
265
+ │ pdf-parse Parse a PDF file using MinerU engine, and clean up the output directory. │
266
+ │ mineru-parse Parse mineru output content_list_v2.json into canonical sectioned JSON. │
267
+ │ mineru-export-md Export structured mineru JSON to a clean Markdown file for LLM processing. │
268
+ │ github-export Export GitHub links from merged PubMed JSON, validate accessibility, │
269
+ │ and aggregate `ghresearcher parse <owner/repo> --view` outputs into one markdown. │
270
+ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
257
271
 
258
272
  ```
259
273
 
@@ -275,11 +289,20 @@ bioRxiv Modules:
275
289
  - biorxiv-search # search bioRxiv and return matching IDs
276
290
  - biorxiv-fetch # fetch bioRxiv metadata and attempt to download PDFs
277
291
 
292
+ medRxiv Modules:
293
+ - medrxiv-search # search medRxiv and return matching IDs
294
+ - medrxiv-fetch # fetch medRxiv metadata and attempt to download PDFs
295
+
296
+ ChemRxiv Modules:
297
+ - chemrxiv-search # search ChemRxiv and return matching IDs
298
+ - chemrxiv-fetch # fetch ChemRxiv metadata and attempt to download PDFs
299
+
278
300
  Third-party Modules:
279
301
  - paper-fetch # fetch PDFs by DOI
280
302
  - pdf-parse # parse PDF files into JSON, Markdown format using the MinerU engine
281
303
  - mineru-parse # Based on your custom section configuration, re-parse the MinerU output file into a structured JSON format clustered by standard literature sections
282
304
  - mineru-export-md # Based on your custom section configuration, export the structured mineru JSON to a clean Markdown file for LLM processing (🌟 e.g., batch export of introductions as your research background)
305
+ - github-export # export GitHub links from merged PubMed JSON, validate accessibility, and aggregate `ghresearcher parse <owner/repo> --view` outputs into one markdown
283
306
  ```
284
307
 
285
308
  > ⚠️ `Other preprint platforms modules are under development, please stay tuned!`
@@ -323,6 +346,20 @@ Our literature database primarily covers biomedical research and computational i
323
346
  - arXiv
324
347
  - bioRxiv,medRxiv,chemRxiv
325
348
 
349
+ > **⚠️ — Preprint search = Crossref relevance search + local boolean re-check (not a full-corpus pull, and not each server's official API).** Each request asks Crossref to search ONLY that platform's prefix (`filter=prefix:10.64898 / 10.26434,type:posted-content`) — platform scoping happens server-side, not by post-filtering a global result set. bioRxiv and medRxiv share the openRxiv prefix `10.64898`, so those two are then told apart locally by DOI-accession digit count (6 = bioRxiv, 8 = medRxiv).
350
+ >
351
+ > How the relevance step behaves: `query.bibliographic` is a fuzzy, OR-like ranking (a two-term query returns more than either single term — e.g. chemRxiv "base editing" ≈ the union of "base" and "editing"), i.e. a **superset** of the strict matches. The fetcher cursor-paginates the whole result set (not a capped top-N), then keeps only records in which **every** query term is actually present in the metadata (local boolean AND over title/abstract/…). Because strict matches ⊆ relevance superset, ranking never drops a metadata-level exact match — it only changes the order.
352
+ >
353
+ > **Limitations of the relevance search:**
354
+ >
355
+ > ① **Deposit lag** — a preprint posted minutes ago may not be indexed in Crossref yet.
356
+ >
357
+ > ② **Metadata-only matching** — Crossref scores deposited metadata (title/abstract/…), so query terms that appear only in the paper body are invisible to it. Only the bioRxiv/medRxiv Europe PMC leg indexes full text; **ChemRxiv has no full-text leg at all**, so body-only-term misses are expected there.
358
+ >
359
+ > ③ **Version duplication** — every revision is its own DOI work, so `.../v1` and `.../v2` both match a search and may need manual dedup.
360
+ >
361
+ > **Difference vs. exhaustive full-corpus enumeration:** a relevance search is a heuristic over the deposited metadata. The "no-omission-by-construction" alternative is to list the *whole* platform corpus (`filter=prefix…` with no `query`, cursor-paging every record — ≈ 55k ChemRxiv / 436k openRxiv) and run the boolean AND locally, with no relevance engine in the loop; recall is then exactly "all records whose metadata fully matches the query" (a `--start/--end-date` window shrinks the pull). The cost is downloading the full corpus per search, and it still inherits the source-level boundaries above (deposit lag, metadata-only, version duplication). This tool's `search()` path is relevance-based today; the exhaustive mode is not currently exposed as a flag.
362
+
326
363
  We recommend that you proactively learn and master the search syntax of these databases, as our built‑in search module functions similarly to the search bar on official web portals.
327
364
 
328
365
  For instance, here is a typical complex query example tailored for PubMed:
@@ -599,6 +636,8 @@ examples:
599
636
 
600
637
  We acknowledge the work of [paper-fetch](https://github.com/Agents365-ai/paper-fetch)!We have modified, refactored, and encapsulated one of its core scripts for tailored integration into our pipeline.
601
638
 
639
+ > 🔙 Rollback boundary for the paper-fetch module: commit `bc8394c` (`update paper-fetch module according to upstream repo`) contains the **original upstream script**. Commit `89eda06bac1f853254b04aee9e8916109c7771a1` is the first local **modification** to it. To restore the original module, use `89eda06` as the boundary — e.g. `git show 89eda06^:src/pyPaperFlow/integrations/pdf_fetch.py` (identical to `bc8394c`).
640
+
602
641
  The workflow of our paper acquisition module is outlined below:
603
642
 
604
643
  ```bash
@@ -624,6 +663,8 @@ The workflow of our paper acquisition module is outlined below:
624
663
  ┌─────────────────────────────────────────┐
625
664
  │ 3. arXiv (via S2 externalIds.ArXiv) │
626
665
  │ 4. Europe PMC → PMC (via PMCID) │
666
+ │ No PMCID: DOI→PMCID recovery │
667
+ │ (Europe PMC hasPDF=Y / OpenAIRE) │
627
668
  │ 5. bioRxiv / medRxiv (DOI prefix: 10.1101/)
628
669
  └─────────────────────────────────────────┘
629
670
  Total Failure ↓
@@ -634,7 +675,12 @@ The workflow of our paper acquisition module is outlined below:
634
675
  └─────────────────────────────────────────┘
635
676
  Persistent Failure ↓
636
677
  ┌─────────────────────────────────────────┐
637
- │ 7. Sci‑Hub Mirror Fallback (enabled by default, configurable)
678
+ │ 7. CORE Repository Aggregator (optional, requires CORE_API_KEY)
679
+ │ → core.ac.uk aggregates OA full-text downloadUrl
680
+ └─────────────────────────────────────────┘
681
+ Persistent Failure ↓
682
+ ┌─────────────────────────────────────────┐
683
+ │ 8. Sci‑Hub Mirror Fallback (enabled by default, configurable)
638
684
  │ → 1 request‑per‑second rate‑limiting to prevent CAPTCHA triggers
639
685
  │ → Automatic discovery of active new mirrors
640
686
  └─────────────────────────────────────────┘
@@ -647,9 +693,10 @@ Resolution Priority Sequence
647
693
  Unpaywall: The optimal open‑access source covering the broadest range of publishers with the highest hit rate.
648
694
  Semantic Scholar: Retrieves OA PDF links and cross‑platform external identifiers.
649
695
  arXiv: Activated when an arXiv identifier is available for the target paper.
650
- PubMed Central (PMC) OA Subset: Activated when a PMCID is associated with the paper.
696
+ PubMed Central (PMC) OA Subset: Activated when a PMCID is associated with the paper; when no PMCID is known, a DOI→PMCID recovery is attempted first (Europe PMC search hasPDF=Y / OpenAIRE originalId).
651
697
  bioRxiv / medRxiv: Triggered for preprints with the DOI prefix 10.1101/.
652
698
  Publisher Direct Links: Enabled only under institutional mode (PAPER_FETCH_INSTITUTIONAL=1), authorized via the caller’s institutional subscription IP, cookies, or EZproxy access.
699
+ CORE Repository Aggregator: Optional, requires CORE_API_KEY; aggregates full texts from many repositories, attempted only when all other OA sources miss (free tier ~5 req/10s).
653
700
  Sci‑Hub Mirror Fallback: Enabled by default as the final retrieval backup.
654
701
  Mirrors are attempted in the order specified by the environment variable PAPER_FETCH_SCIHUB_MIRRORS (default list: sci‑hub.ru, sci‑hub.st, sci‑hub.su, sci‑hub.box, sci‑hub.red, sci‑hub.al, sci‑hub.mk, sci‑hub.ee).
655
702
  If all predefined mirrors fail, the module fetches the latest live mirror list from https://www.sci‑hub.pub/ and retries.
@@ -665,6 +712,120 @@ export UNPAYWALL_EMAIL=you@example.com
665
712
  ```
666
713
 
667
714
 
715
+ **Cloudflare-blocked PDFs (optional)**
716
+
717
+ Some publishers (e.g. `science.org`) sit behind Cloudflare and return `403`/`429` or a "Just a moment…" JS challenge page instead of the PDF. Set `PAPER_FETCH_CLOAK=1` to retry those URLs through [CloakBrowser](https://github.com/CloakHQ/CloakBrowser) (a stealth Chromium that can pass the challenge). This fallback lives at the download layer (covers all sources), fails silently when CloakBrowser is unavailable, is operator-controlled (agents cannot enable it), and re-validates returned bytes through the same `%PDF` + 50 MB checks. Successful cloak downloads carry `via:"cloak"`.
718
+
719
+ Setup — install once, then treat `PAPER_FETCH_CLOAK` as an always-on safety net:
720
+
721
+ ```bash
722
+ # One-time install: put cloakbrowser into the same Python that runs paperflow
723
+ # (auto-detected), or into a separate venv and point CLOAKBROWSER_PYTHON at it.
724
+ pip install cloakbrowser
725
+
726
+ # Recommended: keep as a safety net (fires ONLY when a download is blocked;
727
+ # normal OA downloads are unaffected).
728
+ export PAPER_FETCH_CLOAK=1
729
+
730
+ # Cleaner per-command alternative, when you only occasionally hit a blocked URL:
731
+ PAPER_FETCH_CLOAK=1 paperflow paper-fetch 10.1126/sciadv.aee6105 --out ./pdfs
732
+
733
+ # ⚠️ PAPER_FETCH_CLOAK_HEADED=1 is NOT a default. Set it only for strong
734
+ # challenges (e.g. science.org) AND only on a machine with a display:
735
+ # export PAPER_FETCH_CLOAK_HEADED=1
736
+ ```
737
+
738
+ > **Practical notes (from testing)**
739
+ > - Cloak is **not** a paywall bypass. It only fires when an *OA* download is Cloudflare-blocked; a paywalled paper (no OA copy, e.g. `10.1016/j.cels.2025.101486`) never reaches the download layer, so Cloak is never invoked.
740
+ > - `science.org` is a "strong" challenge that **headless cannot pass** (it stalls on "Just a moment…") — it requires `PAPER_FETCH_CLOAK_HEADED=1` on a real display (a desktop, or a headless server wrapped with `xvfb-run`).
741
+ > - Cloak only has a URL to retry when a source returned a direct `url_for_pdf`. Papers whose only OA copy is in PMC (Unpaywall reports a landing page with no `url_for_pdf`) are **not** attempted by the stock script.
742
+ > - Keeping `PAPER_FETCH_CLOAK=1` always on has no effect on normal downloads, but once `cloakbrowser` is installed, each blocked URL adds a ~30–90 s browser attempt before falling through; use the inline form if you'd rather not wait.
743
+ > - Sci-Hub discovery contacts `www.sci-hub.pub` — on networks where that DNS is blocked you'll see `scihub_discover_failed`, which removes the last fallback. For genuinely unavailable papers, use institutional mode (`PAPER_FETCH_INSTITUTIONAL=1`) or download via a browser / interlibrary loan.
744
+
745
+
746
+ **Institutional access (optional)**
747
+
748
+ Some paywalled papers are exactly what your institution's subscription covers — a generic OA source just can't see them. Set `PAPER_FETCH_INSTITUTIONAL=1` to enable the publisher-direct-link source (step 6 of the chain): the script builds a direct PDF URL for the DOI's publisher and downloads it.
749
+
750
+ ```bash
751
+ export PAPER_FETCH_INSTITUTIONAL=1 # only effective while on the institutional network
752
+ ```
753
+
754
+ > **Key requirement (verified by testing):** authorization comes from the *caller's network*, not the script. Publisher-direct downloads succeed only when the machine runs **on-campus or on the institutional VPN** — the publisher recognizes your institution's IP range (or cookies / EZproxy). From an off-network IP (a datacenter or home machine) the URL is still built correctly but the publisher answers `HTTP 403 Forbidden`, and the run ends with `download_network_error` (retryable) instead of `not_found` — that error-type change is how you can tell institutional mode actually fired.
755
+ > - The result envelope's `auth_mode` field reports `"institutional"` (vs `"public"`).
756
+ > - Direct-link templates are matched by DOI prefix; Elsevier (`10.1016/`) needs an extra PII lookup via Crossref and `sciencedirect.com/.../pdfft` — verified working. Supported publishers: Nature, Science, Wiley, Springer, ACS, PNAS, NEJM, SAGE, Taylor & Francis, Elsevier, MDPI.
757
+ > - Auto rate-limits to **1 req/s** to respect publisher ToS (protects your institution's IP from throttling).
758
+ > - In public mode, when a paper looks paywalled the error payload sets `suggest_institutional: true` and hints to set this variable and re-run from on-campus / VPN.
759
+
760
+
761
+ **CORE repository-aggregator fallback (optional)**
762
+
763
+ When a paper misses across all OA sources (Unpaywall / Semantic Scholar / arXiv / PMC / bioRxiv) but an institutional or subject repository may still hold a copy, set `CORE_API_KEY` to enable the [CORE](https://core.ac.uk) (core.ac.uk) aggregator fallback. CORE aggregates full-text metadata from thousands of OA repositories and journals worldwide; its v3 search API queries by DOI and the `downloadUrl` field of a matching record is the directly downloadable OA full-text link (paywalled records leave this field empty and are skipped automatically).
764
+
765
+ ```bash
766
+ export CORE_API_KEY=your_core_api_key # free signup: https://core.ac.uk/services/api
767
+ ```
768
+
769
+ > **Principle & notes**
770
+ > - CORE is a *repository aggregator*, not a single publisher: it pools full texts from institutional and subject repositories, covering repository copies that other sources miss.
771
+ > - This source fires **only** when all earlier OA sources (Unpaywall / Semantic Scholar / arXiv / PMC / bioRxiv) have missed — it never interferes with the normal OA download path, so keeping it on is harmless.
772
+ > - Requires a free API key (Bearer auth); the source is silently skipped when `CORE_API_KEY` is unset.
773
+ > - The free tier rate-limits to roughly **5 requests / 10 seconds** and returns `403` when exceeded — imperceptible for single papers, and batch fetching is serialized/throttled.
774
+ > - Returned links pass the same `%PDF` magic-byte + 50 MB checks; successful hits carry `source:"core"`.
775
+ > - Only records with a non-empty `downloadUrl` are used, naturally filtering out paywalled entries with no OA copy.
776
+
777
+
778
+ **Recommended setup (best practice)**
779
+
780
+ For a mixed workload — OA papers, Cloudflare-gated OA papers, and the occasional paywalled paper your institution subscribes to — keep three things on:
781
+
782
+ ```bash
783
+ export UNPAYWALL_EMAIL=you@example.com # fastest / broadest OA source
784
+ export PAPER_FETCH_CLOAK=1 # safety net for Cloudflare-gated OA PDFs (harmless otherwise)
785
+ # Run the line below only while on campus / the institutional VPN:
786
+ export PAPER_FETCH_INSTITUTIONAL=1 # publisher-direct links for paywalled papers
787
+ ```
788
+
789
+ Decision logic for a single paper:
790
+
791
+ - **OA paper** → Unpaywall / Semantic Scholar / arXiv / PMC handle it; `PAPER_FETCH_CLOAK` only adds a retry when the download is Cloudflare-blocked.
792
+ - **Cloudflare-blocked OA** (e.g. `science.org`) → Cloak retries it (headless may stall; use `PAPER_FETCH_CLOAK_HEADED=1` on a machine with a display).
793
+ - **Paywalled paper** (e.g. `10.1016/j.cels.2025.101486`) → only the institutional chain can fetch it, and only from on-campus / VPN: Unpaywall → Semantic Scholar → publisher-direct (Elsevier via PII lookup → `sciencedirect.com/.../pdfft`) → Sci-Hub fallback. From an off-network IP the publisher answers `403` and no automated path remains — use your library portal / EZproxy or interlibrary loan.
794
+
795
+
796
+ **Notes & Environment Variables**
797
+
798
+ All settings are read from environment variables when the process starts — there is no config file. Every var above can be combined freely.
799
+
800
+ | Env var | Effect | Default | When to set |
801
+ | --- | --- | --- | --- |
802
+ | `UNPAYWALL_EMAIL` | Contact email for the Unpaywall API (sent in the User-Agent); without it the Unpaywall source is skipped. | empty | Recommended — Unpaywall is the fastest / broadest source |
803
+ | `CORE_API_KEY` | API key for the CORE (core.ac.uk) aggregator; without it the `core` repository source is skipped. | empty | When you need coverage for institutional / subject repository OA copies |
804
+ | `PAPER_FETCH_NO_SCIHUB` | Set to `1` to disable the Sci-Hub mirror fallback. | Sci-Hub ON | If your institution / compliance forbids Sci-Hub |
805
+ | `PAPER_FETCH_SCIHUB_MIRRORS` | Comma-separated mirror list, tried in priority order (hostnames only). | built-in default list | When the default mirrors stop working |
806
+ | `PAPER_FETCH_INSTITUTIONAL` | Set to `1` to enable publisher direct links (authorized by your institutional IP / cookies / EZproxy). Auto rate-limits to 1 req/s to respect publisher ToS. | off | If you have an institutional subscription |
807
+ | `PAPER_FETCH_CLOAK` | Set to `1` to retry Cloudflare-blocked PDFs through CloakBrowser. | off | For publishers behind Cloudflare (e.g. `science.org`) |
808
+ | `CLOAKBROWSER_PYTHON` | Python interpreter that can `import cloakbrowser`. | auto-detected | Only if not auto-detected |
809
+ | `PAPER_FETCH_CLOAK_HEADED` | Set to `1` for a headed browser (needs a display). | headless | For strong challenges that fail headless (e.g. `science.org`) |
810
+
811
+ > ⚠️ **Boolean flags are presence-based, not value-based.** The code checks `os.environ.get(...)`, so *any* non-empty value enables the feature. Setting `PAPER_FETCH_CLOAK=0` or `=false` still **enables** Cloak; setting `PAPER_FETCH_NO_SCIHUB=0` still **disables** Sci-Hub. To turn a feature off, `unset` the variable — never write `=0`/`=false`.
812
+
813
+ **Usage notes**
814
+
815
+ - The output directory flag is `--out` — there is **no `-o` short option** (the `paperflow paper-fetch` passthrough does not rewrite args).
816
+ - One DOI as a positional arg; `-` reads a single DOI from stdin; `--batch FILE` (or `--batch -`) reads DOIs line-by-line.
817
+ - Files already downloaded are skipped by default; pass `--overwrite` to force a re-download.
818
+ - stdout carries the machine-readable JSON envelope, stderr carries progress; `--format json|text` plus TTY auto-detection control the shape. Exit codes: `0` all resolved, `1` some unresolved, `3` bad arguments, `4` transport error (retryable).
819
+ - `paperflow paper-fetch schema` prints the machine-readable CLI schema (no network).
820
+ - `--idempotency-key KEY` replays the original result envelope on retry, with no network I/O.
821
+
822
+ **Known limitations**
823
+
824
+ - Some publisher redirects land on HTML pages instead of PDFs — the `%PDF` magic-byte check rejects them.
825
+ - No browser automation by default (no CAPTCHA solving) — only the optional `PAPER_FETCH_CLOAK` CloakBrowser fallback.
826
+ - SSRF protection rejects private IPs, non-`http(s)` schemes, non-80/443 ports, and cloud metadata hosts.
827
+ - Each PDF is capped at 50 MB.
828
+
668
829
  Unlike PMC parsing, non‑PubMed papers can only be obtained as PDF files via the paper‑fetch module.
669
830
 
670
831
  We recommend standardizing all paper information into Markdown or JSON formats.
@@ -1328,8 +1489,8 @@ You may directly run the test scripts to verify the correctness and completeness
1328
1489
  <details>
1329
1490
  <summary><b>3. Literature Acquisition (and Full‑Text Download)</b></summary>
1330
1491
 
1331
- > - [ ] Refine and encapsulate the `paper‑fetch` module. Refer to [2026‑05‑08 paper‑fetch Encapsulation](https://github.com/Agents365‑ai/paper‑fetch); evaluate integration or replacement with more robust modules offering higher hit rates.
1332
- > - [ ] The `pdf‑parse` module currently wraps basic MinerU parsing commands with the CPU backend (`‑b pipeline`). Future integration of GPU‑accelerated features; see [MinerU Repository](https://github.com/opendatalab/MinerU) for details.
1492
+ > - [x] Refine and encapsulate the `paper‑fetch` module. Refer to [2026‑05‑08 paper‑fetch Encapsulation](https://github.com/Agents365‑ai/paper‑fetch); evaluate integration or replacement with more robust modules offering higher hit rates.
1493
+ > - [x] The `pdf‑parse` module currently wraps basic MinerU parsing commands with the CPU backend (`‑b pipeline`). Future integration of GPU‑accelerated features; see [MinerU Repository](https://github.com/opendatalab/MinerU) for details.
1333
1494
 
1334
1495
  </details>
1335
1496
 
@@ -1348,7 +1509,10 @@ You may directly run the test scripts to verify the correctness and completeness
1348
1509
  <details>
1349
1510
  <summary><b>5. Processing for Other Literature Databases</b></summary>
1350
1511
 
1351
- > - [ ] Develop a unified `search‑fetch‑parse` pipeline for non‑PubMed databases and complete corresponding modules. Refer to open‑source implementations such as [paperscraper](https://github.com/jannisborn/paperscraper) and [paper‑tracker](https://github.com/RainerSeventeen/paper‑tracker).
1512
+ > - [x] Develop a unified `search‑fetch‑parse` pipeline for non‑PubMed databases and complete corresponding modules. Refer to open‑source implementations such as [paperscraper](https://github.com/jannisborn/paperscraper) and [paper‑tracker](https://github.com/RainerSeventeen/paper‑tracker).
1513
+ > - [ ] Multi‑source retrieval merge (primitive ready, orchestration pending): `preprint/source_merge.py` already provides `merge_papers` (backfill missing DOIs → dedupe by DOI, key cascade DOI → title+authors → source_id). The cross‑source orchestration command is not yet implemented; when adding a unified `search --sources arxiv,biorxiv,medrxiv,chemrxiv` that merges results into a single corpus, call `merge_papers` directly.
1514
+ > - [ ] Optional connectors (add on demand): skip a standalone Semantic Scholar metadata connector — it overlaps with the S2 usage inside `pdf_fetch.py` (`openAccessPdf`/`externalIds`), avoiding dual maintenance. Introduce an OpenAlex connector (`api.openalex.org/works`, inverted‑index abstract reconstruction + authors + citations/references) only when there is a concrete need for batch DOI‑based metadata completion or citation‑graph harvesting; emit `SourcePaper`.
1515
+ > - [ ] Infrastructure alignment (partially done): `extract_doi` unified into `source_merge.py`; `safe_filename` already present (`source_utils.py`); `get_env` deferred (mismatches the existing flat `os.environ.get` style); OAI‑PMH base class deferred until a generic OAI repository is actually integrated.
1352
1516
 
1353
1517
  </details>
1354
1518