Skip to content

Instantly share code, notes, and snippets.

@cgillinger
Last active August 15, 2026 04:36
Show Gist options
  • Select an option

  • Save cgillinger/6b301279e519a6890cfa82883aec37d1 to your computer and use it in GitHub Desktop.

Select an option

Save cgillinger/6b301279e519a6890cfa82883aec37d1 to your computer and use it in GitHub Desktop.
Kindle Library Exporter – Export Kindle Library as CSV from read.amazon.com
/**
* 📚 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.");
})();
@Abu5217

Abu5217 commented Feb 26, 2026

Copy link
Copy Markdown

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.

@cgillinger

Copy link
Copy Markdown
Author

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.

@lminuzzi

Copy link
Copy Markdown

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.");

})();

@cgillinger

cgillinger commented Aug 10, 2026

Copy link
Copy Markdown
Author

@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.`);
})();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment