Блок People Also Ask (PAA) от Google появляется в значительной части поисковых запросов, располагаясь чуть ниже или между органическими результатами в виде стека раскрываемых пар «вопрос, ответ». Для тех, кто занимается контентом или SEO-исследованиями, это один из наиболее прямых сигналов реального намерения пользователя, который можно считать со страницы результатов: точные формулировки, которые используют люди, уточняющие вопросы и пробелы, которые конкуренты ещё не закрыли. Откройте один вопрос, и Google загрузит ниже новые связанные вопросы, так что один запрос способен разрастись в целое дерево намерений.
Это руководство показывает, как парсить Google People Also Ask с помощью Python надёжным и воспроизводимым способом. Вы создадите небольшой рабочий скрейпер, который получает отрендеренную страницу поиска Google через Crawling API, разбирает вопросы и ответы PAA, раскрывает вложенные элементы для захвата более глубоких уровней, которые упускают большинство скрейперов, и экспортирует чистые пары вопросов и ответов в JSON. Всё руководство ограничено публичными данными результатов поиска, доступными без авторизации, а раздел о законности в конце, не просто формальность, поэтому прочитайте его перед тем, как применять это на реальных объёмах.
Что вы создадите
Python-скрипт, который принимает публичный поисковый запрос Google, получает отрендеренный HTML через Crawling API и извлекает структурированную запись для каждого элемента PAA на странице, включая вопросы, выявляемые при раскрытии первого уровня. В качестве рабочего примера будет использоваться образец запроса, из каждой записи PAA мы извлекаем следующие поля:
- Вопрос, текст вопроса PAA именно в той формулировке, в которой его использует Google, что расширяет охват ключевых слов и тем.
- Ответ, краткий ответ-сниппет, который Google показывает при раскрытии элемента, полезен для исследования featured snippet.
- URL источника, страница, которую Google цитирует в качестве источника ответа, поддерживает анализ конкурентов.
- Дочерние элементы, вложенные вопросы, появляющиеся при раскрытии элемента, позволяют захватить более глубокие уровни дерева расширений.
Почему обычный запрос не работает в Google
Если отправить обычный HTTP-запрос на URL поиска Google из скрипта, раздел PAA чаще всего окажется пустым. Работают против вас два фактора. Во-первых, блок PAA рендерится JavaScript после инициализации страницы и обновляется каждый раз при клике на вопрос, поэтому необработанный HTML, возвращаемый обычным запросом, либо полностью не содержит блок, либо содержит лишь пустую оболочку. Во-вторых, Google отслеживает автоматизированный трафик: запросы, не похожие на браузерные, получают вызов верификации или блокируются до того, как добираются до результатов.
Таким образом, рабочий скрейпер PAA нуждается в двух вещах одновременно: в IP-адресе, воспринимаемом платформой как реальный посетитель, и в браузере, который действительно рендерит страницу и выполняет её скрипты. Можно собрать это самостоятельно с помощью headless-браузера и пула ротирующихся жилых прокси, но поддержка их работоспособности требует большей части усилий. Crawling API объединяет оба компонента в один вызов: вы отправляете ему URL поиска, он запрашивает его с доверенного жилого IP и рендерит страницу в настоящем браузере, ожидая загрузки динамического контента, после чего возвращает готовый HTML для парсинга.
Контент PAA загружается после инициализации страницы и изменяется при взаимодействии, поэтому запрос без рендеринга возвращает неполный или пустой блок. Отрендерите страницу, дайте скриптам немного времени на завершение, и раздел PAA появится в HTML. Crawling API выполняет рендеринг и ротацию IP на стороне сервера, и вы начинаете с бесплатного тарифа до 20 000 запросов без необходимости вводить данные карты.
Предварительные требования
Перед написанием кода вам потребуется несколько вещей. Ни одна из них не занимает много времени.
Базовые знания Python. Вы должны уметь писать и запускать Python-скрипты и устанавливать пакеты с помощью pip. Если BeautifulSoup для вас в новинку, в нашем руководстве по BeautifulSoup в Python рассмотрены основы парсинга, которые предполагаются в данном материале.
Python 3.8 или новее. Проверьте версию командой python --version. Если Python не установлен, загрузите его с python.org или через дистрибутив вроде Anaconda.
Аккаунт Crawlbase и JavaScript-токен. Зарегистрируйтесь, откройте дашборд и скопируйте токен со страницы документации аккаунта. Для поиска в Google требуется рендеринг, поэтому используйте JavaScript-токен (также называемый ключом с включённым браузером), а не обычный. Вы получаете до 20 000 бесплатных запросов: 1 000 при регистрации и больше по мере прохождения шагов онбординга. Обращайтесь с токеном как с паролем: он аутентифицирует ваши запросы, поэтому не добавляйте его в систему контроля версий.
Настройка проекта
Создайте виртуальное окружение, чтобы зависимости проекта были изолированы, затем установите две библиотеки, необходимые скрейперу.
python --version python -m venv paa_env source paa_env/bin/activate pip install requests beautifulsoup4
В Windows активируйте окружение командой paa_env\Scripts\activate вместо строки с source. Два зависимых пакета выполняют всю работу: requests отправляет HTTP-вызов к Crawling API, а beautifulsoup4 разбирает возвращаемый HTML, позволяя извлекать отдельные поля по CSS-селектору.
Шаг 1: Получение отрендеренной страницы поиска через Crawling API
Начните с получения отрендеренного HTML. Напишите небольшую функцию crawl(), которая отправляет URL поиска Google в Crawling API с вашим JavaScript-токеном, запрашивает рендеринг и ожидание, проверяет, что базовая страница вернула статус 200, и возвращает тело HTML. Параметры gl и hl в URL задают страну и язык, а page_wait даёт скриптам PAA время завершиться до захвата HTML.
import json import requests from urllib.parse import urlencode JS_TOKEN = "YOUR_CRAWLBASE_TOKEN" # use your JavaScript token API_ENDPOINT = "https://api.crawlbase.com/" def build_serp_url(query, gl="us", hl="en"): base = "https://www.google.com/search?" return base + urlencode({"q": query, "gl": gl, "hl": hl}) def crawl(url, page_wait=2000): params = { "token": JS_TOKEN, "url": url, "page_wait": page_wait, } response = requests.get(API_ENDPOINT, params=params, timeout=90) response.raise_for_status() data = json.loads(response.text) if data["original_status"] != 200: raise Exception(f"Unable to crawl '{url}'") return data["body"] if __name__ == "__main__": url = build_serp_url("how to scrape google", gl="us", hl="en") html = crawl(url) print(html[:500])
API возвращает JSON-обёртку, поэтому вы разбираете ответ с помощью json.loads и читаете два поля: original_status, статус, который вернул сам Google, а body, HTML отрендеренной страницы. Проверка original_status означает, что страница верификации или блокировка проявится как исключение, а не попадёт в парсер в виде мусора. Значение page_wait около 2000 миллисекунд обычно достаточно для загрузки блока PAA; таймаут запроса в 90 секунд даёт рендерингу время завершиться без зависания вызова. Запустите скрипт командой python crawling.py и вы должны увидеть реальную разметку поиска в первых 500 символах, что подтверждает работоспособность получения и рендеринга до написания единого селектора. Если раздел PAA выглядит неполным, увеличение page_wait, почти всегда первый шаг к исправлению.
Параметр page_wait работает именно потому, что запрос достиг Google как настоящий браузер с доверенного IP. Crawling API получает страницу поиска с ротирующегося жилого адреса, рендерит её в реальном headless-браузере и ожидает завершения скриптов PAA перед тем, как вернуть готовый HTML, избавляя вас от необходимости запускать headless-флот и sourcing жилого пула прокси самостоятельно. Направьте его на публичный URL поиска с бесплатного уровня.
Шаг 2: Парсинг вопросов и ответов PAA
Имея отрендеренный HTML, загрузите его в BeautifulSoup и извлеките каждый элемент PAA. Google не присваивает блоку PAA единого стабильного имени класса, поэтому надёжный подход предусматривает многоуровневые запасные селекторы: каждый элемент PAA содержит атрибут data-q с вопросом, а видимый ответ находится в раскрываемом блоке рядом с ним. Парсер ниже читает вопрос из data-q, ответ из отрендеренного текста, а цитируемый источник из первого исходящего якоря в элементе.
from bs4 import BeautifulSoup # Layered fallbacks: Google rotates these class names, so try # the stable data-q attribute first, then known container classes. PAA_ITEM_SELECTORS = [ "div[data-q]", "div.related-question-pair", "div[jsname='Cpkphb']", ] def find_paa_items(soup): for selector in PAA_ITEM_SELECTORS: items = soup.select(selector) if items: return items return [] def parse_paa(html): soup = BeautifulSoup(html, "html.parser") questions = [] for item in find_paa_items(soup): question = item.get("data-q") if not question: heading = item.select_one("div[role='heading'], span") question = heading.get_text(strip=True) if heading else None if not question: continue answer_el = item.select_one("div[data-attrid], div.wDYxhc, span.hgKElc") answer = answer_el.get_text(" ", strip=True) if answer_el else None link = item.select_one("a[href^='http']") source_url = link["href"] if link else None questions.append({ "question": question, "answer": answer, "source_url": source_url, "children": [], }) return questions
Структура записи точно соответствует первоначальному шаблону: question расширяет охват ключевых слов, answer помогает в работе с featured snippet, source_url поддерживает анализ конкурентов, а children зарезервировано для вложенных расширений, которые вы захватите на следующем шаге. Чтение вопроса сначала из стабильного атрибута data-q, а затем откат к тексту заголовка, гарантирует работоспособность парсера даже когда Google перетасовывает имена классов. Проверка if not question: continue пропускает пустые оболочки, так что в вывод попадают только реальные элементы PAA.
Google ротирует обфусцированные имена классов в разметке страницы поиска, поэтому работавший сегодня селектор может ничего не вернуть в следующем месяце. Именно поэтому парсер сначала пробует стабильный атрибут data-q, а не имя класса, и сохраняет список запасных вариантов. Когда все поля PAA возвращают пустые значения, повторно проверьте живую страницу результатов в инструментах разработчика браузера и обновите список. Записывайте, какой селектор сработал при каждом запуске, чтобы внезапное падение количества совпадений было легко заметить.
Шаг 3: Раскрытие вложенных элементов PAA
До этого момента вы извлекаете только начальный набор вопросов PAA. Этого уже достаточно, но это неполная картина: настоящая ценность скрыта глубже в дереве расширений. Когда пользователь нажимает на вопрос PAA, Google динамически загружает от двух до четырёх новых связанных вопросов под ним, каждый из которых может спровоцировать дальнейшие расширения. Чтобы захватить их, вы указываете Crawling API симулировать клики до захвата HTML, используя параметр css_click_selector, чтобы дополнительные вопросы загрузились в DOM, который вы затем парсите.
def crawl_expanded(url, page_wait=3000): # css_click_selector clicks each PAA question so Google loads # the nested questions before the HTML is captured. params = { "token": JS_TOKEN, "url": url, "page_wait": page_wait, "css_click_selector": "div[data-q]", } response = requests.get(API_ENDPOINT, params=params, timeout=90) response.raise_for_status() data = json.loads(response.text) if data["original_status"] != 200: raise Exception(f"Unable to crawl '{url}'") return data["body"] def scrape_with_expansions(query, gl="us", hl="en"): url = build_serp_url(query, gl, hl) # First pass: the visible PAA questions. base_items = parse_paa(crawl(url)) seen = {item["question"] for item in base_items} # Second pass: click to load the nested questions, then diff. expanded_items = parse_paa(crawl_expanded(url)) for item in expanded_items: if item["question"] not in seen: base_items[0]["children"].append(item) seen.add(item["question"]) return base_items
Алгоритм таков: сформируйте URL поиска с вашим запросом и гео-параметрами, один раз получите видимый набор PAA, затем получите снова с css_click_selector, установленным в селектор элементов PAA, чтобы API кликал по каждому вопросу и загружал новые в DOM. Парсинг обоих проходов с сохранением только новых вопросов даёт более глубокий уровень без дублирования исходных. На практике один запрос может вырасти с трёх-четырёх видимых вопросов до двенадцати-двадцати суммарно после нескольких раундов расширения. Этот шаг необязателен с точки зрения реализации, но именно здесь скрывается большая часть упускаемой ценности.
Шаг 4: Собираем всё вместе и экспортируем пары вопросов и ответов
Теперь объедините построение, получение, расширение и парсинг в один рабочий скрипт, который записывает структурированные данные PAA в JSON. Установка ensure_ascii=False сохраняет нелатинские символы читаемыми в файле, а не экранирует их в последовательности \u, что важно при выполнении запросов на других языках.
import sys def main(): query = sys.argv[1] if len(sys.argv) > 1 else "how to scrape google" country = sys.argv[2] if len(sys.argv) > 2 else "us" paa = scrape_with_expansions(query, gl=country) outfile = f"paa_{country}.json" with open(outfile, "w", encoding="utf-8") as f: json.dump(paa, f, ensure_ascii=False, indent=2) total = len(paa) + sum(len(q["children"]) for q in paa) print(f"Saved {total} PAA questions to {outfile}") if __name__ == "__main__": main()
Запустите полный скрипт командой python main.py "content gap analysis" uk. Он формирует URL поиска Google для данного запроса в выбранной стране, получает отрендеренный HTML, раскрывает элементы PAA и записывает пары вопросов и ответов в paa_uk.json. Этих нескольких функций достаточно: замените запрос или код страны, и парсер обработает всё, что придёт в ответ. Если результаты выглядят неполными, в первую очередь увеличьте page_wait, поскольку медленный рендеринг, наиболее частая причина короткого списка PAA.
Как выглядит результат
Вы получаете чистый список объектов с вопросами, каждый из которых содержит ответ, цитируемый источник и вложенные вопросы, захваченные при расширении, готовые для записи в JSON, загрузки в контент-бриф или в базу данных для кластеризации.
[ { "question": "Is it legal to scrape Google?", "answer": "Scraping public search results is generally permitted, but it can conflict with Google's terms of service.", "source_url": "https://example.com/is-scraping-google-legal", "children": [ { "question": "Can Google detect scraping?", "answer": "Yes, Google uses rate limits and behavioral signals to flag automated traffic.", "source_url": "https://example.com/google-bot-detection", "children": [] } ] }, { "question": "What is the best tool to scrape Google?", "answer": "A rendering API that handles proxies and JavaScript is the most reliable approach.", "source_url": "https://example.com/google-scraping-tools", "children": [] } ]
Каждый вопрос становится узлом, а каждое расширение добавляет новые узлы под ним в children. Отсюда экспорт в CSV для таблицы или преобразование дерева в контент-бриф займут лишь несколько строк кода. Поскольку каждая запись содержит URL источника, можно также группировать вопросы по доменам, которые цитирует Google, чтобы увидеть, кто уже владеет ответами.
Сравнение PAA по странам
Результаты PAA не являются универсальными: они варьируются в зависимости от местоположения и языка, поскольку Google персонализирует их под рынок пользователя. Для сравнения запустите один и тот же запрос с разными значениями gl и сравните результаты.
import time query = "best running shoes" markets = ["us", "uk", "de"] by_market = {} for gl in markets: items = scrape_with_expansions(query, gl=gl) by_market[gl] = {q["question"] for q in items} time.sleep(3) # Questions unique to the UK market. uk_only = by_market["uk"] - by_market["us"] print(f"UK-only PAA questions: {len(uk_only)}")
Сравнение уникальных вопросов, пересекающихся тем и различий в ответах по рынкам особенно полезно при выходе на новый регион или локализации контента. Вызов time.sleep между запросами задаёт темп, чтобы не отправлять их подряд. Для масштабирования за пределы нескольких запросов асинхронный Crawler позволяет передавать URL в пакетах и получать результаты через вебхук, вместо того чтобы ждать каждого вызова.
Оставаться незаблокированным
Даже при обработке рендеринга с доверенным IP за вас, Google отслеживает трафик, похожий на скрейперский. Несколько привычек помогают поддерживать работоспособность запуска.
- Задавайте темп запросов. Непрерывные обращения к странице поиска в жёстком цикле, это самый быстрый способ получить вызов. Распределяйте запросы и варьируйте запросы, а не обращайтесь к одному термину на полной скорости.
- Опирайтесь на ротацию. Пул жилых IP-адресов распределяет запросы по множеству реальных пользовательских адресов, чтобы ни один из них не превысил лимит. Crawling API делает это за вас; если вы используете собственный стек, именно здесь нужно всё правильно настроить.
- Следите за статусами и счётчиками. Если запуск начинает возвращать вызовы или количество PAA падает до нуля, это означает, что темп, уровень IP или селекторы требуют внимания. Воспринимайте это как сигнал, а не шум.
- Повторно проверяйте при пустых полях. Google периодически перетасовывает разметку. Если парсинг PAA перестаёт возвращать элементы, откройте живую страницу в инструментах разработчика и обновите список селекторов.
Более широкое руководство по стратегии см. в статье как парсить сайты без блокировки. Если вы хотите охватить все функции поиска за пределами PAA, наше руководство по парсингу страниц поиска Google охватывает органические результаты, рекламу и панель знаний, а специализированное руководство по Python для результатов поиска Google является парным к данному материалу. Получив данные PAA, статьи об извлечении и анализе SEO-данных Google и об использовании scraped-данных для улучшения SEO покажут, что с ними делать.
Законно ли парсить Google PAA?
Допустимость парсинга данных Google People Also Ask зависит от условий использования Google, вашей юрисдикции и того, что вы делаете с данными. Парсинг публично видимых результатов поиска находится в правовой серой зоне: вопросы и ответы в блоке PAA показываются всем без авторизации, однако условия использования Google накладывают ограничения на автоматизированный доступ, поэтому скрейпер может противоречить этим условиям вне зависимости от тщательности инструментария. Ни один из приведённых здесь примеров кода этого не меняет, он лишь обеспечивает техническую реализацию. Прочитайте условия использования Google и его robots.txt и руководствуйтесь ими как границами того, что вы собираете.
Несколько правил, которых стоит придерживаться. Собирайте только публичные данные PAA: вопросы, ответы и URL цитируемых источников, которые появляются на странице результатов без авторизации. Поддерживайте объём запросов достаточно низким, чтобы не перегружать серверы Google, и задавайте темп вместо выполнения запросов в полную силу. Не собирайте персональные данные, не распространяйте оптом тексты ответов, защищённые авторским правом, и не обращайтесь ни к чему за логином. URL цитируемых источников ведут на чужие страницы, поэтому относитесь к их контенту в соответствии с его собственной лицензией, а не воспринимайте его как свой для повторной публикации.
Там, где существует официальный путь, предпочитайте его. Google предлагает Programmable Search и другие официальные API для санкционированного доступа к данным поиска, а для потребностей производственного масштаба правильным решением является официальное соглашение о данных, а не более умный скрейпер. Это руководство намеренно ограничено публичными страницами PAA, поскольку именно здесь проходит граница, позволяющая сохранять обоснованную позицию: пары вопросов и ответов, которые может увидеть любой, используемые для исследований и планирования контента и ничего более.
Ключевые выводы
- PAA требует рендеринга. Блок загружается через JavaScript после инициализации страницы, поэтому обычный запрос возвращает пустой или отсутствующий раздел; отрендерите страницу и дайте ей немного времени.
-
Crawling API рендерит за реальным IP. Отправьте ему URL поиска с JavaScript-токеном и
page_wait, и он ротирует жилые IP, запускает настоящий браузер и возвращает готовый HTML. -
Парсите с многоуровневыми запасными вариантами. Читайте вопрос сначала из стабильного атрибута
data-q, затем откатывайтесь к именам классов, поскольку Google ротирует свою обфусцированную разметку. - Расширяйте дерево с помощью css_click_selector. Нажимайте видимые вопросы, чтобы Google загрузил вложенные, затем сравнивайте проходы для роста с трёх-четырёх вопросов до двенадцати-двадцати.
- Оставайтесь в рамках публичных данных. Соблюдайте условия использования Google и robots.txt, задавайте темп запросов, предпочитайте официальные API при масштабировании и никогда не обращайтесь к авторизованным данным или персональным данным.
Часто задаваемые вопросы
Что такое блок People Also Ask?
Блок PAA, это функция поиска Google, показывающая набор раскрываемых пар «вопрос, ответ», связанных с поисковым запросом. Он появляется в большой доле поисковых запросов и динамически расширяется при клике, загружая от двух до четырёх новых связанных вопросов каждый раз, что делает его настолько богатым источником данных о намерениях пользователей для SEO и контент-исследований.
Почему обычный запрос пропускает раздел PAA?
Блок PAA рендерится JavaScript после загрузки исходного HTML и обновляется при взаимодействии, поэтому обычный HTTP-запрос возвращает пустую оболочку или ничего. Получение через Crawling API с JavaScript-токеном рендерит страницу в настоящем браузере и ожидает выполнения скриптов, поэтому контент PAA присутствует в HTML, который вы парсите.
Как захватить вложенные вопросы PAA?
Используйте параметр css_click_selector, чтобы API нажимал на каждый видимый вопрос PAA до захвата HTML, что заставляет Google загружать связанные вопросы в DOM. Разберите этот расширенный HTML и сравните с первым проходом, чтобы собрать новые вопросы под children каждого элемента. Трёхуровневое расширение обычно даёт от двенадцати до двадцати вопросов на запрос.
Можно ли парсить Google PAA с помощью Python?
Да. С помощью requests и BeautifulSoup вы получаете отрендеренную страницу поиска и извлекаете вопрос, ответ, URL источника и вложенные дочерние элементы из каждого элемента PAA. Crawling API является мостом, доставляющим ваш запрос в Google с доверенного IP с включённым рендерингом. Для более широкого введения см. наше руководство по парсингу сайтов с помощью Python.
Почему PAA различается по странам?
Google персонализирует результаты PAA по стране и языку пользователя, поэтому один и тот же запрос в США и Великобритании часто возвращает разные вопросы, поскольку поведение пользователей, языковые паттерны и доступный контент различаются по рынкам. Запускайте один запрос с разными значениями gl и сравнивайте наборы вопросов, чтобы увидеть различия, что полезно при локализации контента.
Мои селекторы ничего не возвращают. Что изменилось?
Почти наверняка разметка Google. Google ротирует обфусцированные имена классов в своей странице поиска, поэтому работавшие в прошлом месяце селекторы могут сломаться, и парсер молча вернёт пустой список. Читайте вопрос сначала из стабильного атрибута data-q, сохраняйте список запасных селекторов, записывайте, какой из них сработал при каждом запуске, и повторно проверяйте живую страницу в инструментах разработчика при падении счётчика.
Обходите любой сайт в масштабе, без борьбы с инфраструктурой.
Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.

