
Если вы регулярно занимаетесь автоматизацией или парсингом современных веб-приложений (Single Page Applications, SPA), вы наверняка сталкивались с ситуацией, когда данные на сайте загружаются, но не через обычные REST-запросы. Вместо этого в сетевой активности мелькают запросы к единственной точке входа, а тело запроса — это длинный JSON-код с полями query и variables. Добро пожаловать в мир GraphQL!
GraphQL — это язык запросов для API, который позволяет клиенту запрашивать ровно те данные, которые ему нужны, и не более. Для нас, автоматизаторов, это означает, что нам нужно «заглянуть» внутрь этого запроса и понять, как он работает.
Я сам долго искал универсальный и надежный инструмент для работы с GraphQL в автоматизации, и Playwright с его мощным API для перехвата сети оказался лучшим решением. В этой статье мы глубоко погрузимся в то, как настроить Playwright GraphQL Python для перехвата, анализа и даже модификации этих запросов, чтобы собирать большие объемы данных максимально эффективно, без необходимости рендерить всю страницу.
Почему обычный парсинг не работает с GraphQL
Чтобы понять, почему нам нужен Playwright, давайте посмотрим, как GraphQL меняет подход к сбору данных.
Отличия GraphQL от REST для автоматизации

- Одна точка входа: В REST вы обращаетесь к разным URL для разных ресурсов (например,
/api/users,/api/products). В GraphQL вы всегда обращаетесь к одной и той же точке входа (часто/graphql), а то, какие данные вы получите, определяется сложным JSON-запросом в теле. - Сложность тела запроса: Тело запроса GraphQL содержит сложную структуру
query(схема данных) иvariables(параметры пагинации, фильтры). Если мы просто попытаемся повторить этот запрос черезrequests, нам нужно точно воссоздать эту структуру, что непросто.
Playwright решает эту проблему, позволяя нам перехватить запрос, который уже правильно сформирован браузером, извлечь его структуру и выполнить его повторно, меняя только переменные (например, номер страницы или лимит).
Перехват GraphQL-запросов с помощью Playwright
Основной инструмент, который мы будем использовать, — это метод route() в Playwright. Он позволяет нам перехватить сетевой запрос до того, как он достигнет сервера, проанализировать его и, при необходимости, изменить.
Настройка перехвата и анализ данных
Мы настроим маршрутизацию (routing) так, чтобы она реагировала только на запросы, идущие к нашему GraphQL-эндпоинту.
Установка: Вы можете установить Playwright стандартным способом, используя pip. (Если вы ищете способ ускорить управление пакетами, рекомендую использовать uv — самый быстрый пакетный менеджер на Python).
pip install playwright
playwright install
Код для перехвата:
import asyncio
import json
from playwright.async_api import async_playwright
# Укажите адрес, по которому сайт отправляет GraphQL-запросы
GRAPHQL_ENDPOINT = "/api/graphql"
TARGET_URL = "https://example.com/data-page" # Страница, которая вызывает запрос
async def intercept_graphql_request():
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
# 1. Настраиваем маршрут для перехвата запросов
async def handle_route(route):
request = route.request
url = request.url
# Проверяем, что запрос идет к нашему GraphQL-эндпоинту
if GRAPHQL_ENDPOINT in url and request.method == "POST":
# 2. Получаем тело запроса
# Тело POST-запроса всегда в виде байтов, парсим его в JSON
try:
post_data = json.loads(request.post_data)
print("📄 Перехвачен GraphQL-запрос:")
print(f" Operation Name: {post_data.get('operationName')}")
print(f" Variables (фрагмент): {list(post_data.get('variables', {}).keys())[:2]}...")
# Сохраняем запрос для дальнейшего повтора
# В реальном коде сохраняем 'query' и 'variables'
global SAVED_QUERY
SAVED_QUERY = post_data
except Exception as e:
print(f"Ошибка парсинга тела запроса: {e}")
# Обязательно продолжаем запрос, чтобы страница загрузилась
await route.continue_()
# Регистрируем функцию-обработчик маршрута
await page.route(f"**{GRAPHQL_ENDPOINT}**", handle_route)
# Загружаем страницу, которая вызовет GraphQL-запрос
print(f"🔎 Загружаем целевую страницу: {TARGET_URL}")
await page.goto(TARGET_URL)
await page.wait_for_selector('div#loaded_data') # Ждем загрузки данных
await browser.close()
# Глобальная переменная для сохранения запроса
SAVED_QUERY = None
if __name__ == "__main__":
asyncio.run(intercept_graphql_request())
# Теперь SAVED_QUERY содержит полную структуру запроса, включая "query"
if SAVED_QUERY:
print("\n⚡ Структура запроса сохранена и готова к повтору без браузера!")
Объяснение кода:
page.route(f"**{GRAPHQL_ENDPOINT}**", handle_route): Устанавливает перехват всех POST-запросов, чья URL содержит наш эндпоинт.route.request.post_data: Получаем тело запроса (payload) в виде байтов, которое содержит сам GraphQL-запрос.json.loads(request.post_data): Десериализуем байты в словарь Python, который содержит поляquery(сам GraphQL-запрос) иvariables(параметры).await route.continue_(): Критически важно! Без этой строки Playwright заблокирует загрузку страницы. Мы говорим ему: «Проанализировав, продолжай запрос».
Автоматизация без браузера: повтор и модификация запроса
После того как мы перехватили и сохранили структуру GraphQL-запроса (SAVED_QUERY), самое неэффективное — это продолжать запускать Playwright для каждой новой страницы или порции данных. Наша цель — собрать данные напрямую через HTTP (используя библиотеку requests), но с помощью ранее перехваченного GraphQL-запроса.
Циклический сбор данных через requests
Используя перехваченный query и меняя только variables, мы можем быстро собирать большие объемы данных. В этом и заключается вся мощь подхода Playwright GraphQL Python: используем Playwright для анализа (один раз), а requests для сбора (много раз).
import requests
import json
import time
# Используем структуру запроса, которую мы сохранили на предыдущем шаге
# В реальном коде SAVED_QUERY был бы импортирован или загружен из файла
SAVED_QUERY = {
"operationName": "DataListQuery",
"query": "query DataListQuery($first: Int!, $after: String) { dataList(first: $first, after: $after) { edges { node { id title createdAt } } pageInfo { hasNextPage endCursor } } }",
"variables": {
"first": 10, # Количество элементов на странице
"after": None # Курсор для пагинации (null для первой страницы)
}
}
GRAPHQL_URL = "https://example.com/api/graphql" # Полный URL
def fetch_graphql_data(cursor_id=None, limit=10):
"""Выполняет запрос GraphQL напрямую через requests."""
# 1. Модифицируем переменные
SAVED_QUERY['variables']['first'] = limit
SAVED_QUERY['variables']['after'] = cursor_id
# 2. Отправляем запрос
headers = {
'Content-Type': 'application/json',
# Добавьте заголовки авторизации, если нужны (например, 'Authorization')
}
response = requests.post(
GRAPHQL_URL,
headers=headers,
data=json.dumps(SAVED_QUERY) # Отправляем JSON-строку
)
response.raise_for_status() # Вызывает ошибку для HTTP-статусов 4xx/5xx
return response.json()
# Пример цикла для пагинации (сбора нескольких страниц)
all_collected_data = []
current_cursor = None # Начинаем с первой страницы (null/None)
page_count = 0
while True:
page_count += 1
print(f"\n📅 Собираем страницу #{page_count}...")
try:
result = fetch_graphql_data(cursor_id=current_cursor)
# 1. Обработка данных
new_items = result['data']['dataList']['edges']
all_collected_data.extend(new_items)
print(f" Собрано новых элементов: {len(new_items)}")
# 2. Проверка пагинации (курсор)
page_info = result['data']['dataList']['pageInfo']
has_next_page = page_info['hasNextPage']
current_cursor = page_info['endCursor'] # Получаем курсор для следующей страницы
if not has_next_page:
print("✅ Пагинация завершена.")
break
# Пауза, чтобы не перегружать сервер
time.sleep(1)
except requests.exceptions.HTTPError as e:
print(f"Ошибка HTTP: {e}")
break
except Exception as e:
print(f"Непредвиденная ошибка: {e}")
break
print(f"\n🎉 Общий результат: собрано {len(all_collected_data)} элементов.")
Объяснение кода:
requests.post(..., data=json.dumps(SAVED_QUERY)): Мы отправляем точно такой же POST-запрос, как и браузер, но используем быструю библиотекуrequestsвместо тяжелого Playwright.- Модификация переменных: Перед каждым запросом мы меняем
current_cursor(часто называемыйafter) иlimit(first), чтобы запросить следующую порцию данных. - Пагинация по курсору (
endCursor): В GraphQL обычно используется курсорная пагинация, а не нумерация страниц. Мы извлекаемendCursorизpageInfoтекущего ответа и передаем его какafterв следующий запрос.
Преимущества и Best Practices подхода Playwright + GraphQL
Использование Playwright GraphQL Python таким образом дает огромные преимущества в скорости и эффективности по сравнению с традиционным парсингом.
Эффективность и рекомендации
Скорость и ресурсы: После первого запуска Playwright для анализа, вся дальнейшая работа по сбору данных выполняется быстрым и легким HTTP-запросом через requests. Это в разы быстрее, чем рендерить страницу в браузере для каждого блока данных.
Точность данных: Вы запрашиваете данные напрямую из API, получая чистый JSON, который легко обрабатывать, без необходимости парсить сложный HTML.
Обработка авторизации: Если для GraphQL-запроса требуется токен, его можно перехватить в заголовках первого запроса (или найти в локальном хранилище Playwright) и затем добавить в заголовок Authorization в ваших запросах requests. Узнайте, как безопасно хранить такие токены: Как безопасно настроить хранение API ключей Python.
Соблюдение лимитов: Даже при быстром сборе данных через requests, всегда включайте паузы (time.sleep(1)) между запросами, чтобы не перегружать сервер и не получить бан IP.
✅ Заключение
Автоматизация сбора данных с современных SPA-сайтов требует использования передовых инструментов. Подход Playwright GraphQL Python (используя Playwright для анализа схемы и requests для быстрого сбора) является самым эффективным способом работы с GraphQL API.
Вы научились перехватывать сложный запрос, извлекать его структуру и затем использовать эту структуру для циклического и контролируемого сбора данных, меняя только параметры пагинации. Это позволяет собирать информацию не в секундах, а в миллисекундах.
• Как ускорить Python в 10 раз с помощью асинхронности — узнайте больше о высокопроизводительных сетевых операциях, которые можно применить и к `requests`
• Как писать чистый и читаемый код на Python — структурируйте свой асинхронный код Playwright для большей надежности
• Как безопасно настроить хранение API ключей Python — защитите свои токены, которые могут потребоваться для авторизации в GraphQL
📢 Подписывайтесь на Telegram-канал PythonAuto, чтобы не пропустить новые гайды по автоматизации, парсингу и Python.
👉 Ваш интерес — лучшая мотивация для новых статей!