// ============================================================================ // ПАРСЕР ПОДПИСЧИКОВ И ПОДПИСОК INSTAGRAM (обновлённая версия 2026) // Оригинал: Леонид Залюбовский, 2021 | https://github.com/leoneedpro/instagram-parser // ---------------------------------------------------------------------------- // Зачем переписан: // Instagram давно перешёл на CSS-in-JS (атомарные классы вида "x1i10hfl", // "_a6hd" и т.п.), которые генерируются на сборке и меняются от деплоя к // деплою. Старые селекторы вида isgrP, jSC57 _6xe7A, wo9IH, m82CD, g47SY, // wFPL8 в текущей разметке просто не существуют, поэтому скрипт падал в // catch() с "Нажмите на странице Instagram на Подписчиков или Подписки...". // // Что используется вместо классов: // 1) Модалка списка определяется через [role="dialog"] (атрибут, не класс). // 2) Заголовок ("Подписчики"/"Followers"/"Подписки"/"Following") // определяется через [role="heading"] внутри диалога — текст не зависит // от верстки. // 3) Каждая строка списка — это , внутри // которого лежит с текстом ника. Это структурные, не косметические // признаки, поэтому они переживают рестайлинг гораздо дольше классов. // 4) Прокручиваемый контейнер находится по инлайн-стилю // "overflow: hidden auto" внутри диалога — он задаётся JS Instagram'а // напрямую через style, а не через className, и тоже куда стабильнее. // ============================================================================ console.log('%c||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||', 'color: #13a555; font-size:16px;'); console.log('%cПАРСЕР ПОДПИСЧИКОВ И ПОДПИСОК INSTAGRAM (fixed 2026)', 'color: #1d6ba3; font-size:24px;'); console.log('%c||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||', 'color: #13a555; font-size:16px;'); (function () { try { // -------------------------------------------------------------- // НАСТРОЙКИ // -------------------------------------------------------------- var speed_scrolling = 1000; // мс между прокрутками var user_count = 700; // 0 = собрать всех var user_name = false; // true = собирать ещё и отображаемое имя // -------------------------------------------------------------- // ПОИСК ДИАЛОГА СПИСКА ПОДПИСЧИКОВ/ПОДПИСОК // -------------------------------------------------------------- var dialog = document.querySelector('div[role="dialog"]'); if (!dialog) { throw new Error('Диалог со списком не найден. Откройте список подписчиков или подписок.'); } var headingEl = dialog.querySelector('[role="heading"]'); var title = headingEl ? headingEl.textContent.trim() : ''; var isFollowers = (title === 'Подписчики' || title === 'Followers'); var isFollowing = (title === 'Подписки' || title === 'Following'); if (!isFollowers && !isFollowing) { throw new Error('Не удалось распознать заголовок диалога ("' + title + '"). Убедитесь, что открыт именно список подписчиков/подписок.'); } // -------------------------------------------------------------- // ПРОКРУЧИВАЕМЫЙ КОНТЕЙНЕР СПИСКА // Ищем элемент со style="overflow: hidden auto" (или auto/scroll) // внутри диалога — он же содержит реальный scrollHeight списка. // -------------------------------------------------------------- function findScrollContainer(root) { var all = root.querySelectorAll('div, section'); var candidates = []; for (var i = 0; i < all.length; i++) { var el = all[i]; var computed = window.getComputedStyle(el); var ovY = computed.overflowY; var ovShort = el.getAttribute('style') || ''; var looksScrollable = ovY === 'auto' || ovY === 'scroll' || /overflow(-y)?:\s*(hidden\s+)?(auto|scroll)/.test(ovShort); if (looksScrollable) { candidates.push(el); } } if (candidates.length === 0) return null; // Предпочитаем кандидата, где реально есть прокрутка ИЛИ который // содержит больше всего ссылок role="link" (сам список аккаунтов). var best = null; var bestScore = -1; for (var j = 0; j < candidates.length; j++) { var c = candidates[j]; var linksInside = c.querySelectorAll('a[role="link"]').length; var scrollable = c.scrollHeight > c.clientHeight ? 1 : 0; var score = linksInside * 10 + scrollable; if (score > bestScore) { bestScore = score; best = c; } } return best; } var scrollContainer = findScrollContainer(dialog); // Фолбэк: если ничего не нашли по стилям — пробуем ближайшего // прокручиваемого родителя первой ссылки role="link" в списке. if (!scrollContainer) { var firstLink = dialog.querySelector('a[role="link"][href^="/"]'); if (firstLink) { var node = firstLink.parentElement; while (node && node !== dialog) { if (node.scrollHeight > node.clientHeight) { scrollContainer = node; break; } node = node.parentElement; } } } if (!scrollContainer) { scrollContainer = dialog; // последний рубеж — скроллим сам диалог console.log('%cНе удалось точно определить контейнер списка, использую сам диалог как запасной вариант.', 'color: #d99a1c; font-size:14px;'); } else { console.log('%cКонтейнер списка найден.', 'color: #13a555; font-size:14px;'); } // -------------------------------------------------------------- // ПОЛУЧЕНИЕ ОБЩЕГО КОЛИЧЕСТВА (по возможности, необязательно) // Пытаемся найти число рядом со словом "подписчиков"/"подписок" // в шапке профиля. Если не находим — просто не показываем total. // -------------------------------------------------------------- var total_count = null; try { var headerLinks = document.querySelectorAll('a[href*="/followers/"], a[href*="/following/"], header a, section a'); for (var j = 0; j < headerLinks.length; j++) { var t = headerLinks[j].textContent || ''; if (isFollowers && /подписчик|follower/i.test(t)) { var m1 = t.match(/[\d.,\s]+/); if (m1) { total_count = m1[0].trim(); break; } } if (isFollowing && /подписк|following/i.test(t)) { var m2 = t.match(/[\d.,\s]+/); if (m2) { total_count = m2[0].trim(); break; } } } } catch (e) { /* не критично */ } if (total_count) { console.log('%cОбщее кол-во аккаунтов (примерно): ' + total_count, 'color: #13a555; font-size:16px;'); } if (user_count !== 0) { console.log('%cКол-во заданное пользователем: ' + user_count + ' шт.', 'color: #13a555; font-size:16px;'); } console.log('%cНачался сбор данных, дождитесь выполнения...', 'color: #13a555; font-size:16px;'); // -------------------------------------------------------------- // СБОР НИКОВ/ИМЁН ИЗ ТЕКУЩЕГО DOM // Каждая строка — со span внутри. // -------------------------------------------------------------- function collectAccounts() { var result = []; // [{nick, name}] var seen = {}; var links = dialog.querySelectorAll('a[role="link"][href^="/"]'); for (var i = 0; i < links.length; i++) { var href = links[i].getAttribute('href') || ''; var match = href.match(/^\/([a-zA-Z0-9._]+)\/?$/); if (!match) continue; var nick = match[1]; // отсекаем служебные ссылки (explore, reels и т.п.) if (['explore', 'reels', 'p', 'stories', 'direct'].indexOf(nick) !== -1) continue; if (seen[nick]) continue; var span = links[i].querySelector('span'); if (!span || !span.textContent.trim()) continue; // ссылка-аватарка без текста — пропускаем seen[nick] = true; var name = ''; if (user_name) { // Реальная структура строки в Instagram (2026): // <- "обёртка ника" (W) //
...nick
// // <- сосед обёртки (N), содержит имя // Имя // // // То есть нужный span с именем — это nextElementSibling // того предка ссылки, который сам обёрнут в span и у // которого вообще есть соседний элемент. Поднимаемся от // вверх максимум на 5 уровней и на каждом шаге // проверяем nextElementSibling. var node = links[i]; for (var up = 0; up < 5 && node && node !== dialog; up++) { var sib = node.nextElementSibling; if (sib) { var txt = sib.textContent.trim(); if (txt && txt !== nick) { name = txt; break; } } node = node.parentElement; } } result.push({ nick: nick, name: name }); } return result; } function printResults(accounts) { var sliced = accounts.slice(0, user_count > 0 ? user_count : accounts.length); if (user_name) { var lines = sliced.map(function (a) { return a.nick + ' : ' + (a.name || ''); }); console.log(lines.join('\n')); } else { var nicks = sliced.map(function (a) { return a.nick; }); console.log(nicks.join('\n')); } console.log('%cАккаунтов собрано: ' + sliced.length + ' шт.', 'color: #13a555; font-size:18px;'); console.log('%cВыделите собранные имена аккаунтов выше и нажмите CTRL-C, чтобы скопировать.', 'color: #13a555; font-size:16px;'); } // -------------------------------------------------------------- // СКРОЛЛИНГ С ОПРЕДЕЛЕНИЕМ ОСТАНОВКИ // Останавливаемся, когда: // - набрали нужное количество аккаунтов (user_count), или // - scrollHeight контейнера перестал расти 3 прокрутки подряд // (значит список догружен полностью / Instagram перестал отдавать). // -------------------------------------------------------------- var stableCounter = 0; var lastHeight = -1; var maxStableChecks = 3; function runScrolling() { var currentAccounts = collectAccounts(); var currentHeight = scrollContainer.scrollHeight; var enoughCollected = user_count !== 0 && currentAccounts.length >= user_count; if (currentHeight === lastHeight) { stableCounter++; } else { stableCounter = 0; lastHeight = currentHeight; } var listExhausted = stableCounter >= maxStableChecks; if (enoughCollected || listExhausted) { printResults(currentAccounts); return; } scrollContainer.scrollBy(0, 600); setTimeout(runScrolling, speed_scrolling); } runScrolling(); } catch (e) { console.log('%cОшибка: ' + e.message, 'color: #a22e1c; font-size:18px;'); console.log('%cУбедитесь, что на странице Instagram открыт список "Подписчики" или "Подписки" (клик на счётчик в профиле), и запустите скрипт заново.', 'color: #a22e1c; font-size:16px;'); } })();