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

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

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

Скрипт на Python, который принимает URL поиска Gumtree, получает отрисованную страницу результатов через Crawling API и извлекает структурированную запись для каждой плитки объявления. В качестве примера используется поиск по слову headset (гарнитура), тот же запрос, что и в предыдущем руководстве, и для каждой плитки извлекаются следующие поля:

  • Название, заголовок объявления на плитке, например марка и модель автомобиля.
  • Цена, запрашиваемая продавцом цена, если плитка её показывает.
  • Местоположение, район, в котором размещён товар, полезен для анализа регионального спроса.
  • Ссылка, абсолютный URL страницы самого объявления.

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

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

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

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

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

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

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

Аккаунт Crawlbase и токен. Зарегистрируйтесь бесплатно, откройте панель управления и скопируйте токен. Бесплатный тариф включает до 20 000 запросов без карты, что вполне достаточно для создания и тестирования этого скрапера. Храните токен как пароль и не добавляйте его в систему контроля версий.

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

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

bash
python --version

python -m venv gumtree_env
source gumtree_env/bin/activate

pip install crawlbase beautifulsoup4

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

bash
touch gumtree_scraper.py

Изучение страницы поиска и выбор селекторов

Прежде чем писать какие-либо селекторы, откройте страницу результатов поиска Gumtree в браузере, кликните правой кнопкой на плитке объявления и выберите «Просмотр кода». Gumtree оборачивает каждый результат в элемент article с атрибутом data-q="search-result" и раскрывает поля внутри этого контейнера через собственные устойчивые атрибуты data-*. Опирайтесь на эти структурные атрибуты, а не на хрупкую цепочку генерируемых имён классов: они гораздо лучше переживают косметические редизайны.

После проверки страницы результатов вот какие селекторы были использованы в предыдущем руководстве и остаются правильной отправной точкой:

  • Название, <div> с data-q="tile-title".
  • Цена, <div> с data-testid="price".
  • Местоположение, <div> с data-q="tile-location".
  • Ссылка, атрибут href тега <a> с data-q="search-result-anchor"; ссылка является относительной и требует добавления хоста https://www.gumtree.com в начало.

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

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

python
from crawlbase import CrawlingAPI

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

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

if __name__ == "__main__":
    search_url = "https://www.gumtree.com/search?q=headset"
    html = crawl(search_url)
    print(html[:500] if html else "No HTML returned")

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

Crawlbase Crawling API

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

Шаг 2: парсинг плиток объявлений с помощью BeautifulSoup

Получив отрисованный HTML, загрузите его в BeautifulSoup, найдите каждую плитку результата и извлеките каждое поле по его селектору. Gumtree оборачивает каждый результат в контейнер article[data-q="search-result"] и раскрывает название, цену и местоположение внутри него через собственные атрибуты data-*. Считайте ссылку объявления из якоря плитки и приведите её к абсолютному URL. Оберните каждую плитку в try/except, чтобы один неправильно сформированный листинг не прервал весь процесс.

python
from bs4 import BeautifulSoup

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

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

def parse_link(tile):
    a = tile.select_one('a[data-q="search-result-anchor"]')
    if not a or not a.get("href"):
        return None
    href = a["href"]
    return href if href.startswith("http") else BASE + href

def scrape_gumtree_search(html):
    soup = BeautifulSoup(html, "html.parser")
    tiles = soup.select('article[data-q="search-result"]')
    results = []
    for tile in tiles:
        try:
            results.append({
                "title": text_of(tile, 'div[data-q="tile-title"]'),
                "price": text_of(tile, 'div[data-testid="price"]'),
                "location": text_of(tile, 'div[data-q="tile-location"]'),
                "link": parse_link(tile),
            })
        except Exception as e:
            print(f"Skipped a tile: {e}")
    return results

Вспомогательная функция text_of запрашивает один элемент внутри плитки и возвращает None, если он отсутствует, вместо того чтобы вызывать ошибку при вызове .get_text() от ничего. Это делает извлечение устойчивым при отсутствии поля, что часто бывает, поскольку не каждое объявление показывает чёткую цену. Ссылка берётся из якоря a[data-q="search-result-anchor"] и приводится к абсолютному URL, поскольку Gumtree возвращает относительный href, указывающий на страницу объявления.

Селекторы устаревают

Атрибуты data-q и data-testid Gumtree более долговечны, чем генерируемые имена классов, но не являются постоянными: редизайн может их переименовать или убрать. Относитесь к приведённым выше селекторам как к отправной точке, а не к договору. Если поле начинает возвращать None для каждой плитки, проверьте живую страницу поиска в инструментах разработчика браузера и обновите селектор. Периодическое обновление селекторов, норма для любого рабочего скрапера.

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

Одна страница результатов, это демонстрация; реальная задача обходит несколько страниц. Gumtree пагинирует результаты поиска с помощью параметра запроса ?page=N, поэтому можно перебирать фиксированное количество страниц, получать каждую и собирать плитки. Теперь соедините получение, парсинг и цикл пагинации в один рабочий скрипт, затем запишите записи в JSON и CSV, чтобы загрузить их в блокнот или таблицу.

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

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})
BASE = "https://www.gumtree.com"
FIELDS = ["title", "price", "location", "link"]

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

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

def parse_link(tile):
    a = tile.select_one('a[data-q="search-result-anchor"]')
    if not a or not a.get("href"):
        return None
    href = a["href"]
    return href if href.startswith("http") else BASE + href

def scrape_gumtree_search(html):
    soup = BeautifulSoup(html, "html.parser")
    tiles = soup.select('article[data-q="search-result"]')
    results = []
    for tile in tiles:
        try:
            results.append({
                "title": text_of(tile, 'div[data-q="tile-title"]'),
                "price": text_of(tile, 'div[data-testid="price"]'),
                "location": text_of(tile, 'div[data-q="tile-location"]'),
                "link": parse_link(tile),
            })
        except Exception as e:
            print(f"Skipped a tile: {e}")
    return results

def scrape_pages(base_url, max_pages):
    all_listings = []
    for page in range(1, max_pages + 1):
        page_url = f"{base_url}&page={page}"
        html = crawl(page_url)
        if not html:
            break
        found = scrape_gumtree_search(html)
        if not found:
            break
        all_listings.extend(found)
        print(f"Page {page}: {len(found)} listings")
        time.sleep(2)
    return all_listings

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

def main():
    base_url = "https://www.gumtree.com/search?q=headset"
    rows = scrape_pages(base_url, max_pages=5)
    export(rows)

if __name__ == "__main__":
    main()

Запустите полный скрипт командой python gumtree_scraper.py. Он обходит до пяти страниц поиска по слову "headset", разбирает одну строку на плитку объявления и записывает файлы gumtree_listings.json и gumtree_listings.csv. Цикл scrape_pages добавляет &page=N к URL поиска, прерывается досрочно, когда страница не возвращает плиток (чтобы остановиться в конце результатов, а не получать пустые страницы), и time.sleep(2) между запросами регулирует частоту. Общий список FIELDS синхронизирует порядок столбцов CSV с ключами словаря, чтобы два экспорта никогда не расходились.

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

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

json
[
  {
    "title": "SteelSeries Arctis 7 Wireless Gaming Headset",
    "price": "£65.00",
    "location": "Manchester, Greater Manchester",
    "link": "https://www.gumtree.com/p/headphones/steelseries-arctis-7/1488114476"
  },
  {
    "title": "Sony WH-1000XM4 Noise Cancelling Headphones",
    "price": "£180.00",
    "location": "Leeds, West Yorkshire",
    "link": "https://www.gumtree.com/p/headphones/sony-wh-1000xm4/1483456978"
  }
]

CSV-копия воспроизводит эти строки с одной строкой на объявление под заголовком title,price,location,link. Отсюда можно сортировать по цене, группировать по местоположению, чтобы увидеть, где тот или иной товар встречается чаще всего, или сравнивать последовательные запуски, чтобы отслеживать движение объявлений и запрашиваемых цен со временем. Пример преобразования экспортов объявлений в сравнительный вид приведён в руководстве по веб-скрапингу в e-commerce.

Масштабирование за пределы одного поиска

Тот же шаблон распространяется на столько поисков, сколько нужно. Создайте словарь с названиями запросов и URL поиска, переберите его и запустите пагинированный скрапер для каждого, разделяя вывод по ключу запроса. Если нужны более богатые записи, перейдите по ссылке link каждой плитки на страницу объявления и разберите более полные поля (полное описание, дата размещения, URL изображений), затем объедините их с записью листинга. В любом случае поддерживайте умеренную частоту запросов и используйте ротацию Crawling API, чтобы разветвление по множеству поисков не превысило ограничение по частоте.

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

Сохранение доступности

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

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

Более широкое руководство по предотвращению блокировок см. в статье как парсить сайты без блокировок, а о том, почему здесь важна отрисовка, в статье как парсить JavaScript-сайты. Если хотите глубже изучить сторону BeautifulSoup, руководство по использованию BeautifulSoup в Python подробно охватывает библиотеку парсинга.

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

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

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

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

Итоги

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

  • Страницы поиска Gumtree, публичная лента классифайдов. Каждая плитка содержит название, цену, местоположение и ссылку на объявление, что делает данные полезными для сравнения цен и исследования регионального спроса.
  • Нужны вместе отрисовка и доверенный IP. Gumtree заполняет сетку результатов на стороне клиента и блокирует бот-трафик, поэтому Crawling API отрисовывает страницу за резидентским IP в одном вызове.
  • BeautifulSoup выполняет извлечение. Перебирайте плитки article[data-q="search-result"] и считывайте название, цену, местоположение и ссылку из их атрибутов data-*; ожидайте, что эти атрибуты могут меняться.
  • Пагинация через параметр запроса. Добавляйте &page=N для обхода нескольких страниц, прерывайтесь, когда страница не возвращает плиток, и делайте паузы между запросами.
  • Работайте только с публичными данными листинга. Соблюдайте Условия использования и robots.txt Gumtree и никогда не собирайте личные контактные данные частного продавца или что-либо, находящееся за логином.

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

Почему обычный запрос не возвращает объявления с Gumtree?

Две причины. Gumtree заполняет большую часть сетки результатов на стороне клиента по мере загрузки страницы, поэтому обычный requests.get часто получает оболочку без плиток объявлений. Кроме того, Gumtree блокирует трафик, не похожий на реальный браузер. Отрисовка страницы через Crawling API за доверенным IP решает обе проблемы, именно поэтому скрапер маршрутизирует запрос через него.

Какие данные можно парсить со страницы поиска Gumtree?

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

Как обрабатывать пагинацию на Gumtree?

Gumtree пагинирует результаты поиска с помощью параметра запроса ?page=N, поэтому к URL поиска добавляются &page=2, &page=3 и так далее, и каждый получается. Функция scrape_pages в этом руководстве перебирает диапазон страниц, досрочно останавливается, когда страница не возвращает плиток, и добавляет небольшую задержку между запросами, чтобы не нагружать сайт.

Какие селекторы использует скрапер?

Каждый результат, это article[data-q="search-result"]. Внутри него название, div[data-q="tile-title"], цена, div[data-testid="price"], местоположение, div[data-q="tile-location"], а ссылка на объявление, атрибут href у a[data-q="search-result-anchor"]. Эти атрибуты data-* более долговечны, чем генерируемые имена классов, но проверяйте живую страницу, если поле начинает возвращать пустые значения.

Можно ли экспортировать данные в таблицу?

Да. Функция export записывает и JSON-файл, и CSV с заголовком title,price,location,link, по одной строке на объявление. Откройте CSV напрямую в Excel, Google Sheets или любом табличном редакторе, или загрузите JSON в DataFrame pandas или блокнот для анализа и построения графиков.

Как не попасть под блокировку при парсинге Gumtree?

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

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

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

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

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