-
-
Save cgillinger/6b301279e519a6890cfa82883aec37d1 to your computer and use it in GitHub Desktop.
| /** | |
| * 📚 Kindle Library Exporter + ISBN Lookup 📚 | |
| * | |
| * This script extracts book data (ASIN, Title, Authors) from https://read.amazon.com/kindle-library | |
| * and searches for ISBN using Open Library API. The data is exported as a CSV file. | |
| * | |
| * 🚨 IMPORTANT: YOU MUST USE GOOGLE CHROME 🚨 | |
| * 🛑 Why Google Chrome? | |
| * - Amazon Kindle Library uses advanced security restrictions. | |
| * - Other browsers (Firefox, Edge, Safari) may block access to book data due to security policies. | |
| * - Google Chrome allows us to extract book metadata safely using the DOM. | |
| * | |
| * ⚠️ IMPORTANT INSTRUCTIONS: READ BEFORE RUNNING ⚠️ | |
| * 1️⃣ Open **Google Chrome** and go to **https://read.amazon.com/kindle-library** | |
| * 2️⃣ **Manually scroll down** until you reach the end of your Kindle Library | |
| * 3️⃣ **Ensure all books are loaded and visible on the page** | |
| * 4️⃣ **Click anywhere on the page** to ensure focus | |
| * 5️⃣ Press **F12** (Windows/Linux) or **Cmd + Option + I** (Mac) to open Developer Tools | |
| * 6️⃣ Click on the **"Console"** tab | |
| * 7️⃣ Copy & paste this script into the console and press **Enter** | |
| * 8️⃣ A file named **'kindle_books.csv'** will be downloaded automatically | |
| */ | |
| (async function() { | |
| console.clear(); // Clears the console for better readability | |
| console.log("📚 Kindle Library Exporter: Starting..."); | |
| // CSV Header | |
| let csvData = "ASIN,Title,Authors,ISBN-13,ISBN-10\n"; | |
| // Function to fetch ISBN from Open Library API | |
| async function fetchISBN(title, author) { | |
| console.log(`📚 Fetching ISBN for "${title}" by ${author}...`); | |
| let cleanTitle = title.replace(/\s*\(.*?\)\s*/g, ""); // Remove text in parentheses | |
| let searchUrl = `https://openlibrary.org/search.json?title=${encodeURIComponent(cleanTitle)}&author=${encodeURIComponent(author)}&limit=5`; | |
| try { | |
| // Fetch search results | |
| let searchResponse = await fetch(searchUrl); | |
| let searchData = await searchResponse.json(); | |
| if (searchData.docs.length > 0) { | |
| console.log("✅ API Response:", searchData); // Debugging log | |
| for (let book of searchData.docs) { | |
| if (book.cover_edition_key) { | |
| let editionUrl = `https://openlibrary.org/books/${book.cover_edition_key}.json`; | |
| console.log(`🔍 Fetching edition data from: ${editionUrl}`); | |
| // Fetch edition details | |
| let editionResponse = await fetch(editionUrl); | |
| let editionData = await editionResponse.json(); | |
| if (editionData.isbn_13 || editionData.isbn_10) { | |
| let isbn13 = editionData.isbn_13 ? editionData.isbn_13[0] : "N/A"; | |
| let isbn10 = editionData.isbn_10 ? editionData.isbn_10[0] : "N/A"; | |
| console.log(`✅ Found ISBN: ${isbn13} / ${isbn10}`); | |
| return { isbn13, isbn10 }; | |
| } | |
| } | |
| } | |
| } | |
| } catch (error) { | |
| console.error(`❌ Error fetching ISBN for "${title}" by ${author}:`, error); | |
| } | |
| return { isbn13: "N/A", isbn10: "N/A" }; | |
| } | |
| // Collect book data | |
| let books = []; | |
| let elements = document.querySelectorAll('[id^="title-"]'); | |
| for (let titleDiv of elements) { | |
| let asin = titleDiv.id.replace("title-", ""); // Extract ASIN | |
| let title = titleDiv.querySelector("p") ? titleDiv.querySelector("p").innerText.trim().replace(/"/g, '') : "Unknown"; | |
| let cleanTitle = title.replace(/\s*\(.*?\)\s*/g, ""); // Remove text in parentheses | |
| let authorDiv = document.querySelector(`#author-${asin}`); | |
| let authors = authorDiv && authorDiv.querySelector("p") ? authorDiv.querySelector("p").innerText.trim() : "Unknown"; | |
| // Fetch ISBN asynchronously | |
| let { isbn13, isbn10 } = await fetchISBN(cleanTitle, authors); | |
| books.push({ asin, title, authors, isbn13, isbn10 }); | |
| // Log progress | |
| console.log(`✅ Processed: ${title} by ${authors} → ISBN-13: ${isbn13} | ISBN-10: ${isbn10}`); | |
| } | |
| // Convert to CSV | |
| books.forEach(book => { | |
| csvData += `"${book.asin}","${book.title}","${book.authors}","${book.isbn13}","${book.isbn10}"\n`; | |
| }); | |
| // Create and download CSV file | |
| let encodedUri = 'data:text/csv;charset=utf-8,' + encodeURIComponent(csvData); | |
| let link = document.createElement("a"); | |
| link.setAttribute("href", encodedUri); | |
| link.setAttribute("download", "kindle_books.csv"); | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| console.log("📚 Kindle Library Exporter: Done! CSV file downloaded."); | |
| })(); |
Glad to hear it! I’ve since moved over to Kobo and Komga, so I no longer really keep up with what’s going on in the Amazon and Kindle ecosystem.
This script is great. Almost perfect. I wonder how hard it would be to add the Publish Year and number of pages to the script. Both are listed on OpenLibrary.
(async function() {
console.clear(); // Clears the console for better readability
console.log("📚 Kindle Library Exporter: Starting...");
// CSV Header
let csvData = "ASIN,Title,Authors,NumberOfPages,ISBN-13,ISBN-10,\n";
// Function to fetch ISBN from Open Library API
async function fetchISBN(title, author) {
console.log(`📚 Fetching ISBN for "${title}" by ${author}...`);
let cleanTitle = title.replace(/\s*\(.*?\)\s*/g, ""); // Remove text in parentheses
let searchUrl = `https://openlibrary.org/search.json?title=${encodeURIComponent(cleanTitle)}&author=${encodeURIComponent(author)}&fields=number_of_pages_median,number_of_pages,cover_edition_key&limit=5`;
let numberOfPages = '';
try {
// Fetch search results
let searchResponse = await fetch(searchUrl);
let searchData = await searchResponse.json();
if (searchData.docs.length > 0) {
console.log("✅ API Response:", searchData); // Debugging log
for (let book of searchData.docs) {
if (numberOfPages === '') {
if (book.number_of_pages) {
numberOfPages = book.number_of_pages;
} else if (book.number_of_pages_median) {
numberOfPages = book.number_of_pages_median;
}
}
if (book.cover_edition_key) {
let editionUrl = `https://openlibrary.org/books/${book.cover_edition_key}.json`;
console.log(`🔍 Fetching edition data from: ${editionUrl}`);
// Fetch edition details
let editionResponse = await fetch(editionUrl);
let editionData = await editionResponse.json();
if (editionData.isbn_13 || editionData.isbn_10) {
let isbn13 = editionData.isbn_13 ? editionData.isbn_13[0] : "N/A";
let isbn10 = editionData.isbn_10 ? editionData.isbn_10[0] : "N/A";
console.log(`✅ Found ISBN: ${isbn13} / ${isbn10}`);
return { isbn13, isbn10, numberOfPages };
}
}
}
}
} catch (error) {
console.error(`❌ Error fetching ISBN for "${title}" by ${author}:`, error);
}
return { isbn13: "N/A", isbn10: "N/A", numberOfPages: numberOfPages };
}
// Collect book data
let books = [];
let elements = document.querySelectorAll('[id^="title-"]');
for (let titleDiv of elements) {
let asin = titleDiv.id.replace("title-", ""); // Extract ASIN
let title = titleDiv.querySelector("p") ? titleDiv.querySelector("p").innerText.trim().replace(/"/g, '') : "Unknown";
let cleanTitle = title.replace(/\s*\(.*?\)\s*/g, ""); // Remove text in parentheses
let authorDiv = document.querySelector(`#author-${asin}`);
let authors = authorDiv && authorDiv.querySelector("p") ? authorDiv.querySelector("p").innerText.trim() : "Unknown";
// Fetch ISBN asynchronously
let { isbn13, isbn10, numberOfPages } = await fetchISBN(cleanTitle, authors);
books.push({ asin, title, authors, numberOfPages, isbn13, isbn10 });
// Log progress
console.log(`✅ Processed: ${title} by ${authors} → Pages: ${numberOfPages} | ISBN-13: ${isbn13} | ISBN-10: ${isbn10}`);
}
// Convert to CSV
books.forEach(book => {
csvData += `"${book.asin}","${book.title}","${book.authors}","${book.numberOfPages}","${book.isbn13}","${book.isbn10}"\n`;
});
// Create and download CSV file
let encodedUri = 'data:text/csv;charset=utf-8,' + encodeURIComponent(csvData);
let link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", "kindle_books.csv");
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
console.log("📚 Kindle Library Exporter: Done! CSV file downloaded.");
})();
@lminuzzi Thanks for picking this up — nice work, and good call trimming the search response with fields=. I've folded your page count in and added the publish year @Abu5217 originally asked for.
I'm posting this as a comment rather than editing the gist itself: I moved over to Kobo + Komga a while back, so I no longer have a Kindle library to run this against. The DOM-scraping half is unverifiable for me now, and I'd rather not ship an untested edit as the "official" version of the gist. Take whichever version works for you.
The Open Library half is checked against the live API — the fields parameter, first_publish_year, and number_of_pages/isbn_13 on edition records all behave as expected. The Amazon selectors are untouched from the original and untested; if read.amazon.com has changed its markup since, that's where it will break. Prerequisites are unchanged: scroll the whole library first so every book is in the DOM.
(async function() {
console.clear(); // Clears the console for better readability
console.log("📚 Kindle Library Exporter: Starting...");
// CSV header — must match the number of fields written per row below
const COLUMNS = ["ASIN", "Title", "Authors", "FirstPublishYear", "NumberOfPages", "ISBN-13", "ISBN-10"];
let csvData = COLUMNS.join(",") + "\n";
// Quote a CSV field and escape embedded quotes by doubling them (RFC 4180)
const csvField = value => `"${String(value ?? "N/A").replace(/"/g, '""')}"`;
// Be polite to Open Library — they rate-limit aggressive clients
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
// Function to fetch ISBN, publish year and page count from Open Library API
async function fetchMetadata(title, author) {
console.log(`📚 Fetching metadata for "${title}" by ${author}...`);
const fields = "cover_edition_key,first_publish_year,number_of_pages_median";
let searchUrl = `https://openlibrary.org/search.json?title=${encodeURIComponent(title)}&author=${encodeURIComponent(author)}&fields=${fields}&limit=5`;
// Fallback: year/pages from the first search hit, used only if no edition with an ISBN is found
let fallback = { isbn13: "N/A", isbn10: "N/A", year: "N/A", pages: "N/A" };
try {
// Fetch search results
let searchResponse = await fetch(searchUrl);
let searchData = await searchResponse.json();
if (searchData.docs && searchData.docs.length > 0) {
for (let book of searchData.docs) {
if (fallback.year === "N/A" && book.first_publish_year) fallback.year = book.first_publish_year;
if (fallback.pages === "N/A" && book.number_of_pages_median) fallback.pages = book.number_of_pages_median;
if (!book.cover_edition_key) continue;
let editionUrl = `https://openlibrary.org/books/${book.cover_edition_key}.json`;
console.log(`🔍 Fetching edition data from: ${editionUrl}`);
// Fetch edition details
await sleep(250);
let editionResponse = await fetch(editionUrl);
let editionData = await editionResponse.json();
if (editionData.isbn_13 || editionData.isbn_10) {
// Take year and pages from the SAME edition the ISBN came from,
// falling back to the search hit's values when the edition omits them
return {
isbn13: editionData.isbn_13 ? editionData.isbn_13[0] : "N/A",
isbn10: editionData.isbn_10 ? editionData.isbn_10[0] : "N/A",
year: book.first_publish_year || "N/A",
pages: editionData.number_of_pages || book.number_of_pages_median || "N/A"
};
}
}
}
} catch (error) {
console.error(`❌ Error fetching metadata for "${title}" by ${author}:`, error);
}
return fallback;
}
// Collect book data
let books = [];
let elements = document.querySelectorAll('[id^="title-"]');
console.log(`📖 Found ${elements.length} books in the DOM.`);
for (let titleDiv of elements) {
let asin = titleDiv.id.replace("title-", ""); // Extract ASIN
let title = titleDiv.querySelector("p") ? titleDiv.querySelector("p").innerText.trim() : "Unknown";
let cleanTitle = title.replace(/\s*\(.*?\)\s*/g, ""); // Remove text in parentheses
let authorDiv = document.querySelector(`#author-${asin}`);
let authors = authorDiv && authorDiv.querySelector("p") ? authorDiv.querySelector("p").innerText.trim() : "Unknown";
// Fetch metadata asynchronously
let { isbn13, isbn10, year, pages } = await fetchMetadata(cleanTitle, authors);
books.push({ asin, title, authors, year, pages, isbn13, isbn10 });
// Log progress
console.log(`✅ Processed: ${title} by ${authors} → Year: ${year} | Pages: ${pages} | ISBN-13: ${isbn13} | ISBN-10: ${isbn10}`);
await sleep(250);
}
// Convert to CSV
books.forEach(book => {
csvData += [book.asin, book.title, book.authors, book.year, book.pages, book.isbn13, book.isbn10]
.map(csvField)
.join(",") + "\n";
});
// Create and download CSV file — a Blob handles large libraries that a data: URI would choke on
let blob = new Blob(["" + csvData], { type: "text/csv;charset=utf-8;" });
let url = URL.createObjectURL(blob);
let link = document.createElement("a");
link.setAttribute("href", url);
link.setAttribute("download", "kindle_books.csv");
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
console.log(`📚 Kindle Library Exporter: Done! ${books.length} books exported.`);
})();
This script is great. Almost perfect. I wonder how hard it would be to add the Publish Year and number of pages to the script. Both are listed on OpenLibrary.