Instagram Post Exporter

Export Instagram posts with images, captions, and metadata to JSON or CSV format

Size

13.7 KB

Version

1.0.1

Created

Mar 21, 2026

Updated

25 days ago

1// ==UserScript==
2// @name		Instagram Post Exporter
3// @description		Export Instagram posts with images, captions, and metadata to JSON or CSV format
4// @version		1.0.1
5// @match		https://*.instagram.com/*
6// @icon		https://static.cdninstagram.com/rsrc.php/y4/r/QaBlI0OZiks.ico
7// @grant		GM.xmlhttpRequest
8// @grant		GM.setValue
9// @grant		GM.getValue
10// ==/UserScript==
11(function() {
12    'use strict';
13
14    console.log('Instagram Post Exporter loaded');
15
16    // Utility function to wait for element
17    function waitForElement(selector, timeout = 10000) {
18        return new Promise((resolve, reject) => {
19            if (document.querySelector(selector)) {
20                return resolve(document.querySelector(selector));
21            }
22
23            const observer = new MutationObserver(() => {
24                if (document.querySelector(selector)) {
25                    observer.disconnect();
26                    resolve(document.querySelector(selector));
27                }
28            });
29
30            observer.observe(document.body, {
31                childList: true,
32                subtree: true
33            });
34
35            setTimeout(() => {
36                observer.disconnect();
37                reject(new Error('Element not found: ' + selector));
38            }, timeout);
39        });
40    }
41
42    // Debounce function
43    function debounce(func, wait) {
44        let timeout;
45        return function executedFunction(...args) {
46            const later = () => {
47                clearTimeout(timeout);
48                func(...args);
49            };
50            clearTimeout(timeout);
51            timeout = setTimeout(later, wait);
52        };
53    }
54
55    // Extract post data from a post URL
56    async function extractPostData(postUrl) {
57        try {
58            console.log('Extracting data from:', postUrl);
59            
60            const response = await GM.xmlhttpRequest({
61                method: 'GET',
62                url: postUrl,
63                headers: {
64                    'User-Agent': navigator.userAgent
65                }
66            });
67
68            const parser = new DOMParser();
69            const doc = parser.parseFromString(response.responseText, 'text/html');
70
71            // Extract data from meta tags and page content
72            const postData = {
73                url: postUrl,
74                timestamp: new Date().toISOString(),
75                caption: '',
76                images: [],
77                likes: 0,
78                comments: 0,
79                date: '',
80                author: ''
81            };
82
83            // Get caption from meta description
84            const metaDescription = doc.querySelector('meta[property="og:description"]');
85            if (metaDescription) {
86                postData.caption = metaDescription.content;
87            }
88
89            // Get image from meta tag
90            const metaImage = doc.querySelector('meta[property="og:image"]');
91            if (metaImage) {
92                postData.images.push(metaImage.content);
93            }
94
95            // Get author from meta tag
96            const metaTitle = doc.querySelector('meta[property="og:title"]');
97            if (metaTitle) {
98                postData.author = metaTitle.content.split(' on Instagram')[0];
99            }
100
101            // Try to extract additional images from the page
102            const imgElements = doc.querySelectorAll('img[src*="instagram"]');
103            imgElements.forEach(img => {
104                const src = img.src || img.getAttribute('src');
105                if (src && src.includes('scontent') && !postData.images.includes(src)) {
106                    postData.images.push(src);
107                }
108            });
109
110            return postData;
111        } catch (error) {
112            console.error('Error extracting post data:', error);
113            return null;
114        }
115    }
116
117    // Get all post links from current page
118    function getPostLinks() {
119        const links = new Set();
120        const postLinks = document.querySelectorAll('a[href*="/p/"], a[href*="/reel/"]');
121        
122        postLinks.forEach(link => {
123            const href = link.href;
124            if (href && (href.includes('/p/') || href.includes('/reel/'))) {
125                links.add(href);
126            }
127        });
128
129        return Array.from(links);
130    }
131
132    // Export to JSON
133    function exportToJSON(data) {
134        const jsonStr = JSON.stringify(data, null, 2);
135        const blob = new Blob([jsonStr], { type: 'application/json' });
136        const url = URL.createObjectURL(blob);
137        const a = document.createElement('a');
138        a.href = url;
139        a.download = `instagram_posts_${Date.now()}.json`;
140        document.body.appendChild(a);
141        a.click();
142        document.body.removeChild(a);
143        URL.revokeObjectURL(url);
144    }
145
146    // Export to CSV
147    function exportToCSV(data) {
148        const headers = ['URL', 'Author', 'Caption', 'Images', 'Date', 'Timestamp'];
149        const rows = data.map(post => [
150            post.url,
151            post.author,
152            post.caption.replace(/"/g, '""'),
153            post.images.join(' | '),
154            post.date,
155            post.timestamp
156        ]);
157
158        const csvContent = [
159            headers.join(','),
160            ...rows.map(row => row.map(cell => `"${cell}"`).join(','))
161        ].join('\n');
162
163        const blob = new Blob([csvContent], { type: 'text/csv' });
164        const url = URL.createObjectURL(blob);
165        const a = document.createElement('a');
166        a.href = url;
167        a.download = `instagram_posts_${Date.now()}.csv`;
168        document.body.appendChild(a);
169        a.click();
170        document.body.removeChild(a);
171        URL.revokeObjectURL(url);
172    }
173
174    // Create export UI
175    function createExportUI() {
176        // Check if button already exists
177        if (document.getElementById('ig-export-btn')) {
178            return;
179        }
180
181        // Create export button container
182        const buttonContainer = document.createElement('div');
183        buttonContainer.id = 'ig-export-btn';
184        buttonContainer.style.cssText = `
185            position: fixed;
186            bottom: 20px;
187            right: 20px;
188            z-index: 9999;
189            display: flex;
190            flex-direction: column;
191            gap: 10px;
192        `;
193
194        // Create main export button
195        const exportBtn = document.createElement('button');
196        exportBtn.textContent = '📥 Export Posts';
197        exportBtn.style.cssText = `
198            background: linear-gradient(45deg, #f09433 0%, #e6683c 25%, #dc2743 50%, #cc2366 75%, #bc1888 100%);
199            color: white;
200            border: none;
201            padding: 12px 24px;
202            border-radius: 8px;
203            font-size: 14px;
204            font-weight: 600;
205            cursor: pointer;
206            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
207            transition: transform 0.2s, box-shadow 0.2s;
208        `;
209
210        exportBtn.addEventListener('mouseenter', () => {
211            exportBtn.style.transform = 'translateY(-2px)';
212            exportBtn.style.boxShadow = '0 6px 16px rgba(0, 0, 0, 0.2)';
213        });
214
215        exportBtn.addEventListener('mouseleave', () => {
216            exportBtn.style.transform = 'translateY(0)';
217            exportBtn.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
218        });
219
220        exportBtn.addEventListener('click', showExportModal);
221
222        buttonContainer.appendChild(exportBtn);
223        document.body.appendChild(buttonContainer);
224
225        console.log('Export button created');
226    }
227
228    // Show export modal
229    function showExportModal() {
230        // Create modal overlay
231        const modal = document.createElement('div');
232        modal.id = 'ig-export-modal';
233        modal.style.cssText = `
234            position: fixed;
235            top: 0;
236            left: 0;
237            width: 100%;
238            height: 100%;
239            background: rgba(0, 0, 0, 0.7);
240            z-index: 10000;
241            display: flex;
242            align-items: center;
243            justify-content: center;
244        `;
245
246        // Create modal content
247        const modalContent = document.createElement('div');
248        modalContent.style.cssText = `
249            background: white;
250            border-radius: 12px;
251            padding: 24px;
252            max-width: 500px;
253            width: 90%;
254            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
255        `;
256
257        const postLinks = getPostLinks();
258
259        modalContent.innerHTML = `
260            <h2 style="margin: 0 0 16px 0; font-size: 24px; color: #262626;">Export Instagram Posts</h2>
261            <p style="margin: 0 0 20px 0; color: #8e8e8e; font-size: 14px;">Found ${postLinks.length} posts on this page</p>
262            
263            <div style="margin-bottom: 20px;">
264                <label style="display: block; margin-bottom: 8px; color: #262626; font-weight: 600;">Export Format:</label>
265                <select id="export-format" style="width: 100%; padding: 10px; border: 1px solid #dbdbdb; border-radius: 6px; font-size: 14px;">
266                    <option value="json">JSON</option>
267                    <option value="csv">CSV</option>
268                </select>
269            </div>
270
271            <div style="margin-bottom: 20px;">
272                <label style="display: block; margin-bottom: 8px; color: #262626; font-weight: 600;">Number of Posts:</label>
273                <input type="number" id="post-count" value="${Math.min(postLinks.length, 10)}" min="1" max="${postLinks.length}" 
274                    style="width: 100%; padding: 10px; border: 1px solid #dbdbdb; border-radius: 6px; font-size: 14px;">
275            </div>
276
277            <div id="export-progress" style="display: none; margin-bottom: 20px;">
278                <div style="background: #efefef; border-radius: 4px; height: 8px; overflow: hidden;">
279                    <div id="progress-bar" style="background: linear-gradient(45deg, #f09433 0%, #e6683c 25%, #dc2743 50%, #cc2366 75%, #bc1888 100%); height: 100%; width: 0%; transition: width 0.3s;"></div>
280                </div>
281                <p id="progress-text" style="margin: 8px 0 0 0; color: #8e8e8e; font-size: 12px; text-align: center;">Processing...</p>
282            </div>
283
284            <div style="display: flex; gap: 12px; justify-content: flex-end;">
285                <button id="cancel-btn" style="padding: 10px 20px; border: 1px solid #dbdbdb; background: white; color: #262626; border-radius: 6px; font-size: 14px; font-weight: 600; cursor: pointer;">Cancel</button>
286                <button id="export-btn" style="padding: 10px 20px; border: none; background: linear-gradient(45deg, #f09433 0%, #e6683c 25%, #dc2743 50%, #cc2366 75%, #bc1888 100%); color: white; border-radius: 6px; font-size: 14px; font-weight: 600; cursor: pointer;">Export</button>
287            </div>
288        `;
289
290        modal.appendChild(modalContent);
291        document.body.appendChild(modal);
292
293        // Event listeners
294        document.getElementById('cancel-btn').addEventListener('click', () => {
295            modal.remove();
296        });
297
298        modal.addEventListener('click', (e) => {
299            if (e.target === modal) {
300                modal.remove();
301            }
302        });
303
304        document.getElementById('export-btn').addEventListener('click', async () => {
305            const format = document.getElementById('export-format').value;
306            const count = parseInt(document.getElementById('post-count').value);
307            const exportBtn = document.getElementById('export-btn');
308            const cancelBtn = document.getElementById('cancel-btn');
309            const progressDiv = document.getElementById('export-progress');
310            const progressBar = document.getElementById('progress-bar');
311            const progressText = document.getElementById('progress-text');
312
313            exportBtn.disabled = true;
314            cancelBtn.disabled = true;
315            progressDiv.style.display = 'block';
316
317            const postsToExport = postLinks.slice(0, count);
318            const exportedData = [];
319
320            for (let i = 0; i < postsToExport.length; i++) {
321                const postUrl = postsToExport[i];
322                progressText.textContent = `Processing post ${i + 1} of ${postsToExport.length}...`;
323                progressBar.style.width = `${((i + 1) / postsToExport.length) * 100}%`;
324
325                const postData = await extractPostData(postUrl);
326                if (postData) {
327                    exportedData.push(postData);
328                }
329
330                // Small delay to avoid rate limiting
331                await new Promise(resolve => setTimeout(resolve, 500));
332            }
333
334            progressText.textContent = 'Generating file...';
335
336            if (format === 'json') {
337                exportToJSON(exportedData);
338            } else {
339                exportToCSV(exportedData);
340            }
341
342            progressText.textContent = `Successfully exported ${exportedData.length} posts!`;
343            progressBar.style.width = '100%';
344
345            setTimeout(() => {
346                modal.remove();
347            }, 2000);
348        });
349    }
350
351    // Initialize
352    async function init() {
353        console.log('Initializing Instagram Post Exporter');
354
355        // Wait for page to load
356        await waitForElement('header, main').catch(() => {
357            console.log('Timeout waiting for Instagram to load, proceeding anyway');
358        });
359
360        // Create export button
361        createExportUI();
362
363        // Re-create button on navigation (Instagram is SPA)
364        const observer = new MutationObserver(debounce(() => {
365            if (!document.getElementById('ig-export-btn')) {
366                createExportUI();
367            }
368        }, 1000));
369
370        observer.observe(document.body, {
371            childList: true,
372            subtree: true
373        });
374
375        console.log('Instagram Post Exporter initialized');
376    }
377
378    // Start the extension
379    if (document.readyState === 'loading') {
380        document.addEventListener('DOMContentLoaded', init);
381    } else {
382        init();
383    }
384})();