Open Library Scraper: Books, Authors & Editions
Pricing
from $0.37 / 1,000 book scrapeds
Open Library Scraper: Books, Authors & Editions
Scrape Open Library by search term, ISBN or subject: title, authors, ISBNs, publishers, first publication year, subjects, cover images and edition lists. No key.
Pricing
from $0.37 / 1,000 book scrapeds
Rating
0.0
(0)
Developer
Arman Hossain
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share

Turns a search term, an ISBN or a subject heading into structured book records: title, authors, every ISBN across every printing, publishers, first publication year, subjects, languages, cover image and edition list.
Open Library is the Internet Archive's open bibliographic database. This Actor reads it directly, so there is no browser, no proxy, no credentials and no account.
Agent skill: SKILL.md
https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/openlibrary-books-scraper.md
What you get
| Field | What it holds |
|---|---|
query, queryType | Which input produced this record, and whether it came from a search, an ISBN or a subject |
olKey, url | Open Library work key (/works/OL893516W) and the human-readable page |
title, subtitle | Work title and subtitle |
authors, authorKeys | Author names and their Open Library author keys |
isbn10, isbn13 | Every ISBN Open Library knows for the work, split by length |
publishers | Every publisher across all editions |
firstPublishYear | Year of the earliest known edition |
numberOfPages | Median page count across editions |
subjects | Subject headings, capped at 50 |
languages | 3-letter MARC language codes across editions |
coverUrl | Large cover image URL, or null when no cover is on file |
editionCount | How many printings Open Library has catalogued |
ratingsAverage, ratingsCount | Community rating, null when nobody has rated it |
editions | Per-printing detail when includeEditions is on. null when you did not ask for editions, and also when the edition lookup itself failed. Never a fabricated empty list |
scrapedAt | Run timestamp |
RUN_SUMMARY in the key-value store holds the counts and, more usefully, everything that did not happen: failures (queries that errored), targetsWithoutMatches (queries that ran and matched nothing), targetsSkipped (queries never run because maxResults was already spent), rejectedInputs (entries that could not be turned into a query at all) and editionFailures (books delivered with editions: null because that lookup failed). A missing book is always traceable to one of those lines.
Use cases
- Catalogue enrichment. Turn a spreadsheet of ISBNs into full records with covers and subjects.
- Reading-list and library apps. Search by title or author and store the result as-is.
- Publishing market research. Count editions and publishers per subject or per year.
- AI agents and RAG. A permissively licensed book-metadata layer with no key to rotate.
- Deduplication. Resolve any ISBN to its parent work so different printings collapse to one row.
Quick start
One search, twenty books:
{"searchQueries": ["dune"]}
ISBNs from a catalogue, with every printing attached:
{"isbns": ["9780441013593", "0-441-01359-0"],"includeEditions": true,"maxEditionsPerBook": 25}
Subject sweep for market research:
{"subjects": ["science fiction", "cryptography"],"searchQueries": ["author:le guin"],"maxResults": 200}
Input
| Field | Type | Default | Notes |
|---|---|---|---|
searchQueries | array | [] | Free-text searches. Open Library's field syntax works too: author:le guin, title:dune, publisher:penguin. |
isbns | array | [] | ISBN-10 or ISBN-13. Hyphens and spaces are stripped. Resolves to the parent work, so you get all printings, not just the one you asked for. |
subjects | array | [] | Subject headings. Multi-word values are quoted automatically. |
includeEditions | boolean | false | Attach per-printing detail. Costs one extra request per book. |
maxEditionsPerBook | integer | 10 | Cap on attached editions, 1 to 100. Only read when includeEditions is on. It bounds the edition list inside a row, not the number of rows. |
maxResults | integer | 20 | Hard ceiling on the books saved by the whole run, 1 to 1000, across every query, ISBN and subject together. Three queries at maxResults: 20 save 20 books in total, not 60. |
At least one of searchQueries, isbns or subjects must be non-empty. All three can be used together: results are merged and de-duplicated on olKey, so a book found by two different queries is saved once.
maxResults is shared evenly between your targets and whatever one target leaves unused passes to the ones after it, so three queries at maxResults: 30 take 10 each, and if the first matches only 2 books the remaining 28 are available to the other two. Values outside 1 to 1000 are rejected before anything is fetched, rather than quietly rounded into a different run than the one you asked for.
Output example
{"query": "dune","queryType": "search","olKey": "/works/OL893516W","url": "https://openlibrary.org/works/OL893516W","title": "Children of Dune","subtitle": null,"authors": ["Frank Herbert"],"authorKeys": ["OL79034A"],"isbn10": ["0425071790", "0441015905", "0575074906"],"isbn13": ["9782266027229", "9780441104024", "9780425079027"],"publishers": ["Orion Publishing Group, Limited", "Pocket", "Berkley Publishing"],"firstPublishYear": 1976,"numberOfPages": 504,"subjects": ["Dune (Imaginary place)", "Fiction", "Fiction in English", "Science Fiction"],"languages": ["pol", "chi", "ger", "eng", "spa", "dut", "fre", "por"],"coverUrl": "https://covers.openlibrary.org/b/id/6976407-L.jpg","editionCount": 77,"ratingsAverage": 3.96,"ratingsCount": 47,"editions": [{"editionKey": "/books/OL47362500M","title": "Dzieci Diuny","publishDate": "Apr 14, 2020","publishers": ["Rebis"],"isbn10": ["8381880475"],"isbn13": ["9788381880473"],"numberOfPages": 528,"physicalFormat": "hardcover","languages": ["pol"]}],"scrapedAt": "2026-08-06T12:00:00.000Z"}
Works versus editions
Open Library models a book at two levels, and the difference matters when you join data:
| Level | Key looks like | What it is |
|---|---|---|
| Work | /works/OL893516W | The abstract book. One row per title, regardless of printing. |
| Edition | /books/OL47362500M | One physical or digital printing, with its own ISBN, publisher and page count. |
Every dataset item is a work. isbn10, isbn13, publishers and languages are unions across all its editions, which is why a single record can carry hundreds of ISBNs. Set includeEditions: true when you need each printing separated out.
API example
curl -X POST "https://api.apify.com/v2/acts/arman-bd~openlibrary-books-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{"isbns": ["9780441013593"],"includeEditions": true,"maxEditionsPerBook": 5}'
JavaScript example
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: 'YOUR_TOKEN' });const run = await client.actor('arman-bd/openlibrary-books-scraper').call({searchQueries: ['dune'],subjects: ['science fiction'],maxResults: 50,});const { items } = await client.dataset(run.defaultDatasetId).listItems();for (const book of items) console.log(`${book.title} - ${book.authors.join(', ')} (${book.firstPublishYear})`);
Notes
- One bad query won't kill the run. A failed query is recorded in
RUN_SUMMARY.failuresand the Actor moves on. It only errors out if every query failed and nothing was saved, because a run that saved nothing must never look like a run that found nothing. - A query that matches nothing is not an error, but it is not silence either: you get zero rows and the query is named in
RUN_SUMMARY.targetsWithoutMatches. maxResultsis the most rows a run can save, so it is also the most a run can cost. Rows are only charged when they are saved: duplicates, unusable inputs and failed queries are not.- The fields you get back are the fields the Actor asks for. The default projection is thin, so this Actor requests an explicit 15-column field list.
- Subjects are capped at 50 per book. Popular works carry hundreds of near-duplicate headings, and the first 50 are the useful ones.
- Public data only. Open Library's bibliographic data is published under CC0. Covers have their own terms, so this Actor returns cover URLs rather than image bytes.
FAQ
Do I need an Open Library account or key? No. You supply no credentials.
Why does one record have 248 ISBNs? Because it is a work, not an edition. Those are the ISBNs of all 160 printings. Turn on includeEditions to see which ISBN belongs to which printing.
Can I search by author? Yes, put author:le guin in searchQueries. The full Open Library search syntax is passed through unchanged.
Is maxResults a run total or a per-query cap? A run total. Five queries at maxResults: 100 save 100 books between them, not 500. Raise it when you want more.
What happens if I send maxResults: 0 or -5? The run stops with an error naming the field, before anything is fetched or charged. There is no "unlimited" value: an out-of-range cap used to be quietly rewritten into a different one, which meant a bill nobody agreed to.
Why did a query return nothing when maxResults was still high enough? Check RUN_SUMMARY. targetsWithoutMatches means it ran and matched nothing; targetsSkipped means the run total was already spent before that query was reached; failures means it errored.
Why is ratingsAverage null? Most works have no community ratings. Rather than reporting a fake 0.0, this Actor returns null when ratingsCount is zero.
Can I plug it into something else? Yes. Apify API, the client libraries, webhooks, scheduled runs, dataset exports to JSON, CSV or Excel, or MCP. The output is structured JSON.