Всем привет. Сегодня разберем парсинг eBay. Начнем с простого и посмотрим, как устроен сайт изнутри.
Что вы узнаете из статьи:
- Как анализировать структуру запросов eBay через DevTools
- Парсинг HTML с помощью Python и BeautifulSoup
- Обход защиты от ботов через requests-html
- Масштабирование парсера на несколько страниц
- Сохранение результатов в CSV формат
Изучаем главную страницу
Видим главную. Ничего особенного тут нет. Переходим в поиск.
Анализируем поисковые запросы
Будем работать с ноутбуками для примера.
Написали запрос. Теперь открываем консоль. Жмем F12, переходим в Сеть. В списке запросов находим нужный:
Анализ структуры ответа
Двигаемся дальше. Смотрим на структуру ответа. Видим кашу из символов и тегов:
Разобрать руками сложно. В таком случае можно воспользоваться поиском по странице. Берем характеристику товара, допустим "500 gb", и ищем в коде:
Находим совпадения. Формат не самый удобный для чтения. Попробуем вытащить данные с помощью 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()
Результат выглядит так:
Получили список товаров с названиями, ID и ссылками. Уже можно работать.
Масштабирование: парсинг нескольких страниц
Двигаемся дальше. Парсить одну страницу полезно, но обычно нужно больше данных. Значит, придется масштабировать код на несколько страниц.
Сначала разберемся, как eBay переключает страницы. Возвращаемся в браузер. Нажимаем на вторую страницу и смотрим запрос:
Жмем правой кнопкой и копируем запрос:
Открываем POSTMAN или INSOMNIA. Вставляем запрос и смотрим параметры:
Тут много лишнего. Попробуем убрать куки:
Получше. Видим параметр &_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 файл с данными:
Заключение
Разобрали полный процесс парсинга 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). Можно настроить автоматический запуск скрипта каждый день или час для обновления базы товаров.