如何高效抓取eBay:API限额、代理选择与Python实战指南

全面解析eBay数据抓取策略:对比Finding/Browse API与HTML抓取的取舍,详解搜索结果与详情页结构,提供基于ProxyHat住宅代理的Python实战代码,涵盖拍卖追踪与卖家分析。

如何高效抓取eBay:API限额、代理选择与Python实战指南

为什么你需要抓取eBay——以及为什么它越来越难

无论你是做转售情报、竞品定价研究,还是构建电商数据管道,scrape eBay的需求始终存在。eBay拥有超过13亿活跃商品列表,是全球最大的二手与新品交易市场之一。但eBay的反爬体系同样成熟——数据中心IP几乎秒封,高频请求触发验证码,区域定价差异让全球视角的数据采集变得复杂。

本文从实践角度出发,先厘清API与HTML抓取的边界,再深入页面结构、代理选型、拍卖数据处理和卖家分析,最后给出可运行的Python代码。面向转售情报团队和市场研究人员,不废话。

eBay API vs HTML抓取:何时用哪个?

eBay官方提供两套核心API:Finding API(搜索与筛选)和Browse API(商品详情与分类浏览)。听起来很美好,但实际使用中有硬性限制。

API的硬限制

  • Finding API:每日调用上限约5,000次(标准App),每次返回最多100条结果,分页上限100页——即单次搜索最多10,000条。
  • Browse API:需要OAuth token,速率限制约每秒5次请求;部分字段(如卖家详细反馈、跨类目关联)不返回。
  • API申请门槛:需要eBay开发者账号、App Key审批,生产环境需额外审核。
  • 数据新鲜度:API缓存通常5-15分钟,拍卖倒计时和实时竞价数据存在延迟。

当你需要超量采集实时拍卖数据卖家深度画像区域差异化定价时,API力不从心,HTML抓取成为必要的补充方案。

API vs 抓取对比

维度Finding/Browse APIHTML抓取
每日数据量~5,000次调用 × 100条取决于代理池规模
实时性5-15分钟缓存页面即所得
字段丰富度结构化但有限完整页面数据
反爬风险高(需代理)
维护成本低(结构化)中(CSS选择器可能变动)
区域定价需指定siteid参数直接访问区域站点
卖家深度数据部分缺失可完整采集

最佳实践:API做基础批量查询,抓取做深度补充。先用API拿到商品ID列表,再用eBay listing scraper逐页抓取详情和卖家数据。

目标页面HTML结构详解

eBay的页面结构经历过多次改版,但核心选择器相对稳定。以下基于2024-2025年的页面版本。

搜索结果页:.s-item 结构

搜索结果URL格式:https://www.ebay.com/sch/i.html?_nkw=KEYWORD&_pgn=PAGE

每个商品卡片在.s-item容器内,关键字段选择器:

  • 商品标题.s-item__title
  • 价格.s-item__price
  • 运费.s-item__shipping
  • 商品链接.s-item__link(href属性)
  • 图片.s-item__image-img(src属性)
  • 拍卖信息.s-item__bid-count.s-item__time-left
  • Buy It Now标记.s-item__purchase-option-with-icon
  • 卖家名.s-item__seller-info

商品详情页

详情页URL格式:https://www.ebay.com/itm/ITEM_ID

核心选择器:

  • 标题#itemTitle.x-item-title__mainTitle
  • 价格区域.x-price-primary
  • 商品描述:iframe嵌套#desc_ifr,需二次请求
  • Item Specifics.x-about-this-item .ux-labels-values__content
  • 卖家信息区块.x-seller-info

卖家Profile页

URL格式:https://www.ebay.com/str/SELLER_NAMEhttps://www.ebay.com/usr/SELLER_NAME

关键字段:

  • Feedback Score.feedback-score
  • Positive Rating.positive-feedback
  • 注册时间.member-since
  • 活跃类目.seller-store-category

代理选择:为什么eBay必须用住宅代理

eBay的反爬系统对数据中心IP极为敏感。实测数据:同一DC IP段连续请求3-5次搜索页后即触发CAPTCHA或返回空结果页。这不是猜测——eBay长期与反欺诈厂商合作,DC IP库标记非常精准。

代理类型对比

代理类型eBay兼容性适用场景
数据中心代理极差(秒封)仅用于API调用
住宅代理优秀大规模搜索与详情页抓取
移动代理优秀模拟移动端用户行为

对于eBay proxy策略,推荐配置:

  • 大规模搜索结果采集:住宅代理 + 轮转模式(每次请求换IP)
  • 详情页批量抓取:住宅代理 + sticky session(同一会话保持IP 5-10分钟)
  • 区域站点采集:geo-targeted住宅代理,指定目标国家

区域化采集的必要性

eBay在不同区域站点(eBay.de、eBay.co.uk、eBay.com.au)展示的价格、运费、可见商品列表可能完全不同。一个德国买家看到的搜索结果和一个美国买家看到的截然不同。要获取区域真实数据,必须使用对应国家的住宅IP。

通过ProxyHat的geo-targeting功能,可以在用户名中指定国家和城市:

# 德国IP - 访问eBay.de获取区域定价
http://user-country-DE:password@gate.proxyhat.com:8080

# 英国伦敦IP - 访问eBay.co.uk
http://user-country-GB-city-london:password@gate.proxyhat.com:8080

# 美国纽约IP - 访问eBay.com
http://user-country-US-city-new_york:password@gate.proxyhat.com:8080

实战:Python抓取eBay搜索结果

以下代码使用requests + BeautifulSoup,通过ProxyHat住宅代理抓取搜索结果并解析.s-item为结构化记录。

import requests
from bs4 import BeautifulSoup
import json
import time
import random

# ProxyHat residential proxy configuration
PROXY_URL = "http://user-country-US:YOUR_PASSWORD@gate.proxyhat.com:8080"
PROXIES = {"http": PROXY_URL, "https": PROXY_URL}

HEADERS = {
    "User-Agent": (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 (KHTML, like Gecko) "
        "Chrome/125.0.0.0 Safari/537.36"
    ),
    "Accept-Language": "en-US,en;q=0.9",
    "Accept": "text/html,application/xhtml+xml",
}

def fetch_ebay_search(keyword, max_pages=3):
    """Fetch eBay search results and parse .s-item cards."""
    all_items = []

    for page in range(1, max_pages + 1):
        url = f"https://www.ebay.com/sch/i.html?_nkw={keyword}&_pgn={page}"
        try:
            resp = requests.get(url, headers=HEADERS, proxies=PROXIES, timeout=15)
            resp.raise_for_status()
        except requests.RequestException as e:
            print(f"Page {page} failed: {e}")
            continue

        soup = BeautifulSoup(resp.text, "html.parser")
        items = soup.select(".s-item")

        for item in items:
            title_el = item.select_one(".s-item__title")
            price_el = item.select_one(".s-item__price")
            link_el = item.select_one(".s-item__link")
            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__purchase-option-with-icon")

            record = {
                "title": title_el.get_text(strip=True) if title_el else None,
                "price": price_el.get_text(strip=True) if price_el else None,
                "url": link_el["href"] if link_el and link_el.get("href") else None,
                "shipping": shipping_el.get_text(strip=True) if shipping_el else None,
                "bid_count": bid_el.get_text(strip=True) if bid_el else None,
                "time_left": time_el.get_text(strip=True) if time_el else None,
                "buy_it_now": bool(bin_el),
                "is_auction": bool(bid_el),
            }

            # Skip ad placeholders ("Shop on eBay")
            if record["title"] and "Shop on eBay" not in record["title"]:
                all_items.append(record)

        # Polite delay between pages
        time.sleep(random.uniform(2, 4))

    return all_items


if __name__ == "__main__":
    results = fetch_ebay_search("vintage camera", max_pages=3)
    print(json.dumps(results[:2], indent=2, ensure_ascii=False))
    print(f"\nTotal items: {len(results)}")

示例输出(截断):

[
  {
    "title": "Vintage Canon AE-1 35mm Film Camera SLR Body Only",
    "price": "$89.99",
    "url": "https://www.ebay.com/itm/1234567890",
    "shipping": "+$12.30 shipping",
    "bid_count": null,
    "time_left": null,
    "buy_it_now": true,
    "is_auction": false
  },
  {
    "title": "Nikon FM2 35mm Film Camera Black Body EXC+",
    "price": "$185.00",
    "url": "https://www.ebay.com/itm/0987654321",
    "shipping": "Free shipping",
    "bid_count": "12 bids",
    "time_left": "1h 23m left",
    "buy_it_now": false,
    "is_auction": true
  }
]
Total items: 144

处理拍卖数据:倒计时、竞价次数与Buy It Now

拍卖商品是eBay区别于Amazon的核心特征,也是转售情报团队最关心的数据类型。抓取拍卖数据需要注意几个关键点:

时间敏感性与刷新策略

拍卖的.s-item__time-left字段返回的是相对时间字符串(如1h 23m left6d 5h left)。详情页的倒计时更精确,但也是JavaScript动态渲染的。

对于即将结束的拍卖(<24小时),建议:

  • 使用sticky session代理,避免频繁换IP触发验证
  • 缩短刷新间隔至5-10分钟(而非搜索页的2-4秒)
  • 记录每次抓取的时间戳,将相对时间换算为绝对截止时间
# Sticky session proxy for auction monitoring
# The session flag keeps the same IP for the duration
AUCTION_PROXY = "http://user-session-auction_monitor_1:YOUR_PASSWORD@gate.proxyhat.com:8080"
AUCTION_PROXIES = {"http": AUCTION_PROXY, "https": AUCTION_PROXY}

def parse_time_left(time_str):
    """Convert eBay time-left string to estimated minutes remaining."""
    if not time_str:
        return None
    total_minutes = 0
    import re
    days = re.search(r'(\d+)d', time_str)
    hours = re.search(r'(\d+)h', time_str)
    mins = re.search(r'(\d+)m', time_str)
    if days:
        total_minutes += int(days.group(1)) * 1440
    if hours:
        total_minutes += int(hours.group(1)) * 60
    if mins:
        total_minutes += int(mins.group(1))
    return total_minutes

def monitor_auction(item_url, check_interval=600, max_checks=60):
    """Monitor a single auction listing at regular intervals."""
    import datetime
    checks = []
    for i in range(max_checks):
        resp = requests.get(item_url, headers=HEADERS, proxies=AUCTION_PROXIES, timeout=15)
        soup = BeautifulSoup(resp.text, "html.parser")

        # Detail page selectors
        price_el = soup.select_one(".x-price-primary")
        bid_el = soup.select_one(".x-bid-count")
        time_el = soup.select_one(".x-time-left")

        snapshot = {
            "timestamp": datetime.datetime.utcnow().isoformat(),
            "price": price_el.get_text(strip=True) if price_el else None,
            "bids": bid_el.get_text(strip=True) if bid_el else None,
            "time_left_raw": time_el.get_text(strip=True) if time_el else None,
            "time_left_minutes": parse_time_left(
                time_el.get_text(strip=True) if time_el else None
            ),
        }
        checks.append(snapshot)
        print(f"Check {i+1}: {snapshot['price']} | {snapshot['bids']} | {snapshot['time_left_raw']}")

        if snapshot["time_left_minutes"] is not None and snapshot["time_left_minutes"] <= 0:
            print("Auction ended.")
            break

        time.sleep(check_interval)
    return checks

Buy It Now标志的识别

在搜索结果中,.s-item__purchase-option-with-icon存在即表示有BIN选项。但注意:部分商品同时支持拍卖和BIN——即"或立购"。在详情页中,BIN价格和起拍价分别在不同元素中,需同时抓取两个价格字段做比较。

卖家分析:反馈评分、类目分布与跨平台关联

对于市场研究团队,卖家画像比单品数据更有战略价值。一个卖家的反馈趋势、主营类目变化、上架节奏,都能揭示供应链信号。

核心指标采集

  • Feedback Score:累计正面反馈数,反映历史交易规模
  • Positive Rate:近12个月正面评价百分比
  • 注册时长:结合Feedback Score可算日均交易量
  • 活跃类目:卖家店铺的类目树,识别主营业务
  • 在架商品数:当前active listing数量
def scrape_seller_profile(seller_name):
    """Scrape seller profile page for analytics data."""
    url = f"https://www.ebay.com/str/{seller_name}"
    resp = requests.get(url, headers=HEADERS, proxies=PROXIES, timeout=15)
    soup = BeautifulSoup(resp.text, "html.parser")

    feedback_el = soup.select_one(".feedback-score")
    positive_el = soup.select_one(".positive-feedback")
    since_el = soup.select_one(".member-since")

    # Active listings count from store page
    count_el = soup.select_one(".srp-controls__count-heading")

    # Store categories
    categories = []
    for cat in soup.select(".seller-store-category a"):
        categories.append(cat.get_text(strip=True))

    profile = {
        "seller": seller_name,
        "feedback_score": feedback_el.get_text(strip=True) if feedback_el else None,
        "positive_rate": positive_el.get_text(strip=True) if positive_el else None,
        "member_since": since_el.get_text(strip=True) if since_el else None,
        "active_listings_count": count_el.get_text(strip=True) if count_el else None,
        "store_categories": categories[:20],  # Cap at 20
    }
    return profile


# Batch seller analysis
sellers = ["camera_vault", "vintage_watches_uk", "retro_tech_deals"]
for s in sellers:
    data = scrape_seller_profile(s)
    print(json.dumps(data, indent=2, ensure_ascii=False))
    time.sleep(random.uniform(3, 6))

跨平台关联与交叉分析

进阶用法:将eBay卖家数据与其他平台(如Amazon Seller Central、Etsy店铺)交叉对比,识别同一卖家在不同平台的定价策略差异。这需要:

  1. 从eBay卖家Profile中提取品牌名或店铺名
  2. 在其他平台搜索相同标识
  3. 对比同SKU的跨平台价格与库存状态

这类分析对套利发现品牌保护团队非常有价值。

反爬对抗:eBay的防御体系与应对

eBay使用多层反爬技术:

  • IP信誉库:数据中心IP段标记精准,住宅IP信誉度更高
  • 请求频率检测:同一IP短时间内高频请求触发CAPTCHA(Arkose Labs / FunCaptcha)
  • 浏览器指纹:TLS指纹、JS环境检测
  • 行为分析:请求路径模式(只搜不点、无图片加载等)

应对策略:

  • 使用住宅代理轮转,每次搜索请求换IP
  • 模拟完整浏览行为:加载图片、随机点击详情页后再返回
  • 控制请求速率:搜索页间隔2-4秒,详情页间隔3-6秒
  • 使用真实浏览器UA和合理的Accept头
  • 遇到CAPTCHA时立即停止该IP,切换新IP重试

法律与合规边界

抓取eBay数据前,务必了解:

  • eBayrobots.txt:允许部分路径的爬取,但限制/sch/等搜索路径的速率
  • eBay用户协议:禁止自动化数据采集用于竞争目的,需评估法律风险
  • GDPR/CCPA:涉及欧盟/加州用户数据时需合规处理
  • 版权:商品图片和描述受版权保护,大规模复制需授权

建议:仅采集公开可见的结构化数据(价格、标题、卖家评分),不存储个人身份信息,数据仅用于内部分析而非再分发。

关键原则:抓取是为了获取市场洞察,而非复制eBay平台。保持克制,遵守速率限制,尊重robots.txt声明。

关键要点

  • API优先,抓取补充:eBay Finding/Browse API适合结构化批量查询,但有5,000次/日调用上限和字段缺失;HTML抓取用于深度数据和实时拍卖。
  • 必须使用住宅代理:eBay对数据中心IP封禁极快,住宅代理(尤其是geo-targeted)是大规模采集的前提。
  • .s-item是搜索页核心:标题、价格、运费、拍卖信息、BIN标记都在这个容器内。
  • 拍卖数据需sticky session:监控即将结束的拍卖时,保持同一IP避免触发验证。
  • 卖家分析比单品更有价值:反馈评分、类目分布、上架节奏揭示供应链信号。
  • 合规先行:遵守robots.txt、用户协议和隐私法规,数据仅用于内部分析。

准备好开始eBay数据采集?查看ProxyHat代理套餐,获取覆盖195+国家的住宅代理池,或了解网页抓取用例中的更多实战方案。需要SERP级别的结构化数据?参考我们的SERP追踪方案

准备开始了吗?

通过AI过滤访问148多个国家的5000多万个住宅IP。

查看价格住宅代理
← 返回博客