なぜeBayスクレイピングが難しいのか
リセラーインテリジェンスや市場調査チームにとって、eBayのデータは金鉱です。しかし、eBayはデータセンターIPを積極的にブロックし、APIには厳格なクォータがあり、HTML構造は頻繁に変更されます。このガイドでは、eBayプロキシの選定から、検索結果のパース、オークション情報の抽出、出品者分析まで、実運用で使えるアプローチを解説します。
API vs HTMLスクレイピング:eBayのトレードオフ
eBayは公式APIを提供していますが、大規模データ収集には致命的な制限があります。
| 観点 | eBay Finding/Browse API | HTMLスクレイピング |
|---|---|---|
| 1日あたり制限 | 5,000コール/日(Finding API) | プロキシ次第で実質無制限 |
| データ鮮度 | 最大15分の遅延 | リアルタイム |
| オークション残時間 | 非対応または不正確 | DOMから直接取得可能 |
| 出品者フィードバック詳細 | 限定的 | フル取得可能 |
| Geo別価格差 | APIは地域コンテキストを反映しない | 地域プロキシで正確な価格表示 |
| 初期コスト | App Key申請+OAuth設定 | 即座に開始可能 |
| ブロックリスク | なし(公式) | あり(プロキシで軽減) |
結論:APIは初期プロトタイプや低頻度モニタリングに最適。数千件以上のリスティングを日次で追跡するなら、eBayプロキシ経由のHTMLスクレイピングが現実的な選択です。
eBay APIのレート制限詳細
Finding APIのデフォルト制限は5,000 calls/日。Browse APIはアプリごとに異なりますが、実務上は1秒あたり2〜5リクエスト程度が上限です。1ページ50件としても、1日で25万件が限界——複数カテゴリ・地域を跨ぐ調査には到底足りません。APIが返すデータも、出品ページに表示される「残り時間」「入札数」「Buy It Nowフラグ」を含まないため、オークション追跡には不向きです。
eBayのHTMLターゲット構造
eBayの検索結果ページは比較的安定した構造を持っています。以下のセレクタを押さえておけば、大半のデータを抽出できます。
検索結果グリッド:.s-item
検索結果の各商品は.s-itemクラスの<li>要素に格納されています。
- タイトル:
.s-item__title— 商品名テキスト - 価格:
.s-item__price— 「$129.99」形式。セール時は.s-item__price--originalに元価格 - 送料:
.s-item__shipping— 「Free shipping」「+$5.99 shipping」 - URL:
.s-item__linkのhref属性 - 画像:
.s-item__image-imgのsrc - 出品者:
.s-item__seller-info-text - オークション情報:
.s-item__bid-count、.s-item__time-left - Buy It Now:
.s-item__buy-it-now要素の有無
出品詳細ページ
個別出品ページ(/itm/ITEM_ID)には、より豊富なデータがあります。
- 商品説明:
#desc_div内のiframeまたはインラインHTML - Item Specifics:
.ux-layout-section--itemDetails内のdlリスト - 入札履歴:
.bid-historyテーブル(オークションのみ) - 残り時間:
#vi-cdown_timeLeftまたは[data-testid="x-countdown-timer"]
出品者プロフィールページ
URLパターン:/str/SELLER_NAMEまたは/usr/SELLER_NAME
- フィードバックスコア:
.feedback-scoreまたは[data-testid="x-feedback-score"] - ポジティブ率:
.feedback-percent - 出品カテゴリ:
.seller-categories内のリスト - 他の出品一覧:
.str-itemsグリッド
プロキシ選定:eBayでレジデンシャル一択の理由
eBayはデータセンターIPに対して非常に攻撃的なブロックを行います。CloudflareベースのWAFに加え、独自のボット検知が動いており、DC IPからのリクエストは数回でCAPTCHA(hCaptcha)または403を返します。
プロキシタイプ別のeBay対応力
| プロキシタイプ | eBayブロック率 | 推奨用途 |
|---|---|---|
| データセンター | 90%以上(数リクエストでブロック) | 非推奨 |
| モバイル(3G/4G/5G) | 5%未満 | 高価値オークション監視 |
| レジデンシャル | 10〜20% | 大規模リスティング収集 |
地域プロキシの重要性も見逃せません。eBay.de・eBay.co.ukなどの地域ドメインは、アクセス元IPの国に基づいて価格・送料・出品visibilityを変えることがあります。ドイツの価格動向を正確に追跡するなら、ドイツのレジデンシャルIPが必要です。
ProxyHatでは、ユーザー名に国・都市フラグを追加するだけでジオターゲティングが可能です:
# ドイツIPでeBay.deにアクセス
http://user-country-DE:pass@gate.proxyhat.com:8080
# ロンドンIPでeBay.co.ukにアクセス
http://user-country-GB-city-london:pass@gate.proxyhat.com:8080Python実装:eBay検索結果スクレイパー
以下は、ProxyHatのレジデンシャルプロキシ経由でeBay検索結果を取得し、.s-itemから構造化レコードを抽出する完全な例です。
import requests
from bs4 import BeautifulSoup
from dataclasses import dataclass, field
from typing import Optional
import time, random
@dataclass
class EbayListing:
title: str
price: Optional[float]
shipping: Optional[str]
url: str
item_id: str
bid_count: Optional[int] = None
time_left: Optional[str] = None
is_buy_it_now: bool = False
seller: Optional[str] = None
def get_proxy_url(country: str = "US", session: str = None) -> str:
"""ProxyHatレジデンシャルプロキシURLを生成"""
user_part = f"user-country-{country}"
if session:
user_part += f"-session-{session}"
return f"http://{user_part}:YOUR_PASSWORD@gate.proxyhat.com:8080"
def scrape_ebay_search(
query: str,
country: str = "US",
max_pages: int = 5,
) -> list[EbayListing]:
"""eBay検索結果をスクレイピング"""
listings = []
proxy = get_proxy_url(country=country)
proxies = {"http": proxy, "https": proxy}
headers = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/124.0.0.0 Safari/537.36"
),
"Accept-Language": "en-US,en;q=0.9",
}
base_domain = "ebay.com" if country == "US" else f"ebay.{country.lower()}"
for page in range(1, max_pages + 1):
url = (
f"https://www.{base_domain}/sch/i.html"
f"?_nkw={query}&_pgn={page}"
)
resp = requests.get(url, headers=headers, proxies=proxies, timeout=30)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "html.parser")
items = soup.select(".s-item")
if not items:
break # ページ終了またはブロック
for item in items:
title_el = item.select_one(".s-item__title")
if not title_el or "Shop on eBay" in title_el.text:
continue
link_el = item.select_one(".s-item__link")
price_el = item.select_one(".s-item__price")
shipping_el = item.select_one(".s-item__shipping")
bid_el = item.select_one(".s-item__bid-count")
time_el = item.select_one(".s-item__time-left")
bin_el = item.select_one(".s-item__buy-it-now")
seller_el = item.select_one(".s-item__seller-info-text")
# item_idをURLから抽出
item_id = ""
if link_el and link_el.get("href"):
href = link_el["href"]
if "/itm/" in href:
item_id = href.split("/itm/")[1].split("?")[0]
# 価格をパース
price = None
if price_el:
price_text = price_el.text.replace("$", "").replace(",", "").strip()
try:
price = float(price_text.split(" to ")[0])
except ValueError:
pass
# 入札数をパース
bid_count = None
if bid_el:
try:
bid_count = int("".join(filter(str.isdigit, bid_el.text)))
except ValueError:
pass
listings.append(EbayListing(
title=title_el.text.strip() if title_el else "",
price=price,
shipping=shipping_el.text.strip() if shipping_el else None,
url=link_el["href"] if link_el else "",
item_id=item_id,
bid_count=bid_count,
time_left=time_el.text.strip() if time_el else None,
is_buy_it_now=bin_el is not None,
seller=seller_el.text.strip() if seller_el else None,
))
# 礼儀的な遅延
time.sleep(random.uniform(2, 5))
return listings
# 実行例
results = scrape_ebay_search("vintage rolex", country="US", max_pages=3)
for r in results[:5]:
print(f"{r.title[:50]}... ${r.price} | Bids: {r.bid_count} | BIN: {r.is_buy_it_now}")このスクリプトは、各ページの.s-item要素を走査し、タイトル・価格・送料・入札数・残り時間・Buy It Nowフラグを構造化データとして返します。プロキシは国別に切り替え可能で、country="DE"を指定すればeBay.deのドイツ語結果を取得できます。
オークションデータの詳細抽出
オークション追跡では、残り時間と入札数のリアルタイム性が重要です。APIはこれらを提供しないため、HTMLスクレイピングが唯一の選択肢になります。
出品詳細ページからのオークション情報
def parse_auction_detail(html: str) -> dict:
"""個別出品ページからオークション詳細を抽出"""
soup = BeautifulSoup(html, "html.parser")
auction = {
"time_left": None,
"bid_count": None,
"current_bid": None,
"is_buy_it_now": False,
"bin_price": None,
"bid_history": [],
}
# 残り時間 — カウントダウンタイマー
countdown = soup.select_one("[data-testid='x-countdown-timer']")
if not countdown:
countdown = soup.select_one("#vi-cdown_timeLeft")
if countdown:
auction["time_left"] = countdown.get_text(strip=True)
# 入札数
bid_el = soup.select_one(".bid-count")
if not bid_el:
bid_el = soup.select_one("[data-testid='x-bid-count']")
if bid_el:
try:
auction["bid_count"] = int(
"".join(filter(str.isdigit, bid_el.text))
)
except ValueError:
pass
# 現在価格
price_el = soup.select_one("[data-testid='x-price-primary']")
if price_el:
auction["current_bid"] = price_el.text.strip()
# Buy It Now
bin_btn = soup.select_one("[data-testid='x-bin-btn']")
if bin_btn:
auction["is_buy_it_now"] = True
bin_price_el = soup.select_one("[data-testid='x-bin-price']")
if bin_price_el:
auction["bin_price"] = bin_price_el.text.strip()
# 入札履歴(ログイン不要で閲覧可能な範囲)
bid_rows = soup.select(".bid-history tbody tr")
for row in bid_rows:
cells = row.select("td")
if len(cells) >= 3:
auction["bid_history"].append({
"bidder": cells[0].text.strip(),
"amount": cells[1].text.strip(),
"time": cells[2].text.strip(),
})
return auction
# 使用例
proxy = get_proxy_url(country="US", session="auction-track-1")
proxies = {"http": proxy, "https": proxy}
resp = requests.get(
"https://www.ebay.com/itm/1234567890",
headers=headers,
proxies=proxies,
timeout=30,
)
auction_data = parse_auction_detail(resp.text)
print(auction_data)実務ポイント:オークションの終了直前(残り5分以内)は価格変動が激しいため、スティッキーセッション(
session-abc123)で同一IPから連続ポーリングするのが有効です。ProxyHatのセッションID指定で、同じIPを最大30分間維持できます。
出品者分析:フィードバック・カテゴリ・クロスリスティング
リセラーインテリジェンスでは、個別商品だけでなく出品者全体の動向を追跡することが重要です。出品者プロフィールページから、フィードバックスコア・取扱カテゴリ・出品パターンを抽出できます。
def scrape_seller_profile(
seller_name: str,
country: str = "US",
) -> dict:
"""出品者プロフィールから分析データを抽出"""
proxy = get_proxy_url(country=country, session=f"seller-{seller_name}")
proxies = {"http": proxy, "https": proxy}
url = f"https://www.ebay.com/str/{seller_name}"
resp = requests.get(url, headers=headers, proxies=proxies, timeout=30)
soup = BeautifulSoup(resp.text, "html.parser")
seller = {
"name": seller_name,
"feedback_score": None,
"positive_pct": None,
"categories": [],
"active_listings_count": None,
}
# フィードバックスコア
fb_score = soup.select_one(".feedback-score")
if fb_score:
try:
seller["feedback_score"] = int(
"".join(filter(str.isdigit, fb_score.text))
)
except ValueError:
pass
# ポジティブ率
fb_pct = soup.select_one(".feedback-percent")
if fb_pct:
seller["positive_pct"] = fb_pct.text.strip()
# 取扱カテゴリ
cat_els = soup.select(".seller-categories a")
seller["categories"] = [
el.text.strip() for el in cat_els if el.text.strip()
]
# アクティブ出品数
count_el = soup.select_one(".str-items-count")
if count_el:
try:
seller["active_listings_count"] = int(
"".join(filter(str.isdigit, count_el.text))
)
except ValueError:
pass
return seller
# クロスリスティング検出:複数出品者が同一商品を出品しているか確認
def find_cross_listings(
item_title: str,
country: str = "US",
) -> list[dict]:
"""同一商品を複数出品している出品者を検出"""
results = scrape_ebay_search(item_title, country=country, max_pages=2)
# タイトルの類似度でグルーピング(簡易実装)
from collections import defaultdict
title_groups = defaultdict(list)
for r in results:
# 最初の30文字で大まかにグループ化
key = r.title[:30].lower()
title_groups[key].append(r)
# 2件以上出品されているグループを返す
return [
{"title_key": k, "listings": v}
for k, v in title_groups.items()
if len(v) >= 2
]
# 実行例
seller_info = scrape_seller_profile("top-rated-seller-1")
print(f"Feedback: {seller_info['feedback_score']} ({seller_info['positive_pct']})")
print(f"Categories: {', '.join(seller_info['categories'][:5])}")
# Node.js + axios の場合
// const axios = require("axios");
// const resp = await axios.get("https://www.ebay.com/sch/i.html?_nkw=vintage+rolex", {
// proxy: {
// host: "gate.proxyhat.com",
// port: 8080,
// auth: { username: "user-country-US", password: "YOUR_PASSWORD" }
// }
// });
// console.log(resp.data.length);eBayのアンチボット対策と回避戦略
eBayは以下の技術でスクレイパーを検知します:
- Cloudflare WAF:データセンターIP範囲をブロック。ASNベースのフィルタリングも行う
- hCaptcha:疑わしいトラフィックにCAPTCHAを提示。解決には$1.5〜3/1000回のコストがかかる
- レートリミット:同一IPから1分間に30リクエスト以上で429を返す
- ブラウザフィンガープリント:TLSフィンガープリント・HTTP/2フレーム順序でボットを識別
これらに対する実務的な対策:
- レジデンシャルプロキシのローテーション:ProxyHatのデフォルト設定ではリクエストごとにIPが変わるため、ASNブロックを回避
- 自然なリクエスト間隔:2〜5秒のランダム遅延を入れる
- 適切なHTTPヘッダー:最新のChrome User-Agent、Accept-Language、Refererを設定
- セッション管理:出品詳細ページへの連続アクセスにはスティッキーセッションを使用
curlによるクイックテスト
プロキシが正しく動作するか確認するには、curlでテストします:
# 米国IPでeBay検索結果を取得
curl -x "http://user-country-US:YOUR_PASSWORD@gate.proxyhat.com:8080" \
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \
-H "Accept-Language: en-US,en;q=0.9" \
"https://www.ebay.com/sch/i.html?_nkw=iphone+15+pro" \
-o ebay_search.html
# ドイツIPでeBay.deをテスト
curl -x "http://user-country-DE:YOUR_PASSWORD@gate.proxyhat.com:8080" \
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \
-H "Accept-Language: de-DE,de;q=0.9" \
"https://www.ebay.de/sch/i.html?_nkw=vintage+uhr" \
-o ebay_de_search.htmlサンプルレスポンス(検索結果HTMLの構造)
eBay検索結果のHTMLは概ね以下の構造を持ちます(簡略化):
<ul class="srp-results srp-list clearfix">
<li class="s-item s-item__pl-on-bottom">
<div class="s-item__wrapper clearfix">
<a class="s-item__link" href="https://www.ebay.com/itm/1234567890">
<div class="s-item__image-section">...</div>
</a>
<div class="s-item__info clearfix">
<a class="s-item__link">
<h3 class="s-item__title">Vintage Rolex Submariner 1680</h3>
</a>
<div class="s-item__details clearfix">
<span class="s-item__price">$8,500.00</span>
<span class="s-item__shipping">Free shipping</span>
<span class="s-item__bid-count">12 bids</span>
<span class="s-item__time-left">2h 15m left</span>
<span class="s-item__buy-it-now">Buy It Now</span>
</div>
<div class="s-item__seller-info">
<span class="s-item__seller-info-text">luxury_watches_nyc (15,234) 99.7%</span>
</div>
</div>
</div>
</li>
<!-- more .s-item entries... -->
</ul>この構造はeBayがA/Bテストを実施するため、時々微変化があります。.s-item自体は安定していますが、サブクラス名(__details→__detail-sectionなど)が変わることがあるため、定期的なセレクタ検証が必要です。
倫理的・法的考慮事項
- robots.txt:eBayのrobots.txtは
/sch/(検索)と/itm/(出品詳細)のクローリングを許可していません。実務上はプロキシ経由でアクセスしますが、これはeBayのToSに違反する可能性があります - ToS:eBayの利用規約は自動データ収集を禁止しています。大規模スクレイピングはアカウント停止リスクがあります
- GDPR/CCPA:出品者名・フィードバックなど個人データを扱う場合、適切な取り扱いが必要
- レート制限の尊重:eBayのサーバーに負荷をかけないよう、適切な遅延を入れる
推奨アプローチ:APIで取得できるデータはAPIを使い、APIでカバーできない部分のみスクレイピングに頼るハイブリッド戦略が、リスクと効率のバランスが最も良いです。
Key Takeaways
- eBay Finding APIは1日5,000コール制限があり、オークションデータを含まない——大規模追跡にはeBayプロキシ経由のHTMLスクレイピングが必須
- 検索結果は
.s-itemセレクタで安定してパース可能。サブセレクタは定期的に検証すること - データセンターIPはeBayでほぼ確実にブロックされる——レジデンシャルプロキシが必須
- 地域プロキシでeBay.de・eBay.co.ukの地域固有価格・送料を正確に取得
- オークション追跡にはスティッキーセッション(同一IP維持)が効果的
- 出品者分析はフィードバックスコア・カテゴリ・クロスリスティングパターンの3軸で行う
- API+スクレイピングのハイブリッド戦略が、リスクとカバレッジの最適バランス
ProxyHatのレジデンシャルプロキシでeBayスクレイピングを開始するには、料金プランを確認し、対応ロケーションから必要な地域を選択してください。大規模なウェブスクレイピングのユースケースについては、ウェブスクレイピングのユースケースも参照してください。






