PythonでeBayをスクレイピングする方法:本当に動くコード

最終更新日 June 9, 2026
PythonでeBayをスクレイピングする方法:本当に動くコード

「動作確認済み」と書かれたeBayスクレイピングのチュートリアル。これを真に受けて半年後に動いていることは、まずありません。GitHubに上がっているサンプルの大半は、eBayが2回ほどUIを刷新した時点で静かに息絶えています。それでも次の開発者がそれを拾い、コピペし、403を食らい、また別のリポジトリを探す——Thunderbitのチームは、この堂々巡りを数えきれないほど横で見てきました。だから断言できるんです。

なぜそこまでして取りに行く価値があるのか。eBayには 約25億件のライブ出品 があり、Amazonの次に大きいロングテール価格データの宝庫がオープンウェブ上に存在するからです。リセラーの値付けにも競合分析にも効くこのデータ、けれどもプログラムで取りに行こうとした瞬間、相手は別の顔を見せます。CSSクラス名はReactフロントエンドのデプロイごとに書き換わり、A/Bテストはユーザーごとに違うDOMを返し、HTMLにたどり着く前にAkamai Bot Managerが門前払いをかけてくる。本記事では、そうした壁の正体——スクレイパーがなぜ崩れるのか、どう組めば崩れにくいのか、API とスクレイピングのどちらに賭けるべきか——を整理しつつ、今日コピペして動く Python コードを示します。Python が割に合わないときのノーコード退避ルートも、最後にちゃんと用意しています。

PythonでeBayをスクレイピングするとは?

PythonでeBayをスクレイピングするとは、Pythonスクリプトを使って eBay のWebページを自動で取得し、HTML(あるいはページ内に隠されたJSON)を解析して、タイトル、価格、出品者情報、販売日、バリエーション情報などを、CSVやスプレッドシート、データベースなど実用的な形式にまとめることです。

eBayでは、次のようなページを取得できます。

  • 検索結果ページ(例:「AirPods Pro」の出品一覧)
  • 個別の商品詳細ページ(仕様、画像、出品者情報など)
  • 売却済み/終了済みリスト(実際の取引価格と日付)
  • 出品者プロフィールとレビュー

ページ取得・HTML解析・データ加工という3工程を、Requests、BeautifulSoup、lxml、pandas といったライブラリで一気通貫にこなせる——だからこの用途では Python が長らく定番です。ただし注意したいのは、サイトのHTMLを直接スクレイピングするやり方と、eBay公式APIを叩くやり方は、まったくの別物だということ。その違いは次の章で掘ります。

なぜeBayをスクレイピングするのか?ビジネスチームでの実践例

eBayデータのROIは、率直に言って桁が大きいです。Bain の調査では、実現価格がわずか1%改善するだけで利益は11.1%増える とされ、McKinsey も小売のダイナミックプライシングが 売上を最大5%、利益率を2〜7%押し上げる と報告しています。この記事にたどり着いた時点で目的はお持ちでしょうが、その価値を数字で裏打ちしておくと判断がぶれません。

よくある活用例は次のとおりです。

用途必要なデータ得られる成果
価格モニタリング/再価格設定出品中価格、送料、コンディション競争力のある価格設定、利益率の保護
競合分析商品ラインアップ、販促、送料条件戦略的なポジショニング、品揃えのギャップ把握
市場調査/トレンド把握出品数の推移、カテゴリトレンド、需要パターン新商品発掘、需要予測
リセラー向け価格査定売却価格、販売日、コンディション適正価格の把握、仕入れ判断
感情分析レビュー、評価、返品ポリシー商品品質の洞察、顧客満足度の把握
リード獲得出品者プロフィール、ストア情報、連絡先高GMV出品者へのB2Bアプローチ

ebayscraping_stats_77c7da1611.png

どの用途にも共通するのは、価値あるデータがWebページの内側に閉じ込められているという一点です。

その鍵を開けて競争優位に変える——それがスクレイピングの役割です。

eBay公式API vs Pythonスクレイピング:どちらを選ぶべき?

正直に語っているチュートリアルが少なすぎる、というのがこのテーマです。eBay は公式API、なかでも Browse API を提供しています。ではAPIに乗るか、直接スクレイピングするか——その分かれ目は、結局のところ何のデータが欲しいかで決まります。

比較項目eBay Browse/Finding APIPythonスクレイピング
売却済み/終了済みリスト限定的 — Marketplace Insights API はあるが、アクセスが通らないことが多いURLパラメータ LH_Sold=1&LH_Complete=1 で完全取得可能
レート制限ベーシックプランで 1日5,000回自前管理(プロキシ依存)
データ項目事前定義済み(タイトル、価格、カテゴリ、基本的な出品者情報)ページ上で見えるものは何でも取得可能(レビュー、詳細仕様、バリエーション表など)
導入難易度OAuth 2.0、アプリ登録、APIキーpip install とコード記述のみ
安定性エンドポイントは安定HTML変更で壊れる
コスト無料枠あり、大量利用は有料コード自体は無料だが、大量取得ではプロキシ費用が発生
バリエーション/MSKUデータ部分的 — 多くの場合は親SKUのみ完全取得可能(隠しJSONを解析)
ページネーション上限1万件のハード上限理論上は無制限

ひとつ注意点を。旧 Finding API(findCompletedItems を含む)は 2025年2月に完全終了 しました。ebaysdk-python や Finding モジュール頼みのライブラリは、もう本番では沈黙します。

私のおすすめ: 出品中商品の、ある程度安定した中規模の構造化検索なら Browse API。売却済み価格やレビュー、バリエーション情報、APIが出してくれない項目が欲しいなら Python スクレイピング。実務では、この両者を場面で使い分けるチームが多数派です。

PythonでeBayをスクレイピングするのに必要なツールとライブラリ

eBayの多くのページでは、ヘッドレスブラウザの出番はありません。データはサーバーレンダリング済みのHTMLにそのまま埋まっています。コードに手をつける前に、まずは道具箱を整えましょう。

ライブラリ用途
requests または httpxeBayページを取得するHTTPクライアント
curl_cffi実ブラウザ相当のTLSフィンガープリントを持つHTTPクライアント(Akamai回避に重要)
beautifulsoup4CSSセレクタで抽出するためのHTMLパーサー
lxmlBeautifulSoup向けの高速パーサー
jmespath入れ子JSONを扱うためのクエリ言語
pandasデータ加工とCSV/Excel出力
gspreadGoogle Sheets 連携

まとめてインストールするならこちらです。

pip install requests httpx curl_cffi beautifulsoup4 lxml jmespath pandas gspread

バージョンは Python 3.11以上 を選びましょう。pandas 3.0 が 3.10 以上を要求しますし、3.11 ならI/O中心の処理で10〜60%ほど速くなります。

このリストの中で、ひとつだけ別格のライブラリがあります。それが curl_cffi で、2026年のeBayスクレイパーにとって投資対効果が最も大きいアップグレードです。eBay が使う Akamai Bot Manager の主な検知ポイントは、TLS フィンガープリント。通常の requests はPython特有のJA3フィンガープリントを晒してしまい、即座に弾かれます。curl_cffi は本物のChromeブラウザのTLSハンドシェイクをそっくり真似るので、ヘッドレスブラウザを立ち上げなくても、Akamai系サイトのおよそ90%に通用します。

どんなWebサイトでもThunderbitを試す

手順付き:PythonでeBay検索結果をスクレイピングする方法

いよいよ実践です。ここでは商品検索結果ページを対象にスクレイピングを進めます。

  • 難易度: 初級〜中級
  • 所要時間: 最初に動くところまで約30分
  • 必要なもの: Python 3.11以上、上記ライブラリ、ターミナル、対象となるeBay検索URL

ステップ1:Pythonプロジェクトを準備する

まずはプロジェクト用ディレクトリを切り、依存関係をインストールします。

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

続けて scrape_ebay.py を新規作成します。これからの作業はこのファイルに書き込んでいきます。

ステップ2:eBay検索URLを組み立てる

eBayの検索URLは複雑ではありません。要となるのは _nkw(キーワード)です。

import urllib.parse

keyword = "airpods pro"
base_url = "https://www.ebay.com/sch/i.html"
params = {
    "_nkw": keyword,
    "_ipg": "120",    # 1ページあたりの件数: 60, 120, 240(240はbot判定の原因になりやすい)
    "_pgn": "1",      # ページ番号
}
url = f"{base_url}?{urllib.parse.urlencode(params)}"
print(url)
# https://www.ebay.com/sch/i.html?_nkw=airpods+pro&_ipg=120&_pgn=1

覚えておくと便利なパラメータも挙げておきます。

  • LH_BIN=1 — 即決のみ
  • _sacat=175673 — 特定カテゴリ
  • _sop=12 — ベストマッチ順(10 = 価格+送料の安い順、13 = 新着順)
  • LH_Complete=1&LH_Sold=1 — 売却済み/終了済みリスト(後半で詳しく説明します)

ステップ3:リクエストを送信し、レスポンスを扱う

通常の requests.get() でアクセスすると、Akamai は遠慮なく 403 を返してきます。ここで curl_cffi を挟むと、本物のChromeブラウザからの訪問のように振る舞え、その真価が出ます。

from curl_cffi import requests as cffi_requests
import random, time

USER_AGENTS = [
    "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",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36",
    "Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0",
]

HEADERS = {
    "User-Agent": random.choice(USER_AGENTS),
    "Accept-Language": "en-US,en;q=0.9",
    "Accept-Encoding": "gzip, deflate, br",
}

def fetch_page(url, max_retries=5):
    delay = 2
    for attempt in range(max_retries):
        try:
            r = cffi_requests.get(url, impersonate="chrome124", headers=HEADERS, timeout=30)
            if r.status_code == 200:
                return r.text
            if r.status_code in (403, 429, 503):
                retry_after = r.headers.get("Retry-After")
                sleep_for = float(retry_after) if retry_after else delay + random.uniform(0, 1)
                print(f"  Status {r.status_code}, retrying in {sleep_for:.1f}s...")
                time.sleep(sleep_for)
                delay *= 2
                continue
            r.raise_for_status()
        except Exception as e:
            print(f"  Request error: {e}, retrying...")
            time.sleep(delay)
            delay *= 2
    raise RuntimeError(f"Failed after {max_retries} retries: {url}")

待機時間を固定値にすると、それ自体がbot判定の手がかりになります。指数バックオフにジッターを足しておくのは、そのためです。

ステップ4:検索結果ページから商品情報を解析する

いま eBay は2種類の検索結果レイアウトを並走させている移行期にあります。どちらが返ってきても拾える——そこまで備えて初めて、堅牢なスクレイパーと言えます。

項目旧レイアウト新レイアウト
カードのコンテナli.s-itemli.s-card または div.su-card-container
タイトル.s-item__title.s-card__title
URLa.s-item__link[href]a.su-link[href]
価格span.s-item__price.s-card__price

両レイアウトを同時にカバーする解析コードがこちらです。

from bs4 import BeautifulSoup

def parse_search_results(html):
    soup = BeautifulSoup(html, "lxml")
    cards = soup.select("li.s-item, li.s-card, div.su-card-container")
    results = []

    for card in cards:
        # タイトル — 両方のレイアウトを確認
        title_el = card.select_one(".s-item__title, .s-card__title")
        title = title_el.get_text(strip=True) if title_el else None

        # 先頭に出るダミーの「Shop on eBay」カードを除外
        if not title or "Shop on eBay" in title:
            continue

        # 価格
        price_el = card.select_one("span.s-item__price, .s-card__price")
        price = price_el.get_text(strip=True) if price_el else None

        # URL
        link_el = card.select_one("a.s-item__link[href], a.su-link[href]")
        url = link_el["href"].split("?")[0] if link_el else None

        # 画像
        img_el = card.select_one("img.s-item__image-img, .s-card__image img")
        image = None
        if img_el:
            image = img_el.get("src") or img_el.get("data-src")

        # 送料
        ship_el = card.select_one("span.s-item__shipping, span.s-item__logisticsCost, .s-card__attribute-row")
        shipping = ship_el.get_text(strip=True) if ship_el else None

        results.append({
            "title": title,
            "price": price,
            "url": url,
            "image": image,
            "shipping": shipping,
        })

    return results

定番の落とし穴が、先頭のダミーカードです。多くのeBay検索ページでは、最初の li.s-item が中身のないプレースホルダーで、タイトルには「Shop on eBay」が入っています。ここは必ず弾いてください。

ステップ5:ページネーション対応で複数ページを取得する

ページ送りを握っているのは _pgn パラメータ。次ページへのリンクは a.pagination__next です。

import urllib.parse

def scrape_ebay_search(keyword, max_pages=5):
    all_results = []

    for page_num in range(1, max_pages + 1):
        params = {"_nkw": keyword, "_ipg": "120", "_pgn": str(page_num)}
        url = f"https://www.ebay.com/sch/i.html?{urllib.parse.urlencode(params)}"
        print(f"Scraping page {page_num}: {url}")

        html = fetch_page(url)
        results = parse_search_results(html)

        if not results:
            print(f"  No results on page {page_num}, stopping.")
            break

        all_results.extend(results)
        print(f"  Found {len(results)} listings (total: {len(all_results)})")

        # 丁寧な間隔 — 3〜8秒のランダム待機
        time.sleep(random.uniform(3, 8))

    return all_results

3〜8秒のランダムジッターは、入れても入れなくてもいい飾りではありません。

単一IPから1秒に1件を超えてアクセスし続ける動き——eBayのAkamaiレイヤーは、そこをしっかり見ています。

ステップ6:スクレイピングしたデータをCSVまたはJSONに出力する

import pandas as pd

results = scrape_ebay_search("airpods pro", max_pages=3)
df = pd.DataFrame(results)
df.to_csv("ebay_airpods.csv", index=False)
df.to_json("ebay_airpods.json", orient="records", indent=2)
print(f"{len(df)}件の出品情報をCSVとJSONに出力しました。")

これで、eBayの出品一覧が見やすいスプレッドシートにまとまります。私の手元では、3ページ分の360件を、待機時間も含めて約45秒で取り切れました。

PythonでeBayの商品詳細ページをスクレイピングする方法

検索結果ページが見せてくれるのは要約だけ。本当に欲しい中身——たとえば詳しい説明文、出品者の評価、商品スペック、画像カルーセル、バリエーション情報——は、商品詳細ページに眠っています。

単一の商品ページを解析する

商品ページのURLは /itm/<ITEM_ID> の形です。ここで最も足腰の強い取得経路が JSON-LD。eBay は Product スキーマのブロックを埋め込んでおり、CSSがいくら変わってもほとんど揺らぎません。

import json

def parse_item_page(html):
    soup = BeautifulSoup(html, "lxml")
    item = {}

    # 1. JSON-LD — 最も安定した取得経路
    for tag in soup.find_all("script", type="application/ld+json"):
        try:
            data = json.loads(tag.string or "")
        except (json.JSONDecodeError, TypeError):
            continue
        if isinstance(data, dict) and data.get("@type") == "Product":
            item["title"] = data.get("name")
            item["brand"] = (data.get("brand") or {}).get("name")
            item["images"] = data.get("image")
            offers = data.get("offers") or {}
            item["price"] = offers.get("price")
            item["currency"] = offers.get("priceCurrency")
            break

    # 2. JSON-LDにない項目のCSSフォールバック
    def first_text(selectors):
        for sel in selectors:
            el = soup.select_one(sel)
            if el and el.get_text(strip=True):
                return el.get_text(strip=True)
        return None

    item.setdefault("title", first_text([
        "h1.x-item-title__mainTitle",
        "h1.x-item-title__mainTitle .ux-textspans--BOLD",
    ]))
    item["condition"] = first_text([
        ".x-item-condition-text .ux-textspans",
    ])
    item["seller"] = first_text([
        ".x-sellercard-atf__info__about-seller a .ux-textspans",
    ])
    item["shipping"] = first_text([
        "div.ux-labels-values--shipping .ux-textspans--BOLD",
    ])

    # 3. 商品の詳細項目
    specifics = {}
    for dl in soup.select(".ux-layout-section-evo__item--table-view dl.ux-labels-values"):
        k = dl.select_one(".ux-labels-values__labels-content .ux-textspans")
        v = dl.select_one(".ux-labels-values__values-content .ux-textspans")
        if k and v:
            specifics[k.get_text(strip=True).rstrip(":")] = v.get_text(strip=True)
    item["specifics"] = specifics

    return item

まずJSON-LDを読み、足りない分だけCSSで補う——この順番こそが、四半期ごとの改修に耐えるスクレイパーの肝です。この考え方は、のちほどもう少し掘り下げます。

eBayの商品バリエーション(MSKUデータ)を取得する

色やサイズ、容量で複数バリエーションを持つ商品も、eBayには少なくありません。画面に最初に出るのは「$899〜$1,099」のような価格帯だけで、各バリエーションの実価格はユーザーが選択肢をクリックするまで隠れています。その実価格が収まっているのが、MSKU と呼ばれる隠しJavaScriptオブジェクトです。

eBay API では親SKUまでしか拾えないことが多く、まさにスクレイピングに軍配が上がる領域がここです。

import re, json

def extract_variants(html):
    # 非貪欲マッチが重要 — 貪欲な .+ はページ全体を飲み込んでしまう
    m = re.search(r'"MSKU"\s*:\s*(\{.+?\})\s*,\s*"QUANTITY"', html, re.DOTALL)
    if not m:
        return []

    try:
        msku = json.loads(m.group(1))
    except json.JSONDecodeError:
        return []

    item_labels = {str(k): v["displayLabel"] for k, v in msku.get("menuItemMap", {}).items()}
    skus = []

    for combo_key, variation_id in msku.get("variationCombinations", {}).items():
        option_ids = combo_key.split("_")
        options = [item_labels.get(oid, oid) for oid in option_ids]
        var = msku.get("variationsMap", {}).get(str(variation_id), {})
        bin_model = var.get("binModel", {})
        price_spans = bin_model.get("price", {}).get("textSpans", [{}])
        price = price_spans[0].get("text") if price_spans else None
        qty = var.get("quantity")

        skus.append({
            "options": options,
            "price": price,
            "quantity_available": qty,
            "variation_id": variation_id,
        })

    return skus

eBayスクレイパーが最もつまずくのが、この正規表現の非貪欲な (.+?) です。ここを貪欲な .+ にしてしまうと、ページ内の末尾にある "QUANTITY" まで一気に飲み込み、JSONが崩壊します。「動きます」と銘打ったチュートリアルでこのバグを踏んだのを、私は最低でも3回は目撃しています。

PythonでeBayの売却済み・終了済みリストをスクレイピングする方法

APIよりスクレイピングを選ぶ理由が最もくっきり浮かび上がるのが、この用途です。何が、いくらで、いつ売れたか——この売却済みデータは、市場調査にもリセラーの値付けにも査定にも、土台として欠かせません。ところが eBay Browse API は、これを明示的には出してくれません。 Marketplace Insights API が一応対応してはいるものの、アクセスは「限定提供」で、通らないことが多い のが現場の実感です。

URLパラメータには LH_Complete=1(終了済み)と LH_Sold=1(実際に売れたものに限定)の 両方 が要ります。LH_Sold=1 だけでは、カテゴリによって通常の出品一覧にこっそり戻ってしまう——これがコミュニティで最多の落とし穴です。

def scrape_sold_listings(keyword, max_pages=3):
    all_sold = []

    for page_num in range(1, max_pages + 1):
        params = {
            "_nkw": keyword,
            "_ipg": "120",
            "_pgn": str(page_num),
            "LH_Complete": "1",
            "LH_Sold": "1",
        }
        url = f"https://www.ebay.com/sch/i.html?{urllib.parse.urlencode(params)}"
        print(f"Scraping sold page {page_num}...")

        html = fetch_page(url)
        soup = BeautifulSoup(html, "lxml")
        cards = soup.select("li.s-item")

        for card in cards:
            title_el = card.select_one(".s-item__title")
            title = title_el.get_text(strip=True) if title_el else None
            if not title or "Shop on eBay" in title:
                continue

            # 実際に売れたものだけを対象にする(緑色のPOSITIVE表示)
            sold_tag = card.select_one(
                ".s-item__title--tag .POSITIVE, .s-item__caption--signal.POSITIVE"
            )
            if sold_tag is None:
                continue  # 売れていない終了済み出品は除外

            price_el = card.select_one("span.s-item__price")
            price = price_el.get_text(strip=True) if price_el else None

            # 売却日を解析
            sold_date = None
            import re, datetime as dt
            card_text = card.get_text()
            m = re.search(r"Sold\s+([A-Z][a-z]{2}\s+\d{1,2},\s*\d{4})", card_text)
            if m:
                sold_date = dt.datetime.strptime(m.group(1), "%b %d, %Y").strftime("%Y-%m-%d")

            link_el = card.select_one("a.s-item__link[href]")
            url = link_el["href"].split("?")[0] if link_el else None

            all_sold.append({
                "title": title,
                "sold_price": price,
                "sold_date": sold_date,
                "url": url,
            })

        if not cards:
            break
        time.sleep(random.uniform(3, 8))

    return all_sold

HTML上で見るべき差はここです。売れたアイテムの価格は緑色の .POSITIVE ラッパーに入りますが、売れ残った終了済みリストは赤い取り消し線で表示されます。判定は必ず .POSITIVE クラスで行ってください。

なぜeBayスクレイパーは壊れるのか(そして、どうすれば壊れにくくできるのか)

ある日いきなりeBayスクレイパーが沈黙する——これに悩んでいるのはあなただけではありません。私が目を通してきたeBayスクレイピング系フォーラムで、これは群を抜いて多い相談です。論点は「壊れるか否か」ではなく、「いつ壊れるか」のほうにあります。

なぜ壊れるのか:

  • eBay は React ベースの描画と動的生成されたクラス名を使っており、デプロイのたびに変わる
  • A/B テストで、ユーザーごとに異なる DOM が返る(今まさに s-items-card の2系統が共存しているのがその例)
  • 定期的なUI刷新で、データ自体は同じでもHTMLのネスト構造が変わる
  • #itemTitle#prcIsum のような古いセレクタは何年も前に廃止されているのに、いまだにチュートリアルで見かける

Scrapflyの2026年版ガイド も、「eBayのWebスクレイピングで本当に難しいのはCSSセレクタの変更対応だ。eBayはフロントエンドを定期的に更新するため、特定のクラス名に依存したスクレイパーは壊れやすい」と同じ点を突いています。

ebayscraping_section_50be5cc410.png

長く使えるeBayスクレイパーの防御策

eBayの四半期ごとの変更をくぐり抜けるための、4つの構えを示します。

1. CSSセレクタよりJSON-LDを優先する。 eBayは商品ページごとに構造化された Product スキーマを埋め込んでいます。CSSなどの見た目レイヤーは変わりやすい一方で、pricenameseller といったデータ層は内部APIに紐づいており、名前が変わることはまれです。

2. セレクタは必ず複数の候補を持たせる。 ひとつのCSSセレクタに頼ってはいけません。常に代替候補を用意します。

def first_text(soup, selectors):
    for sel in selectors:
        el = soup.select_one(sel)
        if el and el.get_text(strip=True):
            return el.get_text(strip=True)
    return None

title = first_text(soup, [
    "h1.x-item-title__mainTitle",
    "h1.x-item-title__mainTitle .ux-textspans--BOLD",
    "[data-testid='x-item-title'] h1",
])

3. 隠しJSONを解析する。 MSKU のバリエーションオブジェクトや、インラインJavaScriptのデータは、サーバー側で生成されるため CSS 変更の影響を受けにくいです。<script> タグから正規表現で抜き出すのは手間ですが、保守コストをかなり下げられます。

4. セレクタ失敗をログに残す。 データが空になってから気づくのではなく、いつ壊れたか分かるように監視を入れましょう。

if title is None:
    print(f"WARNING: title selector failed for {url}")

5. ブラウザ偽装つきの curl_cffi を使う。 これで Akamai の TLS フィンガープリント対策を、ヘッドレスブラウザなしで回避しやすくなります。

AIを使う代替手段:セレクタ保守が不要

数か月おきにセレクタを直す——その繰り返しにうんざりしたなら、根本から発想を変える手があります。Thunderbit のようなツールは、アクセスのたびにページをAIが読み直し、その場で抽出ロジックを組み上げます。McGill大学が3,000ページで AI方式とセレクタ方式を比べた研究では、レイアウトが変わったあとでもAIは98.4%の精度を保ち、業界ベンチマークでも 保守工数が60〜80%減る と報告されています。

手法eBayのHTML変更で壊れる?保守負荷
ハードコードされたCSSセレクタはい、四半期ごとに壊れやすい高い — 継続的な修正が必要
隠しJSON / JSON-LD抽出ほとんど壊れない低い
AIベースのスクレイピング(Thunderbit)いいえ — 毎回AIがセレクタを再推定なし

AIでeBayデータをスクレイピング Get Started Free

Thunderbit のワークフローは後ほど詳しく紹介します。ここで押さえておきたいのは、長く回すスクレイパーなら JSON優先の抽出とフォールバックセレクタに投資すべきだということ。そもそもセレクタ保守から解放されたいなら、AIアプローチは一考の価値があります。

eBayの定期スクレイピングを自動化して価格監視する

一度きりのスクレイピングでも十分に役立ちます。とはいえ、価格監視や在庫追跡、競合分析となると、データを継続的に集め続ける仕組みが要ります。競合記事は価格監視をユースケースとして掲げますが、それを実際どう自動化するかまで踏み込んだものは、ほとんど見当たりません。

方法1:Cronジョブ(Linux/macOS)またはタスクスケジューラ(Windows)

いちばん素直な手は、Pythonスクリプトを cron に登録することです。cron は最小限の環境で動くため、venv の Python は必ず絶対パスで指してください。

crontab -e
# 毎日08:15に実行
15 8 * * * /Users/me/ebay/venv/bin/python /Users/me/ebay/scrape_ebay.py >> /Users/me/ebay/scrape.log 2>&1

Windows では PowerShell を使います。

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

この方法には常時稼働のマシンが必要で、プロキシもbot対策も自分の手で面倒を見ることになります。

方法2:クラウド関数(サーバーレス)

AWS Lambda や Google Cloud Functions なら、専用サーバーを抱えずにスクレイパーを走らせられます。依存関係のパッケージ化やタイムアウト対策(Lambdaは最大15分)、プロキシ管理が要るぶんセットアップはやや重めですが、その代わりサーバー保守からは解放されます。

方法3:Thunderbitによるノーコードのスケジュール実行

Thunderbit の Scheduled Scraper は、「毎日8時」のように自然言語で間隔を伝え、eBayのURLを入れてスケジュールするだけ。実行はクラウド側で完結し、bot対策も最初から組み込まれています。

手法導入の手間サーバーは必要?bot対策対応?
Cron + Pythonスクリプトはい(常時稼働マシンが必要)プロキシは自分で管理
クラウド関数(Lambda)いいえ(サーバーレス)プロキシは自分で管理
Thunderbit Scheduled Scraper低(言葉で指定するだけ)いいえ(クラウドベース)標準搭載

定期実行の結果を蓄積するなら、価格履歴の保管先にはローカルのSQLiteデータベースが向いています。書き込みは INSERT OR REPLACE ではなく ON CONFLICT ... DO UPDATE で。INSERT OR REPLACE外部キーを壊し、列を消し飛ばす ことがあるからです。

CREATE TABLE IF NOT EXISTS listings (
    item_id       TEXT PRIMARY KEY,
    title         TEXT NOT NULL,
    price         REAL,
    last_price    REAL,
    first_seen_at TEXT DEFAULT (datetime('now')),
    last_seen_at  TEXT DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS price_history (
    item_id     TEXT NOT NULL,
    observed_at TEXT NOT NULL DEFAULT (datetime('now')),
    price       REAL NOT NULL,
    PRIMARY KEY (item_id, observed_at)
);

Thunderbit Scheduled Scraperを試す

コーディングしたくない?Thunderbitで2分でeBayをスクレイピングする方法

ここまで Python のコードをたっぷり並べてきましたが、それが要らない場面もある——その事実は、ちゃんとお伝えしておきたいのです。

単発の市場調査をしたいビジネス担当者、比較価格をさっと確かめたいリセラー、開発スプリントの完了を待たずに今すぐデータが欲しいECチーム。こうした人たちにとって、Python はやり過ぎです。セットアップに、セレクタ保守に、プロキシ管理……「200件の出品をスプレッドシートに落としたいだけ」なのに、背負う荷物が多すぎます。

ThunderbitでeBayをスクレイピングする流れ

  1. Thunderbit Chrome拡張 をインストール — クレジットカードは不要です。
  2. ChromeでeBayの検索結果ページまたは商品ページを開く
  3. Thunderbitサイドバーの「AIで項目を提案」をクリック。AIがページを読み取り、Title、Price、Condition、Shipping、Seller、Rating などの列を提案します。
  4. 「スクレイプ」をクリック。 拡張機能がページネーションを処理しながら、データ表を埋めていきます。eBay向けには、Thunderbit には ワンクリックで使える事前構築済みテンプレート もあります。
  5. Google Sheets、Airtable、Notion、CSV、JSON、Excel に無料でエクスポート

最初から最後まで、2分とかかりません。

これは私自身が時計で測った数字です。

サブページ強化:追加コードなしで詳細ページのデータを取得

検索結果ページをスクレイピングしたあと、Thunderbit は各出品の詳細ページまで自動で回り、より細かい項目を足していきます。詳細仕様、出品者情報、説明文、全画像といった具合です。先ほど書いた20行超のサブページ取得用Pythonコードを、これがワンクリックに置き換えます。

それでもPythonを使うべき場面

Python が本領を発揮するのは、こんなケースです。

  • 大規模スクレイピング(1回あたり数万ページ)
  • 高度にカスタマイズした解析ロジックやデータ変換
  • 既存のデータパイプラインとの統合(Airflow、dbt、Kafka など)
  • 高度なbot対策のための細かなTLS/セッション制御
  • 単位経済性 — 数百万行規模なら、保守済みの自前スタックのほうがSaaS課金より有利

単発から中規模までの多くの案件では、Thunderbit のほうが速く、手軽です。一方、大規模な本番パイプラインを組むなら、Python が与えてくれる自由度がものを言います。

PythonでeBayをスクレイピングしてブロックされないためのコツ

eBay の Akamai 対策は、かなり本腰が入っています。実戦で効く打ち手を並べます。

  • impersonate="chrome124" を指定した curl_cffi を使う — 通常の requests よりいちばん効果が大きい改善点です
  • 現行ブラウザ版の User-Agent を複数ローテーションする(Chrome 143、Firefox 124、Safari 26 など)
  • リクエスト間に 3〜8秒のランダム遅延 を入れる — 固定間隔は識別材料になります
  • 数十ページを超えるなら住宅回線プロキシまたはローテーションプロキシを使う。データセンターIP(AWS、GCP、DigitalOcean)は Akamai にすぐ弾かれがちです。
  • robots.txt を尊重する — ほとんどの絞り込み済み browse URL は明示的に Disallow されています。商品詳細ページ(/itm/<id>)は対象外です
  • CAPTCHA は丁寧に扱う — 検知して別IPで再試行するか、CAPTCHA解決サービスを使いましょう
  • サーバーを叩きすぎないこと。 eBay v. Bidder's Edge の前例では、実際にサーバー性能を損なうスクレイピングに対して trespass to chattels が適用されました。1IPあたり1req/s程度に抑えていれば、その閾値からはかなり離れられます。

商用で大量に取りに行くなら、出品中商品は Browse API で取り、売却済み比較やAPI非公開データだけを狙ってスクレイピングする——このハイブリッド構成をおすすめします。技術面でも法務面でも、見通しがすっきりします。

PythonでeBayをスクレイピングするのは合法?

念のため。私は弁護士ではなく、本記事は法的助言でもありません。ここは要点だけに絞ります。

公開データのスクレイピングをめぐる法的な風向きは、ここ数年でかなりスクレイピング側に傾いてきました。押さえておきたい判例はこちらです。

  • hiQ v. LinkedIn(第9巡回区、2022年):公開データのスクレイピングは CFAA に違反しない
  • Van Buren v. United States(米連邦最高裁、2021年):CFAA の「許可されたアクセスを超える」条項を狭く解釈
  • Meta v. Bright Data(カリフォルニア北部地区、2024年):ログアウト状態でのスクレイピングは、スクレイパーが「ユーザー」ではないためプラットフォームの利用規約違反にならない

もっとも、eBay の 2026年2月の利用規約更新 は、「buy-for-me エージェント、LLM駆動ボット、または人間の確認なしに注文完了を目指すエンドツーエンドのフロー」をはっきり禁じています。線引きは明快です。公開ページの読み取り専用スクレイピングは比較的安全な一方、チェックアウトの自動化はまったく別の話になります。

守るべきは、公開表示されているデータだけを取るというベストプラクティス。偽アカウントは作らない、ログイン壁は回避しない、著作権のある画像を無断で大量再利用しない、商用規模なら法務に相談する——この4点は外さないでください。

まとめと重要ポイント

eBayをスクレイピングする手段として Python が最も柔軟なのは確かですが、サイトのHTMLが変わるたびに保守が付いて回ります。選択の目安を整理します。

  • eBay Browse API は、出品中商品の安定した中規模の構造化検索に使う
  • Pythonスクレイピング は、売却済み一覧、レビュー、バリエーション情報、APIにない項目に使う
  • Thunderbit は、コードを書いたり保守したりせずに eBay データを取りたい場合に最適

本ガイドのコードは、何より堅牢性を最優先に組んでいます。すなわち、最初に JSON-LD を見て、次に CSS のフォールバックに回り、バリエーションは隠しJSONから取る——この三層構えです。これだけ重ねておけば、eBayのフロントエンドチームが次にUIを刷新しても、スクレイパーがあっけなく崩れることはありません。

ノーコードを試したいなら、Thunderbit の無料枠 で今すぐ eBay ページを相手に動かせます。eBayスクレイパーテンプレート の挙動を見たい場合も、同じくワンクリックです。

Webスクレイピングツールをもっと深掘りしたい方は、おすすめの自動WebスクレイピングツールWebサイトのデータをExcelに取り込む方法おすすめのPython Webスクレイピングツール もあわせてどうぞ。ThunderbitのYouTubeチャンネル では、チュートリアル動画も公開しています。

eBayスクレイピングにThunderbitを試す Get Started Free

FAQ

1. PythonでeBayを無料でスクレイピングできますか?

はい。Requests、BeautifulSoup、curl_cffi、pandas といったライブラリは、いずれも無料のオープンソースです。費用が発生するのは大量取得の局面で、住宅回線プロキシは帯域しだいでおおむね月50〜500ドルが相場。数百ページ規模の小さな案件なら、レート制限さえ慎重に効かせれば自宅回線からでも取り切れます。

2. PythonでeBayの売却済み・終了済みリストを取得するには?

検索URLのパラメータに LH_Complete=1&LH_Sold=1 を足します。両方が必須で、LH_Sold=1 だけだとカテゴリによっては通常の出品一覧に静かに戻ってしまいます。取得後は、価格要素に .POSITIVE のCSSクラスが付いているかを確かめ、実際に売れたものだけを残しましょう。売れ残った終了済み出品と切り分けるためです。

3. eBayはWebスクレイピングをブロックしますか?

eBay は Akamai Bot Manager を導入し、おもに TLS フィンガープリントと行動分析でスクレイパーをあぶり出します。通常の requests は 403 を食らいがちです。curl_cffi によるブラウザ偽装、User-Agent のローテーション、リクエスト間の3〜8秒のランダム遅延を組み合わせれば、多くのブロックはかわせます。大量取得の場面では住宅回線プロキシが効きます。

4. eBay APIとWebスクレイピング、どちらを使うべき?

出品中商品の安定した中規模クエリには Browse API(1日5,000回まで)。売却価格の履歴、完全なバリエーション/MSKUデータ、レビュー、APIが出してくれない項目が要るなら、スクレイピングです。Marketplace Insights API は理屈の上では売却データを返しますが、アクセスに制限があり、通らないことが多い のが実情です。

5. コードなしでいちばん簡単にeBayをスクレイピングする方法は?

Thunderbit Chrome拡張 は、AIで eBay ページを読み取ってデータ列を提案し、ワンクリックで出品情報を抜き出します。ページネーション、詳細ページの追加取得、Google Sheets、Excel、Airtable、Notion への出力にも対応済み。事前構築済みの eBayスクレイパーテンプレート を使えば、定番の用途ならさらに手早く片付きます。

さらに学ぶ

Ke
Ke
Thunderbit の CTO | シニアデータサイエンティスト & ML エキスパート 機械学習とデータサイエンスで約10年の経験を持つ Ke Shen は、コロンビア大学の卒業生であり、Walmart Labs の元シニアデータサイエンティストです。Python、R、Java、統計学における深い専門性は同業者からも高く評価されており、理論段階の複雑な AI アルゴリズムを本番運用レベルのアーキテクチャへと落とし込むための、実践に裏打ちされた知見を共有しています。
目次

ただ伝えるだけで、Webページをスクレイピング

必要なことをそのまま英語で伝えるだけ。いや、何も言わなくてもOKです。

Thunderbitを試す 無料
AIでデータを抽出
Google Sheets、Airtable、Notionへ簡単にデータを移行できます
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week