Zenodo Scraper · Research Records, DOIs, Authors & Files
Pricing
from $1.30 / 1,000 record returneds
Zenodo Scraper · Research Records, DOIs, Authors & Files
Scrape open research records, DOIs, publications, datasets, software, authors, and file metadata from Zenodo. Fast HTTP scraper charging per returned record with tiered pricing.
Pricing
from $1.30 / 1,000 record returneds
Rating
0.0
(0)
Developer
Tarek Etman
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share

Zenodo Scraper · Research Records, DOIs, Authors & Files
The Zenodo Scraper extracts open research records, DOIs, publications, scientific datasets, software packages, author lists, licensing terms, and direct file download URLs from Zenodo, the open-access research repository operated by CERN. It runs over fast HTTP API endpoints with no headless browser overhead, returning structured data ready for bibliometrics, literature reviews, open science tracking, and AI dataset curation.
Maintained by reapX. Every row cites the Apify run that produced it — nothing is inferred, modelled or filled in, and a field absent from the source is absent from the row. The extracted archive for this source is browsable at reapx.dev/data/zenodo-scraper/ and mirrored as an open dataset on Hugging Face and Kaggle. Questions: reapxdev@proton.me
Maintained by reapx (reapxdev@proton.me). Public sources in, addressable records out.
Features
- 🔬 Complete Record Metadata: Extract Digital Object Identifiers (DOIs), concept DOIs, titles, abstracts, publication dates, and resource classifications.
- 👥 Author & Creator Disambiguation: Collect full list of authors, creators, affiliations, and subject taxonomy keywords.
- 📁 File & Dataset Discovery: Access direct download links, file sizes, MD5 checksums, and total view/download statistics.
- 🏷️ Community & License Filters: Filter by Zenodo communities (
biosyslit,eu,cern, etc.), licensing (cc-by-4.0,mit,cc0-1.0), and publication year ranges. - ⚡ Fast & Cost Efficient: Uses direct HTTP API queries with exponential backoff on rate limits (HTTP 429), charging strictly per returned record on a Pay-Per-Event model.
⬇️ Input
Every field is described here exactly as the Actor reads it. Run it with the prefilled input and it returns real rows without any further configuration.
| Field | Type | Required | Prefilled | What it does |
|---|---|---|---|---|
query | string | no | "climate change" | Full-text search query across titles, abstracts, subjects, and authors (e.g. climate change, machine learning). Leave empty to query all Zenodo records. The broader the query, the more... |
community | string | no | - | Filter records belonging to a specific Zenodo community slug (e.g. biosyslit, eu, ecfunded). Leave this field empty to search across all communities nationwide and worldwide. |
resource_type | string | no | - | Filter by resource type identifier. Select a specific publication type, dataset, software, or image category. Leave empty to retrieve records of all resource types without restrictions. |
access_right | string | no | - | Filter by record access status. Choose open for open-access publications, or filter restricted/embargoed records. Leave empty to return records of any access level. |
license | string | no | - | Filter records by license identifier (e.g. cc-by-4.0, mit, cc0-1.0, apache-2.0). Leave empty to return research records regardless of license. |
creator | string | no | - | Return only records with this person or organisation among the creators (e.g. Hansen, European Commission). Matched against Zenodo's own metadata.creators.person_or_org.name field. |
file_type | string | no | - | Return only records that carry at least one file of this type (e.g. pdf, csv, zip, xlsx). Sent as Zenodo's own file_type facet, not as free text. |
year | string | no | - | Filter by publication year (e.g. 2024) or Lucene year range (e.g. [2020 TO 2024]). Leave empty to include records published in any year. |
date_from | string | no | - | Only records published on or after this date (e.g. 2024-01-01). Compiled into a Zenodo publication_date range together with Published To. |
date_to | string | no | - | Only records published on or before this date (e.g. 2024-12-31). |
sort | string | no | "newest" | Order Zenodo returns matches in: newest for the latest uploads, bestmatch for relevance to your query, mostrecent for the most recently updated, mostviewed for the most read. |
all_versions | boolean | no | false | Return every version of a record, not just the latest one. Zenodo mints a DOI per version and a concept DOI for the record as a whole, and both are on every row. |
max_records | integer | yes | 50 | Maximum number of Zenodo research records to return and write to the dataset (1 to 10,000). The higher the number, the longer the run takes and the more it costs. |
What empty means, and what a larger value costs
The two questions an input table normally does not answer. Both columns are read off this Actor's own code and its own price list.
| Field | Left empty | Turned up |
|---|---|---|
query | Leave empty to query all Zenodo records. | Narrowing this reduces rows returned, so it lowers the bill. |
community | Leave this field empty to search across all communities nationwide and worldwide. | Narrowing this reduces rows returned, so it lowers the bill. |
resource_type | Leave empty to retrieve records of all resource types without restrictions. | Narrowing this reduces rows returned, so it lowers the bill. |
access_right | Leave empty to return records of any access level. | Narrowing this reduces rows returned, so it lowers the bill. |
license | Leave empty to return research records regardless of license. | Narrowing this reduces rows returned, so it lowers the bill. |
creator | Leave empty to include records by any author. | The match is on the name string Zenodo holds, so a common surname still returns many people; combine it with a community or a year to narrow it. |
file_type | Leave empty to include records whatever they carry, including metadata-only records with no files at all. | Filtering to pdf is the quickest way to drop metadata-only and embargoed records from a corpus you intend to read. |
year | Leave empty to include records published in any year. | Narrowing this reduces rows returned, so it lowers the bill. |
date_from | Leave empty for no lower bound. | A tight window is the cheapest way to make a repeat run return only what is new since the last one. |
date_to | Leave empty for no upper bound, which is what you want for a monitoring run. | Pairs with Published From; either bound works on its own, and both are applied by Zenodo rather than after the fact. |
sort | Falls back to newest. | Ordering decides which records you get when Max Records is smaller than the match count, which it usually is; it does not change what a row costs. |
all_versions | One row per record, the current version. | This genuinely multiplies rows and therefore cost - the same query measured 218,626 matches with it off and 260,002 with it on - so raise Max Records deliberately, not by accident. |
max_records | The prefill 50 is used | Each row returned is one $0.002 charge, so this field is your cost cap as well as your row cap. |
⬆️ Output
One row per record, as JSON, CSV, Excel or XML, and through the API. Every field below is on every row this Actor writes; a field the source did not return is empty rather than guessed.
| Field | Type | What it gives you | Example |
|---|---|---|---|
doi | string | Digital Object Identifier (DOI) of the Zenodo record (e.g. 10.5281/zenodo.21778617). | "10.5281/zenodo.21778617" |
paperId | string | Standardized paper ID derived from DOI with colon replaced by hyphen (e.g. 10.5281-zenodo.21778617). | "10.5281-zenodo.21778617" |
recordId | string | Unique numerical ID assigned by Zenodo to the record. | "21778617" |
conceptDoi | string | Concept DOI representing all versions of this research record. | "10.5281/zenodo.21778616" |
conceptRecId | string | Concept record ID representing all versions of this item. | "21778616" |
title | string | Title of the research publication, dataset, or software package. | "Comparing and Conceptualizing Data Protection Requiremen... |
description | string | Abstract, summary text, or detailed description of the record. | "This dataset comprises different materials of the projec... |
publicationDate | string | Date when the record was published (YYYY-MM-DD format). | "2026-08-03" |
resourceType | string | Human-readable label of the resource type (e.g. Journal article, Dataset, Software). | "Journal article" |
resourceTypeId | string | Canonical identifier for the resource type. | "publication-article" |
creators | array | List of creators/authors of the research record. | ["Hassan Saleh", "Jane Doe"] |
authors | array | List of author names (alias for creators). | ["Hassan Saleh", "Jane Doe"] |
license | string | License under which the record or data is shared. | "cc-by-4.0" |
accessRight | string | Access status of the record (e.g. open, restricted, embargoed). | "open" |
communities | array | List of Zenodo community identifiers associated with this record. | ["biosyslit", "eu"] |
keywords | array | List of subject keywords attached to the record. | ["privacy", "data protection", "gdpr"] |
files | array | List of attached file objects including filename, size, checksum, and download URL. | [{"filename": "data.zip", "size": 22301469, "checksum": "... |
fileCount | integer | Total number of files attached to this record. | 1 |
downloads | integer | Number of times files in this record have been downloaded. | 15 |
views | integer | Number of times this record page has been viewed. | 120 |
doiUrl | string | Permanent DOI URL pointing to the record. | "https://doi.org/10.5281/zenodo.21778617" |
url | string | Direct web URL of the record on Zenodo. | "https://zenodo.org/records/21778617" |
created | string | ISO 8601 timestamp when the record was created in Zenodo. | "2026-08-03T18:14:32.401201+00:00" |
updated | string | ISO 8601 timestamp when the record was last updated. | "2026-08-03T18:14:32.656983+00:00" |
grantIds | array | Funder grant numbers attached to the record, such as a Horizon Europe project number. Empty on records with no funding... | ["101071008"] |
grantTitles | array | Title of each funded project the record is attached to. | ["Closed-loop Individualized image-guided Transcranial Ul... |
funders | array | Funding bodies named on the record, e.g. European Commission. | ["European Commission"] |
revision | integer | Zenodo's own revision counter for the record, so an edited record is detectable between runs. | 4 |
Dataset views
The dataset ships with named views, so the Output tab, the API and any agent reading this Actor get the columns that matter for a job without asking for all of them.
Overview (?view=overview) - 7 columns: doi, title, publicationDate, resourceType, license, accessRight, url
| Column | Header |
|---|---|
doi | DOI |
title | Title |
publicationDate | Published |
resourceType | Type |
license | License |
accessRight | Access |
url | Zenodo Page |
Authors, Communities & Funding (?view=authors) - 7 columns: doi, title, creators, communities, funders, grantIds, keywords
| Column | Header |
|---|---|
doi | DOI |
title | Title |
creators | Creators |
communities | Communities |
funders | Funders |
grantIds | Grant Numbers |
keywords | Keywords |
Files & Usage (?view=files) - 7 columns: doi, title, fileCount, files, downloads, views, updated
| Column | Header |
|---|---|
doi | DOI |
title | Title |
fileCount | Files |
files | File List |
downloads | Downloads |
views | Views |
updated | Last Updated |
Fetch one directly:
GET https://api.apify.com/v2/datasets/{datasetId}/items?view=overview&format=csv
One real row
Unedited, from this Actor's own most recent successful run.
{"doi": "10.5281/zenodo.19983311","paperId": "10.5281/zenodo.19983311","recordId": "19983311","conceptDoi": "10.5281/zenodo.19983308","conceptRecId": "19983308","title": "A Recurrent Latent Variable Model for Sequential Data","description": "<p>In this paper, we explore the inclusion of latent random variables into the hidden state of a recurrent neural network (RNN) by combining the elements of the variational autoencoder. We argue that through the use of high-level latent random variables, the variational RNN (VRNN) can model the kind of variability observed in highly structured sequential data such as natural speech. We empirically evaluate the proposed model against other related sequential models on four speech datasets and one handwriting dataset. Our results show the important roles that latent random variables can play in the RNN dynamics.</p>","publicationDate": "2026-05-02","resourceType": "Conference proceeding","resourceTypeId": "publication","creators": ["Chung, Junyoung","Kastner, Kyle","Dinh, Laurent","Goel, Kratarth","Courville, Aaron","Bengio, Yoshua"],"authors": ["Chung, Junyoung","Kastner, Kyle","Dinh, Laurent","Goel, Kratarth","Courville, Aaron","Bengio, Yoshua"],"license": "cc-by-4.0","accessRight": "open","communities": [],"keywords": [],"grantIds": [],"grantTitles": [],"funders": [],"revision": 4,"files": [{"filename": "A Recurrent Latent Variable Model.pdf","size": 920496,"checksum": "md5:34de781099f964c019089171d73034ce","url": "https://zenodo.org/api/records/19983311/files/A%20Recurrent%20Latent%20Variable%20Model.pdf/content"}],"fileCount": 1,"downloads": 10,"views": 28,"doiUrl": "https://doi.org/10.5281/zenodo.19983311","url": "https://zenodo.org/records/19983311","created": "2026-05-02T16:50:37.085659+00:00","updated": "2026-05-02T16:50:37.635936+00:00"}
🧪 Worked examples
Every block below is an input this Actor has actually run, with the row count of the run that produced it. Copy one straight into the Console.
PDFs only, a publication-date window, most read first
{"query": "deep learning","file_type": "pdf","date_from": "2025-01-01","date_to": "2026-08-01","sort": "mostviewed","max_records": 25}
That run returned 25 rows in 8s.
Everything one author has deposited
{"creator": "Bengio","max_records": 25}
That run returned 25 rows in 10s. The status message reports the size of the whole match set as well, so you always know what you did not collect: 25 Zenodo records of 39 matching.
Peer-Reviewed Journal Articles
Filter and extract peer-reviewed journal articles and scholarly publications on Zenodo.
{"resource_type": "publication-article","max_records": 50}
That run returned 50 rows.
Zenodo General Community Records
Harvest general research publications and open datasets deposited directly into Zenodo.
{"query": "zenodo","max_records": 50}
That run returned 50 rows.
2026 Newly Published Records
Scrape brand new research records, publications, and datasets uploaded in 2026.
{"year": "2026","max_records": 50}
That run returned 50 rows.
Creative Commons Attribution 4.0 (CC-BY 4.0)
Harvest research records shared under the liberal CC-BY 4.0 open access license.
{"license": "cc-by-4.0","max_records": 50}
That run returned 50 rows.
How it works
The endpoint it calls
Plain HTTP against the source's own public API. No headless browser, no login, no key of yours:
https://zenodo.org/api/recordshttps://zenodo.org/records/<record_id>
One endpoint, the same public search API that serves zenodo.org itself, so every record you get back has a resolvable DOI on the day it was read.
The paging it does
The run starts by asking Zenodo for the size of the match set with a single size=1 request, which is why the status message can tell you 287 records of 41,930 matching rather than just how many it wrote. It then walks pages of at most 25 - Zenodo's own page ceiling - until your Max Records is reached.
Filters are compiled into one Lucene query against the same q parameter: resource type becomes metadata.resource_type.id:, access status becomes access_right:, licence becomes metadata.rights.id:, year becomes publication_date: and accepts a range such as [2020 TO 2024]. Community is a separate communities parameter, not part of q, so a community filter composes with any of the others.
The rate it holds
Every request is retried up to 6 times, with the wait doubling between attempts, and HTTP 429, 500, 502, 503, 504 are treated as retryable rather than fatal. Each request times out at 30s.
On top of that there is a deliberate half-second pause between pages. Zenodo publishes a 60 requests/minute limit for unauthenticated callers and this sits well inside it, which is why long runs finish instead of being cut off part-way.
What you are charged
One record-returned event per row written to the dataset, $0.002 at list price. Charged once per complete Zenodo record written to the default dataset. A rate-limited or unreachable API, a rejected query and a search that matches nothing are never charged.
| Apify plan tier | Price per row |
|---|---|
| Free | $0.002 |
| Bronze | $0.0018 |
| Silver | $0.0015 |
| Gold | $0.0013 |
| Platinum | $0.001 |
| Diamond | $0.0008 |
Apify platform usage is absorbed, not passed on, so the row price is the whole bill.
Why the dataset is correct at every moment
Rows are flushed to the dataset in batches of 50 as they are built and charged one at a time before the batch is written, so a run that hits its budget cap, a Zenodo error or the run's time limit still leaves you every complete row it had. The status message always names how many of the matching records you actually got.
⚠️ Error reference
Derived from this Actor's own exception classes and the HTTP branches in its client. The run always reports what happened in its status message, so you never need the log to know what you got.
| Raised | When | What the run does |
|---|---|---|
ZenodoError | Base exception for Zenodo API errors. | Run fails with a status message naming the source and the cause. Nothing further is charged. |
SourceBlocked | Raised when Zenodo API returns persistent 429/5xx or block. | Run fails with: Zenodo API was unreachable: |
BadQuery | Raised when Zenodo API rejects a query parameter. | Run fails with: Zenodo rejected this query: |
HTTP statuses the client actually handles
| Status | Handling |
|---|---|
429 | Retried with backoff. The run continues if a later attempt succeeds. |
500 | Retried with backoff. The run continues if a later attempt succeeds. |
502 | Retried with backoff. The run continues if a later attempt succeeds. |
503 | Retried with backoff. The run continues if a later attempt succeeds. |
504 | Retried with backoff. The run continues if a later attempt succeeds. |
400 | The source rejected the query itself, so retrying it cannot help; the input is at fault. |
What each outcome costs
| Outcome | Rows | Charged |
|---|---|---|
| Run finished | Everything the source returned for your filters | One record-returned per row |
| Nothing matched your filters | 0 | Nothing |
| Source refused or was unreachable | Whatever was already collected | Only the rows you got |
| Budget cap reached mid-run | The rows collected before the cap | Only those rows |
Status messages this Actor can end on, verbatim from its own source:
- Run budget of $ cannot cover a single record at $
- Zenodo rejected this query:
- Zenodo API was unreachable:
- No Zenodo records match this query, so nothing was charged. Widen search terms or loosen filters.
- Harvested
❓ FAQ
How do I search for open access datasets only?
Set resource_type to dataset and access_right to open.
How are DOIs formatted?
Every record includes a standard doi (e.g. 10.5281/zenodo.21778617) and a canonical paperId where colons are replaced by hyphens (10.5281-zenodo.21778617).
What happens if Zenodo rate limits the request?
The scraper automatically detects HTTP 429 status codes and pauses execution using exponential backoff before retrying, ensuring no data loss.
Do I need an API key, an account or a proxy?
No. The Actor reads the source's public interface over plain HTTP, with no credential of yours anywhere in the run and no headless browser. You need an Apify account and nothing else.
What does a run cost?
One record-returned event per row written, $0.002 at list price, falling to $0.0008 on the highest Apify plan tier. Apify platform usage is absorbed rather than passed on, so the row price is the whole bill. A run that matches nothing is charged nothing.
Can I get just some of the columns?
Yes. The dataset ships named views (overview, authors, files); add ?view=<name> to the dataset items URL, or pick one in the Output tab, and you get that view's columns in JSON, CSV or Excel.
Can an agent call this?
Yes, through Apify's MCP server at https://mcp.apify.com with nothing to integrate on our side: search-actors finds it, fetch-actor-details returns the input schema above, and call-actor runs it.
Will the dataset still be there next month?
Yes. The run names its own default dataset at startup, and Apify keeps named storages past the retention window that deletes unnamed ones, so the run id in a row you cited a year ago still resolves.
Is the data complete?
It is exactly what the source returned for your filters. Nothing is inferred, modelled or filled in, and a field the source did not return is empty rather than guessed. The run's status message tells you what you got, so you never have to read the log to find out.
💬 Your feedback
Have suggestions, feature requests, or custom extraction needs? Contact us at reapxdev@proton.me.
Disclaimer: Unofficial - not affiliated with Zenodo or CERN. Collects public data only. reapx. Contact reapxdev@proton.me.