Эта статья была полезной?
Как использовать 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
BASE_URL="https://scraper.2captcha.com"
API_KEY="YOUR_API_KEY"
Не указывайте ключ в адресе запроса, не храните его в репозитории и не записывайте в логи. В коде берите его из переменной окружения.
Scraper API тарифицируется по количеству запросов, актуальные цены указаны на странице тарифов. Оплачивается каждая завершённая задача, даже если целевой сайт оказался недоступен. Задачи, отклонённые из-за неверных параметров (HTTP 422), не оплачиваются.
Шаг 1. Первый запрос
Синхронный метод создаёт задачу и возвращает её результат в ответе на тот же запрос:
bash
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
{
"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 проверяйте в таком порядке:
- status: "error" — сайт недоступен, причина указана в поле error.
- status: "warn" — страница получена, но условие ожидания не выполнено, подробности — в поле warning.
- http_code — 400 или больше: сайт отказал в доступе или не нашёл страницу.
- Только после этих проверок берите данные из 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
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
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
{"waitFor": {"element": ".price"}}
Доступны ещё три вида условий:
json
{"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
{"error": "timeout", "response_id": "01a17ca9-4164-7bc1-8ff9-725331f56d7b", "status": "pending"}
Задача при этом продолжает выполняться, поэтому не отправляйте её повторно. Вместо этого запрашивайте результат раз в несколько секунд:
bash
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
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
{
"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
"""Небольшой клиент 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
первая строка 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 запрашивайте результат, а не отправляйте задачу повторно.
Полезные ссылки
- Scraper API — https://rucaptcha.com/scraper/scraper-api
- Цены Scraper API — https://rucaptcha.com/pricing/scraper-api
- Документация Scraper API — https://rucaptcha.com/scraper/scraper-api/api
- Browser API — https://rucaptcha.com/scraper/browser-api
- Как использовать Browser API вместе с решением капч — https://rucaptcha.com/h/how-to-use-browser-api-with-captcha-solving