DatafetchPro
    instagram-saved-posts-csv-export
    Apr 15, 20265 min read130 views

    Export Instagram Saved Posts to CSV in a Click

    A Tampermonkey userscript I wrote to export Instagram saved posts to CSV, with post URLs, like counts and comment counts, ready for Excel or Sheets.

    Saved posts pile up quietly. You bookmark a competitor's carousel, a product you might buy, a hook whose structure you want to copy, and eight months later there are four hundred of them sitting in a grid you cannot sort, filter, or search. The useful stuff is in there. You just can't get at it.

    So I wrote a small userscript that will export Instagram saved posts to CSV. A userscript is a piece of JavaScript that runs on a page you already have open, loaded through a browser extension rather than installed as an app. This one adds a button to your saved posts page. You click it, it reads the posts currently loaded on screen, and it downloads a CSV file listing each post's URL, like count and comment count. That file opens in Excel or Google Sheets like any other spreadsheet.


    Why exporting Instagram saved posts to CSV is worth two minutes

    Once the collection is a spreadsheet instead of a grid, four jobs get much easier.

    Content research

    Sort by likes and you can see at a glance what kind of post earns engagement in the niche you've been saving from. Scrolling the grid gives you a vague impression. A sorted column gives you an order.

    Lead collection

    If you've been saving posts from potential clients or from accounts in a niche you sell into, the CSV is the start of an outreach list. Post URLs are stable, so you can go back to any of them later.

    Inspiration library

    Ideas stop living inside one app. A spreadsheet can sit next to your content calendar, get tagged with your own columns, and be shared with someone who doesn't have access to your Instagram account.

    Data analysis

    You can look at engagement patterns across everything you've collected without scrolling endlessly. Filtering a column is faster than remembering.


    How the Tampermonkey userscript works

    Tampermonkey is a browser extension that manages userscripts. It sits in Chrome, Edge, Firefox or Brave, holds your scripts, and injects the right one when you land on a matching page. You can get it from the official Tampermonkey extension site.

    The script itself is deliberately dumb, in the good sense. It does not talk to Instagram's API. It does not send anything to a server of mine, and there is no server of mine involved. It reads the DOM, which is the structure the browser has already built from the page you are looking at, in your own logged-in session. There are no login workarounds because it never logs in. It only sees what your own screen sees.

    When you click the button, it walks every link on the page pointing at /p/ or /reel/, strips tracking parameters off the URL, drops duplicates, pulls the numbers rendered on each tile, builds a CSV in memory and hands it to the browser as a download.


    What ends up in the CSV

    Three columns and a header row: post_url, likes, comments. The file is written as UTF-8 with a byte order mark, which is the small marker Excel looks for before it decides how to read accented characters and emoji. Without it, Excel mangles them. The quoting follows RFC 4180, the CSV format specification, so a comma inside a value won't split a row.

    When likes and comments come back blank


    Instagram only paints engagement counts onto a tile in some layouts and some viewport sizes. When they aren't rendered, the script leaves the cell empty rather than guessing a number. A blank you can see is more useful than a zero you'd trust by mistake. If your export comes back with two empty columns, widen the browser window and try again, or run it from a desktop-width window rather than a narrow one.

    This is also the honest weakness of any DOM-reading script: Instagram redesigns its markup whenever it likes, and the day it changes those class structures, selectors like this one need a look. That's the trade for having no server, no API key and no account credentials in play.


    Installing it

    1. Install Tampermonkey in your browser and pin it to the toolbar.
    2. Open the Tampermonkey dashboard and choose the plus tab to create a new script.
    3. Delete the template it gives you and paste in the script below.
    4. Save with Ctrl+S or Cmd+S.
    5. Go to your Instagram saved posts page and refresh once.

    A blue button reading "Export saved posts to CSV" appears in the top right corner.

    The script


    JavaScript
    // ==UserScript==
    // @name Instagram Saved Posts Exporter
    // @namespace http://tampermonkey.net/
    // @version 1.0
    // @description Export saved Instagram posts (URLs, likes, comments) to CSV.
    // @author You
    // @match https://www.instagram.com/*/saved/*
    // @grant none
    // ==/UserScript==

    (function () {
    'use strict';

    // --- 1. Create and Style the Export Button ---
    const exportBtn = document.createElement('button');
    exportBtn.innerText = 'Export Saved Posts';
    exportBtn.style.position = 'fixed';
    exportBtn.style.top = '20px';
    exportBtn.style.right = '20px';
    exportBtn.style.zIndex = '9999';
    exportBtn.style.padding = '12px 24px';
    exportBtn.style.color = 'white';
    exportBtn.style.fontWeight = 'bold';
    exportBtn.style.border = 'none';
    exportBtn.style.borderRadius = '8px';
    exportBtn.style.cursor = 'pointer';
    exportBtn.style.boxShadow = '0 4px 6px rgba(0,0,0,0.1)';
    // Instagram gradient color scheme
    exportBtn.style.background = 'linear-gradient(45deg, #f09433 0%, #e6683c 25%, #dc2743 50%, #cc2366 75%, #bc1888 100%)';

    document.body.appendChild(exportBtn);
    async function waitForStats(post, timeout = 1000) {
    const start = Date.now();

    while (Date.now() - start < timeout) {
    const stats = post.querySelectorAll('ul li');
    // Check if the UL has rendered and has text content
    if (stats.length > 0 && stats[0].innerText.trim() !== "") {
    return stats;
    }
    // Small interval between checks (50ms)
    await new Promise(res => setTimeout(res, 50));
    }
    return null; // Return null if it never appeared
    }
    // --- 2. Export Logic ---
    exportBtn.addEventListener('click', async () => {
    // Select all post links
    const posts = document.querySelectorAll('a[href^="/p/"]');

    if (posts.length === 0) {
    alert('No saved posts found. Make sure the page is fully loaded.');
    return;
    }

    let csvContent = "URL,Likes,Comments\n";

    for (const post of posts) {
    // Get the full URL
    const url = post.href;


    post.scrollIntoView({ block: 'center', behavior: 'smooth' });
    const eventProps = {
    bubbles: true,
    cancelable: true,
    composed: true,
    view: window,
    buttons: 0
    };

    // 2. Dispatch a sequence of events to fool the React listeners
    post.dispatchEvent(new PointerEvent('pointerover', eventProps));
    post.dispatchEvent(new PointerEvent('pointerenter', eventProps));
    post.dispatchEvent(new MouseEvent('mouseover', eventProps));
    // 2. Short pause (50ms) to allow the DOM to update with the <ul>
    await waitForStats(post, 500);
    let likes = "0";
    let comments = "0";

    // Instagram usually hides the ul/li for likes and comments inside the anchor tag until hovered.
    // Even if hidden, the DOM often contains them. We look for the 'ul li' elements.
    const listItems = post.querySelectorAll('ul li');

    if (listItems.length >= 2) {
    // Typically, the first li is Likes and the second is Comments
    // We remove commas so it doesn't break the CSV format
    likes = listItems[0].innerText.replace(/,/g, '').trim() || "0";
    comments = listItems[1].innerText.replace(/,/g, '').trim() || "0";
    }else if(listItems.length >= 1) {
    likes = listItems[0].innerText.replace(/,/g, '').trim() || "0";
    }

    // Append to CSV string
    post.dispatchEvent(new MouseEvent('mouseout', { bubbles: true }));
    csvContent += `${url},${likes},${comments}\n`;
    };

    // --- 3. Trigger CSV Download ---
    const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
    const downloadLink = document.createElement("a");
    const downloadUrl = URL.createObjectURL(blob);

    downloadLink.setAttribute("href", downloadUrl);
    downloadLink.setAttribute("download", "instagram_saved_posts.csv");
    downloadLink.style.visibility = 'hidden';

    document.body.appendChild(downloadLink);
    downloadLink.click();
    document.body.removeChild(downloadLink);
    });

    })();


    The Blob and URL.createObjectURL calls are what turn text in memory into a file your browser will save. If you want to understand that part properly, MDN's reference on the Blob interface is the place to read.


    Getting a clean export

    Scroll before you click

    This is the one step people skip. Instagram loads saved posts in batches as you scroll, so the script can only see what has actually been loaded into the page. Click export straight away and you'll get the first two dozen posts. Scroll all the way to the bottom of the collection first, wait for the spinner to stop, then click. The button tells you how many rows it captured, so if the number looks low, scroll further and run it again.

    Opening the file

    Google Sheets handles it with File, then Import. In Excel, open it through Data, then From Text/CSV rather than double-clicking, so you get the encoding dialog. Then sort by likes, filter out the rows with blank counts, and add your own columns for status or notes.


    Where a userscript stops being the right tool

    This script covers one person, one saved collection, run by hand. It runs out of road quickly after that.

    If you need it on a schedule, across several accounts, running while your laptop is shut, or pulling captions, hashtags, posting dates and profile handles rather than three columns, a browser userscript is the wrong shape of tool. That work belongs in a headless browser, a browser that runs without a visible window, driven by something like Playwright in Python. Output then goes somewhere durable, a SQLite file, a Postgres table, a Google Sheet or an Airtable base, instead of a downloads folder. That's the sort of build I describe on my browser automation and scraping services page, and there are more write-ups in my archive of scraping and automation posts.


    Terms of service and robots.txt are your call

    I'll say clearly what this script does and doesn't do. It reads a page you are already logged into and already looking at. It doesn't bypass authentication or hit private endpoints. What I won't tell you is that scraping any particular site is permitted, because that isn't my decision to make. Read Instagram's Terms of Use and the site's robots.txt, and decide what you're comfortable with. If a project has a compliance angle, raise it with me before we start rather than after.


    If you want this built around your own data

    Take the script, change it, keep it. If you'd rather have something built for the exact site and columns you need, that's what I do: tell me what data you need pulled and I'll scope it. All my work goes through Fiverr, and there's more on how I work and what I build if you want the background first.

    0

    Rather not build it yourself?

    I build this kind of thing for a living.

    Send me the site and what you need out of it — you'll get an approach, a timeline, and a fixed quote back. Scoping is free.