This repository calls the Open Library Books Scraper from Node.js using one ES module and the official apify-client package. Send a search string, optionally tied to the title, author, subject or ISBN field, and get back plain objects describing works from Open Library: names, years, edition counts, ISBN arrays, publishers, a large cover URL and the e-book status. The work happens on Apify, so a route handler, a queue worker or a scheduled function only has to start a run and collect rows. Reading apps, bookshop backends and catalogue tools all get the same shape every time, which is the whole bargain; no deals with the devil required.
Actor: Open Library Scraper β Book Metadata in Bulk Β· Publisher: Devil Scrapes Β· Also in Python: openlibrary-scraper-python
- Works, not editions: each row is one Open Library work, with
edition_counttelling you how many printings and translations sit behind it. - Point the query at one field:
searchFieldsends your text to title, author, subject or ISBN matching, or leaves it free-form withall. - A language filter: a three-letter code such as
engorspainlanguageis passed to Open Library's search as a language filter. - Identifiers you can join on: the work key, a canonical
work_url, every ISBN-10 and ISBN-13 Open Library lists, and the numericcover_id. - A ready cover link:
cover_url_lis built from the cover ID and points at the large JPEG on Open Library's cover service. - Reader signals where they exist: average rating, rating count and an
ebook_accessvalue that says whether a scan is public, borrowable or absent. - Bounded subject lists: subjects are cut at 30 per work, so one heavily tagged classic cannot bloat a CSV column.
Expose GET /books/:isbn in Express or Fastify, start a run with searchField: 'isbn' and maxResults: 1, cache the row by ISBN and answer from the cache next time. Your users get title, author and cover from one scan of a barcode.
When a user adds a book by title, look it up and store cover_url_l next to it. The link points straight at Open Library's cover service, so your front end can render it in an <img> tag without hosting images yourself.
Filter rows on ebook_access and show a 'read now' badge for public or borrowable works, linking to work_url. One Set lookup on a single field is all the logic it takes.
Pull works for a few subjects on a schedule and store subjects, authors and ratings_average in your database. A recommender can then suggest books that share tags with the ones a reader already finished.
You need an Apify account and an API token from Console β Settings β Integrations. Apify's free plan includes monthly usage credit; this Actor's event prices are listed under Pricing below.
git clone https://github.com/devilscrapes/openlibrary-scraper-javascript.git
cd openlibrary-scraper-javascript
npm install
export APIFY_TOKEN=your_token_here
node index.js # uses input.json
node index.js my-input.json --out data --max-charge 2The script starts the Open Library Scraper β Book Metadata in Bulk, waits for it to finish, prints a link to the run in Apify Console and saves every result to results.json and results.csv. It exits with code 1 and the run's status message if the run fails.
The integration your service needs is just the following. call() resolves after the run has finished, and listItems() hands back that run's dataset as one array of objects.
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('DevilScrapes/openlibrary-books-scraper').call({"searchQuery": "isaac asimov foundation", "searchField": "all", "maxResults": 10, "language": "", "proxyConfiguration": {"useApifyProxy": false}});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);index.js reads input.json and sends it unchanged. This sample matches the Actor's shipped defaults: Asimov's Foundation as free text, at most 10 works, every language, direct connection, for about 21.5 cents. Only searchQuery is mandatory. From a request handler, set searchField to the kind of value the user typed and keep maxResults small for interactive lookups; bulk jobs can go up to 1,000.
{
"searchQuery": "isaac asimov foundation",
"searchField": "all",
"maxResults": 10,
"language": "",
"proxyConfiguration": {
"useApifyProxy": false
}
}| Field | Type | Required | Default | Allowed values |
|---|---|---|---|---|
searchQuery |
string | yes | "isaac asimov foundation" |
β |
searchField |
string | no | "all" |
all, title, author, subject, isbn |
maxResults |
integer | no | 30 |
β |
language |
string | no | "" |
β |
proxyConfiguration |
object | no | {"useApifyProxy": false} |
β |
You get an array of works. Treat openlibrary_key, title and work_url as guaranteed and type the rest as optional. Array fields come back as empty arrays rather than null when Open Library has nothing, so .join() and .length are safe on them.
| Field | Type | Always present |
|---|---|---|
openlibrary_key |
string | yes |
title |
string | yes |
subtitle |
string or null | no |
authors |
array | no |
first_publish_year |
integer or null | no |
edition_count |
integer | no |
languages |
array | no |
subjects |
array | no |
isbns |
array | no |
publishers |
array | no |
cover_id |
integer or null | no |
cover_url_l |
string or null | no |
ratings_average |
number or null | no |
ratings_count |
integer or null | no |
ebook_access |
string or null | no |
work_url |
string | yes |
scraped_at |
string | no |
| Event | Price (USD) |
|---|---|
| Actor start | $0.2 |
| Result emitted | $0.0015 |
Prices as configured on 2026-08-20. The Actor page always shows the current price. Runs are billed to the Apify account that owns the token. Use --max-charge to cap what a single run can cost.
This module turns the saved rows into a small 'shelf' for a front end: only works with a public or borrowable e-book, with the first ISBN-13 picked out, sorted oldest first and written to shelf.json. Node.js built-ins only.
import { readFile, writeFile } from 'node:fs/promises';
const books = JSON.parse(await readFile('results.json', 'utf8'));
const readable = new Set(['public', 'borrowable']);
const shelf = books
.filter((book) => readable.has(book.ebook_access))
.map((book) => ({
id: book.openlibrary_key.replace('/works/', ''),
title: book.subtitle ? `${book.title}: ${book.subtitle}` : book.title,
by: (book.authors ?? []).join(', '),
year: book.first_publish_year,
cover: book.cover_url_l,
isbn13: (book.isbns ?? []).find((code) => /^97[89]\d{10}$/.test(code)) ?? null,
link: book.work_url,
}))
.sort((a, b) => (a.year ?? 9999) - (b.year ?? 9999));
await writeFile('shelf.json', JSON.stringify(shelf, null, 2));
console.table(shelf.map(({ title, by, year, isbn13 }) => ({ title, by, year, isbn13 })));For a handful of lookups you can. The Actor earns its keep when you want paging, a fixed row shape, scheduling and run history handled for you, and when Apify's dataset is where the results should live anyway.
Small lookups finish quickly, so awaiting call() usually fits in a function's time limit. For bulk pulls, start the run with .start() and pick up the dataset from a webhook when Apify reports it finished.
apify-client brings its own declarations for runs and datasets. Add a Work interface from the output table above, with openlibrary_key, title and work_url as required strings, and cast items to Work[].
Either the query genuinely matched fewer works, or Open Library answered one page with an error, which ends paging without a retry. A language filter also narrows results more than people expect.
Set APIFY_TOKEN first: export the token in the same shell, or put it in a local.envfile your shell loads. Never commit it.- Run finished with
FAILEDorABORTED: open the printed Console link; the run log says why. Input validation errors name the field. - Fewer rows than expected: a
--max-chargecap also limits how many results can be charged. Raise the cap or lower the limits in your input. - Something looks wrong in the data: open an issue on the Actor page so the maintainers see it.
- Open Library Scraper β Book Metadata in Bulk on Apify Store
- Python version of this example
- All Devil Scrapes Actors
- Apify web scraping cookbook
Related Actors:
- Goodreads Book & Reviews Scraper β reader reviews to set next to Open Library's catalogue data.
- Crossref Works Scraper β DOIs, Authors & Citations β DOI metadata for journal articles and scholarly books.
- arXiv Scraper β Search & Export Paper Metadata β preprint records when the reading list turns academic.
MIT licensed example code. The Actor itself runs on Apify and is maintained by Devil Scrapes. Not affiliated with Open Library or the Internet Archive.