Яндекс является поисковой системой, которую большинство жителей России выбирают в первую очередь, и он пользуется схожей популярностью в ряде соседних стран. По большинству оценок его доля превышает половину российского поискового рынка, что делает публичные результаты полезным сигналом для всех, кто отслеживает спрос на русскоязычные запросы, региональные позиции или представленность бренда на рынке, где не доминирует Google. Страница результатов предоставляет те же структурированные данные, которые нужны любому SERP-инструменту: заголовки, ссылки, сниппеты и порядок их отображения.

В этом руководстве показано, как парсить результаты поиска Яндекса с помощью Python надёжным способом. Вы создадите небольшой, выполняемый скрапер, который получает отрендеренную страницу результатов через Crawling API, разбирает каждый органический результат с помощью BeautifulSoup и экспортирует данные в JSON и CSV. Всё руководство ограничено публичными данными поисковой выдачи, которые любой может видеть без аккаунта, а раздел о законности в конце не является формальностью, поэтому прочитайте его, прежде чем направлять скрапер на реальный объём.

Что вы создадите

Скрипт на Python, который принимает публичный поисковый URL Яндекса, получает HTML через Crawling API и извлекает структурированную запись для каждого органического результата на странице. В качестве работающего примера используется запрос "Winter Jackets", и из каждого результата мы извлекаем следующие поля:

  • Заголовок текст заголовка результата, как отображается в листинге.
  • URL целевая ссылка результата.
  • Описание сниппет, отображаемый под заголовком.
  • Позиция ранг результата на странице, считая сверху.

Почему обычный запрос не работает на Яндексе

Если отправить обычный HTTP-запрос на URL результатов Яндекса из скрипта, вы редко получите чистую страницу, которую видите в собственном браузере. Яндекс внимательно отслеживает автоматический трафик. Запросы, не похожие на реальный браузер, или слишком быстро приходящие с одного адреса, встречают страницу верификации (интерстициал «Вы не робот?») или блокируются полностью ещё до попадания в листинги. Повторение одного и того же IP при большом количестве запросов быстро срабатывает эти проверки.

Таким образом, работающий скрапер Яндекса требует двух вещей в одном запросе: IP, который платформа воспринимает как реального посетителя, и, когда страница использует скрипты, браузер, который их рендерит. Вы можете собрать это самостоятельно из безголового браузера и пула ротирующих резидентских прокси, но поддержание их в рабочем состоянии составляет большую часть работы. Crawling API объединяет оба компонента в одном вызове: вы отправляете ему URL, он получает данные с доверенного IP и рендерит при необходимости, а возвращает готовый HTML для разбора.

Почему важна ротация IP

Антибот-проверки Яндекса в значительной мере опираются на частоту запросов с каждого адреса. Нескольких быстрых запросов с одного IP достаточно, чтобы запустить верификационный интерстициал. Crawling API ротирует через множество адресов на стороне сервера, распределяя запросы между ними так, чтобы ни один не превышал лимит. Вы можете начать с до 20 000 бесплатных запросов без привязки карты.

Предварительные требования

Несколько вещей, которые нужно иметь под рукой. На это не уйдёт много времени.

Базовые знания Python. Вы должны уметь писать и запускать скрипт на Python и устанавливать пакеты с помощью pip. Если BeautifulSoup вам незнаком, наше руководство по использованию BeautifulSoup в Python охватывает основы разбора, на которые опирается этот материал.

Python версии 3.8 или выше. Проверьте версию командой python --version. Если не установлен, загрузите с python.org или через дистрибутив вроде Anaconda.

Аккаунт Crawlbase и токен. Зарегистрируйтесь, откройте панель управления и скопируйте токен запроса со страницы документации аккаунта. Crawlbase предоставляет два типа токенов: Normal-токен для статических страниц и JavaScript-токен для страниц с обилием скриптов. Органические результаты Яндекса возвращаются в исходном HTML, поэтому здесь правильным выбором является Normal-токен. Вы получаете до 20 000 бесплатных запросов. Относитесь к токену как к паролю: он аутентифицирует ваши запросы, поэтому не храните его в системе контроля версий.

Настройка проекта

Создайте виртуальное окружение, чтобы зависимости проекта оставались изолированными, затем установите три библиотеки, необходимые скраперу.

bash
python --version

python -m venv yandex_env
source yandex_env/bin/activate

pip install crawlbase beautifulsoup4 pandas

В Windows активируйте окружение командой yandex_env\Scripts\activate вместо строки с source. Три зависимости выполняют основную работу: crawlbase является официальным клиентом, отправляющим запросы в Crawling API, beautifulsoup4 разбирает возвращаемый HTML, позволяя извлекать поля по CSS-селектору, а pandas обрабатывает экспорт в CSV в конце.

Шаг 1: Получение страницы через Crawling API

Начните с получения HTML. URL поиска Яндекса, это основной домен плюс запрос в параметре text, то есть https://yandex.com/search/?text=Winter%20Jackets ищет "Winter Jackets". Кодируйте запрос через urllib.parse.quote, чтобы пробелы и специальные символы корректно передавались. Напишите небольшую функцию fetch_page_html(), которая передаёт URL в Crawling API с вашим токеном, проверяет, что Яндекс вернул статус 200, и возвращает декодированное тело HTML. Проверка статуса перед разбором делает ошибки явными.

python
from crawlbase import CrawlingAPI
from urllib.parse import quote

API_TOKEN = "YOUR_CRAWLBASE_TOKEN"  # replace with your Normal token
crawling_api = CrawlingAPI({"token": API_TOKEN})

def fetch_page_html(url):
    response = crawling_api.get(url)
    if response["headers"]["cb_status"] == "200":
        return response["body"].decode("utf-8")
    print(f"Request failed with Crawlbase status {response['headers']['cb_status']}")
    return None

if __name__ == "__main__":
    url = f"https://yandex.com/search/?text={quote('Winter Jackets')}"
    html = fetch_page_html(url)
    if html:
        print(html[:500])

Вызов crawling_api.get(url) возвращает ответ, в котором headers["cb_status"], это статус, который вернул сам Яндекс, а body, сырые байты страницы. Проверка cb_status == "200" означает, что блокировка или страница верификации проявляется как явная ошибка, а не передаёт мусор в парсер. Декодирование тела как UTF-8 обеспечивает читаемость кириллических заголовков и описаний. Сохраните файл как yandex_scraper.py, запустите командой python yandex_scraper.py, и в первых 500 символах вы должны увидеть реальную разметку результатов, что подтверждает работоспособность получения данных до написания единого селектора.

Crawlbase Yandex Scraper

Проверка cb_status (legacy pc_status) всегда читает 200, потому что запрос добрался до Яндекса, выглядя как реальный посетитель, минуя интерстициал «Вы не робот?». Crawling API получает страницу с ротирующего IP, при необходимости рендерит её и возвращает готовый HTML, поэтому не нужно запускать собственный парк безголовых браузеров и искать резидентский пул прокси. Сначала попробуйте на URL публичных результатов на бесплатном тарифе.

Шаг 2: Разбор результатов с помощью BeautifulSoup

Имея HTML, загрузите его в BeautifulSoup и извлеките каждый результат по его селектору. Яндекс оборачивает каждый органический результат в контейнер .serp-item; внутри него заголовок находится в h2.organic__url-text, целевая ссылка в a.organic__url, а сниппет в div.organic__content-wrapper. Чтобы подтвердить это на живой странице, откройте URL результатов Яндекса в браузере, щёлкните правой кнопкой на результате, выберите «Просмотреть код» и прочитайте имена классов у элемента; приведённые ниже селекторы соответствуют макету на момент написания.

python
from bs4 import BeautifulSoup

def scrape_yandex_search(html_content):
    soup = BeautifulSoup(html_content, "html.parser")

    search_results = []
    for position, result in enumerate(soup.select(".serp-item"), start=1):
        title_element = result.select_one("h2.organic__url-text")
        url_element = result.select_one("a.organic__url")
        description_element = result.select_one("div.organic__content-wrapper")

        if not title_element or not url_element:
            continue

        search_results.append({
            "position": position,
            "title": title_element.get_text(strip=True),
            "url": url_element["href"],
            "description": description_element.get_text(strip=True) if description_element else None,
        })

    return search_results

Выбор .serp-item даёт по одному элементу на результат, а enumerate(..., start=1) предоставляет позицию бесплатно в ходе цикла, поэтому ранг определяется по порядку на странице, а не из хрупкого атрибута. Чтение URL из атрибута href элемента a.organic__url разделяет URL назначения и текст заголовка. Защита if not title_element or not url_element: continue пропускает всё, что не является настоящим органическим результатом, не допуская рекламные блоки и случайную разметку в вывод. Описание возвращается как None, когда его контейнер отсутствует.

Селекторы дрейфуют

Яндекс периодически пересматривает фронтенд-разметку, и имена классов вроде organic__url-text могут меняться при редеплое. Относитесь к приведённым выше селекторам как к стартовому шаблону, а не к контракту. Когда поле возвращается пустым для каждого результата, заново проинспектируйте живую страницу в браузерных инструментах разработчика и обновите селектор. Периодическое обслуживание селекторов является нормой для любого производственного скрапера, а не признаком сбоя.

Шаг 3: Сборка

Теперь соединим получение и разбор в один выполняемый скрипт. Получите отрендеренную страницу результатов, передайте HTML в парсер и выведите структурированный результат в виде JSON. Установка ensure_ascii=False сохраняет кириллические символы читаемыми в выводе, а не экранирует их в последовательности \u.

python
from crawlbase import CrawlingAPI
from bs4 import BeautifulSoup
from urllib.parse import quote
import json

API_TOKEN = "YOUR_CRAWLBASE_TOKEN"
crawling_api = CrawlingAPI({"token": API_TOKEN})

def fetch_page_html(url):
    response = crawling_api.get(url)
    if response["headers"]["cb_status"] == "200":
        return response["body"].decode("utf-8")
    print(f"Request failed with Crawlbase status {response['headers']['cb_status']}")
    return None

def scrape_yandex_search(html_content):
    soup = BeautifulSoup(html_content, "html.parser")
    search_results = []
    for position, result in enumerate(soup.select(".serp-item"), start=1):
        title_element = result.select_one("h2.organic__url-text")
        url_element = result.select_one("a.organic__url")
        description_element = result.select_one("div.organic__content-wrapper")
        if not title_element or not url_element:
            continue
        search_results.append({
            "position": position,
            "title": title_element.get_text(strip=True),
            "url": url_element["href"],
            "description": description_element.get_text(strip=True) if description_element else None,
        })
    return search_results

def main():
    search_query = "Winter Jackets"
    url = f"https://yandex.com/search/?text={quote(search_query)}"
    html_content = fetch_page_html(url)
    if html_content:
        search_results = scrape_yandex_search(html_content)
        print(json.dumps(search_results, ensure_ascii=False, indent=2))

if __name__ == "__main__":
    main()

Запустите полный скрипт командой python yandex_scraper.py. Он получает страницу результатов для "Winter Jackets", извлекает запись для каждого органического листинга и печатает список в виде отформатированного JSON. Те же две функции, это всё, что вам нужно: замените запрос в main(), и парсер обработает всё, что придёт.

Как выглядит результат

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

json
[
  {
    "position": 1,
    "title": "Best Winter Jackets of 2024 | Switchback Travel",
    "url": "https://www.switchbacktravel.com/best-winter-jackets",
    "description": "Patagonia Tres 3-in-1 parka. Category: Casual. Fill: 4.2 oz. of 700-fill-power down."
  },
  {
    "position": 2,
    "title": "Winter jacket: купить по низкой цене на Яндекс Маркете",
    "url": "https://market.yandex.ru/search?text=winter%20jacket",
    "description": "Купить winter jacket: 97 предложений, низкие цены, быстрая доставка."
  },
  {
    "position": 3,
    "title": "Amazon.com: Winter Jackets",
    "url": "https://www.amazon.com/Winter-Jackets/s?k=Winter+Jackets",
    "description": "CAMEL CROWN Men's Mountain Snow Waterproof Ski Jacket, Detachable Hood, Fleece Parka."
  }
]

На что стоит обратить внимание в реальном выводе: кириллический заголовок на позиции 2 сохраняет оригинальные символы благодаря декодированию UTF-8 и параметру ensure_ascii=False. Если вместо них вы видите escape-последовательности \u, один из этих двух шагов отсутствует.

Масштабирование по страницам и запросам

Один запрос на одной странице, это демонстрация; реальная задача охватывает несколько поисковых запросов и более глубокие результаты. Яндекс использует параметр запроса p для пагинации с нулевым базовым индексом: p=0, первая страница, p=1, вторая и так далее. Структура остаётся прежней: формируйте каждый URL со следующим номером страницы, получайте его через Crawling API и разбирайте той же функцией. Небольшое изменение позволяет поддерживать непрерывную нумерацию позиций по страницам вместо перезапуска с 1 на каждой, а короткая пауза между запросами задаёт темп обхода.

python
import time
from urllib.parse import quote

def scrape_all_pages(query, max_pages=5):
    base_url = f"https://yandex.com/search/?text={quote(query)}&p="
    all_results = []
    position = 1

    for page in range(max_pages):
        html_content = fetch_page_html(base_url + str(page))
        if not html_content:
            break
        page_results = scrape_yandex_search(html_content)
        for result in page_results:
            result["position"] = position
            position += 1
        all_results.extend(page_results)
        time.sleep(2)  # pace requests to respect the server

    return all_results

Цикл по умолчанию обходит пять страниц, получает каждую через Crawling API и переназначает текущую position, чтобы ранги были непрерывными с первой по пятую страницу. Двухсекундный time.sleep между запросами не даёт интенсивно обращаться к Яндексу в плотном цикле. Увеличивайте max_pages только настолько, насколько вам действительно нужно; более глубокие результаты обычно менее релевантны. Если вы предпочитаете маршрутизировать собственный трафик через ротирующий пул вместо управляемого API, Smart AI Proxy предоставляет ту же IP-ротацию как прокси-эндпоинт с возможностью подключения.

Экспорт в CSV

JSON удобен в терминале, но CSV сразу открывается в таблице, что большинство аналитиков и предпочитают. pandas преобразует список словарей результатов в CSV двумя строками.

python
import pandas as pd

def save_to_csv(results, filename="yandex_search_results.csv"):
    df = pd.DataFrame(results)
    df.to_csv(filename, index=False, encoding="utf-8-sig")
    print(f"Saved {len(results)} results to {filename}")

if __name__ == "__main__":
    results = scrape_all_pages("Winter Jackets", max_pages=3)
    save_to_csv(results)

Построение DataFrame из списка словарей даёт по одному столбцу на поле (позиция, заголовок, url, описание) и по одной строке на результат. Параметр index=False убирает собственный индекс строк pandas из файла, а encoding="utf-8-sig" добавляет метку порядка байтов, чтобы кириллические описания корректно открывались в Excel, а не превращались в мусор. Отсюда можно расширить тот же паттерн для записи в базу данных, например SQLite, если предпочитаете хранилище с возможностью запросов.

Защита от блокировок

Даже при доверенном IP, обрабатываемом за вас, Яндекс отслеживает трафик, похожий на скрапер, а его антибот-проверки строже многих западных целей. Несколько привычек помогут сохранить здоровье прогона.

  • Задавайте темп запросов. Интенсивный обход страниц результатов в плотном цикле, самый быстрый способ получить верификационный интерстициал. Распределяйте запросы и чередуйте запросы вместо перебора одного термина на полной скорости.
  • Опирайтесь на ротацию. Пул IP-адресов распределяет запросы, чтобы ни один не превышал поадресные ограничения Яндекса. Crawling API делает это за вас; если вы строите собственный стек, именно здесь нужно всё сделать правильно.
  • Читайте коды состояния. Прогон, начавший возвращать запросы или не-200 статусы, говорит вам, что текущая частота или уровень IP больше недостаточны. Относитесь к этому как к сигналу снизить темп, а не как к шуму.
  • Обновляйте селекторы при пустых полях. Яндекс периодически меняет разметку. Если результаты перестали разбираться, откройте живую страницу в инструментах разработчика и обновите селекторы.

Для общего руководства смотрите как парсить сайты без блокировок и более глубокий анализ в статье о том, как обходить CAPTCHA при веб-скрапинге. Тот же подход переносится на другие поисковые системы: наши руководства по парсингу результатов Bing и парсингу страниц Google используют ту же структуру «получи, затем разбери» с другими селекторами.

Законно ли парсить Яндекс?

Разрешён ли парсинг Яндекса, зависит от условий использования Яндекса, вашей юрисдикции и того, что вы делаете с данными. Как и большинство поисковых систем, Яндекс устанавливает ограничения на автоматический доступ в своих условиях, поэтому парсинг может нарушать их вне зависимости от аккуратности вашего инструментария. Ни один из кодов здесь не меняет этого; он только делает рабочей техническую часть. Прочитайте условия Яндекса и его robots.txt и относитесь к обоим как к границе того, что вы собираете.

Несколько линий, которых стоит придерживаться. Собирайте только публичные данные поисковой выдачи: заголовки, ссылки, описания и позиции, которые любой может видеть на странице результатов без аккаунта. Поддерживайте объём запросов достаточно низким, чтобы не нагружать серверы Яндекса, и задавайте темп обхода, а не запускайте его на полную скорость. Яндекс публикует официальные продукты для некоторых структурированных данных, например Яндекс.Карты и API Яндекс.Маркета, поэтому там, где существует санкционированный эндпоинт для ваших нужд, он является лучшим путём, чем парсинг SERP.

Это руководство намеренно ограничено публичными страницами поисковой выдачи, поскольку именно это является линией, которая делает работу защищённой. Оно не охватывает ничего за логином, данные аккаунта или персональные данные, а также защищённый авторским правом контент с целевых страниц по ссылкам. Только публичные данные SERP. Если вашему проекту нужно больше, официальное соглашение о данных является правильным путём, а не более хитрый скрапер.

Итоги

Ключевые выводы

  • Яндекс силён в своём регионе. Он лидирует в русскоязычном и региональном поиске, поэтому его публичные результаты являются полезным сигналом там, где Google не доминирует.
  • Crawling API получает данные за доверенным IP. Отправьте ему URL, он ротирует адреса на стороне сервера и при необходимости рендерит, возвращая готовый HTML и обходя интерстициал «Вы не робот?».
  • BeautifulSoup выполняет извлечение. Выбирайте каждый элемент .serp-item, затем читайте заголовок, URL, описание и позицию из него, ожидая, что имена классов со временем изменятся.
  • Пагинация по индексу p. Увеличивайте p на единицу для каждой страницы, чтобы углубляться в результаты, поддерживайте непрерывную нумерацию позиций и задавайте паузы между страницами.
  • Оставайтесь в рамках публичных данных. Соблюдайте ToS и robots.txt Яндекса, поддерживайте низкий объём, там, где подходит, предпочитайте официальные API вроде Яндекс.Маркета и никогда не касайтесь аккаунтов или персональных данных.

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

Что такое Яндекс и зачем его парсить?

Яндекс является ведущей поисковой системой в России, которую часто называют «русским Google», а также предлагает карты, почту, маркет и облачные сервисы. Люди парсят его публичные результаты для отслеживания русскоязычных и региональных позиций, изучения поисковых тенденций, мониторинга представленности брендов на этом рынке и сбора данных для исследований, которые пропускают инструменты, ориентированные на Google.

Можно ли парсить результаты поиска Яндекса с помощью Python?

Да. С помощью клиента crawlbase и BeautifulSoup вы можете получить страницу результатов и извлечь заголовки, URL, описания и позиции. Crawling API выступает мостом, доставляющим ваш запрос к Яндексу с доверенного IP, поэтому запросы обрабатываются без лишних трудностей и не упираются в верификационный интерстициал. Для общего введения в Python смотрите наше руководство по парсингу сайтов на Python.

Почему обычный запрос блокируется на Яндексе?

Яндекс помечает трафик, не похожий на реальный браузер, и внимательно следит за частотой запросов с каждого IP. Несколько быстрых запросов с одного адреса запускают страницу верификации «Вы не робот?» или полную блокировку. Получение данных через Crawling API, который ротирует IP и при необходимости рендерит страницы, делает каждый запрос похожим на обычного посетителя, и вы получаете настоящую страницу результатов.

Как постранично перебирать результаты Яндекса?

Используйте параметр запроса p с нулевым базовым индексом: p=0, первая страница, p=1, вторая и так далее. Формируйте URL каждой страницы со следующим индексом, получайте через Crawling API, разбирайте той же функцией, переназначайте текущую позицию для непрерывности рангов и делайте паузу в пару секунд между запросами, чтобы задавать темп обхода, а не интенсивно нагружать сервер.

Как обрабатывать русскоязычные результаты и кириллический текст?

Декодируйте тело ответа как UTF-8 при чтении, а при экспорте записывайте JSON с ensure_ascii=False или CSV с encoding="utf-8-sig". Эти два шага сохраняют кириллические заголовки и описания читаемыми, не превращая их в escape-последовательности \u или мусор. Региональная сила Яндекса означает, что многие результаты приходят на русском языке, поэтому это важнее, чем при парсинге Google.

Мои селекторы ничего не возвращают. Что изменилось?

Почти наверняка разметка Яндекса. Имена классов вроде organic__url-text и organic__content-wrapper могут меняться при редеплое фронтенда Яндекса, поэтому селекторы, работавшие в прошлом месяце, могут перестать работать. Заново проинспектируйте живую страницу результатов в браузерных инструментах разработчика и обновите селекторы. Периодическое обслуживание является нормой для любого производственного скрапера.

Начать создавать

Обходите любой сайт в масштабе, без борьбы с инфраструктурой.

Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.

Самообслуживание · Звонок отдела продаж не требуется · Доступны корпоративные объёмы краулинга