Scraping eBay za pomocą Pythona: kod, który naprawdę działa

Ostatnia aktualizacja: April 16, 2026

Większość poradników o scrapowaniu eBay ma termin ważności mniej więcej trzech miesięcy. Wiem to, bo zespół Thunderbit widział już niejednego dewelopera błądzącego między zepsutymi kawałkami kodu, nieaktualnymi selektorami CSS i „działającymi” repozytoriami na GitHubie, które przestawały działać po dwóch kolejnych odświeżeniach interfejsu eBay.

eBay ma — to największy publicznie dostępny zbiór danych o cenach w długim ogonie po Amazonie. Te dane napędzają wszystko: od ustalania cen przez resellerów po analizę konkurencji. Ale dostęp programistyczny do nich to ruchomy cel: frontend eBay oparty na React stale zmienia nazwy klas CSS, testy A/B pokazują różne struktury DOM różnym użytkownikom, a Akamai Bot Manager stoi między tobą a HTML-em. Ten przewodnik pokazuje kod Pythona, który działa dziś, wyjaśnia, dlaczego scrapery się psują, żebyś mógł budować bardziej odporne rozwiązania, uczciwie porównuje eBay API i scrapowanie oraz pokazuje wyjście bez kodu na wypadek, gdy Python nie jest wart całej tej roboty.

Co to znaczy scrapować eBay w Pythonie?

Scrapowanie eBay w Pythonie oznacza pisanie skryptów, które programowo pobierają strony eBay, analizują HTML (albo ukryty JSON) i wyciągają ustrukturyzowane dane — tytuły, ceny, informacje o sprzedawcach, daty sprzedaży, warianty produktów — do formatu, z którego naprawdę możesz korzystać, np. CSV, arkusza kalkulacyjnego albo bazy danych.

Możesz scrapować kilka typów stron eBay:

  • Wyniki wyszukiwania (np. wszystkie oferty „AirPods Pro”)
  • Strony pojedynczych produktów (pełna specyfikacja, obrazy, dane sprzedawcy)
  • Oferty sprzedane/zakończone (rzeczywiste ceny transakcyjne i daty)
  • Profile sprzedawców i recenzje

Python jest tutaj najczęściej wybieranym językiem. Jego ekosystem — Requests, BeautifulSoup, lxml, pandas — ułatwia pobieranie stron, parsowanie HTML i obrabianie danych. Jest jednak ważna różnica między scrapowaniem HTML strony a korzystaniem z oficjalnego API eBay — do tego wrócę za chwilę.

Dlaczego scrapować eBay? Rzeczywiste zastosowania w zespołach biznesowych

Jeśli to czytasz, pewnie już masz swój powód. Warto jednak osadzić temat w konkretnej wartości biznesowej, bo zwrot z danych eBay potrafi być naprawdę mocny. Bain ustalił, że w tysiącach firm. McKinsey przypisuje dynamicznemu ustalaniu cen w handlu detalicznym .

Najczęstsze przypadki użycia, które widzę:

Przypadek użyciaPotrzebne daneEfekt biznesowy
Monitorowanie cen i repricingCeny aktywnych ofert, koszty dostawy, stanKonkurencyjne ceny, ochrona marży
Analiza konkurencjiAsortyment, promocje, warunki dostawyPozycjonowanie strategiczne, luki w ofercie
Badania rynku i wykrywanie trendówTempo publikacji ofert, trendy kategorii, wzorce popytuIdentyfikacja nowych produktów, prognozowanie popytu
Wycena resellerska / appraisalCeny sprzedaży, daty sprzedaży, stanRynkowa wycena, decyzje buy-box
Analiza sentymentuRecenzje, oceny, polityka zwrotówWgląd w jakość produktu, satysfakcja klienta
Generowanie leadówProfile sprzedawców, dane sklepu, dane kontaktoweOutreach B2B do sprzedawców o wysokim GMV

ebayscraping_stats_77c7da1611.png

Wspólny mianownik: eBay ma te dane, ale są one zamknięte w stronach internetowych.

Scraping pozwala zamienić je w przewagę konkurencyjną.

Oficjalne API eBay kontra scrapowanie w Pythonie: co wybrać?

To pytanie, na które chciałbym częściej widzieć uczciwą odpowiedź w poradnikach. eBay udostępnia oficjalne API — przede wszystkim — i wielu użytkowników zastanawia się, czy lepiej z niego korzystać, czy scrapować bezpośrednio. Odpowiedź zależy wyłącznie od tego, jakich danych potrzebujesz.

KryteriumeBay Browse/Finding APIScraping w Pythonie
Oferty sprzedane/zakończoneOgraniczone — istnieje Marketplace Insights API, ale dostęp jest często odrzucanyPełny dostęp przez parametry URL LH_Sold=1&LH_Complete=1
Limity zapytań5000 wywołań dziennie w podstawowym planieZarządzane samodzielnie (zależne od proxy)
Pola danychZ góry zdefiniowane (tytuł, cena, kategoria, podstawowe dane sprzedawcy)Wszystko, co widać na stronie (recenzje, pełna specyfikacja, macierz wariantów)
Złożoność konfiguracjiOAuth 2.0, rejestracja aplikacji, klucze APIpip install + kod
StabilnośćStabilne endpointyPsuje się, gdy zmienia się HTML
KosztDostępny darmowy plan, płatny przy większej skaliDarmowy kod, ale przy dużej skali dochodzą koszty proxy
Dane wariantów/MSKUCzęściowe — często tylko SKU nadrzędnePełne (poprzez parsowanie ukrytego JSON)
Głębokość paginacjiTwardy limit 10 000 elementówTeoretycznie bez limitu

Krótka uwaga: stare Finding API (z findCompletedItems) zostało . Jeśli używasz ebaysdk-python albo jakiejkolwiek biblioteki, która uderza w moduł Finding, to teraz nie działa to produkcyjnie.

Moja rekomendacja: używaj Browse API do stabilnych, umiarkowanych ilościowo, ustrukturyzowanych zapytań dotyczących aktywnych ofert. Używaj scrapowania w Pythonie, gdy potrzebujesz cen sprzedaży, recenzji, danych wariantów albo dowolnego pola, którego API nie udostępnia. Wiele zespołów łączy oba podejścia.

Jakich narzędzi i bibliotek potrzebujesz, aby scrapować eBay w Pythonie?

Zanim napiszemy kod, spójrzmy na zestaw narzędzi. W przypadku większości stron eBay nie potrzebujesz headless browsera — dane są osadzone w HTML renderowanym po stronie serwera.

BibliotekaZastosowanie
requests lub httpxKlient HTTP do pobierania stron eBay
curl_cffiKlient HTTP z prawdziwym fingerprintem TLS przeglądarki (kluczowy do obchodzenia Akamai)
beautifulsoup4Parser HTML do wyciągania danych selektorami CSS
lxmlSzybkie zaplecze parsera dla BeautifulSoup
jmespathJęzyk zapytań do parsowania zagnieżdżonych bloków JSON
pandasObróbka danych oraz eksport do CSV/Excel
gspreadIntegracja z Google Sheets

Zainstaluj wszystko jedną komendą:

1pip install requests httpx curl_cffi beautifulsoup4 lxml jmespath pandas gspread

Używaj Pythona 3.11+ — pandas 3.0 wymaga 3.10+, a 3.11 daje 10–60% zysku wydajności przy pracy I/O-bound.

Jedna biblioteka zasługuje na szczególne wyróżnienie: curl_cffi to najważniejsza modernizacja, jaką może zrobić scraper eBay w 2026 roku. eBay korzysta z , a głównym sygnałem wykrywania jest fingerprint TLS. Zwykły requests wysyła fingerprint JA3 „w kształcie Pythona”, który jest natychmiast flagowany. curl_cffi udaje handshake TLS prawdziwej przeglądarki Chrome, co pozwala obsłużyć około 90% chronionych przez Akamai celów bez potrzeby uruchamiania headless browsera.

Krok po kroku: jak scrapować wyniki wyszukiwania eBay w Pythonie

To jest główny tutorial. Będziemy scrapować strony wyników wyszukiwania eBay dla ofert produktów.

  • Poziom trudności: początkujący–średnio zaawansowany
  • Czas potrzebny: ok. 30 minut do pierwszego działającego scrapera
  • Czego potrzebujesz: Python 3.11+, biblioteki z sekcji powyżej, terminal i docelowy URL wyszukiwania eBay

Krok 1: przygotuj projekt Pythona

Utwórz katalog projektu i zainstaluj zależności:

1mkdir ebay-scraper && cd ebay-scraper
2python -m venv venv
3source venv/bin/activate  # Windows: venv\Scripts\activate
4pip install requests curl_cffi beautifulsoup4 lxml pandas

Utwórz plik o nazwie scrape_ebay.py. To będzie twoje środowisko pracy.

Krok 2: zbuduj URL wyszukiwania eBay

Struktura URL wyszukiwania na eBay jest prosta. Najważniejszy parametr to _nkw (keyword):

1import urllib.parse
2keyword = "airpods pro"
3base_url = "https://www.ebay.com/sch/i.html"
4params = {
5    "_nkw": keyword,
6    "_ipg": "120",    # liczba elementów na stronę: 60, 120 lub 240 (240 może uruchamiać flagi botowe)
7    "_pgn": "1",      # numer strony
8}
9url = f"{base_url}?{urllib.parse.urlencode(params)}"
10print(url)
11# https://www.ebay.com/sch/i.html?_nkw=airpods+pro&_ipg=120&_pgn=1

Inne przydatne parametry:

  • LH_BIN=1 — tylko Buy It Now
  • _sacat=175673 — konkretna kategoria
  • _sop=12 — sortowanie według najlepszego dopasowania (10 = najniższa cena + wysyłka, 13 = najnowsze)
  • LH_Complete=1&LH_Sold=1 — oferty sprzedane/zakończone (omówione w osobnej sekcji poniżej)

Krok 3: wyślij żądanie i obsłuż odpowiedź

To tutaj curl_cffi pokazuje swoją wartość. Zwykły requests.get() często zwróci 403 od Akamai. Z curl_cffi udajemy prawdziwą przeglądarkę Chrome:

1from curl_cffi import requests as cffi_requests
2import random, time
3USER_AGENTS = [
4    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36",
5    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36",
6    "Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0",
7]
8HEADERS = {
9    "User-Agent": random.choice(USER_AGENTS),
10    "Accept-Language": "en-US,en;q=0.9",
11    "Accept-Encoding": "gzip, deflate, br",
12}
13def fetch_page(url, max_retries=5):
14    delay = 2
15    for attempt in range(max_retries):
16        try:
17            r = cffi_requests.get(url, impersonate="chrome124", headers=HEADERS, timeout=30)
18            if r.status_code == 200:
19                return r.text
20            if r.status_code in (403, 429, 503):
21                retry_after = r.headers.get("Retry-After")
22                sleep_for = float(retry_after) if retry_after else delay + random.uniform(0, 1)
23                print(f"  Status {r.status_code}, ponawiam za {sleep_for:.1f}s...")
24                time.sleep(sleep_for)
25                delay *= 2
26                continue
27            r.raise_for_status()
28        except Exception as e:
29            print(f"  Błąd żądania: {e}, ponawiam...")
30            time.sleep(delay)
31            delay *= 2
32    raise RuntimeError(f"Nie udało się po {max_retries} próbach: {url}")

Eksponencjalny backoff z losowym jitterem jest ważny — stałe odstępy czasowe same w sobie są fingerprintem bota.

Krok 4: parsuj oferty produktów ze strony wyników wyszukiwania

eBay jest obecnie w trakcie migracji między dwoma układami wyników wyszukiwania. Odporny scraper musi obsługiwać oba:

PoleStarszy układNowy układ
Kontener kartyli.s-itemli.s-card lub div.su-card-container
Tytuł.s-item__title.s-card__title
URLa.s-item__link[href]a.su-link[href]
Cenaspan.s-item__price.s-card__price

Kod parsujący oba układy:

1from bs4 import BeautifulSoup
2def parse_search_results(html):
3    soup = BeautifulSoup(html, "lxml")
4    cards = soup.select("li.s-item, li.s-card, div.su-card-container")
5    results = []
6    for card in cards:
7        # Tytuł — sprawdź oba układy
8        title_el = card.select_one(".s-item__title, .s-card__title")
9        title = title_el.get_text(strip=True) if title_el else None
10        # Pomiń fantomową kartę zastępczą „Shop on eBay”
11        if not title or "Shop on eBay" in title:
12            continue
13        # Cena
14        price_el = card.select_one("span.s-item__price, .s-card__price")
15        price = price_el.get_text(strip=True) if price_el else None
16        # URL
17        link_el = card.select_one("a.s-item__link[href], a.su-link[href]")
18        url = link_el["href"].split("?")[0] if link_el else None
19        # Obraz
20        img_el = card.select_one("img.s-item__image-img, .s-card__image img")
21        image = None
22        if img_el:
23            image = img_el.get("src") or img_el.get("data-src")
24        # Wysyłka
25        ship_el = card.select_one("span.s-item__shipping, span.s-item__logisticsCost, .s-card__attribute-row")
26        shipping = ship_el.get_text(strip=True) if ship_el else None
27        results.append({
28            "title": title,
29            "price": price,
30            "url": url,
31            "image": image,
32            "shipping": shipping,
33        })
34    return results

Pułapka z pierwszą, fantomową kartą to klasyczny problem. Pierwsze li.s-item na wielu stronach wyników eBay to ukryty placeholder z tytułem „Shop on eBay” i bez realnej ceny. Zawsze go filtruj.

Krok 5: obsłuż paginację, aby scrapować wiele stron

eBay paginuje przez parametr _pgn. Link do następnej strony używa a.pagination__next:

1import urllib.parse
2def scrape_ebay_search(keyword, max_pages=5):
3    all_results = []
4    for page_num in range(1, max_pages + 1):
5        params = {"_nkw": keyword, "_ipg": "120", "_pgn": str(page_num)}
6        url = f"https://www.ebay.com/sch/i.html?{urllib.parse.urlencode(params)}"
7        print(f"Scraping page {page_num}: {url}")
8        html = fetch_page(url)
9        results = parse_search_results(html)
10        if not results:
11            print(f"  Brak wyników na stronie {page_num}, zatrzymuję się.")
12            break
13        all_results.extend(results)
14        print(f"  Znaleziono {len(results)} ofert (łącznie: {len(all_results)})")
15        # Uprzejma przerwa — 3 do 8 sekund z losowym jitterem
16        time.sleep(random.uniform(3, 8))
17    return all_results

Losowy jitter 3–8 sekund nie jest opcjonalny.

eBay i warstwa Akamai wykrywają ciągłe tempo powyżej 1 żądania na sekundę z jednego IP.

Krok 6: eksportuj zeskrobane dane do CSV lub JSON

1import pandas as pd
2results = scrape_ebay_search("airpods pro", max_pages=3)
3df = pd.DataFrame(results)
4df.to_csv("ebay_airpods.csv", index=False)
5df.to_json("ebay_airpods.json", orient="records", indent=2)
6print(f"Wyeksportowano {len(df)} ofert do CSV i JSON.")

Powinieneś teraz mieć czysty arkusz z ofertami eBay. Na moim komputerze scrapowanie 3 stron (360 ofert) zajęło około 45 sekund, łącznie z opóźnieniami.

Jak scrapować strony szczegółów produktu eBay w Pythonie

Wyniki wyszukiwania dają tylko skrót. Strony szczegółów produktu zawierają najcenniejsze informacje: pełny opis, oceny sprzedawcy, cechy przedmiotu, karuzele obrazów i dane wariantów.

Parsowanie pojedynczej strony oferty

Strony przedmiotów eBay mają adres w formacie /itm/<ITEM_ID>. Najstabilniejszą metodą wyciągania danych jest JSON-LD — eBay osadza blok schematu Product, który przetrwa niemal wszystkie zmiany CSS:

1import json
2def parse_item_page(html):
3    soup = BeautifulSoup(html, "lxml")
4    item = {}
5    # 1. JSON-LD — najstabilniejsza ścieżka ekstrakcji
6    for tag in soup.find_all("script", type="application/ld+json"):
7        try:
8            data = json.loads(tag.string or "")
9        except (json.JSONDecodeError, TypeError):
10            continue
11        if isinstance(data, dict) and data.get("@type") == "Product":
12            item["title"] = data.get("name")
13            item["brand"] = (data.get("brand") or {}).get("name")
14            item["images"] = data.get("image")
15            offers = data.get("offers") or {}
16            item["price"] = offers.get("price")
17            item["currency"] = offers.get("priceCurrency")
18            break
19    # 2. Fallbacki CSS dla pól, których nie ma w JSON-LD
20    def first_text(selectors):
21        for sel in selectors:
22            el = soup.select_one(sel)
23            if el and el.get_text(strip=True):
24                return el.get_text(strip=True)
25        return None
26    item.setdefault("title", first_text([
27        "h1.x-item-title__mainTitle",
28        "h1.x-item-title__mainTitle .ux-textspans--BOLD",
29    ]))
30    item["condition"] = first_text([
31        ".x-item-condition-text .ux-textspans",
32    ])
33    item["seller"] = first_text([
34        ".x-sellercard-atf__info__about-seller a .ux-textspans",
35    ])
36    item["shipping"] = first_text([
37        "div.ux-labels-values--shipping .ux-textspans--BOLD",
38    ])
39    # 3. Cechy przedmiotu
40    specifics = {}
41    for dl in soup.select(".ux-layout-section-evo__item--table-view dl.ux-labels-values"):
42        k = dl.select_one(".ux-labels-values__labels-content .ux-textspans")
43        v = dl.select_one(".ux-labels-values__values-content .ux-textspans")
44        if k and v:
45            specifics[k.get_text(strip=True).rstrip(":")] = v.get_text(strip=True)
46    item["specifics"] = specifics
47    return item

Ten wzorzec — najpierw JSON-LD, potem fallbacki CSS — jest kluczem do budowania scraperów, które nie psują się co kwartał. Więcej o tym poniżej.

Scrapowanie wariantów produktów eBay (dane MSKU)

Niektóre oferty eBay mają wiele wariantów — różne kolory, rozmiary, pojemności pamięci. Widoczny DOM pokazuje tylko zakres cen, np. „899 do 1099 dolarów”, dopóki użytkownik nie kliknie opcji. Faktyczna cena dla każdego wariantu znajduje się w ukrytym obiekcie JavaScript o nazwie MSKU.

To jeden z obszarów, w których API eBay daje tylko częściowe dane (SKU nadrzędne), więc scraping jest lepszym rozwiązaniem.

1import re, json
2def extract_variants(html):
3    # Nietrywialne dopasowanie niechciwe jest kluczowe — chciwe .+ pochłania całą stronę
4    m = re.search(r'"MSKU"\s*:\s*(\{.+?\})\s*,\s*"QUANTITY"', html, re.DOTALL)
5    if not m:
6        return []
7    try:
8        msku = json.loads(m.group(1))
9    except json.JSONDecodeError:
10        return []
11    item_labels = {str(k): v["displayLabel"] for k, v in msku.get("menuItemMap", {}).items()}
12    skus = []
13    for combo_key, variation_id in msku.get("variationCombinations", {}).items():
14        option_ids = combo_key.split("_")
15        options = [item_labels.get(oid, oid) for oid in option_ids]
16        var = msku.get("variationsMap", {}).get(str(variation_id), {})
17        bin_model = var.get("binModel", {})
18        price_spans = bin_model.get("price", {}).get("textSpans", [{}])
19        price = price_spans[0].get("text") if price_spans else None
20        qty = var.get("quantity")
21        skus.append({
22            "options": options,
23            "price": price,
24            "quantity_available": qty,
25            "variation_id": variation_id,
26        })
27    return skus

Ten niechciwy fragment (.+?) w regexie to miejsce, na którym potyka się niemal każdy scraper eBay. Chciwe .+ pochłania wszystko aż do ostatniego "QUANTITY" na stronie, co daje uszkodzony JSON. Widziałem ten błąd w co najmniej trzech „działających” poradnikach.

Jak scrapować sprzedane i zakończone oferty eBay w Pythonie

To właśnie ten przypadek użycia najmocniej uzasadnia scrapowanie zamiast API. Dane o sprzedanych przedmiotach — co faktycznie zostało sprzedane, za ile i kiedy — to złoty standard dla badań rynku, wyceny resellerskiej i appraisal. Browse API eBay nie udostępnia tego wprost. Marketplace Insights API , ale dostęp ma status „Limited Release” i .

Potrzebne parametry URL to LH_Complete=1 (oferty zakończone) oraz LH_Sold=1 (ograniczenie do faktycznie sprzedanych). Musisz podać oba. Samo LH_Sold=1 na niektórych kategoriach po cichu wraca do aktywnych ofert — to najczęstsza pułapka społeczności.

1def scrape_sold_listings(keyword, max_pages=3):
2    all_sold = []
3    for page_num in range(1, max_pages + 1):
4        params = {
5            "_nkw": keyword,
6            "_ipg": "120",
7            "_pgn": str(page_num),
8            "LH_Complete": "1",
9            "LH_Sold": "1",
10        }
11        url = f"https://www.ebay.com/sch/i.html?{urllib.parse.urlencode(params)}"
12        print(f"Scraping sold page {page_num}...")
13        html = fetch_page(url)
14        soup = BeautifulSoup(html, "lxml")
15        cards = soup.select("li.s-item")
16        for card in cards:
17            title_el = card.select_one(".s-item__title")
18            title = title_el.get_text(strip=True) if title_el else None
19            if not title or "Shop on eBay" in title:
20                continue
21            # Uwzględniaj tylko faktycznie sprzedane przedmioty (zielona, dodatnia cena)
22            sold_tag = card.select_one(
23                ".s-item__title--tag .POSITIVE, .s-item__caption--signal.POSITIVE"
24            )
25            if sold_tag is None:
26                continue  # Niesprzedana zakończona oferta — pomiń
27            price_el = card.select_one("span.s-item__price")
28            price = price_el.get_text(strip=True) if price_el else None
29            # Parsowanie daty sprzedaży
30            sold_date = None
31            import re, datetime as dt
32            card_text = card.get_text()
33            m = re.search(r"Sold\s+([A-Z][a-z]{2}\s+\d{1,2},\s*\d{4})", card_text)
34            if m:
35                sold_date = dt.datetime.strptime(m.group(1), "%b %d, %Y").strftime("%Y-%m-%d")
36            link_el = card.select_one("a.s-item__link[href]")
37            url = link_el["href"].split("?")[0] if link_el else None
38            all_sold.append({
39                "title": title,
40                "sold_price": price,
41                "sold_date": sold_date,
42                "url": url,
43            })
44        if not cards:
45            break
46        time.sleep(random.uniform(3, 8))
47    return all_sold

Kluczowa różnica w HTML: sprzedane przedmioty pokazują cenę na zielono (w opakowaniu .POSITIVE), a niesprzedane zakończone oferty pokazują cenę na czerwono, przekreśloną. Zawsze filtruj po klasie .POSITIVE.

Dlaczego scrapery eBay się psują (i jak budować odporne rozwiązania)

Jeśli twój scraper eBay przestał działać, nie jesteś sam. To najczęstszy ból w każdym wątku na forum o scrapowaniu eBay, jaki czytałem. Pytanie nie brzmi czy scraper się zepsuje — tylko kiedy.

Dlaczego tak się dzieje:

  • eBay używa renderowania opartego na React z dynamicznie generowanymi klasami, które zmieniają się przy wdrożeniach
  • Testy A/B pokazują różne struktury DOM różnym użytkownikom (obecny podwójny układ s-item / s-card jest tego żywym przykładem)
  • Okresowe redesigny zmieniają zagnieżdżenie HTML, nawet jeśli same dane pozostają takie same
  • Stare selektory, takie jak #itemTitle i #prcIsum, zostały usunięte lata temu, ale nadal pojawiają się w poradnikach

Jak ujmuje to : „Prawdziwe wyzwanie przy scrapowaniu eBay polega na radzeniu sobie ze zmianami selektorów CSS. eBay regularnie aktualizuje frontend, przez co psuje scraperom zależnym od konkretnych nazw klas.”

ebayscraping_section_50be5cc410.png

Strategie obronne dla długowiecznych scraperów eBay

Cztery strategie, które przetrwają kwartalne przetasowania w eBay:

1. Priorytet dla JSON-LD zamiast selektorów CSS. eBay osadza ustrukturyzowane dane schematu Product na każdej stronie przedmiotu. Warstwa danych zmienia się znacznie rzadziej niż warstwa prezentacji — projektanci refaktoryzują klasy CSS co kwartał, ale nazwy pól backendowych, takich jak price, name czy seller, są mapowane do wewnętrznych API i rzadko się zmieniają.

2. Używaj kaskadowych selektorów zapasowych. Nigdy nie polegaj na jednym selektorze CSS. Zawsze podawaj alternatywy:

1def first_text(soup, selectors):
2    for sel in selectors:
3        el = soup.select_one(sel)
4        if el and el.get_text(strip=True):
5            return el.get_text(strip=True)
6    return None
7title = first_text(soup, [
8    "h1.x-item-title__mainTitle",
9    "h1.x-item-title__mainTitle .ux-textspans--BOLD",
10    "[data-testid='x-item-title'] h1",
11])

3. Parsuj ukryte bloki JSON. Obiekt wariantów MSKU i wbudowane dane JavaScript przetrwają zmiany CSS, bo są generowane po stronie serwera. Wyciąganie ich regexem z tagów <script> wymaga więcej pracy na początku, ale dramatycznie zmniejsza koszty utrzymania.

4. Loguj błędy selektorów. Dodaj monitoring, żeby wiedzieć kiedy selektor przestaje pasować, a nie tylko że dane są puste:

1if title is None:
2    print(f"WARNING: selector tytułu nie zadziałał dla {url}")

5. Używaj curl_cffi z impersonacją przeglądarki. To pozwala obejść TLS fingerprinting Akamai bez headless browsera.

Alternatywa napędzana przez AI: bez utrzymania selektorów

Jeśli masz dość łatania selektorów co kilka miesięcy, istnieje zupełnie inne podejście. Narzędzia takie jak używają AI, aby za każdym razem czytać stronę na nowo i dynamicznie wyprowadzać logikę ekstrakcji. Badanie McGill University porównujące AI ze scraperami opartymi na selektorach na 3000 stron pokazało, że , a branżowe benchmarki wskazują na .

PodejścieCzy psuje się po zmianie HTML przez eBay?Nakład na utrzymanie
Twardo zakodowane selektory CSSTak, co kwartałWysoki — ciągłe poprawki
Ekstrakcja ukrytego JSON / JSON-LDRzadkoNiski
Scrapowanie oparte na AI (Thunderbit)Nie — AI odtwarza selektory przy każdym uruchomieniuBrak

Workflow Thunderbit omówię szczegółowo później. Na razie najważniejsze jest to: jeśli budujesz scraper, który ma działać miesiącami, zainwestuj w ekstrakcję opartą najpierw na JSON-ie i selektory zapasowe. Jeśli w ogóle nie chcesz utrzymywać selektorów, podejście AI naprawdę warto rozważyć.

Automatyzacja cyklicznego scrapowania eBay do monitorowania cen

Jednorazowe scrapowanie jest przydatne. Ale monitorowanie cen, śledzenie stanów magazynowych i analiza konkurencji wymagają cyklicznego zbierania danych. Każdy artykuł konkurencji, który czytałem, wspomina monitoring cen jako zastosowanie, ale prawie żaden nie pokazuje, jak to faktycznie zautomatyzować.

Opcja 1: Cron joby (Linux/macOS) lub Harmonogram zadań (Windows)

Najprostsze podejście. Owiń swój skrypt Pythona w cron. Zawsze używaj pełnej ścieżki do Pythona z wirtualnego środowiska — cron działa w minimalnym środowisku:

1crontab -e
2# Codziennie o 08:15
315 8 * * * /Users/me/ebay/venv/bin/python /Users/me/ebay/scrape_ebay.py >> /Users/me/ebay/scrape.log 2>&1

W Windows użyj PowerShell:

1$A = New-ScheduledTaskAction -Execute "C:\Users\me\ebay\venv\Scripts\python.exe" -Argument "C:\Users\me\ebay\scrape_ebay.py"
2$T = New-ScheduledTaskTrigger -Daily -At 8:15am
3Register-ScheduledTask -TaskName "eBayScraper" -Action $A -Trigger $T

To wymaga stale włączonej maszyny, a proxy i zabezpieczenia anty-botowe zarządzasz samodzielnie.

Opcja 2: funkcje chmurowe (serverless)

AWS Lambda albo Google Cloud Functions pozwalają uruchamiać scrapery bez dedykowanego serwera. To większy wysiłek konfiguracyjny — musisz spakować zależności, obsłużyć timeouty (Lambda ma limit 15 minut) i nadal zarządzać proxy. Ale nie ma utrzymania serwera.

Opcja 3: planowanie bez kodu w Thunderbit

Funkcja pozwala opisać interwał zwykłym językiem (np. „co dzień o 8 rano”), wkleić URL-e eBay i kliknąć Schedule. Działa w chmurze z wbudowaną obsługą anty-bot.

PodejścieNakład konfiguracjiWymaga serwera?Obsługa anty-bota?
Cron + skrypt PythonaŚredniTak (maszyna zawsze włączona)Proxy po twojej stronie
Funkcja chmurowa (Lambda)WysokiNie (serverless)Proxy po twojej stronie
Thunderbit Scheduled ScraperNiski (opis słowny)Nie (w chmurze)Wbudowana

Do przechowywania danych z cyklicznych scrape’ów lokalna baza SQLite jest najlepszym rozwiązaniem dla historii cen. Używaj ON CONFLICT ... DO UPDATE (a nie INSERT OR REPLACE, które ):

1CREATE TABLE IF NOT EXISTS listings (
2    item_id       TEXT PRIMARY KEY,
3    title         TEXT NOT NULL,
4    price         REAL,
5    last_price    REAL,
6    first_seen_at TEXT DEFAULT (datetime('now')),
7    last_seen_at  TEXT DEFAULT (datetime('now'))
8);
9CREATE TABLE IF NOT EXISTS price_history (
10    item_id     TEXT NOT NULL,
11    observed_at TEXT NOT NULL DEFAULT (datetime('now')),
12    price       REAL NOT NULL,
13    PRIMARY KEY (item_id, observed_at)
14);

Nie chcesz kodować? Jak scrapować eBay w 2 minuty z Thunderbit

Poświęciłem już 2000 słów na kod Pythona. Teraz chcę uczciwie powiedzieć, kiedy nie jest on potrzebny.

Jeśli jesteś użytkownikiem biznesowym robiącym jednorazowe badanie rynku, resellerem sprawdzającym porównywalne oferty albo zespołem e-commerce, który potrzebuje danych na już bez sprintu deweloperskiego, Python to przesada. Konfiguracja, utrzymanie selektorów, zarządzanie proxy — to sporo narzutu na coś w stylu: „po prostu potrzebuję tych 200 ofert w arkuszu”.

Jak Thunderbit scrapuje eBay — krok po kroku

  1. Zainstaluj — bez karty kredytowej.
  2. Otwórz dowolną stronę wyników wyszukiwania lub produkt na eBay w Chrome.
  3. Kliknij „AI Suggest Fields” w panelu bocznym Thunderbit. AI odczyta stronę i zaproponuje kolumny: Tytuł, Cena, Stan, Dostawa, Sprzedawca, Ocena.
  4. Kliknij „Scrape”. Rozszerzenie przejdzie przez paginację i wypełni tabelę danych. Dla eBay Thunderbit ma , które działają jednym kliknięciem.
  5. Wyeksportuj do Google Sheets, Airtable, Notion, CSV, JSON lub Excel — za darmo.

Cały proces zajmuje mniej niż 2 minuty.

Sprawdzałem to.

Wzbogacanie podstron: dane ze strony szczegółów bez dodatkowego kodu

Po zeskrobaniu strony wyników wyszukiwania Thunderbit może odwiedzić stronę szczegółów każdej oferty i dodać kolejne pola — pełną specyfikację, informacje o sprzedawcy, opis, wszystkie obrazy. Zastępuje to 20+ linijek kodu Pythona do scrapowania podstron, które napisaliśmy wcześniej, jednym kliknięciem.

Kiedy nadal warto używać Pythona

Python wygrywa, gdy potrzebujesz:

  • Scrapowania na dużą skalę (dziesiątki tysięcy stron na jedno uruchomienie)
  • Bardzo niestandardowej logiki parsowania lub transformacji danych
  • Integracji z istniejącymi pipeline’ami danych (Airflow, dbt, Kafka)
  • Precyzyjnej kontroli TLS/sesji do zaawansowanej walki z botami
  • Ekonomiki jednostkowej — przy milionach rekordów utrzymywany stack bije SaaS oparty na kredytach

W większości jednorazowych lub średniej skali projektów Thunderbit jest szybszy i prostszy. W produkcyjnych pipeline’ach na dużą skalę Python daje pełną kontrolę.

Wskazówki, jak uniknąć blokady podczas scrapowania eBay w Pythonie

eBay i Akamai naprawdę działają. To, co w praktyce rzeczywiście pomaga:

  • Używaj curl_cffi z impersonate="chrome124" — to największa pojedyncza poprawa względem zwykłego requests
  • Rotuj User-Agenty z listy aktualnych wersji przeglądarek (Chrome 143, Firefox 124, Safari 26)
  • Dodawaj losowe opóźnienia — stałe interwały są fingerprintem
  • Używaj proxy residential albo rotujących przy czymkolwiek większym niż kilka dziesiątek stron. Adresy z centrów danych (AWS, GCP, DigitalOcean) są szybko flagowane przez Akamai.
  • Szanuj robots.txt — większość filtrowanych URL-i przeglądania jest tam wprost oznaczona jako Disallowed; strony szczegółów przedmiotów (/itm/<id>) nie są
  • Obsługuj CAPTCHA łagodnie — wykryj ją i spróbuj ponownie z innym IP albo skorzystaj z usługi rozwiązującej CAPTCHA
  • Nie bombarduj serwera. Precedens pokazuje, że trespass to chattels ma zastosowanie, gdy scraping realnie degraduje serwery. Tempo 1 żądanie na sekundę na IP trzyma cię daleko od tego progu.

W przypadku komercyjnego użycia na dużą skalę rozważ użycie Browse API dla aktywnych ofert i targetowanego scrapowania tylko dla sprzedanych porównań oraz danych, których API nie udostępnia. Taki model hybrydowy jest czystszy zarówno technicznie, jak i prawnie.

Czy scrapowanie eBay w Pythonie jest legalne?

Nie jestem prawnikiem, a ten wpis nie stanowi porady prawnej. Ujmę to więc krótko.

Krajobraz prawny przesunął się na korzyść scrapowania publicznie dostępnych danych. Kluczowe precedensy:

  • (9th Cir., 2022): scrapowanie publicznie dostępnych danych nie narusza CFAA
  • Van Buren v. United States (SCOTUS, 2021): zawęziło zapis CFAA o „przekroczeniu uprawnionego dostępu”
  • (N.D. Cal., 2024): scrapowanie bez logowania nie narusza warunków platformy, bo scraper nie jest „użytkownikiem”

Mimo to aktualizacja wprost zakazuje „agentów kupujących w imieniu użytkownika, botów opartych na LLM oraz wszelkich end-to-end flow próbujących składać zamówienia bez ludzkiej kontroli”. Granica jest jasna: tylko odczyt publicznych stron jest bezpieczny; automatyzacja checkoutu — nie.

Dobre praktyki: zbieraj wyłącznie publicznie widoczne dane. Nie twórz fałszywych kont i nie obchodź logowania. Nie odsprzedawaj hurtowo chronionych prawem autorskim obrazów ofert. W przypadku projektów komercyjnych skonsultuj się z prawnikiem.

Podsumowanie i najważniejsze wnioski

Python to najbardziej elastyczny sposób scrapowania eBay, ale wymaga stałego utrzymania, bo HTML strony się zmienia. Ramy decyzyjne są proste:

  • Użyj eBay Browse API do stabilnych, umiarkowanych ilościowo, ustrukturyzowanych zapytań o aktywne oferty
  • Użyj scrapowania w Pythonie do ofert sprzedanych, recenzji, wariantów i wszystkiego, czego API nie pokazuje
  • Użyj , jeśli chcesz dane eBay bez pisania ani utrzymywania kodu

Kod w tym przewodniku stawia na odporność: najpierw ekstrakcja JSON-LD, potem kaskadowe fallbacki CSS, a dla wariantów parsowanie ukrytego JSON. Takie warstwowe podejście sprawia, że twój scraper nie padnie przy następnym redesignie frontendowym eBay.

Jeśli chcesz spróbować bezkodowej ścieżki, pozwala przetestować to na stronach eBay już teraz. A jeśli chcesz zobaczyć, jak działa , to jest to dosłownie jedno kliknięcie.

Więcej o narzędziach do web scrapingu znajdziesz w naszych poradnikach: , oraz . Możesz też obejrzeć tutoriale na .

Wypróbuj Thunderbit do scrapowania eBay

FAQ

1. Czy mogę scrapować eBay za darmo w Pythonie?

Tak. Wszystkie biblioteki (Requests, BeautifulSoup, curl_cffi, pandas) są darmowe i open source. Koszty pojawiają się przy skali — residential proxy dla dużego wolumenu scrapingu zwykle kosztują 50–500 USD miesięcznie, zależnie od transferu. W małych projektach (kilkaset stron) możesz scrapować z domowego IP, jeśli zachowasz ostrożne limity tempa.

2. Jak scrapować sprzedane przedmioty i oferty zakończone eBay w Pythonie?

Dodaj LH_Complete=1&LH_Sold=1 do parametrów URL wyszukiwania. Musisz podać oba — samo LH_Sold=1 na niektórych kategoriach po cichu wraca do aktywnych ofert. Filtruj wyniki, sprawdzając klasę CSS .POSITIVE na elemencie ceny, co oznacza faktyczną sprzedaż, a nie niesprzedaną wygasłą ofertę.

3. Czy eBay blokuje web scraping?

eBay korzysta z Akamai Bot Manager, który wykrywa scraperów głównie przez fingerprinting TLS i analizę zachowania. Zwykłe wywołania requests często kończą się odpowiedzią 403. Użycie curl_cffi z impersonacją przeglądarki, rotacją User-Agentów i losowymi opóźnieniami 3–8 sekund między żądaniami radzi sobie z większością blokad. Przy dużej skali pomagają residential proxy.

4. Czy powinienem używać API eBay, czy scrapowania?

Użyj Browse API do stabilnych zapytań o aktywne oferty o umiarkowanej skali (do 5000 wywołań dziennie). Scrapowanie stosuj, gdy potrzebujesz historii cen sprzedanych przedmiotów, pełnych danych wariantów/MSKU, recenzji lub czegokolwiek, czego API nie pokazuje. Marketplace Insights API teoretycznie dostarcza danych o sprzedaży, ale dostęp jest ograniczony i .

5. Jaki jest najprostszy sposób na scrapowanie eBay bez kodowania?

używa AI, aby czytać strony eBay, sugerować kolumny danych i wyciągać oferty jednym kliknięciem. Obsługuje paginację, wzbogacanie danych o podstrony i eksport do Google Sheets, Excela, Airtable lub Notion. Gotowe jeszcze bardziej przyspieszają typowe zastosowania.

Dowiedz się więcej

Spis treści

Wypróbuj Thunderbit

Pobieraj leady i inne dane w zaledwie 2 kliknięcia. Napędzane przez AI.

Pobierz Thunderbit To za darmo
Wyciągaj dane z pomocą AI
Łatwo przenieś dane do Google Sheets, Airtable lub Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week