Use Cases

Сбор данных исследований в социальных сетях с обработкой CAPTCHA

Исследовательский парсер соцсетей ломается не на разборе HTML, а на странице проверки: вместо профиля приходит форма reCAPTCHA v2 или виджет Cloudflare Turnstile, и сбор стоит, пока задачу не решат руками. Эта часть автоматизируется целиком: сборщик распознаёт разметку проверки, отправляет sitekey и pageurl в API CaptchaAI, получает токен и продолжает в той же сессии. Ниже — как устроен такой цикл на Python, где платформы включают проверку и какие лимиты частоты держать.

Речь идёт о публичных данных: открытые профили, посты по хэштегу, упоминания бренда в открытом поиске. Всё, что закрыто авторизацией чужого аккаунта, остаётся за рамками руководства.


Правовая рамка: что попадает в выборку

Для команд в России, Казахстане и Беларуси один ориентир стоит зафиксировать до первой строки кода. Если в выгрузке оказываются имена, ники, фото и контакты — это персональные данные, и на них распространяется 152-ФЗ «О персональных данных» и его аналоги в соседних юрисдикциях; в трансграничном проекте добавляется дисциплина уровня GDPR. Собирайте только поля, нужные для проверяемой гипотезы, и агрегируйте их на входе. Это не юридическая консультация, а базовая гигиена проекта.

Второй слой — правила площадки. Условия использования и robots.txt не заменяют закон, но очерчивают допустимый сбор. Если у площадки есть официальный API с нужными полями, берите API. Парсер с решением CAPTCHA оправдан там, где данные публичны, но интерфейса к ним нет.


Где платформы включают проверку

Проверка редко срабатывает случайно: она привязана к конкретным точкам — вход, повторяющийся поиск, серия обращений к профилям с одного адреса.

Платформа Тип проверки Когда срабатывает Контекст
Instagram reCAPTCHA v2 Вход, поиск, доступ к профилю Ограничение частоты запросов
Facebook reCAPTCHA v2 Вход, повторяющиеся поиски Контрольная точка безопасности
Twitter/X Cloudflare Turnstile Вход, доступ к API Фильтрация ботов
TikTok reCAPTCHA v3 Просмотр профилей, поиск Оценка качества трафика
LinkedIn Cloudflare Challenge Сбор данных профиля Обнаружение ботов
Reddit reCAPTCHA v2 Вход, интенсивный просмотр Защита от злоупотреблений

Все перечисленные типы CaptchaAI решает: reCAPTCHA v2 и v3, Cloudflare Turnstile и Challenge, а также GeeTest v3, image/OCR и grid. Чего сервис не решает — hCaptcha и FunCaptcha (Arkose Labs); GeeTest v4 заявлен как «скоро» и пока недоступен. Если площадка стоит на одном из этих трёх типов, планируйте исследование через её официальный API.


Цикл решения: in.phpres.php

Схема одинакова для всех типов и отличается только значением method. Сборщик отправляет POST на in.php с параметрами key, method, googlekey (это sitekey страницы) и pageurl, получает ID задачи и опрашивает res.php, пока вместо CAPCHA_NOT_READY не придёт токен. Токен подставляется в поле формы: g-recaptcha-response для reCAPTCHA, cf-turnstile-response для Turnstile. Класс ниже собирает это в один объект: сессия requests, детектор проверки и обработчик, который сам выбирает method.

import requests
import time
import re

CAPTCHAAI_KEY = "YOUR_API_KEY"
CAPTCHAAI_URL = "https://ocr.captchaai.com"


def solve_captcha(method, sitekey, pageurl, **kwargs):
    data = {
        "key": CAPTCHAAI_KEY,
        "method": method,
        "googlekey": sitekey,
        "pageurl": pageurl,
        "json": 1,
    }
    data.update(kwargs)
    resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data=data)
    task_id = resp.json()["request"]

    for _ in range(60):
        time.sleep(5)
        result = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
            "key": CAPTCHAAI_KEY, "action": "get",
            "id": task_id, "json": 1,
        })
        r = result.json()
        if r["request"] != "CAPCHA_NOT_READY":
            return r["request"]
    raise TimeoutError("Solve timeout")


class SocialMediaResearcher:
    def __init__(self, proxy=None):
        self.session = requests.Session()
        if proxy:
            self.session.proxies = {"http": proxy, "https": proxy}
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) "
            "AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 "
            "Mobile/15E148 Safari/604.1",
            "Accept-Language": "en-US,en;q=0.9",
        })

    def authenticate(self, login_url, credentials, sitekey):
        """Login with CAPTCHA handling."""
        # Load login page
        self.session.get(login_url)

        # Solve CAPTCHA
        token = solve_captcha("userrecaptcha", sitekey, login_url)

        # Submit login
        resp = self.session.post(login_url, data={
            **credentials,
            "g-recaptcha-response": token,
        })
        return resp.status_code == 200

    def collect_profiles(self, profile_urls):
        """Collect public profile data with CAPTCHA handling."""
        profiles = []

        for url in profile_urls:
            try:
                resp = self.session.get(url, timeout=30)

                # Handle CAPTCHA if triggered
                if self._has_captcha(resp.text):
                    resp = self._handle_captcha(resp.text, url)

                profiles.append({
                    "url": url,
                    "data": self._parse_profile(resp.text),
                    "status": "success",
                })
                time.sleep(5)  # Slow down between profiles

            except Exception as e:
                profiles.append({
                    "url": url,
                    "error": str(e),
                    "status": "failed",
                })

        return profiles

    def _has_captcha(self, html):
        return any(tag in html.lower() for tag in [
            'data-sitekey', 'g-recaptcha', 'cf-turnstile',
            'challenge-platform', 'captcha',
        ])

    def _handle_captcha(self, html, url):
        match = re.search(r'data-sitekey="([^"]+)"', html)
        if not match:
            return self.session.get(url)

        sitekey = match.group(1)

        if 'cf-turnstile' in html:
            token = solve_captcha("turnstile", sitekey, url)
            return self.session.post(url, data={"cf-turnstile-response": token})
        else:
            token = solve_captcha("userrecaptcha", sitekey, url)
            return self.session.post(url, data={"g-recaptcha-response": token})

    def _parse_profile(self, html):
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(html, "html.parser")
        return {
            "name": self._safe_text(soup, "h1, .profile-name"),
            "bio": self._safe_text(soup, ".bio, .profile-bio"),
            "followers": self._safe_text(soup, "[data-followers], .followers"),
            "posts": self._safe_text(soup, "[data-posts], .posts-count"),
        }

    def _safe_text(self, soup, selector):
        el = soup.select_one(selector)
        return el.get_text(strip=True) if el else ""

Две детали здесь пропускают чаще всего. Первая: токен живёт ограниченное время — форму отправляют сразу после ответа, а не накапливают токены заранее. Вторая: _has_captcha смотрит на текст ответа, а не на код состояния, потому что страницу проверки обычно отдают со статусом 200, и наивная проверка resp.status_code == 200 засчитает её как успех.


Сценарий 1: исследование хэштега

Типовая задача для агентства в Алматы или продуктовой команды в Минске — собрать публичные посты по нескольким хэштегам и посчитать распределение тональности. Постраничный обход упрётся в проверку уже на третьей-четвёртой странице, поэтому обработчик подключается прямо в цикле.

def research_hashtag(hashtag, platform_url, pages=5):
    """Collect posts for a specific hashtag."""
    researcher = SocialMediaResearcher(
        proxy="http://user:[email protected]:5000"
    )

    all_posts = []
    for page in range(pages):
        url = f"{platform_url}/explore/tags/{hashtag}?page={page}"
        resp = researcher.session.get(url, timeout=30)

        if researcher._has_captcha(resp.text):
            resp = researcher._handle_captcha(resp.text, url)

        from bs4 import BeautifulSoup
        soup = BeautifulSoup(resp.text, "html.parser")
        posts = soup.select(".post-item, article")
        for post in posts:
            all_posts.append({
                "text": post.get_text(strip=True)[:500],
                "hashtag": hashtag,
                "page": page,
            })

        time.sleep(5)

    return all_posts

Пауза в пять секунд не декоративная: она удерживает частоту в диапазоне, при котором проверка срабатывает реже, и снижает расход потоков.


Сценарий 2: ежедневный мониторинг упоминаний бренда

Второй частый случай — регулярный прогон по нескольким площадкам с набором ключевых слов и сводкой в JSON. Важна не скорость, а то, что ночное задание доходит до конца без ручного вмешательства.

import json
from datetime import datetime


class BrandMonitor:
    def __init__(self, brand_name, keywords, proxy=None):
        self.brand = brand_name
        self.keywords = keywords
        self.researcher = SocialMediaResearcher(proxy=proxy)

    def daily_scan(self, platform_urls):
        """Run daily brand mention scan across platforms."""
        report = {
            "brand": self.brand,
            "date": datetime.now().isoformat(),
            "platforms": {},
        }

        for name, url in platform_urls.items():
            mentions = []
            for keyword in self.keywords:
                search_url = f"{url}/search?q={keyword}"
                try:
                    resp = self.researcher.session.get(search_url, timeout=30)

                    if self.researcher._has_captcha(resp.text):
                        resp = self.researcher._handle_captcha(
                            resp.text, search_url,
                        )

                    from bs4 import BeautifulSoup
                    soup = BeautifulSoup(resp.text, "html.parser")
                    results = soup.select(".search-result, .post")
                    mentions.append({
                        "keyword": keyword,
                        "count": len(results),
                    })
                    time.sleep(5)
                except Exception as e:
                    mentions.append({
                        "keyword": keyword,
                        "error": str(e),
                    })

            report["platforms"][name] = mentions

        return report


# Usage
monitor = BrandMonitor(
    brand_name="CaptchaAI",
    keywords=["captchaai", "captcha ai", "captcha solver"],
    proxy="http://user:[email protected]:5000",
)
report = monitor.daily_scan({
    "twitter": "https://twitter-alternative.example.com",
    "reddit": "https://www.reddit.com",
})
print(json.dumps(report, indent=2))

Сводка складывается в один отчёт, а ошибка по одному ключевому слову не роняет прогон — для ежедневного расписания это принципиально.


Частота запросов и объём потоков

Ориентиры ниже — рабочая отправная точка: пороги площадки меняют без предупреждения, поэтому держите их в конфиге, а не в коде.

Платформа Частота запросов Длительность сеанса
Instagram 1/10 с до 5 мин, затем пауза
Facebook 1/5 с до 10 мин
Twitter/X 1/3 с до 15 мин
TikTok 1/5 с до 5 мин
LinkedIn 1/10 с до 5 мин
Reddit 1/2 с до 30 мин

Сколько потоков заказывать? Поток в CaptchaAI — это одна задача в работе; после решения он сразу берёт следующую, а число решений внутри тарифа не ограничено. При частоте порядка одного запроса в 5 с и проверке примерно на каждом двадцатом обращении последовательному сборщику хватает BASIC ($15/мес, 5 потоков). Конвейер на 6–8 параллельных площадок укладывается в STANDARD ($30/мес, 15 потоков), ночной прогон по большому списку профилей — в ADVANCE ($90/мес, 50 потоков). Для команд, выставляющих счета в волатильной валюте, плоский месячный платёж в USD удобнее оплаты за каждое решение: бюджет известен заранее.


Что ломается чаще всего

Симптом Причина Что делать
Проверка на каждом запросе Адрес помечен как источник нагрузки Снизьте частоту, используйте авторизованный сетевой выход
Пустая страница вместо профиля Контент доступен только после входа Проверьте публичность данных; если нет — исключите из выборки
Зацикленная проверка Cloudflare Несогласованные сигналы клиента Работайте через реальный браузер (Puppeteer, Playwright)
Токен не принимается формой Просрочен или не в том поле Отправляйте форму сразу, проверьте имя поля
Ответ не совпадает с браузером Разница по региону и cookie Сопоставьте регион выхода с аудиторией
CAPCHA_NOT_READY дольше минуты Опрос идёт без пауз Опрашивайте res.php с интервалом 5 с

Часто задаваемые вопросы

Сколько потоков нужно исследовательскому парсеру соцсетей?

Считайте по числу одновременных задач, а не по объёму данных. Последовательный сборщик редко выходит за 5 потоков BASIC; несколько площадок параллельно — уровень STANDARD (15 потоков). Решения внутри тарифа не лимитированы, поэтому пересчитывать бюджет при росте выборки не придётся.

Какие типы проверок остаются вне зоны покрытия?

hCaptcha и FunCaptcha (Arkose Labs) CaptchaAI не поддерживает, GeeTest v4 заявлен как «скоро». Поддерживаются reCAPTCHA v2/v3, Cloudflare Turnstile и Challenge, GeeTest v3, image/OCR и grid; CaptchaFox (beta), Friendly Captcha (beta) и Lemin (beta) — в бета-статусе.

Почему проверка возвращается, хотя токен приняли?

Токен закрывает конкретную задачу, а не сессию целиком. Если проверка появляется снова через несколько запросов, причина в частоте обращений: увеличьте паузу и сократите длительность сеанса по таблице выше.

Что делать, если нужные данные закрыты авторизацией?

Считать их вне выборки. Автоматический вход в чужие аккаунты и заведение учётных записей ради снятия лимитов — не исследовательский сценарий. Публичные данные плюс официальный API площадки почти всегда закрывают реальную гипотезу.

Можно ли обойтись без прокси?

Часто да — если частота умеренная и сбор идёт с авторизованного сетевого выхода вашей организации. В исследовательском контуре прокси решают задачу географии: посмотреть публичную выдачу глазами нужного региона.


Связанные руководства


Соберите публичные данные соцсетей без ручных остановок — получите API-ключ CaptchaAI и подключите решение проверки в парсер.

Комментарии для этой статьи отключены.