Как парсить eBay на Python: пошаговая инструкция 2025 | Elevium
RU | EN
ГЛАВНАЯ ПАРСИМ EBAY

Парсим eBay: Пошаговая инструкция

Всем привет. Сегодня разберем парсинг eBay. Начнем с простого и посмотрим, как устроен сайт изнутри.

Что вы узнаете из статьи:

  • Как анализировать структуру запросов eBay через DevTools
  • Парсинг HTML с помощью Python и BeautifulSoup
  • Обход защиты от ботов через requests-html
  • Масштабирование парсера на несколько страниц
  • Сохранение результатов в CSV формат

Изучаем главную страницу

Главная страница eBay с каталогом товаров и поисковой строкой

Видим главную. Ничего особенного тут нет. Переходим в поиск.

Анализируем поисковые запросы

Будем работать с ноутбуками для примера.

Страница поиска ноутбуков на eBay с результатами выдачи

Написали запрос. Теперь открываем консоль. Жмем F12, переходим в Сеть. В списке запросов находим нужный:

Вкладка Network в DevTools Chrome с запросами к серверу eBay

Анализ структуры ответа

Двигаемся дальше. Смотрим на структуру ответа. Видим кашу из символов и тегов:

HTML код ответа сервера eBay с товарами в DevTools

Разобрать руками сложно. В таком случае можно воспользоваться поиском по странице. Берем характеристику товара, допустим "500 gb", и ищем в коде:

Результат поиска текста 500gb в HTML коде страницы eBay

Находим совпадения. Формат не самый удобный для чтения. Попробуем вытащить данные с помощью Python.

Парсинг HTML с помощью Python

Для начала сохраним HTML страницы в файл. Назовем его response.html. Теперь напишем код для парсинга.

Что будем делать:

  • Читаем HTML-файл в память
  • Создаем объект BeautifulSoup для работы с DOM
  • Ищем все ссылки на товары (они содержат /itm/ в URL)
  • Вытаскиваем название и ID товара
  • Удаляем дубликаты
  • Сохраняем результат в CSV файл
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Простой парсер eBay - извлекает названия товаров и ссылки из HTML файла
"""

import csv
from bs4 import BeautifulSoup
import re

def parse_ebay_html(html_file):
    """Извлекает названия товаров и ссылки из HTML файла eBay"""

    with open(html_file, 'r', encoding='utf-8', errors='ignore') as f:
        html = f.read()

    soup = BeautifulSoup(html, 'html.parser')
    products = []

    print(f"Анализирую файл размером {len(html)} символов...")

    # Ищем ссылки на товары
    for link in soup.find_all('a', href=True):
        href = link.get('href').strip()
        text = link.get_text().strip()

        # Проверяем, что это ссылка на товар eBay
        if '/itm/' in href and text and len(text) > 5:
            # Извлекаем ID товара
            item_id_match = re.search(r'/itm/(\d+)', href)
            item_id = item_id_match.group(1) if item_id_match else ""

            products.append({
                'id': item_id,
                'title': text,
                'url': href
            })

    # Убираем дубликаты по названию
    seen = set()
    unique_products = []
    for product in products:
        title_key = product['title'].lower()
        if title_key not in seen:
            seen.add(title_key)
            unique_products.append(product)

    print(f"Найдено товаров: {len(unique_products)}")
    return unique_products

def save_to_csv(products, filename):
    """Сохраняет товары в CSV файл"""
    with open(filename, 'w', newline='', encoding='utf-8') as f:
        writer = csv.DictWriter(f, fieldnames=['id', 'title', 'url'])
        writer.writeheader()
        writer.writerows(products)

def main():
    html_file = "response.html"
    csv_file = "products.csv"

    products = parse_ebay_html(html_file)
    save_to_csv(products, csv_file)

    print(f"Сохранено в: {csv_file}")
    print("\nВСЕ ТОВАРЫ:")
    for i, product in enumerate(products, start=1):
        print(f"{i}. {product['title']}")
        if product['id']:
            print(f"   ID: {product['id']}")
        print(f"   Ссылка: {product['url']}\n")

if __name__ == "__main__":
    main()

Результат выглядит так:

Консольный вывод Python скрипта с названиями и ID товаров eBay

Получили список товаров с названиями, ID и ссылками. Уже можно работать.

Масштабирование: парсинг нескольких страниц

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

Сначала разберемся, как eBay переключает страницы. Возвращаемся в браузер. Нажимаем на вторую страницу и смотрим запрос:

HTTP запрос ко второй странице поиска eBay в DevTools

Жмем правой кнопкой и копируем запрос:

Опция Copy as cURL в контекстном меню DevTools Chrome

Открываем POSTMAN или INSOMNIA. Вставляем запрос и смотрим параметры:

Интерфейс Postman с импортированным cURL запросом и cookies

Тут много лишнего. Попробуем убрать куки:

Postman после удаления cookies с чистыми параметрами запроса

Получше. Видим параметр &_pgn=3. Он отвечает за номер страницы. Это все, что нам нужно.

Важное замечание о защите eBay

Внимание: Внимание: eBay защищен от ботов. Это усложняет парсинг.

С обычным requests модулем получится сделать примерно 8-12 запросов. Потом eBay начнет блокировать. Защита проверяет наличие JavaScript в браузере.

Решение простое: используйте requests-html вместо обычного requests. Эта библиотека умеет выполнять JavaScript.

Финальный код

Теперь собираем все вместе. Код ниже парсит несколько страниц и сохраняет результаты в CSV:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
  
import csv
import time
import random
from requests_html import HTMLSession
import re
from urllib.parse import urlsplit, urlunsplit

class SimpleEbayParser:
    def __init__(self):
        self.session = HTMLSession()
        self.base_url = "https://www.ebay.com/sch/i.html"
        self.params = {
            "_nkw": "notebook",
            "_sacat": "0",
            "_from": "R40",
            "_pgn": 1
        }
        self.all_products = []
  
    def parse_page(self, page_num):
        """Loads and parses a single page"""
        self.params['_pgn'] = page_num
  
        try:
            print(f"Loading page {page_num}...")
            r = self.session.get(self.base_url, params=self.params)
            print(f"Status: {r.status_code}, Size: {len(r.html.html)} characters")
            
            if 'Checking your browser' in r.html.html:
                print("BLOCKING DETECTED - trying JavaScript render...")
                r.html.render(timeout=15, wait=2, sleep=1)
                print(f"After rendering: {len(r.html.html)} characters")

            with open(f'debug_{page_num}.html', 'w', encoding='utf-8') as f:
                f.write(r.html.html)

            products = self.extract_products(r, page_num)
            return products

        except Exception as e:
            print(f"Error on page {page_num}: {e}")
            return []
  

    def extract_products(self, r, page_num):
        """Extracts products from HTML"""
        products = []
        links = r.html.find('a[href*="/itm/"]')
        print(f"Found product links: {len(links)}")

        for link in links:
            try:
                href = link.attrs.get('href', '')
                title = link.text.strip()
                if not href or not title or len(title) < 5:
                    continue

                item_id = ''
                match = re.search(r'/itm/(\d+)', href)
                if match:
                    item_id = match.group(1)

                if href.startswith('/'):
                    href = 'https://www.ebay.com' + href
                clean_url = href.split('?')[0]

                price = "Price not found"
                parent = link.element.getparent()
                for _ in range(3):
                    if parent is not None:
                        parent_text = parent.text_content() if hasattr(parent, 'text_content') else ''
                        price_match = re.search(r'[\$€£₽][\d,]+\.?\d*', parent_text)
                        if price_match:
                            price = price_match.group(0)
                            break
                        parent = parent.getparent()
                    else:
                        break
  
                products.append({
                    'page': page_num,
                    'id': item_id,
                    'title': title,
                    'url': clean_url,
                    'price': price
                })
  
            except Exception as e:
                print(f"Error processing link: {e}")
                continue

        seen_titles = set()
        unique_products = []
        for product in products:
            title_key = product['title'].lower()
            if title_key not in seen_titles and len(product['title']) > 10:
                seen_titles.add(title_key)
                unique_products.append(product)
  
        print(f"Unique products: {len(unique_products)}")
        return unique_products


    def parse_multiple_pages(self, start=1, end=3):
        """Parses multiple pages"""
        print(f"Starting to parse pages {start}-{end}")

        for page_num in range(start, end + 1):
            products = self.parse_page(page_num)
            self.all_products.extend(products)
            print(f"Page {page_num}: {len(products)} products")
            
            if page_num < end:
                pause = random.uniform(3, 7)
                print(f"Pause {pause:.1f} sec...")
                time.sleep(pause)

        print(f"Total found: {len(self.all_products)} products")


    def save_results(self, filename='simple_results.csv'):
        """Saves results to CSV"""
        if not self.all_products:
            print("No data to save")
            return

        with open(filename, 'w', newline='', encoding='utf-8') as f:
            writer = csv.DictWriter(f, fieldnames=['page', 'id', 'title', 'url', 'price'])
            writer.writeheader()
            writer.writerows(self.all_products)

        print(f"Saved to {filename}")


    def print_results(self):
        """Shows results"""
        if not self.all_products:
            print("No results")
            return

        print("\nResults:")
        for i, product in enumerate(self.all_products[:10], 1):
            print(f"{i:2d}. {product['title'][:60]}...")
            print(f"    Price: {product['price']} | ID: {product['id']}")
            print()

  
def main():
    parser = SimpleEbayParser()
    try:
        parser.parse_multiple_pages(1, 3)
        parser.save_results()
        parser.print_results()

    except KeyboardInterrupt:
        print("Interrupted")
    except Exception as e:
        print(f"Error: {e}")
    finally:
        parser.session.close()

if __name__ == "__main__":
    main()

В итоге получаем CSV файл с данными:

Таблица CSV с результатами парсинга eBay: названия, ID, цены товаров

Заключение

Разобрали полный процесс парсинга eBay. От анализа структуры сайта до рабочего парсера с обходом защиты. Что важно помнить:

  • DevTools помогает понять структуру запросов
  • BeautifulSoup отлично справляется с парсингом HTML
  • Requests-html обходит защиту eBay через JavaScript
  • Паузы между запросами снижают риск бана
  • CSV подходит для хранения результатов

Этот подход можно применить к другим маркетплейсам. Главное понимать структуру сайта и учитывать его защиту.

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

Законен ли парсинг eBay?

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

Почему requests блокируется после нескольких запросов?

eBay использует защиту от ботов, которая проверяет наличие JavaScript в браузере. Обычный requests не выполняет JS, поэтому быстро блокируется. Решение: использовать requests-html, который умеет рендерить JavaScript.

Можно ли парсить eBay без сохранения HTML файла?

Да. Пример с сохранением HTML нужен для обучения. В реальном коде вы делаете запрос напрямую через requests-html и сразу парсите результат с помощью BeautifulSoup.

Сколько страниц можно парсить за раз?

С requests-html и паузами 3-7 секунд между запросами можно парсить десятки страниц без блокировки. Но для больших объемов лучше использовать прокси и распределенные запросы.

Какие альтернативы BeautifulSoup существуют?

Альтернативы: lxml (быстрее, но сложнее), Scrapy (фреймворк для больших проектов), Selenium (если нужно сложное взаимодействие с сайтом). Для простых задач BeautifulSoup оптимален.

Как обновлять данные автоматически?

Используйте планировщики задач: cron (Linux/Mac) или Task Scheduler (Windows). Можно настроить автоматический запуск скрипта каждый день или час для обновления базы товаров.