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

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

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

Скрипт на Python, который принимает URL страницы листинга Cars and Bids, загружает отрисованную страницу через Crawling API и извлекает структурированную запись для каждой карточки аукциона. В качестве рабочего примера используется страница поиска с фильтром по марке, аналогично подходу из предыдущей версии руководства. Из каждого листинга извлекаются следующие поля:

  • Title (заголовок) заголовок аукциона, содержащий год выпуска, марку и модель автомобиля.
  • Subtitle (подзаголовок) короткая описательная строка под заголовком (комплектация, примечательные опции, статус резерва).
  • Current bid (текущая ставка) максимальная ставка на аукционе на момент сбора данных.
  • Time left (оставшееся время) сколько времени осталось до закрытия аукциона.
  • Location (местоположение) город и регион, где размещён автомобиль.
  • Link (ссылка) URL страницы аукциона с подробным описанием.

Почему обычный запрос не работает на Cars and Bids

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

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

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

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

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

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

Аккаунт Crawlbase и токен. Зарегистрируйтесь, откройте панель управления и скопируйте токен. Поскольку Cars and Bids загружает аукционы с помощью JavaScript, вам нужен JavaScript (JS) токен, а не обычный. Бесплатный тариф включает до 20 000 запросов без привязки карты, чего вполне достаточно для создания и тестирования этого скрапера. Относитесь к токену как к паролю и не добавляйте его в систему контроля версий.

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

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

bash
python --version

python -m venv carsandbids-scraper
source carsandbids-scraper/bin/activate

pip install crawlbase beautifulsoup4

В Windows активируйте окружение командой carsandbids-scraper\Scripts\activate вместо строки с source. После установки обеих библиотек создайте файл скрипта, который будет формироваться на протяжении всего руководства:

bash
touch carsandbids_scraper.py

Изучение страницы листинга

Поиск на Cars and Bids доступен по постоянному URL. Например, список с фильтром по марке выглядит так: https://carsandbids.com/search/bmw, и тот же паттерн применяется к другим маркам. На странице отображается сетка карточек аукционов, по одной на каждый автомобиль, и каждая карточка содержит одинаковый набор полей: заголовок аукциона (год, марка, модель), подзаголовок, миниатюру, текущую ставку, оставшееся время, местоположение и ссылку на страницу аукциона.

Перед написанием селекторов откройте страницу листинга в браузере, щёлкните правой кнопкой мыши на карточке аукциона и выберите «Просмотр кода». Каждый аукцион находится в элементе li с классом auction-item. Внутри него заголовок находится в div.auction-title, подзаголовок в p.auction-subtitle, местоположение в p.auction-loc, миниатюра в теге img, а ссылка в якоре карточки. Именно эти элементы вы будете использовать в качестве целей.

Шаг 1: Загрузка отрисованной страницы листинга

Начните с получения готовой страницы. Импортируйте класс CrawlingAPI, инициализируйте его с вашим JS-токеном, задайте URL листинга и выполните запрос. Проверка кода статуса перед парсингом позволяет явно сообщать об ошибках, а не замалчивать их.

python
from crawlbase import CrawlingAPI

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

def make_crawlbase_request(url, options):
    response = crawling_api.get(url, options)
    if response["headers"]["cb_status"] == "200":
        return response["body"].decode("utf-8")
    print(f"Failed to fetch the page. Crawlbase status: {response['headers']['cb_status']}")
    return None

if __name__ == "__main__":
    listing_url = "https://carsandbids.com/search/bmw"
    options = {"ajax_wait": "true", "page_wait": 10000}
    html = make_crawlbase_request(listing_url, options)
    print(html[:500] if html else "No HTML returned")

Два параметра имеют значение для сетки, заполняемой после загрузки. ajax_wait указывает API дождаться завершения асинхронного контента, а page_wait задерживает выполнение на фиксированное количество миллисекунд (здесь 10 000), чтобы карточки аукционов, отрисовывающиеся с задержкой, появились до сохранения страницы. Проверка статуса читает заголовок cb_status (legacy pc_status), возвращаемый Crawling API: значение 200 означает успешную отрисовку. Запустите скрипт, и вы должны увидеть реальную разметку листинга, а не пустую оболочку. Это подтверждает работу отрисовки до написания первого селектора.

Crawlbase Crawling API

Сетка листингов Cars and Bids появляется только после выполнения JavaScript, и одной отрисовки недостаточно, если запрос поступает с IP-адреса, которому сайт не доверяет. Crawling API принимает ваш токен, запускает страницу в реальном браузере с параметрами ajax_wait и page_wait, которые вы только что задали, ротирует жилые IP-адреса на стороне сервера и обрабатывает решение CAPTCHA, а затем возвращает готовый HTML. Вам не нужно самостоятельно запускать флот headless-браузеров и управлять пулом прокси. Начните с бесплатного тарифа до 20 000 запросов.

Шаг 2: Парсинг карточек аукционов с помощью BeautifulSoup

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

python
from bs4 import BeautifulSoup

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

def text_of(listing, tag, css_class):
    el = listing.find(tag, class_=css_class)
    return el.text.strip() if el else None

def scrape_listing_page(html_content):
    soup = BeautifulSoup(html_content, "html.parser")
    car_listings = soup.find_all("li", class_="auction-item")

    extracted_data = []
    for listing in car_listings:
        link_tag = listing.find("a")
        thumbnail = listing.find("img")
        extracted_data.append({
            "title": text_of(listing, "div", "auction-title"),
            "sub_title": text_of(listing, "p", "auction-subtitle"),
            "current_bid": text_of(listing, "span", "bid-value"),
            "time_left": text_of(listing, "span", "td-time"),
            "location": text_of(listing, "p", "auction-loc"),
            "thumbnail": thumbnail["src"] if thumbnail else None,
            "link": BASE + link_tag["href"] if link_tag else None,
        })
    return extracted_data

Вспомогательная функция text_of запрашивает один элемент внутри карточки и возвращает None при его отсутствии, вместо того чтобы выбрасывать исключение при вызове .text на пустом объекте. Заголовок извлекается из div.auction-title и содержит год, марку и модель; подзаголовок из p.auction-subtitle; местоположение из p.auction-loc; текущая ставка из span со значением ставки; оставшееся время из span с отображением времени. Ссылка формируется путём добавления относительного href карточки к базовому URL сайта, чтобы хранить абсолютный URL.

Селекторы меняются

Разметка сайта меняется без предупреждения, и имена классов карточек аукционов, приведённые выше, являются стартовым шаблоном, а не гарантией. Контейнер li.auction-item и классы auction-title / auction-subtitle / auction-loc являются наиболее устойчивыми якорями; span для ставки и оставшегося времени с наибольшей вероятностью изменятся. Когда поле возвращает None для каждой карточки, заново проверьте актуальный листинг в инструментах разработчика вашего браузера и обновите селектор. Периодическое обслуживание селекторов является нормой для любого производственного скрапера.

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

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

python
import csv
import json
from crawlbase import CrawlingAPI
from bs4 import BeautifulSoup

crawling_api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})
BASE = "https://www.carsandbids.com"
FIELDS = ["title", "sub_title", "current_bid", "time_left", "location", "thumbnail", "link"]

def make_crawlbase_request(url, options):
    response = crawling_api.get(url, options)
    if response["headers"]["cb_status"] == "200":
        return response["body"].decode("utf-8")
    print(f"Failed to fetch the page. Crawlbase status: {response['headers']['cb_status']}")
    return None

def text_of(listing, tag, css_class):
    el = listing.find(tag, class_=css_class)
    return el.text.strip() if el else None

def scrape_listing_page(html_content):
    soup = BeautifulSoup(html_content, "html.parser")
    car_listings = soup.find_all("li", class_="auction-item")

    extracted_data = []
    for listing in car_listings:
        link_tag = listing.find("a")
        thumbnail = listing.find("img")
        extracted_data.append({
            "title": text_of(listing, "div", "auction-title"),
            "sub_title": text_of(listing, "p", "auction-subtitle"),
            "current_bid": text_of(listing, "span", "bid-value"),
            "time_left": text_of(listing, "span", "td-time"),
            "location": text_of(listing, "p", "auction-loc"),
            "thumbnail": thumbnail["src"] if thumbnail else None,
            "link": BASE + link_tag["href"] if link_tag else None,
        })
    return extracted_data

def export(rows, name="carsandbids_listings"):
    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)} auctions to {name}.json and {name}.csv")

def main():
    url = "https://carsandbids.com/search/bmw"
    options = {"ajax_wait": "true", "page_wait": 10000}
    html = make_crawlbase_request(url, options)
    if not html:
        return
    rows = scrape_listing_page(html)
    export(rows)

if __name__ == "__main__":
    main()

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

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

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

json
[
  {
    "title": "2014 BMW 335i Sedan",
    "sub_title": "No Reserve: Turbo 6-Cylinder, M Sport Package, California-Owned",
    "current_bid": "$9,500",
    "time_left": "2 days",
    "location": "Los Angeles, CA 90068",
    "thumbnail": "https://media.carsandbids.com/cdn-cgi/image/width=768/photos/rkVPlNqQ.jpg",
    "link": "https://www.carsandbids.com/auctions/9QxJ8nV7/2014-bmw-335i-sedan"
  },
  {
    "title": "2009 BMW 328i Sports Wagon",
    "sub_title": "No Reserve: Inspected 3.0-Liter 6-Cylinder, Premium Package",
    "current_bid": "$12,750",
    "time_left": "5 hours",
    "location": "San Diego, CA 92120",
    "thumbnail": "https://media.carsandbids.com/cdn-cgi/image/width=768/photos/3g6kOmG9.jpg",
    "link": "https://www.carsandbids.com/auctions/30n7Yqaj/2009-bmw-328i-sports-wagon"
  }
]

Обработка пагинации

Одна страница поиска подходит для демонстрации; реальная исследовательская задача охватывает все страницы результатов. Cars and Bids использует пагинацию с параметром ?page=, поэтому вы можете обходить страницы, увеличивая номер, пока страница не вернёт ни одной карточки аукциона. Делайте паузы между запросами, чтобы не перегружать сайт в плотном цикле.

python
import time

def scrape_all_pages(search_url, max_pages=10):
    options = {"ajax_wait": "true", "page_wait": 10000}
    all_rows = []
    for page in range(1, max_pages + 1):
        page_url = f"{search_url}?page={page}"
        html = make_crawlbase_request(page_url, options)
        if not html:
            break
        found = scrape_listing_page(html)
        if not found:
            print(f"No auctions on page {page}; stopping.")
            break
        all_rows.extend(found)
        print(f"Page {page}: {len(found)} auctions")
        time.sleep(2)
    return all_rows

Прерывание при пустых результатах останавливает обход, когда поиск исчерпывает страницы, а time.sleep(2) между запросами регулирует скорость работы, чтобы вас не заблокировали за частые запросы. Замените единственный вызов загрузки в main вызовом scrape_all_pages("https://carsandbids.com/search/bmw"), и остальная часть конвейера (парсинг, экспорт) обработает объединённый список напрямую. Чтобы отслеживать аукцион с течением времени, запускайте задачу по расписанию, проставляйте временную метку в каждом экспорте, затем сравнивайте последовательные снимки, чтобы видеть, как изменялись ставки и оставшееся время.

Как избежать блокировки

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

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

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

Законно ли парсить Cars and Bids?

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

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

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

Итоги

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

  • Листинги Cars and Bids являются актуальным сигналом аукционного рынка. Каждая страница поиска содержит текущий заголовок, ставку, оставшееся время и местоположение для каждого автомобиля, что делает её столь ценной для рыночных исследований и ценообразования.
  • Необходимы одновременно отрисовка и доверенный IP. Сетка листингов загружается на стороне клиента, а бот-трафик блокируется, поэтому Crawling API отрисовывает страницу за жилым IP в одном вызове с заданными параметрами ajax_wait и page_wait.
  • Извлечение данных выполняет BeautifulSoup. Обходите карточки li.auction-item и сопоставляйте заголовок, подзаголовок, текущую ставку, оставшееся время, местоположение и ссылку с актуальными селекторами, ожидая, что они будут меняться.
  • Обходите страницы и экспортируйте данные. Увеличивайте параметр ?page=, пока страница не вернёт пустой результат, затем записывайте объединённые строки в JSON и CSV с общим списком полей, чтобы оба файла оставались синхронизированными.
  • Работайте только с публичными данными. Соблюдайте Условия использования и robots.txt сайта, поддерживайте умеренный объём запросов и никогда не касайтесь аккаунтов, ставок, персональных данных или защищённых авторским правом медиаматериалов, которые вы намерены распространять.

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

Почему обычный запрос не возвращает аукционы с Cars and Bids?

Сетка листингов отрисовывается на стороне клиента: сайт отправляет почти пустую оболочку, а карточки аукционов заполняются по мере выполнения JavaScript, поэтому сырой запрос зачастую получает фрейм без листингов. Кроме того, сайт блокирует или выдаёт проверку для трафика, не похожего на реальный браузер. Отрисовка страницы через Crawling API за доверенным IP с параметрами ajax_wait и page_wait решает обе проблемы, именно поэтому описанный скрапер маршрутизирует запрос через него.

Какой токен Crawlbase использовать для Cars and Bids?

Используйте JavaScript (JS) токен. Cars and Bids загружает аукционы динамически, поэтому требуется отрисовка, которую обеспечивает JS-токен; обычный токен вернёт неотрисованную оболочку. Бесплатный тариф включает до 20 000 запросов без привязки карты, чего достаточно для создания и тестирования скрапера.

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

Направьте скрапер на нужный URL поиска. Фильтр по марке представляет собой просто путь, например https://carsandbids.com/search/bmw для BMW, поэтому замена марки в конце URL нацеливает вас на другой набор аукционов. Чтобы охватить полный набор результатов, обходите параметр ?page=, пока страница не вернёт пустой результат.

Какие поля можно извлечь из листинга Cars and Bids?

Из каждой карточки аукциона можно извлечь заголовок (год, марка, модель), подзаголовок, текущую ставку, оставшееся время, местоположение, миниатюру и ссылку на страницу аукциона с подробным описанием. Парсер сопоставляет каждое из них по CSS-селектору, и ненужные поля можно убрать из словаря вывода.

Как обрабатывать пагинацию при большом количестве аукционов?

Cars and Bids использует для пагинации параметр ?page=. Увеличивайте номер страницы в цикле, загружайте и парсите каждую страницу, прерывая цикл, когда страница не возвращает карточек аукционов. Добавляйте небольшую задержку между запросами, чтобы регулировать скорость, а не отправлять запросы страница за страницей, и собирайте строки в один список перед экспортом.

Как избежать блокировки при парсинге Cars and Bids?

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

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

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

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

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