A personal project that scrapes metadata from GraphicAudio and exposes a lightweight lookup API that can also serve as an Audiobookshelf Custom Metadata Provider.
β οΈ Note: While there is a public instance of this API, itβs hosted on a free plan with a very low data cap. If youβd like access, please send me a message and I can provide it.
This project is a personal hobby project.
β
You may use this project for personal archival or library metadata.
β This project is not affiliated with GraphicAudio, nor endorsed by them.
All trademarks, cover images, metadata, and intellectual property belong to their respective owners.
This project contains three components:
| Component | Language | Purpose |
|---|---|---|
index.js |
Node.js | Scrapes GraphicAudio product pages and saves results to results.json |
index_wayback.js |
Node.js | Scrapes archived GraphicAudio pages from Wayback Machine and saves to wayback_results.json |
index.php |
PHP | Serves metadata via HTTP APIs, including ABS custom metadata provider |
The scrapers produce structured JSON files:
results.json # Live catalog
wayback_results.json # Archived catalogThe PHP API loads these JSON files (cached locally or via APCu), and exposes endpoints such as:
/isbn/{isbn}
/wayback/isbn/{isbn}
/asin/{asin}
/wayback/asin/{asin}
/series/{series-name}
/wayback/series/{series-name}
/search/{query}
/wayback/search/{query}
/audiobookshelf/search?query={isbn|asin|text}
/wayback/audiobookshelf/search?query={isbn|asin|text}- Node.js 20
npm i
| File | Purpose |
|---|---|
index.js |
Scrapes entire GraphicAudio catalog |
urls.json |
Cached product URLs (improves resume) |
results.json |
Output metadata JSON from scraping |
node index.jsThe script will:
- Download the GraphicAudio product list
- Extract each product URL
- Visit each product page
- Save scraped data into
results.json
- Resumable scraping β will not duplicate previously scraped entries
- Cleans ISBN, title, series numbering, etc.
- Detects multipart episodes (example:
4.5from4 : Rhythm of War (5 of 6)) - Saves covers only when valid (ignores
tempcover.jpg)
π§ Metadata captured per entry includes:
{
"link": "https://www.graphicaudio.net/amelia-peabody-4-lion-in-the-valley.html",
"cover": "https://www.graphicaudio.net/media/catalog/product/cache/0164cd528593768540930b5b640a411b/a/m/amelia_peabody_4_lion_in_the_valley.jpg",
"seriesName": "Amelia Peabody",
"title": "Lion in the Valley",
"rawtitle": "Episode number 4 : Lion in the Valley",
"episodeNumber": 4,
"episodePart": "1",
"episodeCode": "4.1",
"totalParts": "1",
"subtitle": "[Dramatized Adaptation]",
"author": "Elizabeth Peters",
"releaseDate": "2025-11-17T00:00:00.000Z",
"isbn": "9798896520030",
"genre": "Mystery",
"description": "The 1895-96 season promises to be an exceptional one ...",
"copyright": "Copyright Β© 1986 Elizabeth Peters. All rights reserved...",
"cast": [
"Ken Jackson",
"Nanette Savard",
"Amelia Peabody",
"Michael Glenn",
"Radcliffe Emerson",
...
]
}This project also includes a secondary scraper that pulls archived GraphicAudio pages from the Internet Archive's Wayback Machine. It can find older product pages that are no longer available on the live site.
- Node.js 20
npm i(same dependencies as the primary scraper)
| File | Purpose |
|---|---|
index_wayback.js |
Scrapes archived catalog snapshots and product pages via web.archive.org |
wayback_urls.json |
Cached list of product URLs extracted from archived catalog snapshots |
wayback_results.json |
Output metadata JSON from the archived pages |
node index_wayback.js- Uses a curated list of Wayback Machine catalog snapshots (
catalogUrls) to discover product pages. - Scrapes each archived product page and adds a
wayback_linkfield pointing to the archived snapshot. - Stores the original live URL in
link(stripping the Wayback prefix). Note: this URL may no longer work; usewayback_linkto access the archived page reliably. - Supports resuming: rerunning will skip entries already saved in
wayback_results.json.
β οΈ Disclaimer: Internet Archive Data Accuracy and Completeness
Data from the Wayback Machine may be inaccurate or incomplete. Archived pages can have missing metadata (covers, ISBNs, descriptions), broken links, or outdated information. The Internet Archive captures snapshots at different times, so not all data may be present or correct. Use this data with caution and verify against other sources when possible.β οΈ Note: Wayback snapshots vary in completeness. Some archived pages may have missing metadata (cover image, ISBN, etc.), depending on the snapshot.
- PHP 8.1+
- Optional: APCu extension (improves caching performance)
| File | Purpose |
|---|---|
index.php |
Main API router |
cache.json |
Cached version of results.json (auto created) |
/covers |
Cached cover images |
Edit these constants:
define("JSON_URL", "https://raw.githubusercontent.com/USERNAME/REPO/main/results.json");
define("WAYBACK_URL", "https://raw.githubusercontent.com/USERNAME/REPO/main/wayback_results.json");
define("REFRESH_KEY", "CHANGE_ME");
define("AUDIOBOOKSHELF_KEY", "abs"); // "abs" = no auth requiredIf you want ABS to require an API key, set:
define("AUDIOBOOKSHELF_KEY", "MYSECRETKEY123");
β οΈ Note: The Docker setup is not tested. Use at your own risk and test thoroughly before production deployment.
For easy deployment, use Docker:
# Build and start the container
docker-compose up -d
# The API will be available at http://localhost:8080# Build the image
docker build -t graphicaudio-api .
# Run the container
docker run -d -p 8080:80 \
-v $(pwd)/cache:/var/www/html/cache \
-v $(pwd)/covers:/var/www/html/covers \
-e REFRESH_KEY=your_key_here \
graphicaudio-apiREFRESH_KEY: Secret key for cache refresh endpoint (auto-generated if not set)AUDIOBOOKSHELF_KEY: API key for Audiobookshelf (use "abs" for no auth)LOW_BANDWIDTH_LIMIT_ENABLE: Enable rate limiting (default: true)DEBUG: Enable debug logging (default: false)JSON_URL: Custom URL for results.json (optional)WAYBACK_URL: Custom URL for wayback_results.json (optional)
Note: If REFRESH_KEY is not set, a random 64-character hex key will be generated on container startup and printed to the console.
The repository includes automated Docker builds via GitHub Actions:
- Triggers: Pushes to
mainbranch and pull requests affecting Docker-related files - Registry: Images are pushed to GitHub Container Registry (
ghcr.io) - Tags:
latestfor main branch- Branch name for other branches
- PR number for pull requests
- Commit SHA for unique builds
To use the pre-built image:
# Pull the latest image
docker pull ghcr.io/binyaminyblatt/graphicaudio_scraper:latest
# Run with docker-compose using the pre-built image
docker-compose up -dMake sure to update your docker-compose.yml to use the registry image:
services:
graphicaudio-api:
image: ghcr.io/binyaminyblatt/graphicaudio_scraper:latest
# ... rest of your configThe API supports two datasets:
- Live Dataset: Uses
results.json(current GraphicAudio catalog) - Wayback Dataset: Uses
wayback_results.json(archived pages from Wayback Machine)
All endpoints work with both datasets. To query the Wayback dataset, prepend /wayback/ to any endpoint (e.g., /wayback/isbn/{isbn}).
/isbn/{isbn}
/wayback/isbn/{isbn}Get cover:
/isbn/{isbn}/cover
/wayback/isbn/{isbn}/cover/asin/{asin}
/wayback/asin/{asin}Get cover:
/asin/{asin}/cover
/wayback/asin/{asin}/cover/search/{query}
/wayback/search/{query}/series/{series-name}
/wayback/series/{series-name}/audiobookshelf/search?query=stormlight
/wayback/audiobookshelf/search?query=stormlightAuto-detects:
| Query type | Handled as |
|---|---|
9781234567890 |
ISBN |
B09C4Y7T1Q |
ASIN |
Stormlight |
fuzzy search |
ABS receives results formatted like:
{
"matches": [
{
"title": "Rhythm of War",
"series": [{ "series": "Stormlight Archive", "sequence": "4.5" }],
"author": "Brandon Sanderson",
"publishedYear": "2020",
"cover": "https://yourdomain/isbn/9781427280583/cover",
"narrator": "Narrator One"
}
]
}PUT /refresh?key=YOURKEY
PUT /wayback/refresh?key=YOURKEYCovers are downloaded automatically and cached in /covers/.
Once cached, they serve instantly without hitting GraphicAudio again.
| Feature | Status |
|---|---|
| Full catalog scraping | β |
| Wayback Machine scraping | β |
| ISBN lookup | β |
| ASIN lookup | β |
| Series fuzzy detection | β |
| Audiobookshelf metadata provider | β |
| Cached covers | β |
| /wayback/* endpoints | β |
| Docker deployment | |
| GitHub Actions CI/CD | β |
- ASINs are not available on the GraphicAudio website. The scraper cannot retrieve them directly from GraphicAudio pages.
- If you want ASINs, you must manually match GraphicAudio titles with Audible or another source.
- Once you add an ASIN to a product entry in
results.json, the PHP API can serve it via:
/asin/{asin}
/asin/{asin}/cover- Example JSON with ASIN field added:
{
"link": "https://www.graphicaudio.net/amelia-peabody-4-lion-in-the-valley.html",
"cover": "https://www.graphicaudio.net/media/catalog/product/cache/0164cd528593768540930b5b640a411b/a/m/amelia_peabody_4_lion_in_the_valley.jpg",
"seriesName": "Amelia Peabody",
"title": "Lion in the Valley",
"rawtitle": "Episode number 4 : Lion in the Valley",
"episodeNumber": 4,
"episodePart": "1",
"episodeCode": "4.1",
"totalParts": "1",
"subtitle": "[Dramatized Adaptation]",
"author": "Elizabeth Peters",
"releaseDate": "2025-11-17T00:00:00.000Z",
"isbn": "9798896520030",
"asin": "B08EXAMPLE", // <- Add this manually
"genre": "Mystery",
"description": "The 1895-96 season promises to be an exceptional one ...",
"copyright": "Copyright Β© 1986 Elizabeth Peters. All rights reserved...",
"cast": [
"Ken Jackson",
"Nanette Savard",
"Amelia Peabody",
"Michael Glenn",
"Radcliffe Emerson",
...
]
}- Once added, the PHP API
findByField()will recognize it automatically.
To edit or improve results, simply delete:
urls.json
results.json
wayback_urls.json
wayback_results.jsonNext run:
node index.js
node index_wayback.jsTo force the PHP endpoint to refresh:
curl -X PUT "https://yourdomain/refresh?key=YOURKEY"
curl -X PUT "https://yourdomain/wayback/refresh?key=YOURKEY"You are free to use the scraped dataset for any purpose (personal or commercial). This project's code is released under the MIT License, but the dataset itself is derived from GraphicAudio content and should be used responsibly.
- β You may use the dataset in your own projects.
- β You may redistribute the dataset (credit to this repository is appreciated but not required).
- β If you like what I'm doing, please star this repository!
- π If you use the Wayback dataset heavily, consider supporting the Internet Archive (e.g., https://archive.org/donate) to help keep these snapshots available.
- π Please do not set up GitHub Actions to run
index_wayback.json a loop. While this project is MIT-licensed and you technically can, I sincerely ask that you respect the costs of running the Internet Archive. The Wayback scraper should be run manually or on rare occasions, not automatically on a schedule. The Internet Archive pages are static and won't change over time, so there's no benefit to running it repeatedlyβonly unnecessary cost. β οΈ Remember the dataset contains third-party content (GraphicAudio), so respect copyright and fair use.
PRs welcome β especially improvements to scraper logic or metadata mapping.
MIT License.