acadfetch
Batch-download files from academic data repositories — resumably, rate-limited, and checked against the checksums the repository published.
$ acadfetch fetch 10.5281/zenodo.23612969 -o ./data
title : Dinler Tarihi Alanında Güncel Konular ve Akademik Değerlendirmeler
doi : 10.5281/zenodo.23612969
license : cc-by-4.0
files : 1, 1.6 MiB
landing : https://zenodo.org/records/23612969
Dinler Tarihi ... .pdf 1.2 MiB/1.6 MiB 312.4 KiB/s ETA 0:02
ok Dinler Tarihi ... .pdf 1.6 MiB (resumed at 512.0 KiB)
1/1 file(s) in ./data
What it is not: a search engine, a scraper for paywalled publishers, or a citation manager. It is the boring part in the middle — the part that gets killed by a flaky VPN at 80%, and the part that nobody writes down.
Why another downloader
Because curl -O in a loop loses three things that matter for research data:
| Problem | What happens with a plain loop | What acadfetch does |
|---|---|---|
| Connection drops at 80% | restart from zero | resumes with Range + If-Range, keeps the partial |
| Silent corruption | nobody notices until analysis breaks | verifies the repository's md5/sha256, re-downloads on mismatch |
| "Which version did I actually use?" | untraceable | writes manifest.json with DOI, license, per-file SHA-256 |
| Getting rate-limited into a 403 | hammering, then blocked | one global token bucket, honours Retry-After |
The manifest is the point. A dataset you cannot re-verify is a dataset you cannot cite.
Install
Python 3.10+. No runtime dependencies.
pipx install acadfetch
# or, from a checkout
pipx install git+https://github.com/young-ski/acadfetch
python -m pip install -e .
Usage
acadfetch fetch 10.5281/zenodo.123456 # a DOI
acadfetch fetch zenodo:123456 # a record id
acadfetch fetch figshare:987654
acadfetch fetch arxiv:2401.12345
acadfetch fetch https://example.org/dataset.tar.gz
acadfetch fetch zenodo:123456 -n # dry run: what would be fetched
acadfetch fetch zenodo:123456 --only raw.tar.gz
acadfetch fetch zenodo:123456 --max-size 2GB # refuse anything bigger
acadfetch fetch zenodo:123456 -j 1 --rate 0.5 # be a good citizen
acadfetch manifest zenodo:123456 -o m.json # metadata only, no bytes
acadfetch fetch zenodo:123456 --header "Authorization: Bearer $TOKEN" # restricted record
acadfetch verify ./data/manifest.json # re-hash a directory you downloaded
acadfetch sources # list adapters
Progress is one aggregate line for the whole run (3 file(s) 12.4/44.1 MiB 1.8 MiB/s ETA 0:17),
redrawn in place on a TTY and written as occasional whole lines when stdout is a pipe.
-q turns it off.
Output defaults to acadfetch-out/<source>-<doi-or-id>. Files land in
<name>.part and are renamed only after the checksum matches, so an interrupted
run never leaves something that looks finished.
Exit codes: 0 success, 1 failure, 2 partial, 130 interrupted (rerun to resume).
Tokens
ZENODO_TOKEN and FIGSHARE_TOKEN are read from the environment and sent only
to that repository, so a token never travels to a host that has no business
seeing it. --header still overrides, --no-token ignores the environment, and
the manifest records the variable name plus the last four characters only:
export ZENODO_TOKEN=... # restricted records, higher rate limits
acadfetch fetch zenodo:123456 # prints: auth : token from $ZENODO_TOKEN (…9f2c)
Verification and the manifest
Every completed run writes manifest.json:
{
"schema": 1,
"doi": "10.5281/zenodo.123456",
"license": "cc-by-4.0",
"files": [
{ "name": "raw.tar.gz", "size": 8123, "checksum": "md5:9f2c…",
"sha256": "b41d…", "state": "downloaded", "verified": true }
]
}
acadfetch verify recomputes SHA-256 for each entry and exits non-zero on any
mismatch or missing file. Commit the manifest next to your analysis; it is what
makes the download reproducible a year later.
When a source publishes no checksum (arXiv), verification degrades to a byte
count and verified is null rather than a false sense of safety.
Supported sources
| Source | Target forms | Checksums |
|---|---|---|
| Zenodo | DOI 10.5281/zenodo.N, zenodo:N, record URL |
md5 |
| Figshare | DOI 10.6084/m9.figshare.N, figshare:N, article URL |
md5 |
| arXiv | arxiv:ID, bare ID, abs/pdf URL |
none (size only) |
| doi.org | any DOI, resolved then dispatched | depends |
| direct | any http(s) URL |
headers, if present |
Figshare's API sits behind a WAF that intermittently answers 403 to non-browser
clients (an AWS ELB block page, some networks always, some never). Those 403s
are retried twice with backoff, then the legacy per-file endpoint is tried;
a 403 that is the API's own permission error is still terminal. Downloads
themselves go to ndownloader, which the WAF leaves alone. The payload shapes
are pinned in tests/fixtures/.
If you know your network gets through, --retry-403 0 skips the retries
entirely and makes every 403 final on the first answer.
Politeness
Defaults: 3 parallel transfers, 2 requests/second, 4 retries with jittered
exponential backoff, Retry-After honoured up to 120 s. A WAF block page is
retried twice (--retry-403 N); Zenodo and arXiv are donation-funded —
--rate 1 -j 1 costs you minutes and costs them less.
Respect the license in the manifest. --dry-run prints it before you spend
bandwidth; unknown means check before redistributing.
Adding a source
- Read
acadfetch/sources/zenodo.py— an adapter ismatch(target) -> id | Noneplusresolve(client, target) -> Record. - Fill
File(checksum=...)whenever the API publishes one. - Never hardcode rate limits; take the shared
Client. - Add a
matchtest and, if possible, a fixture of the real API payload.
scripts/probe_resume.py interrupts a live transfer and resumes it — the only
honest way to test this code path.
Known limits
- Tokens come from the environment only; no interactive
loginflow and no per-file token refresh. - No search/pagination; you bring the target.
- arXiv PDFs are fetched by constructed URL, so a withdrawn ID gives a 404 after resolution.
- Windows ARM is untested.
中文
学术数据仓库的批量下载器:断点续传、全局限速、按仓库公布的校验和验证,并落
manifest.json(DOI、许可协议、每文件 SHA-256)供日后复现。
acadfetch fetch 10.5281/zenodo.123456 -o ./data
acadfetch verify ./data/manifest.json
MIT。
Metadata
Release files for acadfetch 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| acadfetch-0.1.0.tar.gz | 211.3 kB | With provenance | Details for acadfetch-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| acadfetch-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for acadfetch-0.1.0-py3-none-any.whl |
Total release size: 244.9 kB
Release files / acadfetch-0.1.0.tar.gz
| Download URL | acadfetch-0.1.0.tar.gz |
|---|---|
| Size | 211.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1f1e7563db7e222f09050ce1972876e6aa9d481df074b74025af5d21d69e6605
|
|
BLAKE2b-256 checksum How to use checksums |
4508f0f1aedda1246ede8245492a280efcd0a00a9e0e11f5850bffcc7aa61261
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.
Transparency logRelease files / acadfetch-0.1.0-py3-none-any.whl
| Download URL | acadfetch-0.1.0-py3-none-any.whl |
|---|---|
| Size | 33.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
eba3614155cec7b7b9bceb15e998345346bccd6e56c1a8d47f0968f5b564be05
|
|
BLAKE2b-256 checksum How to use checksums |
29cdb22f30edc51234b570d2fee7063db3fc4b7a7fa2467d1077884a1095d59d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.
Transparency logSecurity for acadfetch 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for acadfetch-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.