File size: 12,147 Bytes
029016a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>InstaSpy - Bulk Scraper SaaS</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script src="https://unpkg.com/lucide@latest"></script>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">

    <script>

        tailwind.config = {

            theme: {

                extend: {

                    fontFamily: { sans: ['Inter', 'sans-serif'] },

                    animation: { 'fade-in': 'fadeIn 0.5s ease-out forwards' },

                    keyframes: {

                        fadeIn: {

                            '0%': { opacity: '0', transform: 'translateY(10px)' },

                            '100%': { opacity: '1', transform: 'translateY(0)' },

                        }

                    }

                }

            }

        }

    </script>
    
    <style>

        body { background-color: #020617; color: #e2e8f0; }

        .glass-panel {

            background: rgba(15, 23, 42, 0.6);

            backdrop-filter: blur(12px);

            border: 1px solid rgba(148, 163, 184, 0.1);

        }

        .gradient-text {

            background: linear-gradient(135deg, #f472b6 0%, #a855f7 100%);

            -webkit-background-clip: text;

            -webkit-text-fill-color: transparent;

        }

    </style>
</head>
<body class="min-h-screen flex flex-col font-sans selection:bg-pink-500 selection:text-white">

    <nav class="border-b border-slate-800 bg-slate-950/80 sticky top-0 z-50 backdrop-blur-md">
        <div class="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between">
            <div class="flex items-center gap-2">
                <div class="bg-gradient-to-br from-pink-500 to-purple-600 p-1.5 rounded-lg shadow-lg shadow-purple-500/20">
                    <i data-lucide="instagram" class="w-5 h-5 text-white"></i>
                </div>
                <span class="font-bold text-xl tracking-tight text-white">InstaSpy<span class="text-pink-500">.io</span></span>
            </div>
        </div>
    </nav>

    <main class="flex-grow max-w-7xl mx-auto px-6 py-12 w-full space-y-12">
        <div class="text-center space-y-4 animate-fade-in">
            <h1 class="text-4xl md:text-5xl font-extrabold tracking-tight text-white">
                Instagram <span class="gradient-text">Bulk Data Scraper</span>
            </h1>
            <p class="text-slate-400 max-w-2xl mx-auto text-lg leading-relaxed">
                Extract <span class="text-white font-medium">Views, Likes, and Followers</span> from Reels, Posts, and Profiles instantly. 
            </p>
        </div>

        <div class="glass-panel rounded-2xl p-1.5 shadow-2xl shadow-purple-900/10 animate-fade-in" style="animation-delay: 0.1s;">
            <div class="bg-[#0f172a] rounded-xl p-4">
                <div class="flex justify-between mb-2">
                    <label class="text-xs font-semibold uppercase tracking-wider text-slate-500">Target URLs</label>
                    <button class="text-xs text-pink-400 hover:text-pink-300 transition" onclick="pasteExample()">Paste Example Links</button>
                </div>
                <textarea id="urlInput" placeholder="Paste Instagram links here (separated by commas or new lines)..." class="w-full h-48 bg-transparent text-slate-300 placeholder:text-slate-600 focus:outline-none resize-none font-mono text-sm leading-6 custom-scrollbar"></textarea>
            </div>
            
            <div class="flex justify-between items-center px-4 py-3 bg-slate-900/50 rounded-b-xl border-t border-slate-800">
                <span id="linkCount" class="text-xs text-slate-500 font-mono">0 Links detected</span>
                <button id="scrapeBtn" onclick="startScrape()" class="flex items-center gap-2 px-6 py-2.5 rounded-lg font-semibold text-sm transition-all bg-gradient-to-r from-pink-600 to-purple-600 hover:from-pink-500 hover:to-purple-500 text-white shadow-lg shadow-pink-900/20 active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed">
                    <i data-lucide="play" class="w-4 h-4 fill-current"></i> Start Extraction
                </button>
            </div>
        </div>

        <div id="resultsArea" class="hidden space-y-6 animate-fade-in">
            <div class="border border-slate-800 rounded-xl overflow-hidden bg-slate-900/40 backdrop-blur-sm">
                <div class="flex items-center justify-between p-4 border-b border-slate-800 bg-slate-900/60">
                    <h3 class="font-semibold text-white flex items-center gap-2 text-sm">
                        <i data-lucide="database" class="w-4 h-4 text-slate-400"></i> Extracted Data
                    </h3>
                    <button onclick="downloadCSV()" class="flex items-center gap-2 px-3 py-1.5 bg-slate-800 hover:bg-slate-700 hover:text-white text-xs font-medium rounded-lg text-slate-300 transition border border-slate-700">
                        <i data-lucide="download" class="w-3.5 h-3.5"></i> Export Excel
                    </button>
                </div>

                <div class="overflow-x-auto">
                    <table class="w-full text-sm text-left">
                        <thead class="text-xs text-slate-400 uppercase bg-slate-900/80 border-b border-slate-800">
                            <tr>
                                <th class="px-6 py-3 font-medium">Type</th>
                                <th class="px-6 py-3 font-medium">Source URL</th>
                                <th class="px-6 py-3 font-medium">Author</th>
                                <th class="px-6 py-3 font-medium text-right">Followers</th>
                                <th class="px-6 py-3 font-medium text-right">Likes</th>
                                <th class="px-6 py-3 font-medium text-right">Views</th>
                                <th class="px-6 py-3 font-medium text-center">Status</th>
                            </tr>
                        </thead>
                        <tbody id="tableBody" class="divide-y divide-slate-800/50"></tbody>
                    </table>
                </div>
            </div>
        </div>
    </main>

    <script>

        lucide.createIcons();

        const urlInput = document.getElementById('urlInput');

        const scrapeBtn = document.getElementById('scrapeBtn');

        const resultsArea = document.getElementById('resultsArea');

        const tableBody = document.getElementById('tableBody');



        urlInput.addEventListener('input', () => {

            const lines = urlInput.value.split('\n').filter(line => line.trim() !== '');

            document.getElementById('linkCount').innerText = `${lines.length} Links detected`;

        });



        function pasteExample() {

            urlInput.value = `https://www.instagram.com/reel/DTQEBbwEs_K/\nhttps://www.instagram.com/devs_clicks12/\nhttps://www.instagram.com/reel/DTSetANkglg/`;

            urlInput.dispatchEvent(new Event('input'));

        }



        async function startScrape() {

            const rawInput = urlInput.value.trim();

            // Split by comma OR newline, then filter empty strings

            const urls = rawInput.split(/[\n,]+/).map(u => u.trim()).filter(u => u !== '');



            if (urls.length === 0) { alert("Please enter at least one URL"); return; }



            scrapeBtn.disabled = true;

            scrapeBtn.innerHTML = `<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> Processing...`;

            lucide.createIcons();

            

            try {

                // CALL PYTHON BACKEND

                const response = await fetch('/api/scrape', {

                    method: 'POST',

                    headers: { 'Content-Type': 'application/json' },

                    body: JSON.stringify({ urls: urls })

                });



                const data = await response.json();

                renderTable(data);

                resultsArea.classList.remove('hidden');

                resultsArea.scrollIntoView({ behavior: 'smooth' });



            } catch (error) {

                alert("Error connecting to server. Is app.py running?");

                console.error(error);

            }



            scrapeBtn.disabled = false;

            scrapeBtn.innerHTML = `<i data-lucide="play" class="w-4 h-4 fill-current"></i> Start Extraction`;

            lucide.createIcons();

        }



        function renderTable(data) {

            tableBody.innerHTML = '';

            data.forEach(row => {

                const tr = document.createElement('tr');

                tr.className = "hover:bg-slate-800/40 transition-colors group";

                

                const typeColor = row.type === 'REEL' ? 'text-pink-400 bg-pink-400/10 border-pink-400/20' 

                                : row.type === 'PROFILE' ? 'text-purple-400 bg-purple-400/10 border-purple-400/20' 

                                : 'text-blue-400 bg-blue-400/10 border-blue-400/20';



                const statusColor = row.status === 'Success' ? 'text-emerald-400 bg-emerald-400/10' : 'text-amber-400 bg-amber-400/10';



                // Truncate URL for display

                const shortUrl = row.url.length > 30 ? row.url.substring(0, 30) + "..." : row.url;



                tr.innerHTML = `

                    <td class="px-6 py-4"><span class="px-2 py-1 rounded text-[10px] font-bold border uppercase ${typeColor}">${row.type}</span></td>

                    

                    <td class="px-6 py-4">

                        <a href="${row.url}" target="_blank" class="text-xs text-slate-500 hover:text-pink-400 hover:underline transition font-mono flex items-center gap-1" title="${row.url}">

                            ${shortUrl}

                            <i data-lucide="external-link" class="w-3 h-3"></i>

                        </a>

                    </td>



                    <td class="px-6 py-4 font-medium text-white">@${row.author || 'N/A'}</td>

                    <td class="px-6 py-4 text-right text-slate-300 font-mono">${row.followers}</td>

                    <td class="px-6 py-4 text-right text-slate-300 font-mono">${row.likes}</td>

                    <td class="px-6 py-4 text-right text-white font-bold font-mono">${row.views}</td>

                    <td class="px-6 py-4 text-center">

                        <span class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs font-medium ${statusColor}">

                            <span class="w-1.5 h-1.5 rounded-full ${row.status === 'Success' ? 'bg-emerald-400' : 'bg-amber-400'}"></span>

                            ${row.status}

                        </span>

                    </td>

                `;

                tableBody.appendChild(tr);

            });

            lucide.createIcons();

        }



        function downloadCSV() {

            const rows = Array.from(document.querySelectorAll("table tr"));

            const csvContent = rows.map(row => {

                const cells = Array.from(row.querySelectorAll("th, td"));

                return cells.map(cell => {

                    // Check if cell has a link (for the URL column)

                    const link = cell.querySelector("a");

                    const text = link ? link.getAttribute("href") : cell.innerText;

                    return `"${text.replace(/(\r\n|\n|\r)/gm, " ").trim()}"`;

                }).join(",");

            }).join("\n");

            

            const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });

            const url = URL.createObjectURL(blob);

            const link = document.createElement("a");

            link.href = url;

            link.download = "instagram_export.csv";

            link.click();

        }

    </script>
</body>
</html>