全库搜索 Markdown 图册
Paper Analysis · 1 分钟阅读

【操作指南】- Zenodo数据快速下载

站内编号 KB-000066
clippingsZenodoaria2数据下载
板块与文章导航

原创 河山 牛瞎哞 | 2026年5月7日 19:05 上海

ZENODO 是一个由 CERN(欧洲核子研究组织)数据中心所支持的科研数据存储库,使研究人员能够保存和分享他们的研究成果。但是网页直接点击下载数据的速度较慢,并且容易中断。Zenodo 官方 REST API 支持 Records 和 Files 访问,所以网页下载慢时,用 API/命令行更稳定。在此分享更加快速稳定的数据下载方法:Python 获取文件列表 + aria2c 断点续传下载 + MD5 校验。

1. 网页下载速度#

直接点击下载大概只有 100-200 kB/s 的下载速度,并且不稳定,经常中断或变为 10-20 kB/s,下载 1GB 的数据需要数小时。

2. API/命令行下载速度#

用学校 wifi,实测的下载速度大约为 1 MB/s。

3. Python 代码#

Python 获取文件列表 + aria2c 断点续传下载 + MD5 校验。

需要的 Python 包和下载工具#

PowerShell:

python -m pip install -U pip
python -m pip install requests tqdm PySocks
# requests:访问 Zenodo API,获取文件列表和 MD5
# tqdm:进度条,可选,建议装
# PySocks:避免报错

# 安装后检查
python -c "import requests, tqdm; print('Python packages OK')"
# aria2c.exe 不是 Python 包,不用装在虚拟环境里。
# 它是一个独立下载工具,安装在 Windows 系统层面。
winget install -e --id aria2.aria2
# 安装完成后,关闭当前 PowerShell,重新打开,再检查
aria2c --version

修改目标下载目录#

# 修改为你的目标下载目录
OUT_DIR = Path(r"D:\Zenodo_Download\CropLayer_2020")

指定想要部分下载的数据#

TARGET_FILES = []  # 是下载所有文件
TARGET_FILES = [
    "cf_county_guangdong.tar.gz",
    "cf_county_hainan.tar.gz",
    "cf_county_yunnan.tar.gz",
]
# 是下载指定文件,例如广东、海南、云南的数据

并发参数不要太大,避免 Zenodo 限流#

# aria2 并发参数:不要开太大,避免触发 Zenodo 限流
CONNECTIONS_PER_FILE = 4   # 单个文件连接数
PARALLEL_FILES = 2         # 同时下载几个文件

完整代码#

下载 https://doi.org/10.5281/zenodo.14726428

from pathlib import Path
from urllib.parse import quote
import hashlib
import subprocess
import requests
import sys
# =========================
# 基本参数
# =========================
RECORD_ID = "14726428"
# 修改为你的目标下载目录
OUT_DIR = Path(r"D:\Zenodo_Download\CropLayer_2020")
# 如果为空列表,则下载全部文件
# 如果只想下载部分省份,就写入文件名,例如:
# TARGET_FILES = ["cf_county_hainan.tar.gz", "cf_county_guangdong.tar.gz"]
TARGET_FILES = [
    "cf_county_guangdong.tar.gz",
    "cf_county_hainan.tar.gz",
    "cf_county_yunnan.tar.gz",
    "cf_county_jilin.tar.gz",
    "cf_county_anhui.tar.gz",
]
# aria2 并发参数:不要开太大,避免触发 Zenodo 限流
CONNECTIONS_PER_FILE = 4   # 单个文件连接数
PARALLEL_FILES = 2         # 同时下载几个文件
# 给请求加 User-Agent,避免默认 Python requests 被部分站点识别为低优先级请求
HEADERS = {
    "User-Agent": "Zenodo-CropLayer-Downloader/1.0"
}
# =========================
# 工具函数
# =========================
def fetch_record_metadata(record_id: str) -> dict:
    """从 Zenodo API 获取 record 元数据。"""
    api_url = f"https://zenodo.org/api/records/{record_id}"
    response = requests.get(api_url, headers=HEADERS, timeout=60)
    response.raise_for_status()
    return response.json()

def normalize_checksum(checksum: str) -> str | None:
    """提取 md5 值。Zenodo 常见形式为 md5:xxxxxxxx。"""
    if not checksum:
        return None
    if checksum.startswith("md5:"):
        return checksum.split("md5:", 1)[1]
    return checksum

def build_download_url(record_id: str, filename: str) -> str:
    """构造 Zenodo 文件下载链接。"""
    encoded_name = quote(filename)
    return f"https://zenodo.org/records/{record_id}/files/{encoded_name}?download=1"

def write_aria2_input(files: list[dict], url_file: Path) -> None:
    """生成 aria2c 输入文件。"""
    with url_file.open("w", encoding="utf-8") as f:
        for item in files:
            name = item["key"]
            url = build_download_url(RECORD_ID, name)
            f.write(url + "\n")
            f.write(f"  out={name}\n")

def run_aria2(url_file: Path, out_dir: Path) -> None:
    """调用 aria2c 进行断点续传下载。"""
    cmd = [
        "aria2c",
        "-i", str(url_file),
        "-d", str(out_dir),
        "-c",                         # 断点续传
        "-x", str(CONNECTIONS_PER_FILE),
        "-s", str(CONNECTIONS_PER_FILE),
        "-j", str(PARALLEL_FILES),
        "--max-tries=10",
        "--retry-wait=20",
        "--timeout=60",
        "--connect-timeout=30",
        "--continue=true",
        "--auto-file-renaming=false",
        "--allow-overwrite=true",
    ]
    print("\n开始调用 aria2c 下载:")
    print(" ".join(cmd))
    subprocess.run(cmd, check=True)

def md5sum(path: Path, block_size: int = 1024 * 1024 * 8) -> str:
    """计算本地文件 md5。"""
    h = hashlib.md5()
    with path.open("rb") as f:
        while True:
            block = f.read(block_size)
            if not block:
                break
            h.update(block)
    return h.hexdigest()

def check_files(files: list[dict], out_dir: Path) -> None:
    """校验下载文件的 MD5。"""
    print("\n开始 MD5 校验:")
    failed = []
    for item in files:
        name = item["key"]
        expected_md5 = normalize_checksum(item.get("checksum", ""))
        local_file = out_dir / name
        if not local_file.exists():
            print(f"[缺失] {name}")
            failed.append(name)
            continue
        if not expected_md5:
            print(f"[跳过] {name}:Zenodo 未提供 checksum")
            continue
        real_md5 = md5sum(local_file)
        if real_md5.lower() == expected_md5.lower():
            print(f"[通过] {name}")
        else:
            print(f"[失败] {name}")
            print(f"       期望: {expected_md5}")
            print(f"       实际: {real_md5}")
            failed.append(name)
    if failed:
        print("\n以下文件校验失败或缺失,建议删除后重新下载:")
        for name in failed:
            print(" -", name)
    else:
        print("\n全部文件 MD5 校验通过。")
# =========================
# 主流程
# =========================
def main():
    OUT_DIR.mkdir(parents=True, exist_ok=True)
    print("正在读取 Zenodo record 元数据...")
    record = fetch_record_metadata(RECORD_ID)
    all_files = record.get("files", [])
    if not all_files:
        print("没有读取到 files 字段,请检查 record ID 或网络连接。")
        sys.exit(1)
    print(f"Zenodo record: {record.get('metadata', {}).get('title', '')}")
    print(f"文件总数: {len(all_files)}")
    if TARGET_FILES:
        target_set = set(TARGET_FILES)
        files = [f for f in all_files if f["key"] in target_set]
        missing = target_set - {f["key"] for f in files}
        if missing:
            print("\n以下 TARGET_FILES 没有在 Zenodo 记录中找到:")
            for name in missing:
                print(" -", name)
            sys.exit(1)
    else:
        files = all_files
    print("\n本次将下载以下文件:")
    for f in files:
        size_gb = f.get("size", 0) / 1024**3
        print(f" - {f['key']}  ({size_gb:.2f} GB)")
    url_file = OUT_DIR / "zenodo_aria2_urls.txt"
    write_aria2_input(files, url_file)
    print(f"\n已生成 aria2 下载清单:{url_file}")
    run_aria2(url_file, OUT_DIR)
    check_files(files, OUT_DIR)
if __name__ == "__main__":
    main()