Отзывы покупателей являются одними из наиболее полезных публичных данных в интернете. Оценки, текстовые отзывы и пометки о подтверждённой покупке рассказывают, что люди на самом деле думают о продукте, и постоянно обновляются. Проблема в том, что большинство страниц с отзывами рендерятся на стороне клиента и разбиты на десятки или сотни страниц, поэтому обычный HTTP-запрос возвращает пустую оболочку. Это руководство показывает, как парсить отзывы покупателей с помощью небольшого рабочего конвейера на Python: рендерить страницу с интенсивным использованием JavaScript, парсить структурированные поля, обходить все страницы, сохранять результаты и при необходимости выполнять анализ тональности.

Чтобы всё было честно и обоснованно, всё руководство ограничено публичными отзывами: оценкой, заголовком, текстом, датой и пометкой о проверке, которые кто угодно может видеть без входа в систему. Оно не затрагивает аккаунты пользователей, контент за логином или любые персональные данные, помимо того, что платформа уже отображает публично. Раздел об этике и ToS в конце не является шаблоном, поэтому прочитайте его перед тем, как запускать скрипт на производственных объёмах.

Зачем парсить отзывы покупателей

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

Это та же форма задачи, что и любая другая работа по веб-скрейпингу в электронной коммерции. Разница с отзывами заключается в объёме и пагинации: данные распределены по множеству страниц, загружаются лениво и защищены антиботовой защитой, которая усиливается по мере продвижения. Поэтому подход должен обрабатывать рендеринг, пагинацию и блокировки с первого запроса.

Почему обычный запрос не работает на страницах с отзывами

Запросите современный URL с отзывами обычным HTTP-клиентом, и вы, как правило, получите статус 200 и почти никакого содержимого отзывов в теле. Два фактора работают против вас. Во-первых, большинство платформ рендерят отзывы в браузере с помощью JavaScript, поэтому начальный HTML является скелетом, который заполняется только после выполнения скриптов страницы. Во-вторых, сайты с отзывами быстро помечают автоматический трафик: IP-адреса дата-центров и паттерны запросов, не похожие на реальный браузер, блокируются или получают проверку до того, как они вообще видят отрендеренный контент.

Поэтому рабочий парсер отзывов нуждается в двух вещах одновременно: браузере, который действительно рендерит страницу, и IP-адресе, который платформа воспринимает как реального посетителя. Можно собрать это самостоятельно с headless-браузером и пулом ротирующихся резидентных прокси, но объединение этих компонентов и поддержание их в рабочем состоянии составляет основную часть работы. Crawlbase Crawling API объединяет оба компонента в один вызов: вы передаёте ему URL с JavaScript-токеном, он рендерит страницу через доверенный IP и возвращает готовый HTML для парсинга. Если вы предпочитаете пропустить написание селекторов для распространённых целей, Crawling API возвращает распарсенные поля в формате JSON, а для прямого прокси-доступа есть Smart AI Proxy.

Зачем нужен JS-токен

Crawlbase предлагает два типа токенов. Обычный токен получает статический HTML; JavaScript (JS) токен сначала рендерит страницу в реальном браузере. Страницы с отзывами рендерятся на стороне клиента, поэтому здесь вам нужен JS-токен. Использование обычного токена возвращает ту же пустую оболочку, что и обычный запрос, с отсутствующими карточками отзывов.

Что вы извлекаете из отзыва

Поля, которые стоит захватывать, одинаковы для большинства платформ, даже если разметка отличается. Стремитесь к этим пяти полям в каждой карточке отзыва:

  • rating звёздная оценка, нормализованная до числа. Большинство сайтов используют шкалу от 1 до 5; некоторые от 1 до 10, что вы конвертируете позже.
  • title короткий заголовок, который рецензент даёт отзыву, если платформа его предусматривает.
  • body текст самого отзыва, качественная часть, несущая реальный сигнал.
  • date дата публикации отзыва, предпочтительно из машиночитаемого атрибута datetime, а не отображаемого текста.
  • verified помечает ли платформа его как подтверждённую покупку, что позволяет позже отфильтровать менее надёжные отзывы.

Цель состоит в создании единой стабильной схемы, чтобы отзывы из разных источников выстраивались в ряд без очистки для каждого источника. Единая унифицированная форма работает хорошо:

json
{
  "rating": 4.5,
  "title": "Exactly what I needed",
  "body": "Arrived early and works as described.",
  "date": "2026-01-10",
  "verified": true,
  "url": "https://www.example.com/product/123/reviews?page=2"
}

Как только каждый отзыв соответствует этой структуре, кроссплатформенный анализ сводится к фильтрации и группировке, а не к переформатированию.

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

Вам понадобятся Python 3 и аккаунт Crawlbase с JS-токеном, который вы получаете на панели управления после регистрации. Создайте папку проекта и установите библиотеки.

bash
python --version

mkdir review-scraper && cd review-scraper
python -m venv venv && source venv/bin/activate
pip install crawlbase beautifulsoup4

Две зависимости выполняют основную работу: crawlbase является официальным клиентом для Crawling API, а beautifulsoup4 парсит возвращаемый HTML. Стандартные модули re, csv и json охватывают нормализацию и хранение, поэтому больше ничего устанавливать не нужно. Держите токен вне исходного кода: экспортируйте его как переменную окружения и читайте во время выполнения.

Получение отрендеренного HTML

Начните с получения готовой страницы. Клиент Python оборачивает API в один вызов get. Вы передаёте два параметра, важных для сайта с отзывами: ajax_wait указывает API дождаться загрузки асинхронного контента, а page_wait ожидает фиксированное количество миллисекунд после загрузки, чтобы карточки отзывов с поздним рендерингом успели появиться. Пять секунд является разумной отправной точкой; увеличьте значение, если результаты возвращаются редкими.

python
import os
from crawlbase import CrawlingAPI

# JS token renders the page in a real browser before returning HTML
api = CrawlingAPI({"token": os.environ["CRAWLBASE_JS_TOKEN"]})

options = {
    "ajax_wait": "true",
    "page_wait": 5000,
}

def fetch_html(url):
    response = api.get(url, options)
    if response["status_code"] != 200:
        raise RuntimeError(f"fetch failed: {response['status_code']}")
    return response["body"].decode("utf-8")

if __name__ == "__main__":
    url = "https://www.example.com/product/123/reviews"
    print(fetch_html(url)[:2000])

Запустите скрипт, и вы должны увидеть реальную разметку с карточками отзывов в ней, а не пустую оболочку, возвращаемую обычным запросом. Это подтверждает работу рендеринга до написания единственного селектора. response["body"] возвращается клиентом в виде байтов, поэтому декодируйте его один раз и передайте строку парсеру.

Crawlbase Crawling API

Страницы с отзывами требуют отрендеренной страницы через доверенный IP за один вызов. Crawling API принимает JS-токен, запускает страницу в реальном браузере, ротирует резидентные IP на стороне сервера и передаёт готовый HTML, чтобы вы не запускали headless-флот и пул прокси самостоятельно. Сначала направьте его на публичную страницу с отзывами в рамках бесплатного уровня.

Парсинг отзывов с помощью BeautifulSoup

Имея HTML, загрузите его в BeautifulSoup и пройдитесь по карточкам отзывов. Каждая карточка содержит нужные поля, но имена классов различаются в зависимости от платформы, поэтому парсер использует список возможных селекторов и берёт первый совпавший. Проверьте живую страницу в инструментах разработчика браузера, чтобы найти текущие селекторы для вашей цели, затем сопоставьте каждое поле с одним из них.

python
import re
from bs4 import BeautifulSoup

def first_text(card, selectors):
    for sel in selectors:
        el = card.select_one(sel)
        if el:
            return el.get_text(separator=" ", strip=True)
    return ""

def parse_reviews(html, source_url=""):
    soup = BeautifulSoup(html, "html.parser")
    cards = soup.select("[data-review-id], article[class*='review'], .review-card")
    reviews = []
    for card in cards:
        rating_raw = first_text(card, ["[data-rating]", ".star-rating", "[class*='star']"])
        match = re.search(r"(\d+(?:\.\d+)?)", rating_raw)
        date_el = card.select_one("time[datetime], [data-review-date]")
        reviews.append({
            "rating": float(match.group(1)) if match else None,
            "title": first_text(card, [".review-title", "h3", "[class*='title']"]),
            "body": first_text(card, [".review-body", "[data-review-text]", "p"]),
            "date": (date_el.get("datetime") if date_el else ""),
            "verified": card.select_one("[class*='verified']") is not None,
            "url": source_url,
        })
    return [r for r in reviews if r["body"]]
Селекторы дрейфуют

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

Вспомогательная функция first_text делает парсер портируемым: передайте ей короткий список вероятных селекторов для каждого поля, и она вернёт первое совпадение, поэтому адаптация к новой платформе сводится главным образом к редактированию этих списков, а не к переписыванию логики. Отбрасывание отзывов с пустым полем body отфильтровывает карточки макета и рекламные блоки, разделяющие класс контейнера отзыва.

Обход всех страниц с отзывами

Получения одной страницы почти никогда не бывает достаточно. Большинство платформ делят отзывы на десятки или сотни страниц, обычно с параметром запроса в стиле ?page=2. Если запросить только первую страницу, вы пропустите большинство данных. Паттерн состоит в построении URL страниц в цикле, получении и парсинге каждой и остановке, когда страница не возвращает отзывов или вы достигаете установленного вами лимита.

python
import time

def scrape_all_reviews(base_url, max_pages=25):
    all_reviews = []
    for page in range(1, max_pages + 1):
        sep = "&" if "?" in base_url else "?"
        page_url = f"{base_url}{sep}page={page}"
        html = fetch_html(page_url)
        reviews = parse_reviews(html, page_url)
        if not reviews:
            break  # empty page means we ran past the last one
        all_reviews.extend(reviews)
        print(f"page {page}: {len(reviews)} reviews")
        time.sleep(1)  # pace requests so you stay under rate limits
    return all_reviews

Несколько практических замечаний. Устанавливайте разумный max_pages, чтобы изменение макета не могло отправить вас в бесконечный цикл. Останавливайтесь, как только страница возвращает ноль отзывов. А если ваша цель использует бесконечную прокрутку вместо нумерованных страниц, передайте параметр scroll в словарь options Crawling API вместо построения URL страниц; остальная часть цикла остаётся той же.

Нормализация по платформам

Для одного продукта на одной платформе вывод парсера обычно достаточно чист для прямого хранения. Как только вы начинаете получать отзывы из более чем одного источника, проявляются небольшие несоответствия: один сайт оценивает по шкале до 10, другой использует относительные даты, например «3 дня назад», и имена полей расходятся. Тонкий проход нормализации сохраняет всё сопоставимым.

python
def normalize(review, scale=5):
    rating = review.get("rating")
    if rating is not None and scale != 5:
        # map any scale onto a common 0-5 range
        review["rating"] = round(rating / scale * 5, 2)
    review["body"] = " ".join(review["body"].split())
    return review

Конвертируйте оценки к единой шкале, сворачивайте пробельные символы в тексте и выравнивайте имена полей, если источник называет вещи по-другому. Для одной платформы этот шаг можно пропустить; для многоплатформенного анализа именно это предотвращает расхождение данных.

Сохранение результатов

Вывод в консоль хорош во время итерации, но данные нужно хранить на диске. CSV является самой простой целью и открывается в любой электронной таблице; стандартный модуль csv сопоставляет каждый ключ словаря со столбцом.

python
import csv

def save_csv(reviews, path="reviews.csv"):
    fields = ["rating", "title", "body", "date", "verified", "url"]
    with open(path, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=fields)
        writer.writeheader()
        writer.writerows(reviews)
    print(f"saved {len(reviews)} reviews to {path}")

Если вы предпочитаете запрашивать данные с помощью SQL, запишите те же строки в таблицу SQLite с помощью стандартного модуля sqlite3; парсинг и пагинация остаются идентичными. JSON Lines является ещё одним хорошим вариантом, когда вы хотите передавать записи в нижестоящий конвейер. Свяжите части вместе, и весь прогон сводится к нескольким вызовам:

python
if __name__ == "__main__":
    base = "https://www.example.com/product/123/reviews"
    reviews = scrape_all_reviews(base, max_pages=25)
    reviews = [normalize(r) for r in reviews]
    save_csv(reviews)

По желанию: анализ тональности текста отзыва

Когда отзывы структурированы, анализ тональности является коротким дополнением. Лёгкая, основанная на правилах модель, такая как VADER, даёт оценку полярности для каждого отзыва без обучения, что достаточно для выявления самых гневных и радостных отзывов и отслеживания средней тональности во времени.

python
# pip install vaderSentiment
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer

analyzer = SentimentIntensityAnalyzer()

def add_sentiment(reviews):
    for r in reviews:
        score = analyzer.polarity_scores(r["body"])["compound"]
        r["sentiment"] = round(score, 3)
    return reviews

Оценка compound варьируется от -1 (очень негативно) до +1 (очень позитивно). Для более глубокой работы передайте тот же текст тела классификатору на основе трансформера или размещённому NLP-сервису; конвейер до этого момента не меняется.

Масштабирование на множество продуктов

Цикл с паузой подходит для одного продукта. Когда нужны отзывы по сотням URL, в вашем коде начинаются проблемы с параллелизмом, повторными попытками и планированием. Именно здесь пригождается Crawler: вместо последовательного получения страниц вы отправляете список URL в Crawlbase, и он обрабатывает их в облаке, рендеря каждую страницу и доставляя готовый HTML на ваш эндпоинт через вебхук. Неудачные запросы повторяются автоматически, поэтому вам не нужно следить за очередью. Для нескольких страниц Crawling API достаточен; после этого порога Crawler устраняет большую часть операционных затрат.

Как оставаться незаблокированным

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

  • Соблюдайте темп запросов. Интенсивный обход отзывов одного продукта в жёстком цикле является самым быстрым способом получить ограничение скорости. time.sleep в цикле пагинации присутствует намеренно; сохраняйте его.
  • Используйте ротацию. Пул резидентных прокси распределяет запросы по множеству реальных пользовательских IP, чтобы ни один адрес не превысил ограничение скорости. Crawling API делает это за вас; если вы строите собственный стек, это та часть, которую нужно сделать правильно.
  • Следите за кодами статуса. Прогон, начавший возвращать проверки или ошибки, сигнализирует, что текущего темпа или уровня IP уже недостаточно. Воспринимайте статус ответа как сигнал, а не шум, и откатывайтесь при его изменении.

Для более широкой стратегии смотрите как парсить сайты без блокировок. Если вы хотите сравнить этот управляемый подход с самостоятельно созданным headless-стеком, веб-скрейпинг с Python и Selenium охватывает эту сборку.

Честная часть: ToS и персональные данные

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

Несколько принципов, которых стоит придерживаться. Собирайте только публичные отзывы: оценку, заголовок, текст, дату и пометку о проверке, которые кто угодно может видеть без аккаунта. Соблюдайте robots.txt сайта и его заявленные ожидания по скорости запросов, и поддерживайте объём запросов достаточно низким, чтобы не создавать нагрузку на серверы. Не собирайте персональные данные сверх того, что платформа уже отображает публично, и никогда не пытайтесь деанонимизировать рецензента или объединить его отзывы с данными из других источников. Если вы планируете коммерческое повторное использование данных, получите разрешение или официальное соглашение об использовании данных, а не считайте молчание согласием.

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

Итоги

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

  • Страницы с отзывами рендерятся на стороне клиента. Обычный запрос возвращает пустую оболочку, поэтому необходимо рендерить страницу до парсинга.
  • Рендеринг и доверенный IP неразрывны. Crawling API с JS-токеном делает оба за один вызов; ajax_wait и page_wait управляют временем ожидания контента.
  • Парсите по единой схеме. Захватывайте оценку, заголовок, текст, дату и пометку о проверке с помощью списков возможных селекторов и ожидайте дрейфа этих селекторов со временем.
  • Пагинация и есть данные. Перебирайте URL страниц, останавливайтесь на пустой странице и соблюдайте темп запросов, чтобы не превысить ограничения скорости.
  • Сохраняйте, затем анализируйте. Записывайте в CSV, SQLite или JSON Lines и добавляйте анализ тональности после структурирования данных.
  • Оставайтесь в рамках публичных отзывов. Соблюдайте ToS и robots.txt; никаких аккаунтов, никаких персональных данных сверх того, что публично отображается.

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

Как парсить отзывы покупателей с сайтов с интенсивным использованием JavaScript?

Большинство платформ с отзывами рендерят карточки на стороне клиента, поэтому обычный HTTP-запрос возвращает статус 200 с отсутствующими отзывами. Вам нужен запрос на основе браузера. Передайте URL в Crawling API с JS-токеном, и он отрендерит страницу в реальном браузере перед возвратом HTML, поэтому каждый отзыв присутствует при парсинге BeautifulSoup. Параметры ajax_wait и page_wait управляют временем ожидания контента с поздней загрузкой.

Нужен ли мне обычный токен или JS-токен для парсинга отзывов покупателей?

JS-токен. Обычный токен получает статический HTML, который на сайте с отзывами является той же пустой оболочкой, что и обычный запрос. JS-токен сначала рендерит страницу в реальном браузере, поэтому карточки отзывов существуют в HTML к моменту запуска вашего парсера.

Как получить все отзывы, а не только первую страницу?

Большинство платформ разбивают страницы на страницы с параметром в стиле ?page=2. Строите URL страниц в цикле, получаете и парсите каждую, и останавливаетесь, когда страница не возвращает отзывов или вы достигаете установленного вами лимита. Для сайтов, использующих бесконечную прокрутку вместо нумерованных страниц, передайте параметр scroll Crawling API вместо построения URL страниц; остальная часть цикла остаётся той же.

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

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

Могу ли я выполнять анализ тональности на парсированных отзывах?

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

Законно ли парсить отзывы покупателей?

Это зависит от условий использования платформы, вашей юрисдикции и вашей цели, и многие сайты ограничивают автоматический доступ. Строго придерживайтесь публичного содержимого отзывов, соблюдайте robots.txt и ожидания по скорости запросов, не собирайте персональные данные сверх того, что публично отображается, и не пытайтесь идентифицировать отдельных рецензентов. Для коммерческого повторного использования получите разрешение или официальное соглашение об использовании данных, а не полагайтесь на парсер.

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

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

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

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