Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Bulk download PMC Open Access articles via FTP for large-scale mining
.claude/skills/brycewang-stanford-pmc-ftp-bulk-download/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 116% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 1% | 0% |
| case-05 | ✗→✓ | ▲ Improved | -6% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 55% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 45% | 0% |
The PMC FTP Service provides bulk download access to millions of full-text articles from PubMed Central's Open Access Subset. Unlike the single-article APIs (E-utilities, BioC), the FTP service is designed for large-scale corpus construction — downloading entire collections for text mining, NLP training, systematic reviews, and bibliometric analysis. Free, no authentication required.
Note: PMC is migrating to AWS-based Cloud Service in August 2026. FTP paths may change; check official docs for updates.
bash# FTP (classic) ftp ftp.ncbi.nlm.nih.gov # Navigate to: /pub/pmc # HTTPS alternative (recommended) # Base: https://ftp.ncbi.nlm.nih.gov/pub/pmc/
| Dataset | Path | Content | Format | |---------|------|---------|--------| | OA Commercial | /pub/pmc/oa_comm/ | CC BY/CC0 articles (commercial use OK) | .tar.gz packages | | OA Non-Commercial | /pub/pmc/oa_noncomm/ | CC BY-NC articles | .tar.gz packages | | OA Other | /pub/pmc/oa_other/ | Other open licenses | .tar.gz packages | | Author Manuscripts | /pub/pmc/manuscript/ | NIH-funded manuscripts | .tar.gz packages | | Historical OCR | /pub/pmc/historical_ocr/ | Pre-digital scanned articles | .tar.gz | | File lists | /pub/pmc/oa_file_list.csv | Index of all OA articles | CSV |
Download the master index to plan your downloads:
bash# Download the OA file list (CSV, ~200MB) wget https://ftp.ncbi.nlm.nih.gov/pub/pmc/oa_file_list.csv # CSV columns: # File, Article Citation, AccessionID, LastUpdated, PMID, License
pythonimport requests import tarfile import io import csv def download_article_package(pmcid: str, base_url: str = "https://ftp.ncbi.nlm.nih.gov/pub/pmc"): """Download and extract a specific PMC article package.""" # First, look up the file path from the file list # (In practice, you'd load this once and index by PMCID) file_list_url = f"{base_url}/oa_file_list.csv" # ... lookup pmcid in file list to get path ... # Download the tar.gz package resp = requests.get(f"{base_url}/{file_path}", stream=True) resp.raise_for_status() # Extract with tarfile.open(fileobj=io.BytesIO(resp.content), mode="r:gz") as tar: tar.extractall(path=f"./articles/{pmcid}") print(f"Extracted {pmcid}")
bash#!/bin/bash # Download all commercial-use articles (CC BY / CC0) # WARNING: This is ~100GB+ compressed mkdir -p pmc_corpus/commercial cd pmc_corpus/commercial # Download the baseline (all current articles) wget -r -np -nH --cut-dirs=3 \ https://ftp.ncbi.nlm.nih.gov/pub/pmc/oa_comm/xml/ # Incremental updates (run periodically) wget -r -np -nH --cut-dirs=3 -N \ https://ftp.ncbi.nlm.nih.gov/pub/pmc/oa_comm/xml/
pythonimport csv import requests from pathlib import Path def download_filtered_corpus(file_list_path: str, output_dir: str, license_filter: str = "CC BY", max_articles: int = 1000): """Download articles matching a license filter.""" output = Path(output_dir) output.mkdir(parents=True, exist_ok=True) base = "https://ftp.ncbi.nlm.nih.gov/pub/pmc" downloaded = 0 with open(file_list_path) as f: reader = csv.DictReader(f) for row in reader: if license_filter and license_filter not in row.get("License", ""): continue if downloaded >= max_articles: break file_path = row["File"] url = f"{base}/{file_path}" local_path = output / Path(file_path).name if local_path.exists(): continue resp = requests.get(url, stream=True, timeout=60) if resp.status_code == 200: local_path.write_bytes(resp.content) downloaded += 1 if downloaded % 100 == 0: print(f"Downloaded {downloaded} articles...") print(f"Total downloaded: {downloaded}")
Convert between different article identifiers:
bash# PMID → PMCID → DOI conversion curl "https://www.ncbi.nlm.nih.gov/pmc/utils/idconv/v1.0/?ids=29346600&format=json" # Batch conversion (up to 200 IDs) curl "https://www.ncbi.nlm.nih.gov/pmc/utils/idconv/v1.0/?ids=29346600,30266829,31048553&format=json"
Each article package (.tar.gz) typically contains:
PMC1234567/
├── PMC1234567.xml # Full text in JATS XML
├── PMC1234567.pdf # PDF (if available)
├── figure1.jpg # Figures
├── figure2.jpg
├── table1.html # Tables (sometimes)
└── supplement1.pdf # Supplementary materialsoa_file_list.csv first and filter locally-N flag to only get new/updated files| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-20 | pass→pass | 20,598 | 16,679 | -19% | 1 | 1 | 0% | 3,185 | 4,912 | +54% | 0 | 0 | — |
case-01 | fail→pass | 40,841 | 42,598 | +4% | 1 | 1 | 0% | 1,869 | 4,042 | +116% | 0 | 0 | — |
case-02 | pass→pass | 13,491 | 35,136 | +160% | 1 | 1 | 0% | 2,140 | 2,736 | +28% | 0 | 0 | — |
case-03 | fail→pass | 42,764 | 34,572 | -19% | 1 | 1 | 0% | 2,401 | 2,418 | +1% | 0 | 0 | — |
case-04 | pass→pass | 12,393 | 32,603 | +163% | 1 | 1 | 0% | 723 | 2,200 | +204% | 0 | 0 | — |
case-05 | fail→pass | 45,054 | 3,482 | -92% | 1 | 1 | 0% | 2,477 | 2,336 | -6% | 0 | 0 | — |
case-06 | pass→pass | 6,040 | 4,832 | -20% | 1 | 1 | 0% | 961 | 2,691 | +180% | 0 | 0 | — |
case-07 | pass→pass | 7,216 | 5,326 | -26% | 1 | 1 | 0% | 1,452 | 2,775 | +91% | 0 | 0 | — |
case-08 | pass→pass | 10,755 | 9,521 | -11% | 1 | 1 | 0% | 1,891 | 3,145 | +66% | 0 | 0 | — |
case-09 | pass→pass | 8,436 | 11,766 | +39% | 1 | 1 | 0% | 1,528 | 3,845 | +152% | 0 | 0 | — |
case-10 | fail→pass | 11,779 | 7,724 | -34% | 1 | 1 | 0% | 1,931 | 2,989 | +55% | 0 | 0 | — |
case-11 | pass→pass | 4,193 | 5,230 | +25% | 1 | 1 | 0% | 911 | 2,574 | +183% | 0 | 0 | — |
case-12 | pass→pass | 22,605 | 7,054 | -69% | 1 | 1 | 0% | 1,869 | 2,852 | +53% | 0 | 0 | — |
case-13 | pass→pass | 14,625 | 7,532 | -48% | 1 | 1 | 0% | 2,332 | 3,096 | +33% | 0 | 0 | — |
case-14 | fail→pass | 12,014 | 7,981 | -34% | 1 | 1 | 0% | 2,093 | 3,033 | +45% | 0 | 0 | — |
case-15 | pass→pass | 8,300 | 5,576 | -33% | 1 | 1 | 0% | 1,460 | 2,685 | +84% | 0 | 0 | — |
case-16 | pass→pass | 4,750 | 2,090 | -56% | 1 | 1 | 0% | 694 | 2,012 | +190% | 0 | 0 | — |
case-17 | pass→pass | 9,623 | 4,276 | -56% | 1 | 1 | 0% | 1,572 | 2,498 | +59% | 0 | 0 | — |
case-18 | pass→pass | 4,606 | 2,709 | -41% | 1 | 1 | 0% | 804 | 2,113 | +163% | 0 | 0 | — |
case-19 | pass→pass | 12,324 | 5,725 | -54% | 1 | 1 | 0% | 2,187 | 2,549 | +17% | 0 | 0 | — |
case-21 | pass→pass | 11,739 | 6,850 | -42% | 1 | 1 | 0% | 1,887 | 3,223 | +71% | 0 | 0 | — |
case-22 | pass→pass | 14,815 | 15,934 | +8% | 1 | 1 | 0% | 2,481 | 4,399 | +77% | 0 | 0 | — |
case-23 | pass→pass | 7,763 | 7,380 | -5% | 1 | 1 | 0% | 1,311 | 2,879 | +120% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +22 percentage points is the difference between those two pass rates over the 23 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.