Страницы поиска и товаров Best Buy это публичное окно в один из крупнейших каталогов потребительской электроники в Северной Америке. Каждый результат поиска и страница товара несут один и тот же набор полей, которые любой может увидеть: название товара, его цену, номера модели и SKU, рейтинг в звёздах и наличие товара на складе. Именно эти поля и есть сигнал, который отслеживают аналитики цен, исследователи рынка и покупатели техники, когда хотят следить за движением цен на электронику или сравнивать наличие по категории.

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

Что вы построите

Python-скрипт, который принимает URL поиска Best Buy, получает отрисованную страницу через Crawling API и извлекает структурированную запись на каждый товар. Мы используем поиск по «i phone» в качестве сквозного примера, тот же запрос, что использовал устаревший разбор, и вытащим из каждой карточки объявления следующие поля:

  • Название имя товара, показанное на карточке объявления.
  • Цена текущая цена для покупателя, когда товар её показывает.
  • Модель / SKU номер модели производителя и собственный идентификатор SKU от Best Buy.
  • Рейтинг средний рейтинг в звёздах, с числом отзывов рядом.
  • Наличие есть ли товар на складе, распродан или доступен к отгрузке.
  • URL товара абсолютная ссылка на собственную страницу с подробной информацией о товаре.

Почему обычный запрос не срабатывает на Best Buy

Если направить голый HTTP-клиент на URL поиска Best Buy, вы почти никогда не получите список товаров, за которым пришли. Best Buy отрисовывает свои результаты поиска на стороне клиента: сервер отдаёт лёгкую оболочку, а JavaScript на странице заполняет карточки товаров уже после. Так что сырой HTML, который вы получаете обратно от простого requests.get(), целиком лишён списков, и ваш парсер уходит с пустым списком.

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

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

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

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

Python 3.8 или новее. Подтвердите свою версию командой python --version (или python3 --version). Если его у вас нет, установите его с python.org и убедитесь, что Python прописан в системном PATH.

Учётная запись Crawlbase и токен. Зарегистрируйте бесплатную учётную запись, откройте дашборд и скопируйте свой токен со страницы документации учётной записи. Бесплатный тариф включает до 20 000 запросов без карты, чего с лихвой хватит, чтобы построить и протестировать этот скрапер. Best Buy это отрисовываемый на JavaScript сайт, поэтому здесь вы используете JavaScript-токен запросов. Относитесь к токену как к паролю и держите его вне системы контроля версий.

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

Создайте виртуальное окружение, чтобы зависимости проекта оставались изолированными, затем установите библиотеки, которые нужны скраперу. crawlbase это официальный клиент для Crawling API, а beautifulsoup4 разбирает возвращённый HTML, чтобы вы могли вытащить каждое поле из карточек объявлений по CSS-селектору.

bash
python --version

python -m venv bestbuy_env
source bestbuy_env/bin/activate

pip install crawlbase beautifulsoup4

В Windows активируйте окружение командой bestbuy_env\Scripts\activate вместо строки source. С обеими установленными библиотеками создайте файл скрипта, который наращивает остальная часть руководства:

bash
touch bestbuy_scraper.py

Понимание страницы поиска Best Buy

Поиск Best Buy живёт по стабильному URL на эндпоинте searchpage.jsp, с вашим запросом в параметре st. Поиск по «i phone» это https://www.bestbuy.com/site/searchpage.jsp?st=i+phone. Страница раскладывает упорядоченный список карточек товаров, по одной на позицию, каждая несёт одни и те же поля: название, цену, модель и SKU, рейтинг в звёздах с числом отзывов и состояние наличия.

Прежде чем писать селекторы, откройте страницу поиска в браузере, кликните правой кнопкой по карточке товара и выберите «Просмотреть код». Best Buy оборачивает весь набор результатов в ol.sku-item-list, а каждый товар в контейнер li.sku-item, затем делит каждую карточку на column-middle (название, модель, SKU, рейтинг) и column-right (цена, наличие). Это те элементы, на которые вы нацеливаетесь. Имена классов Best Buy смещаются со временем, поэтому относитесь к селекторам ниже как к отправному шаблону, который вы перепроверяете на живой странице, а не как к постоянному контракту.

Шаг 1: Получите отрисованную страницу поиска

Начните с получения готовой страницы. Импортируйте класс CrawlingAPI, инициализируйте его своим токеном, постройте URL поиска и запросите его. Проверка cb_status (legacy pc_status) от Crawlbase перед разбором делает сбои громкими, а не молчаливыми.

python
from crawlbase import CrawlingAPI
from urllib.parse import quote_plus

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})

def crawl(page_url):
    options = {"ajax_wait": "true", "page_wait": 5000}
    response = api.get(page_url, options)
    if response["headers"]["cb_status"] == "200":
        return response["body"].decode("utf-8")
    print(f"Request failed. Crawlbase status: {response['headers']['cb_status']}")
    return None

if __name__ == "__main__":
    search_term = "i phone"
    search_url = f"https://www.bestbuy.com/site/searchpage.jsp?st={quote_plus(search_term)}"
    html = crawl(search_url)
    print(html[:500] if html else "No HTML returned")

Два параметра ожидания важны для списка, который заполняется после загрузки. ajax_wait сообщает API, что нужно дождаться окончания загрузки асинхронной сетки товаров, а page_wait выдерживает фиксированные 5 000 миллисекунд после загрузки, чтобы поздно отрисовываемые карточки появились до того, как страница будет захвачена. Crawlbase возвращает код успеха от исходного сайта в заголовке cb_status, поэтому вы проверяете его, а не поле статуса верхнего уровня. Запустите скрипт, и вы должны увидеть реальную разметку товаров, а не оболочку страницы проверки. Это подтверждает, что отрисовка работает, прежде чем вы напишете хоть один селектор.

Crawlbase Best Buy Scraper

Best Buy заполняет свою сетку поиска через JavaScript и блокирует трафик, который не выглядит как браузер, и именно поэтому сырой запрос выше приходит обратно пустым. Crawling API принимает ваш токен, выполняет страницу поиска в настоящем браузере с ajax_wait и page_wait, чтобы карточки закончили загрузку, ротирует резидентные IP на стороне сервера и занимается решением CAPTCHA, затем отдаёт вам готовый HTML. Вы избегаете запуска флота headless-браузеров и пула прокси самостоятельно. Начните на бесплатном тарифе до 20 000 запросов.

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

Имея на руках отрисованный HTML, загрузите его в BeautifulSoup, найдите каждую карточку товара и вытащите каждое поле по его селектору. Best Buy перечисляет каждую позицию как li.sku-item внутри ol.sku-item-list, с названием и рейтингом в column-middle и ценой и наличием в column-right. Оберните каждую карточку в try/except, чтобы одно неправильно сформированное объявление не уронило весь прогон.

python
from bs4 import BeautifulSoup

BASE = "https://www.bestbuy.com"

def text_of(card, selector):
    el = card.select_one(selector)
    return el.get_text(strip=True) if el else None

def parse_card(card):
    title_el = card.select_one("div.column-middle h4.sku-title > a")
    href = title_el["href"] if title_el and title_el.has_attr("href") else None
    return {
        "title": title_el.get_text(strip=True) if title_el else None,
        "price": text_of(card, 'div.column-right div.sku-list-item-price div[data-testid="customer-price"] > span'),
        "model": text_of(card, "div.column-middle div.sku-model span.sku-value"),
        "sku": card.get("data-sku-id"),
        "rating": text_of(card, "div.column-middle div.ratings-reviews div.c-ratings-reviews > p"),
        "review_count": text_of(card, "div.column-middle div.ratings-reviews span.c-reviews"),
        "availability": text_of(card, "div.column-right div.fulfillment-add-to-cart-button button"),
        "product_url": BASE + href if href else None,
    }

def scrape_bestbuy_listing(html):
    soup = BeautifulSoup(html, "html.parser")
    cards = soup.select("ol.sku-item-list li.sku-item")
    results = []
    for card in cards:
        try:
            results.append(parse_card(card))
        except Exception as e:
            print(f"Skipped a card: {e}")
    return results

Помощник text_of запрашивает один элемент внутри карточки и возвращает None, когда он отсутствует, вместо того чтобы упасть при вызове .get_text() по пустоте. Это сохраняет извлечение устойчивым, когда поле отсутствует, что бывает часто, поскольку не каждое объявление показывает цену или рейтинг. Название и ссылка на товар оба приходят из якоря h4.sku-title > a, цена из span с test-id customer-price, а SKU из собственного атрибута карточки data-sku-id. Наличие читается из текста кнопки выполнения заказа, который читается как «Add to Cart» для товара в наличии и «Sold Out» или «Coming Soon» в противном случае, так что надпись на кнопке заодно служит сигналом о наличии.

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

Имена классов и значения data-testid у Best Buy меняются без предупреждения. Структурные маркеры вроде ol.sku-item-list, li.sku-item и разделение column-middle / column-right обычно более устойчивы, чем глубокие цепочки классов. Когда поле возвращается как None для каждой карточки, заново осмотрите живую страницу поиска в инструментах разработчика браузера и обновите селектор. Периодическое обслуживание селекторов это норма для любого продакшен-скрапера.

Шаг 3: Соберите скрипт и экспортируйте JSON и CSV

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

python
import csv
import json
from urllib.parse import quote_plus
from crawlbase import CrawlingAPI
from bs4 import BeautifulSoup

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})
BASE = "https://www.bestbuy.com"
FIELDS = ["title", "price", "model", "sku", "rating", "review_count", "availability", "product_url"]

def crawl(page_url):
    options = {"ajax_wait": "true", "page_wait": 5000}
    response = api.get(page_url, options)
    if response["headers"]["cb_status"] == "200":
        return response["body"].decode("utf-8")
    print(f"Request failed. Crawlbase status: {response['headers']['cb_status']}")
    return None

def text_of(card, selector):
    el = card.select_one(selector)
    return el.get_text(strip=True) if el else None

def parse_card(card):
    title_el = card.select_one("div.column-middle h4.sku-title > a")
    href = title_el["href"] if title_el and title_el.has_attr("href") else None
    return {
        "title": title_el.get_text(strip=True) if title_el else None,
        "price": text_of(card, 'div.column-right div.sku-list-item-price div[data-testid="customer-price"] > span'),
        "model": text_of(card, "div.column-middle div.sku-model span.sku-value"),
        "sku": card.get("data-sku-id"),
        "rating": text_of(card, "div.column-middle div.ratings-reviews div.c-ratings-reviews > p"),
        "review_count": text_of(card, "div.column-middle div.ratings-reviews span.c-reviews"),
        "availability": text_of(card, "div.column-right div.fulfillment-add-to-cart-button button"),
        "product_url": BASE + href if href else None,
    }

def scrape_bestbuy_listing(html):
    soup = BeautifulSoup(html, "html.parser")
    cards = soup.select("ol.sku-item-list li.sku-item")
    results = []
    for card in cards:
        try:
            results.append(parse_card(card))
        except Exception as e:
            print(f"Skipped a card: {e}")
    return results

def export(rows, name="bestbuy_products"):
    with open(f"{name}.json", "w", encoding="utf-8") as f:
        json.dump(rows, f, indent=2, ensure_ascii=False)
    with open(f"{name}.csv", "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=FIELDS)
        writer.writeheader()
        writer.writerows(rows)
    print(f"Saved {len(rows)} products to {name}.json and {name}.csv")

def main():
    search_term = "i phone"
    url = f"{BASE}/site/searchpage.jsp?st={quote_plus(search_term)}"
    html = crawl(url)
    if not html:
        return
    rows = scrape_bestbuy_listing(html)
    export(rows)

if __name__ == "__main__":
    main()

Запустите полный скрипт командой python bestbuy_scraper.py. Он получает отрисованную страницу поиска, разбирает по одной строке на товар и записывает и bestbuy_products.json, и bestbuy_products.csv. Общий список FIELDS держит порядок колонок CSV в ногу с ключами словаря, так что два экспорта никогда не расходятся.

Как выглядит вывод

Вы получаете чистый список записей товаров, в порядке поиска, готовый к записи в JSON, CSV или базу данных.

json
[
  {
    "title": "Apple - iPhone 14 128GB (Unlocked) - Midnight",
    "price": "$729.99",
    "model": "MPUA3LL/A",
    "sku": "6507555",
    "rating": "Rating 4.9 out of 5 stars with 155 reviews",
    "review_count": "(155)",
    "availability": "Add to Cart",
    "product_url": "https://www.bestbuy.com/site/apple-iphone-14-128gb-unlocked-midnight/6507555.p?skuId=6507555"
  },
  {
    "title": "Apple - iPhone SE (3rd Generation) 64GB (Unlocked)",
    "price": "$429.99",
    "model": "MMX73LL/A",
    "sku": "6507470",
    "rating": "Rating 4.5 out of 5 stars with 111 reviews",
    "review_count": "(111)",
    "availability": "Add to Cart",
    "product_url": "https://www.bestbuy.com/site/apple-iphone-se-3rd-generation-64gb-unlocked/6507470.p?skuId=6507470"
  }
]

Обработка пагинации по страницам результатов

Одна страница поиска это демо; реальная исследовательская задача проходит по всему набору результатов. Best Buy разбивает результаты поиска на несколько страниц и отслеживает текущую страницу параметром URL cp: &cp=1 это первая страница, &cp=2 вторая и так далее. Чтобы собрать полный датасет, обходите страницы по порядку, останавливайтесь, когда страница возвращает ноль товаров, и размеряйте запросы, чтобы вы не долбили Best Buy в плотном цикле.

python
import time

def scrape_all_pages(search_term, max_pages=5):
    base_url = f"{BASE}/site/searchpage.jsp?st={quote_plus(search_term)}"
    all_rows = []
    for page_number in range(1, max_pages + 1):
        page_url = f"{base_url}&cp={page_number}"
        html = crawl(page_url)
        if not html:
            break
        rows = scrape_bestbuy_listing(html)
        if not rows:
            print(f"No products on page {page_number}, stopping.")
            break
        all_rows.extend(rows)
        print(f"Page {page_number}: {len(rows)} products")
        time.sleep(2)
    return all_rows

if __name__ == "__main__":
    rows = scrape_all_pages("i phone", max_pages=5)
    export(rows)

Прерывание по пустым результатам останавливает вас заранее, когда у поиска кончаются страницы, а пауза time.sleep(2) между запросами размеряет прогон, так что вас не пометят за трафик в стиле автоматной очереди. Замените поисковый термин на любой запрос, который хотите, направьте тот же парсер на отдельную страницу товара, а не на URL поиска, и вы сможете расширить это в конвейер отслеживания цен. Более широкую картину превращения такого рода ленты в инструмент мониторинга смотрите в веб-скрапинге для ценовой аналитики и в руководстве по построению инструмента сравнения цен.

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

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

  • Размеряйте свои запросы. Распределяйте запросы с задержкой между страницами, а не обходите всё на полной скорости. Планируйте более тяжёлые задачи на часы низкой нагрузки, чтобы облегчить нагрузку на серверы Best Buy.
  • Опирайтесь на ротацию. Пул резидентных IP распределяет запросы по множеству адресов реальных пользователей, так что ни один из них не упирается в лимит частоты. Crawling API делает это за вас; если вы собираете свой собственный стек, это та часть, которую нужно сделать правильно.
  • Храните только то, что вам нужно. Сохраняйте поля товара, которые использует ваш проект, и отбрасывайте остальное. Периодически перепроверяйте свои селекторы, чтобы скрапер шёл в ногу с изменениями разметки.

Более широкий план действий смотрите в как скрапить сайты, не попадая в блокировку, а подробнее о том, почему отрисовка здесь имеет значение, в как обходить JavaScript-сайты. Если ваш проект находится в более широком розничном контексте, обзор веб-скрапинга для электронной коммерции охватывает паттерны, общие для магазинов вроде Best Buy и данных о товарах Amazon.

Законно ли скрапить Best Buy?

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

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

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

Итоги

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

  • Best Buy это публичный каталог электроники. Его страницы поиска и товаров выставляют название, цену, модель, SKU, рейтинг и наличие, и именно поэтому они полезны для отслеживания цен и рыночных исследований.
  • Вам нужны отрисовка и доверенный IP вместе. Best Buy заполняет свою сетку поиска на стороне клиента и блокирует трафик ботов, поэтому Crawling API отрисовывает страницу за резидентным IP в одном вызове с ajax_wait и page_wait.
  • BeautifulSoup выполняет извлечение. Пройдите карточки ol.sku-item-list li.sku-item в цикле и сопоставьте каждое поле его селектору, и ожидайте, что эти селекторы будут дрейфовать по мере изменения разметки Best Buy.
  • Пагинируйте по параметру cp. Обходите страницы с &cp=N, останавливайтесь на пустой странице и размеряйте запросы короткой задержкой между страницами.
  • Оставайтесь на публичных данных. Уважайте Условия и положения и robots.txt Best Buy, предпочитайте официальную ленту для лицензированных или массовых данных и никогда не трогайте учётные записи, заказы или персональную информацию.

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

Почему обычный запрос не возвращает товаров от Best Buy?

Best Buy отрисовывает свою сетку поиска на стороне клиента, поэтому сырой requests.get() получает обратно оболочку без карточек товаров в ней, и именно поэтому обычный парсер возвращает пустой список. Вдобавок к этому Best Buy устраивает проверки или блокирует трафик, который не выглядит как настоящий браузер. Отрисовка страницы через Crawling API за доверенным IP решает и то и другое, и именно поэтому скрапер здесь направляет свой запрос через него с установленными опциями ajax_wait и page_wait.

Какие поля я могу скрапить из объявления Best Buy?

Из каждой карточки результата поиска вы можете прочитать название товара, текущую цену для покупателя, номер модели производителя, собственный SKU от Best Buy, рейтинг в звёздах и число отзывов, состояние наличия из кнопки выполнения заказа и ссылку на страницу с подробной информацией о товаре. Скрапер в этом руководстве вытаскивает всё это в одну запись на товар, затем записывает их в JSON и CSV.

Как мне обработать пагинацию на Best Buy?

Best Buy отслеживает текущую страницу поиска параметром URL cp, так что &cp=2 запрашивает вторую страницу и так далее. Пройдите номера страниц в цикле, добавляя &cp=N к URL поиска каждый раз, и останавливайтесь, когда страница возвращает ноль товаров. Добавьте короткую задержку между запросами, чтобы вы размеряли прогон, а не выстреливали страницы одну за другой.

Как Crawling API обрабатывает JavaScript-контент Best Buy?

Crawling API отрисовывает страницу в настоящем браузере, прежде чем вернуть её, а опции ajax_wait и page_wait управляют тем, как долго он ждёт асинхронный контент. Установка ajax_wait в true ждёт окончания асинхронной сетки товаров, а page_wait выдерживает фиксированное число миллисекунд после загрузки, чтобы поздно отрисовываемые карточки появились в HTML, который вы получаете обратно.

Как мне избежать блокировки при скрапинге Best Buy?

Держите частоту запросов на один IP низкой, добавьте задержку между страницами и направляйте трафик через ротируемые резидентные IP, чтобы ни один адрес не упирался в лимит частоты. Crawling API управляет ротацией, пулом доверенных IP и обработкой CAPTCHA за вас; если вы строите собственный стек, это та часть, в которую стоит вложиться. Следите за значениями cb_status и отступайте, когда начинаете видеть проверки.

Могу ли я отслеживать изменения цен Best Buy во времени?

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

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

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

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

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