Skip to main content

acadfetch

CI PyPI License: MIT

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–3.15. No runtime dependencies.

pipx install acadfetch
# or, from a checkout
pipx install git+https://github.com/young-ski/acadfetch
python -m pip install -e .

Supported platforms

Platform Status
Linux tested — CI, every supported Python
Windows tested — CI, every supported Python
macOS best effort — CI runs it on 3.12 and 3.14, a failure is a bug to fix but does not hold a release
Windows ARM untested
Python 3.15 tested — CI, on Linux and Windows

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.

Names are resolved against the manifest's own directory, and a name that points outside it — ../sibling/file, an absolute path, a symlink leading out — is reported as escaped rather than quietly hashed, because a manifest is a document someone can hand you. --allow-escape follows them; --root changes what "inside" means.

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

  1. Read acadfetch/sources/zenodo.py — an adapter is match(target) -> id | None plus resolve(client, target) -> Record.
  2. Fill File(checksum=...) whenever the API publishes one.
  3. Never hardcode rate limits; take the shared Client.
  4. Add a match test 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.

Getting involved

  • Usage questions → Discussions
  • Bugs → the bug template; say whether verify reports MISMATCH
  • Security (bad bytes, path escapes, token leaks) → SECURITY.md, privately
  • Adapters, tests, docs → PRs welcome, read CONTRIBUTING.md first

Known limits

  • Tokens come from the environment only; no interactive login flow 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.
  • macOS is best effort: it is in CI but not release-blocking, and nobody has run it by hand yet.
  • A source that publishes neither a size nor a checksum (bare direct URLs without a Content-Length on HEAD) can still be truncated silently by a server that lies about Content-Length, because HTTP gives no in-band way to notice the extra bytes.

中文

学术数据仓库的批量下载器:断点续传、全局限速、按仓库公布的校验和验证,并落 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.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for acadfetch 0.1.1
File Size Uploaded Provenance Actions
acadfetch-0.1.1.tar.gz 223.0 kB With provenance Details for acadfetch-0.1.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for acadfetch 0.1.1
File Interpreter ABI Platform Actions
acadfetch-0.1.1-py3-none-any.whl Python 3 none any Details for acadfetch-0.1.1-py3-none-any.whl

Total release size: 261.9 kB

Release files / acadfetch-0.1.1.tar.gz

Download URL acadfetch-0.1.1.tar.gz
Size 223.0 kB
Tags Source
SHA-256 checksum
How to use checksums
7efefc64769fa401bca103d1ac301d1d0d90bc537437c33ff6f6a3348fcbb820
BLAKE2b-256 checksum
How to use checksums
4bc2180871bc7392b2b12bc4ed49f5dc0c00079988d3ca0f58cec89884ef5aba
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 26, 2026.

Transparency log

Release files / acadfetch-0.1.1-py3-none-any.whl

Download URL acadfetch-0.1.1-py3-none-any.whl
Size 38.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
043a145c6de24ad2c009d1066a3fbff1650a0f5e7b4b952a1afcf22aeb0a4850
BLAKE2b-256 checksum
How to use checksums
0fed8761846c53510fbb175ceae42c85b71491bc8359f9f6c9f0196966ead845
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 26, 2026.

Transparency log

Security for acadfetch 0.1.1

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.

Select from 2 files

Attestation for acadfetch-0.1.1.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 26, 2026.

Transparency log

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page