Логотип «2Captcha»Перейти на главную страницу
Туториалы по обходу капчи

Эта статья была полезной?

Как использовать Scraper API

Джерри Слиман
Джерри Слиман

Технический специалист

Scraper API позволяет получить содержимое веб-страницы одним HTTP-запросом. Вы передаёте адрес страницы, сервис открывает её в своём браузере и возвращает результат в одном из трёх видов: исходный HTML, который отдал сервер, текст загруженной страницы в формате Markdown или скриншот. Второй метод API выполняет поиск в Google и возвращает результаты, разбитые по типам.

Устанавливать Chrome и поддерживать работу браузера не нужно, поэтому Scraper API удобно использовать в серверных приложениях, cron-задачах и ИИ-агентах, которым нужно содержимое страниц.

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

Scraper API или Browser API?

Scraper API Browser API
Что вы отправляете Один HTTP-запрос с адресом страницы Команды удалённому браузеру
Что получаете HTML, Markdown, скриншот или результаты Google Браузер, которым вы управляете из Playwright или Puppeteer
Клики, формы, вход в аккаунт Нет Да
Для каких задач Сбор большого числа страниц, подготовка текста для анализа или LLM, поисковая выдача Многошаговые сценарии на одном сайте

Если задача требует кликов или входа в аккаунт, используйте Browser API RuCaptcha. Чтобы получить только содержимое страницы, проще воспользоваться Scraper API.

Что понадобится

  • Аккаунт RuCaptcha с положительным балансом и API-ключ RuCaptcha — тот же, что используется для решения капч.
  • Любой HTTP-клиент. В примерах используются curl и Python 3.9 или новее с библиотекой requests.

Задайте адрес API и ключ:

bash Copy
BASE_URL="https://scraper.2captcha.com"
API_KEY="YOUR_API_KEY"

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

Scraper API тарифицируется по количеству запросов, актуальные цены указаны на странице тарифов. Оплачивается каждая завершённая задача, даже если целевой сайт оказался недоступен. Задачи, отклонённые из-за неверных параметров (HTTP 422), не оплачиваются.

Шаг 1. Первый запрос

Синхронный метод создаёт задачу и возвращает её результат в ответе на тот же запрос:

bash Copy
curl -X POST "$BASE_URL/tasks/sync" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "task_type": "scrape",
    "url": "https://example.com",
    "data_format": "raw",
    "format": "json"
  }'

Ответ:

json Copy
{
  "status": "success",
  "http_code": 200,
  "headers": {"content-type": "text/html; charset=utf-8"},
  "body": "<!doctype html><html lang=en><head>..."
}

Поля ответа:

  • status — результат загрузки страницы;
  • http_code — код ответа целевого сайта;
  • headers — заголовки ответа сайта;
  • body — содержимое страницы.

Все параметры передаются на верхнем уровне тела запроса. Размер тела не должен превышать 10 000 байт. Если вложить параметры в объект params, API вернёт ошибку 422.

Шаг 2. Разбор ответа

В ответе есть три разных статуса, которые легко перепутать:

Где Что означает
HTTP-статус ответа API Завершилась ли задача: 200 — готово, 408 — ещё выполняется, 422 — ошибка
Поле status Чем закончилось выполнение метода: success, warn или error (сайт недоступен)
Поле http_code Код ответа целевого сайта: 200, 404, 500…

Код 200 от API не означает, что сайт тоже ответил кодом 200. Например, на запрос несуществующей страницы API вернёт HTTP 200, status: "success" и http_code: 404. Задача выполнена успешно, но сайт сообщил, что страница не найдена.

Результат метода scrape проверяйте в таком порядке:

  1. status: "error" — сайт недоступен, причина указана в поле error.
  2. status: "warn" — страница получена, но условие ожидания не выполнено, подробности — в поле warning.
  3. http_code — 400 или больше: сайт отказал в доступе или не нашёл страницу.
  4. Только после этих проверок берите данные из body.

Шаг 3. Выбор формата результата

Параметр data_format определяет, какие данные вы получите, а format — в каком виде они придут:

data_format С format: "json" С format: "raw"
raw HTML в поле body HTML (text/html)
markdown Markdown в поле body Markdown (text/markdown)
screenshot PNG в кодировке Base64 в поле body Файл PNG (image/png)

В режиме raw возвращается HTML, который отдал сервер, без изменений, внесённых JavaScript. Если страница подгружает данные скриптами, в raw их не будет. Markdown и скриншот создаются уже после загрузки страницы в браузере.

Поэтому для страниц, которые подгружают данные через JavaScript, запрашивайте Markdown или скриншот. Markdown также удобно передавать в LLM: текст в нём занимает меньше места, чем HTML.

md Copy
Example Domain

This domain is for use in documentation examples without needing permission. This is not a service; avoid relying on it for testing and monitoring purposes.

[Learn more](https://iana.org/help/example-domains)

Учтите, что Markdown формируется дольше, чем HTML.

Чтобы получить скриншот всей страницы, а не только видимой области, добавьте параметр "fullPage": true.

С format: "raw" ответ можно сразу сохранить в файл:

bash Copy
curl -X POST "$BASE_URL/tasks/sync" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"task_type": "scrape", "url": "https://example.com",
       "data_format": "screenshot", "format": "raw", "timeout": 120}' \
  --output page.png

Прежде чем открывать файл, проверьте заголовок Content-Type. Если сайт недоступен, вместо PNG API вернёт ошибку в формате JSON. Если задача не завершится за время ожидания, API вернёт её ID в виде текста (см. шаг 5). Поэтому в примере время ожидания увеличено до 120 секунд.

Шаг 4. Ожидание нужного содержимого

Параметр waitFor задаёт условие, которого сервис дождётся перед тем, как сохранить страницу. Условие передаётся объектом JSON:

json Copy
{"waitFor": {"element": ".price"}}

Доступны ещё три вида условий:

json Copy
{"element": "#content", "checkVisible": true}
{"text": "Content loaded"}
{"state": "load"}

В первом случае элемент должен быть ещё и видимым, во втором сервис ждёт появления текста, в третьем — нужного состояния загрузки страницы (load или domcontentloaded).

Что важно учитывать:

  • Ожидание ограничено 30 секундами. Если за это время условие не выполнено, API вернёт страницу в текущем виде со status: "warn" и пояснением в поле warning.
  • Условие text срабатывает только на текст, добавленный после загрузки страницы. Если текст уже есть в исходном HTML, условие не выполнится и ожидание продлится все 30 секунд. Для такого содержимого используйте условие element.
  • Увеличьте timeout запроса до 120 секунд. По умолчанию синхронный запрос ждёт результат 60 секунд. Ожидание условия вместе с загрузкой страницы может занять больше времени.

Если передать waitFor строкой, даже содержащей JSON, API сразу вернёт ошибку 422.

Шаг 5. Длительные задачи: код 408 и асинхронные запросы

Метод /tasks/sync ждёт результат не дольше, чем указано в timeout (от 1 до 120 секунд, по умолчанию 60). Если задача выполняется дольше, API возвращает код 408 и ID задачи:

json Copy
{"error": "timeout", "response_id": "01a17ca9-4164-7bc1-8ff9-725331f56d7b", "status": "pending"}

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

bash Copy
curl "$BASE_URL/tasks/result/$RESPONSE_ID"

Код 202 означает, что задача ещё выполняется, 200 — результат готов, 422 — задача завершилась ошибкой. Для этого запроса ключ не нужен, поэтому не передавайте response_id посторонним.

При пакетной обработке удобнее асинхронный режим. Метод POST /tasks/request принимает те же параметры и сразу возвращает код 201 и response_id. Результат запрашивается так же, как описано выше. Вместо опроса можно указать webhook_url: сервис сам отправит уведомление, когда задача завершится. Формат уведомления описан в документации.

Шаг 6. Поиск в Google

Метод google_search принимает обычную ссылку на поиск Google с параметром q:

bash Copy
curl -X POST "$BASE_URL/tasks/sync" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "task_type": "google_search",
    "url": "https://www.google.com/search?q=web+scraping+python&hl=en&gl=us",
    "page_num": 1,
    "format": "json"
  }'

Результаты возвращаются сгруппированными по типам:

json Copy
{
  "status": "success",
  "layout": "organic",
  "real_location": "United States",
  "groups": {
    "organic": {
      "count": 8,
      "items": [
        {
          "url": "https://realpython.com/python-web-scraping-practical-introduction/",
          "title": "A Practical Introduction to Web Scraping in Python",
          "position": 1,
          "page_position": {"page": 1, "position": 1}
        }
      ]
    },
    "video": {"count": 1, "items": ["…"]}
  }
}
  • Органическая выдача находится в groups.organic.items. Остальные группы (news, video, places, images, ai_overview и т. д.) есть в ответе, только если их показывает Google.
  • Несколько страниц выдачи. Параметр page_num принимает значения от 1 до 10, на каждой странице около 10 результатов, нумерация сквозная. Чтобы начать не с первой позиции, добавьте в ссылку start=10, start=20 и т. д.
  • Тип поиска задаётся в ссылке: tbm=nws — новости, udm=2 — картинки, udm=7 — видео, udm=1 — места. Поиск картинок, видео и мест выполняется дольше, поэтому для него укажите "timeout": 120.
  • Регион задаётся параметром gl в ссылке или параметром uule. В uule можно передать координаты, например "40.7128,-74.0060,5000" (радиус в метрах), или название места. Координаты надёжнее: название места Google учитывает не всегда и может вернуть выдачу для другой страны. Регион, который применил Google, указан в поле real_location. Иногда в этом поле стоит Unknown, даже если регион учтён.
  • status: "no_results" не является ошибкой: Google ничего не нашёл по запросу.
  • Выдача в Markdown. Чтобы получить результаты одним текстом, например для LLM, добавьте "data_format": "markdown".

Ошибки

HTTP-статус Причина Что делать
400 Неизвестный task_type или тело запроса больше 10 000 байт Исправьте запрос
401 Неверный ключ, ключ не передан или баланс нулевой либо отрицательный Проверьте ключ и баланс
402 Баланса не хватает на новую задачу Пополните баланс
408 Время синхронного ожидания истекло, задача ещё выполняется Запросите результат по response_id
422 Задача завершилась ошибкой: неверный адрес или waitFor, неподдерживаемый tbm Прочитайте описание в поле error и исправьте параметры
503 Задачи этого типа временно не принимаются из-за нагрузки Повторите запрос через время, указанное в заголовке Retry-After

Две ошибки возвращаются с кодом 200, потому что сама задача при этом завершена:

  • Сайт недоступен. Причины: домен не существует, не удалось установить соединение, произошла ошибка TLS, страница не загрузилась за отведённое время или сайт ответил кодом 5xx. В ответе будут status: "error", http_code: 0 (или код 5xx) и описание ошибки в поле error. Такой ответ приходит в формате JSON даже с format: "raw".
  • Сайт отвечает кодом 404 без содержимого. В этом случае API тоже возвращает status: "error" с ошибкой net::ERR_HTTP_RESPONSE_CODE_FAILURE. Если у страницы 404 есть содержимое, ответ будет обычным: status: "success", http_code: 404.

Полный рабочий пример (Python)

Клиент ниже объединяет все шаги: запускает задачу в синхронном режиме, при коде 408 переходит к опросу результата, проверяет ответ в описанном порядке и возвращает HTML, Markdown, скриншот в виде байтов PNG или результаты поиска Google.

python Copy
"""Небольшой клиент Scraper API: страница в виде HTML, Markdown или скриншота и поиск в Google.

    pip install requests
    export SCRAPER_API_KEY=...        # ваш API-ключ RuCaptcha
    python3 scraper_api_example_ru.py
"""
import base64
import os
import sys
import time

import requests

BASE_URL = "https://scraper.2captcha.com"
API_KEY = os.environ["SCRAPER_API_KEY"]
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}


class ScraperAPIError(Exception):
    pass


def run_task(task, wait=60, poll_every=3, give_up_after=300):
    """Запускает задачу синхронно; если синхронное ожидание закончилось (408), опрашивает результат."""
    r = requests.post(f"{BASE_URL}/tasks/sync", headers=HEADERS,
                      json={**task, "timeout": wait}, timeout=wait + 30)
    if r.status_code == 408:                       # задача ещё выполняется: повторно не отправляем
        response_id = r.json()["response_id"]
        deadline = time.time() + give_up_after
        while time.time() < deadline:
            time.sleep(poll_every)
            r = requests.get(f"{BASE_URL}/tasks/result/{response_id}", timeout=60)
            if r.status_code != 202:
                break
        else:
            raise ScraperAPIError(f"задача {response_id} не завершилась за {give_up_after} с")
    if r.status_code == 503:                       # задачи этого типа временно приостановлены
        raise ScraperAPIError(f"сервис перегружен, повторите через {r.headers.get('Retry-After')} с")
    if r.status_code != 200:                       # 400, 401, 402, 404, 410, 422 …
        raise ScraperAPIError(f"HTTP {r.status_code}: {r.text[:200]}")
    return r


def scrape(url, data_format="raw", wait_for=None, full_page=False):
    """Страница в виде текста HTML или Markdown либо байты PNG для скриншота."""
    task = {"task_type": "scrape", "url": url, "data_format": data_format, "format": "json"}
    if wait_for:
        task["waitFor"] = wait_for
    if full_page:
        task["fullPage"] = True
    result = run_task(task).json()
    # Порядок проверки: сайт недоступен, предупреждение, HTTP-статус целевого сайта.
    if result["status"] == "error":
        raise ScraperAPIError(f"сайт недоступен: {result.get('error')}")
    if result["status"] == "warn":
        print(f"предупреждение: {result['warning']}", file=sys.stderr)
    if result["http_code"] >= 400:
        raise ScraperAPIError(f"сайт ответил HTTP {result['http_code']}")
    body = result["body"]
    return base64.b64decode(body) if data_format == "screenshot" else body


def google_search(query, country="us", language="en", pages=1):
    """Органическая выдача списком словарей (url, title, description, position)."""
    url = f"https://www.google.com/search?q={requests.utils.quote(query)}&hl={language}&gl={country}"
    result = run_task({"task_type": "google_search", "url": url, "page_num": pages, "format": "json"}).json()
    if result["status"] == "error":
        raise ScraperAPIError(result.get("error", "поиск не выполнен"))
    if result["status"] == "no_results":
        return []
    print(f"регион, который применил Google: {result.get('real_location')}", file=sys.stderr)
    return result["groups"].get("organic", {}).get("items", [])


if __name__ == "__main__":
    # 1. Страница в формате Markdown: компактный текст, например для LLM.
    md = scrape("https://example.com", data_format="markdown")
    print(f"первая строка Markdown: {md.splitlines()[0]}")

    # 2. Скриншот той же страницы.
    png = scrape("https://example.com", data_format="screenshot")
    with open("page.png", "wb") as f:
        f.write(png)
    is_png = png.startswith(b"\x89PNG\r\n\x1a\n")
    print(f"скриншот: {len(png)} байт, PNG: {is_png}")

    # 3. Поиск в Google: первые три результата органической выдачи.
    for item in google_search("web scraping python")[:3]:
        print(f"{item['position']}. {item['title']} — {item['url']}")

Результат запуска:

text Copy
первая строка Markdown: Example Domain
скриншот: 51026 байт, PNG: True
регион, который применил Google: Unknown
1. A Practical Introduction to Web Scraping in Python — https://realpython.com/python-web-scraping-practical-introduction/
2. Python Web Scraping Tutorial — https://www.geeksforgeeks.org/python/python-web-scraping-tutorial/
3. How to start Web scraping with python? : r/learnpython — https://www.reddit.com/r/learnpython/comments/qzr8ir/how_to_start_web_scraping_with_python/

Решение частых проблем

Проблема Вероятная причина Решение
В HTML нет данных, которые видны в браузере Страница подгружает их через JavaScript Запросите markdown или screenshot
Код 408 при использовании waitFor или при поиске картинок, видео и мест 60 секунд ожидания по умолчанию не хватает Укажите "timeout": 120 или запросите результат по response_id
status: "warn" при ожидании text Текст уже есть в исходном HTML Используйте условие element
422, invalid URL "example.com" В адресе не указан протокол Укажите полный адрес https://…
422, params.waitFor must be an object waitFor передан строкой Передайте объект JSON
Сохранённый .png или .html не открывается Вместо файла API вернул ошибку в формате JSON или ID задачи Перед сохранением проверяйте Content-Type
Выдача Google не для той страны Google не учёл запрошенный регион Передайте координаты в uule, проверьте real_location и повторите запрос

Заключение

Scraper API позволяет одним запросом получить HTML, Markdown, скриншот страницы или выдачу Google. Браузер запускает сам сервис, поэтому вашему коду остаётся только обработать данные.

Чтобы парсер работал стабильно, соблюдайте четыре правила:

  • различайте HTTP-статус ответа API, поле status и код ответа сайта в поле http_code;
  • для страниц, которые подгружают данные через JavaScript, запрашивайте Markdown или скриншот;
  • в waitFor указывайте элемент, а в запросе — timeout 120 секунд;
  • при коде 408 запрашивайте результат, а не отправляйте задачу повторно.

Полезные ссылки