الدليل العملي لجلب بيانات إيباي باستخدام البروكسي: واجهات API مقابل كشط HTML

تعلّم متى تستخدم واجهات eBay API ومتى تلجأ لكشط HTML، وكيف تختار البروكسي المناسب، مع أمثلة بايثون جاهزة لتحليل المزادات والبائعين.

الدليل العملي لجلب بيانات إيباي باستخدام البروكسي: واجهات API مقابل كشط HTML

إيباي هو أحد أكبر أسواق التجارة الإلكترونية عالمياً — أكثر من 1.5 مليار قائمة نشطة و138 مليون مستخدم نشط. لفرق أبحاث السوق وبائعي إعادة البيع، هذه البيانات ذهبية: أسعار تنافسية، أنماط مزادات، تحليلات البائعين. لكن السؤال الأصعب ليس ماذا تجلب، بل كيف تجلبه دون أن تُحظر.

في هذا الدليل سنقارن بين واجهات eBay البرمجية وكشط HTML مباشرةً، ونوضح البنية الهيكلية للصفحات المستهدفة، ونقدم أمثلة بايثون عملية تعمل مع بروكسي ProxyHat.

واجهات eBay API مقابل كشط HTML: متى تستخدم كلًا منهما؟

إيباي يوفّر واجهتين برمجيتين رئيسيتين:

  • Finding API — للبحث عن القوائم حسب الكلمات المفتاحية والفئات والسعر. يُرجع حتى 100 نتيجة لكل طلب، بحد أقصى 5,000 طلب يومياً لكل مفتاح تطبيق.
  • Browse API — للتصفح حسب الفئات والفلاتر. يوفر وصولاً أوسع لكن بحدود معدل تختلف حسب مستوى التطبيق.

المشكلة؟ حصص API ضيقة للغاية إذا كنت تراقب آلاف القوائم يومياً. واجهة Finding API تسمح بـ 5,000 طلب/يوم فقط — أي بحد أقصى 500,000 قائمة نظرياً، لكن في الواقع أقل بكثير بسبب التصفح بالصفحات. واجهة Browse API أقل مرونة في الفلاتر ولا تدعم كل الأسواق المحلية بالتساوي.

هنا يأتي دور كشط HTML كخيار بديل عملي:

المعيارFinding APIBrowse APIكشط HTML
حد الطلبات اليومي5,000حسب المستوىغير محدود (مع بروكسي)
نتائج لكل طلب10050–200~60 لكل صفحة
فلاتر البحثمحدودةمحدودةكاملة (كما في الموقع)
بيانات المزاد المفصلةمحدودةلانعم
بيانات البائع الكاملةلالانعم
استقرار البنيةعاليعاليمتوسط (يتغير)
خطر الحظرمنخفضمنخفضعالٍ بدون بروكسي

القاعدة العملية: ابدأ بواجهة API للبيانات الأساسية، واستخدم كشط HTML عندما تحتاج فلاتر أكثر، أو بيانات مزاد مفصلة، أو تحليلات بائع لا توفرها API.

البنية الهيكلية لصفحات إيباي المستهدفة

صفحة نتائج البحث

كل قائمة في شبكة النتائج تُغلف بعنصر .s-item داخل الحاوية .srp-results. المحددات المهمة:

  • .s-item__title — عنوان القائمة
  • .s-item__price — السعر الحالي
  • .s-item__shipping — معلومات الشحن
  • .s-item__bid-count — عدد المزايدات (للمزادات)
  • .s-item__time-left — الوقت المتبقي (للمزادات)
  • .s-item__purchase-info — معلومات الشراء الفوري
  • .s-item__link — رابط صفحة التفاصيل
  • .s-item__seller-info — اسم البائع وتقييمه

ملاحظة: إيباي يضيف أحياناً عناصر إعلانية أو مُقترحة داخل .s-item — تحقق من وجود .s-item__pl-onbo لتصفيتها.

صفحة تفاصيل القائمة

عنوان URL النموذجي: https://www.ebay.com/itm/ITEM_ID. العناصر المهمة:

  • #LeftSummaryPanel .ux-textspans — السعر والسعر المخفض
  • #CenterSummaryInternal .ux-textspans--BID — معلومات المزاد
  • #why2buy .ux-textspans — مزايا الشراء الفوري
  • #SellerC2CModal .ux-textspans — تقييم البائع
  • #desc_ifr — إطار الوصف (يُحمّل من نطاق فرعي)

صفحة ملف البائع

عنوان URL: https://www.ebay.com/str/SELLER_NAME أو https://www.ebay.com/usr/SELLER_NAME. تحتوي على:

  • .str-seller-card__feedback — درجة التقييم
  • .str-items-list .str-item-card — القوائم النشطة
  • .str-profile__info — معلومات المتجر

تقنيات مكافحة الروبوتات لدى إيباي

إيباي يستخدم طبقات متعددة من الحماية:

  • تحليل بصمة المتصفح — User-Agent، HTTP/2 fingerprint، TLS fingerprint
  • معدل الطلبات لكل IP — حظر مؤقت بعد ~100–200 طلب من نفس IP في فترة قصيرة
  • تحليل سلوك الجلسة — طلبات بدون ملفات تعريف الارتباط أو سلوك تصفح طبيعي
  • Arkose Labs / FunCaptcha — يظهر عند سلوك مشبوه
  • حظر مراكز البيانات بشكل صارم — عناوين IP الخاصة بمراكز البيانات تُحظر بسرعة أكبر بكثير من عناوين السكنية

النتيجة: كشط إيباي بدون بروكسي سكنية أو متنقلة = حظر شبه مؤكد خلال دقائق. بروكسي مراكز البيانات تعمل لفترة أقصر بكثير.

اختيار البروكسي المناسب لكشط إيباي

بروكسي سكنية: الخيار الأساسي

إيباي يحظر عناوين مراكز البيانات بسهولة لأن نسبة حركة المرور المشبوهة منها عالية. البروكسي السكنية تستخدم عناوين IP حقيقية من مزودي إنترنت فعليين، ما يجعل طلباتك تبدو كزيارات مستخدمين حقيقيين.

مع ProxyHat، استخدم المنفذ 8080 للبروكسي HTTP:

http://user-country-US:PASSWORD@gate.proxyhat.com:8080

الاستهداف الجغرافي للأسواق المحلية

إيباي يعرض منتجات وأسعاراً مختلفة حسب المنطقة. eBay.de يعرض نتائج مختلفة عن eBay.co.uk أو eBay.com. لجلب بيانات سوق محدد:

# ألمانيا - eBay.de
http://user-country-DE:PASSWORD@gate.proxyhat.com:8080

# المملكة المتحدة - eBay.co.uk
http://user-country-GB:PASSWORD@gate.proxyhat.com:8080

# فرنسا - eBay.fr
http://user-country-FR:PASSWORD@gate.proxyhat.com:8080

هذا ضروري لأن إيباي يفرض توجهاً جغرافياً حتى عند استخدام معلمات URL مثل _ipg أو _fsip.

جلسات لاصقة مقابل دوران لكل طلب

لكشط نتائج البحث (صفحات متعددة)، استخدم دوران IP لكل طلب لتوزيع الحمل. لكن لصفحات تفاصيل القائمة حيث تحتاج جلسة متسقة (ملفات تعريف الارتباط، سلوك التصفح)، استخدم جلسة لاصقة:

# جلسة لاصقة - نفس IP لمدة الجلسة
http://user-session-mylist42-country-US:PASSWORD@gate.proxyhat.com:8080

مثال عملي: كشط نتائج بحث إيباي وتحليلها

المثال التالي يجلب صفحة نتائج بحث ويحول كل .s-item إلى سجل منظّم:

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

PROXY_URL = "http://user-country-US: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": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.9",
    "Accept-Encoding": "gzip, deflate, br",
}

def fetch_search_results(keyword, page=1):
    """جلب صفحة نتائج بحث من إيباي"""
    params = {
        "_nkw": keyword,
        "_pgn": page,
        "_ipg": 60,  # 60 نتيجة لكل صفحة
    }
    url = "https://www.ebay.com/sch/i.html"
    
    try:
        resp = requests.get(
            url, params=params, headers=HEADERS,
            proxies=PROXIES, timeout=30
        )
        resp.raise_for_status()
        return resp.text
    except requests.RequestException as e:
        print(f"خطأ في جلب الصفحة {page}: {e}")
        return None

def parse_s_item(item_el):
    """تحويل عنصر .s-item إلى قاموس منظّم"""
    # تصفية العناصر الإعلانية
    if item_el.select_one(".s-item__pl-onbo"):
        return None
    
    record = {}
    
    # العنوان والرابط
    title_el = item_el.select_one(".s-item__title")
    record["title"] = title_el.get_text(strip=True) if title_el else None
    
    link_el = item_el.select_one(".s-item__link")
    record["url"] = link_el["href"] if link_el else None
    
    # استخراج معرّف القائمة من الرابط
    if record["url"]:
        parts = record["url"].split("/itm/")
        if len(parts) > 1:
            record["item_id"] = parts[1].split("?")[0]
    
    # السعر
    price_el = item_el.select_one(".s-item__price")
    record["price"] = price_el.get_text(strip=True) if price_el else None
    
    # الشحن
    shipping_el = item_el.select_one(".s-item__shipping")
    record["shipping"] = shipping_el.get_text(strip=True) if shipping_el else None
    
    # معلومات المزاد
    bid_el = item_el.select_one(".s-item__bid-count")
    record["bid_count"] = bid_el.get_text(strip=True) if bid_el else None
    
    time_el = item_el.select_one(".s-item__time-left")
    record["time_left"] = time_el.get_text(strip=True) if time_el else None
    
    # علم الشراء الفوري
    buy_el = item_el.select_one(".s-item__purchase-info")
    record["buy_it_now"] = buy_el is not None
    
    # معلومات البائع
    seller_el = item_el.select_one(".s-item__seller-info")
    record["seller_info"] = seller_el.get_text(strip=True) if seller_el else None
    
    return record if record.get("title") else None

def scrape_ebay_search(keyword, max_pages=5):
    """كشط عدة صفحات من نتائج البحث"""
    all_records = []
    
    for page in range(1, max_pages + 1):
        html = fetch_search_results(keyword, page)
        if not html:
            break
            
        soup = BeautifulSoup(html, "html.parser")
        items = soup.select(".s-item")
        
        page_records = []
        for item in items:
            record = parse_s_item(item)
            if record:
                page_records.append(record)
        
        all_records.extend(page_records)
        print(f"الصفحة {page}: {len(page_records)} قائمة")
        
    
        # تأخير عشوائي بين الطلبات
        time.sleep(random.uniform(2, 5))
    
    return all_records

# تشغيل الكشط
results = scrape_ebay_search("vintage mechanical keyboard", max_pages=3)
print(json.dumps(results[:2], indent=2, ensure_ascii=False))

نموذج استجابة (مقتطبة):

[
  {
    "title": "Vintage IBM Model M Mechanical Keyboard 1391401",
    "url": "https://www.ebay.com/itm/1234567890?...",
    "item_id": "1234567890",
    "price": "$149.99",
    "shipping": "+$12.50 shipping",
    "bid_count": "12 bids",
    "time_left": "2h 15m left",
    "buy_it_now": false,
    "seller_info": "keyboardvault (15,234) 99.7%"
  },
  {
    "title": "Apple Extended Keyboard II ADB M3501 Vintage",
    "url": "https://www.ebay.com/itm/9876543210?...",
    "item_id": "9876543210",
    "price": "$289.00",
    "shipping": "Free shipping",
    "bid_count": null,
    "time_left": null,
    "buy_it_now": true,
    "seller_info": "retro_tech_shop (3,421) 98.5%"
  }
]

التعامل مع المزادات: الوقت المتبقي وعدد المزايدات وعلم الشراء الفوري

بيانات المزادات هي الأكثر قيمة لفرق إعادة البيع. إيباي يعرض ثلاثة أنواع من القوائم:

  • مزاد علني — يعرض عدد المزايدات والوقت المتبقي
  • شراء فوري فقط — سعر ثابت بدون مزايدة
  • مزاد مع شراء فوري — خياران معاً

لتحليل أنماط المزادات، تحتاج تتبع القائمة عبر الوقت:

import re
from datetime import datetime, timedelta

def parse_time_left(time_str):
    """تحويل نص الوقت المتبقي إلى تقدير بالدقائق"""
    if not time_str:
        return None
    
    total_minutes = 0
    
    # أنماط مثل "2h 15m left" أو "1d 3h left" أو "5m left"
    days = re.search(r'(\d+)d', time_str)
    hours = re.search(r'(\d+)h', time_str)
    minutes = 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 minutes:
        total_minutes += int(minutes.group(1))
    
    return total_minutes if total_minutes > 0 else None

def parse_bid_count(bid_str):
    """استخراج عدد المزايدات من النص"""
    if not bid_str:
        return 0
    match = re.search(r'(\d+)', bid_str)
    return int(match.group(1)) if match else 0

def classify_listing(record):
    """تصنيف القائمة: مزاد، شراء فوري، أو كلاهما"""
    has_bids = record.get("bid_count") is not None
    has_buy_now = record.get("buy_it_now") is True
    
    if has_bids and has_buy_now:
        return "auction_with_bin"
    elif has_bids:
        return "auction"
    else:
        return "buy_it_now"

def analyze_auction_dynamics(records):
    """تحليل ديناميكيات المزادات"""
    auctions = [r for r in records if classify_listing(r) != "buy_it_now"]
    
    stats = {
        "total_auctions": len(auctions),
        "avg_bids": 0,
        "avg_time_left_min": 0,
        "ending_soon": 0,  # أقل من 30 دقيقة
        "hot_auctions": 0,  # أكثر من 10 مزايدات
    }
    
    if not auctions:
        return stats
    
    total_bids = 0
    total_minutes = 0
    
    for a in auctions:
        bids = parse_bid_count(a.get("bid_count"))
        minutes = parse_time_left(a.get("time_left"))
        
        total_bids += bids
        if minutes:
            total_minutes += minutes
            if minutes < 30:
                stats["ending_soon"] += 1
        if bids > 10:
            stats["hot_auctions"] += 1
    
    stats["avg_bids"] = round(total_bids / len(auctions), 1)
    stats["avg_time_left_min"] = round(total_minutes / len(auctions), 0) if total_minutes else 0
    
    return stats

# استخدام
auction_stats = analyze_auction_dynamics(results)
print(json.dumps(auction_stats, indent=2))

نموذج ناتج:

{
  "total_auctions": 18,
  "avg_bids": 7.3,
  "avg_time_left_min": 342,
  "ending_soon": 3,
  "hot_auctions": 4
}

تحليلات البائع: درجات التقييم والفئات وأنماط الإدراج المتقاطع

بيانات البائعين حيوية لتحديد المنافسين واكتشاف فرص السوق. صفحة ملف البائع تحتوي على معلومات لا توفرها واجهات API.

استخراج بيانات البائع من نتائج البحث

حقل .s-item__seller-info يحتوي على اسم البائع وعدد التقييمات ونسبة الرضا. يمكنك تحليله:

def parse_seller_info(seller_str):
    """تحليل نص معلومات البائع"""
    if not seller_str:
        return None
    
    result = {}
    
    # اسم البائع عادة بين أقواس أو قبل الأقواس
    name_match = re.match(r'^([^(]+)', seller_str.strip())
    if name_match:
        result["name"] = name_match.group(1).strip()
    
    # عدد التقييمات بين أقواس
    feedback_match = re.search(r'\(([\d,]+)\)', seller_str)
    if feedback_match:
        result["feedback_count"] = int(feedback_match.group(1).replace(',', ''))
    
    # نسبة الرضا
    pct_match = re.search(r'(\d+\.?\d*)%', seller_str)
    if pct_match:
        result["positive_pct"] = float(pct_match.group(1))
    
    return result

def build_seller_map(records):
    """بناء خريطة بائعين من سجلات القوائم"""
    sellers = {}
    
    for r in records:
        info = parse_seller_info(r.get("seller_info"))
        if not info or not info.get("name"):
            continue
        
        name = info["name"]
        if name not in sellers:
            sellers[name] = {
                "listings": 0,
                "feedback_count": info.get("feedback_count", 0),
                "positive_pct": info.get("positive_pct", 0),
                "prices": [],
                "has_auctions": False,
            }
        
        sellers[name]["listings"] += 1
        
        price_str = r.get("price", "")
        price_match = re.search(r'[\$€£]([\d,]+\.?\d*)', price_str)
        if price_match:
            sellers[name]["prices"].append(float(price_match.group(1).replace(',', '')))
        
        if r.get("bid_count"):
            sellers[name]["has_auctions"] = True
    
    # حساب الإحصائيات
    for name, data in sellers.items():
        if data["prices"]:
            data["avg_price"] = round(sum(data["prices"]) / len(data["prices"]), 2)
            data["min_price"] = min(data["prices"])
            data["max_price"] = max(data["prices"])
        del data["prices"]
    
    return sellers

# استخدام
seller_map = build_seller_map(results)
top_sellers = sorted(
    seller_map.items(),
    key=lambda x: x[1]["listings"],
    reverse=True
)[:5]
print(json.dumps(dict(top_sellers), indent=2, ensure_ascii=False))

كشط صفحة البائع مباشرة

للحصول على بيانات أكثر تفصيلاً (الفئات، حجم المتجر، أنماط الإدراج المتقاطع)، تحتاج كشط صفحة المتجر:

def fetch_seller_profile(seller_name, country="US"):
    """جلب صفحة ملف البائع"""
    proxy_url = f"http://user-country-{country}:PASSWORD@gate.proxyhat.com:8080"
    proxies = {"http": proxy_url, "https": proxy_url}
    
    url = f"https://www.ebay.com/str/{seller_name}"
    
    try:
        resp = requests.get(
            url, headers=HEADERS, proxies=proxies, timeout=30
        )
        resp.raise_for_status()
        return resp.text
    except requests.RequestException as e:
        print(f"خطأ في جلب ملف البائع {seller_name}: {e}")
        return None

def parse_seller_profile(html):
    """تحليل صفحة ملف البائع"""
    soup = BeautifulSoup(html, "html.parser")
    profile = {}
    
    # درجة التقييم
    feedback_el = soup.select_one(".str-seller-card__feedback")
    if feedback_el:
        profile["feedback_summary"] = feedback_el.get_text(strip=True)
    
    # معلومات المتجر
    info_el = soup.select_one(".str-profile__info")
    if info_el:
        profile["store_info"] = info_el.get_text(strip=True)
    
    # عدد القوائم النشطة
    items = soup.select(".str-item-card")
    profile["active_listings_count"] = len(items)
    
    # فئات القوائم
    categories = set()
    for item in items:
        cat_el = item.select_one(".str-item-card__category")
        if cat_el:
            categories.add(cat_el.get_text(strip=True))
    profile["categories"] = list(categories)
    
    return profile

أفضل الممارسات لتجنب الحظر

  • استخدم بروكسي سكنية دائماً — بروكسي مراكز البيانات تُحظر خلال 50–100 طلب عادةً. اطّلع على حالة استخدام كشط الويب لمزيد من التفاصيل.
  • دوّر User-Agent — لا تستخدم نفس User-Agent لكل الطلبات. استخدم مكتبة مثل fake-useragent.
  • أضف تأخيراً عشوائياً — بين 2–6 ثوانٍ بين الطلبات. لا ترسل طلبات بسرعة أكبر من مستخدم حقيقي.
  • حافظ على جلسات متسقة — عند كشط صفحات متعددة لنفس القائمة، استخدم جلسة لاصقة.
  • احترم robots.txt — إيباي يسمح ببعض المسارات ويمنع أخرى. تحقق من https://www.ebay.com/robots.txt قبل الكشط.
  • راقب معدل النجاح — إذا انخفض عن 90%، قلل السرعة أو غيّر البروكسي.
  • استخدم SOCKS5 للحالات الصعبة — بعض أنظمة مكافحة الروبوتات تحلل بصمة HTTP. SOCKS5 يعطي طبقة إضافية:
# بروكسي SOCKS5 مع ProxyHat
socks5://user-country-US:PASSWORD@gate.proxyhat.com:1080

الاعتبارات الأخلاقية والقانونية

كشط إيباي يخضع لشروط خدمته التي تحظر الكشط الآلي. عملياً:

  • لا تجلب بيانات شخصية (أسماء وعناوين كاملة للمشترين)
  • لا تُحمّل خوادم إيباي بشكل مفرط
  • لا تعيد بيع بيانات إيباي الخام
  • التزم بـ GDPR وCCPA إذا كنت تعالج بيانات مستخدمين أوروبيين أو كاليفورنيين
  • استخدم البيانات للتحليل الداخلي فقط

لمزيد من التفاصيل حول مواقع ProxyHat المتاحة، اطّلع على صفحة المواقع.

النقاط الرئيسية

النقاط الرئيسية:

  • واجهات eBay API عملية للبيانات الأساسية لكن حصصها ضيقة — كشط HTML ضروري للتحليل المتقدم.
  • المحدد .s-item هو عمود كشط نتائج البحث — تعلّم حقوله الفرعية جيداً.
  • بروكسي سكنية ليست رفاهية بل ضرورة — إيباي يحظر مراكز البيانات بسرعة.
  • الاستهداف الجغرافي ضروري للأسواق المحلية — eBay.de وeBay.co.uk يعرضان بيانات مختلفة.
  • بيانات المزادات (الوقت المتبقي، عدد المزايدات، الشراء الفوري) متاحة فقط عبر كشط HTML.
  • تحليل البائعين يتطلب كشط صفحات الملفات — API لا يوفر بيانات كافية.
  • التأخير العشوائي ودوران User-Agent هما خط الدفاع الأول بعد البروكسي.

جاهز لبدء كشط إيباي ببروكسي سكنية موثوقة؟ اطّلع على أسعار ProxyHat واحصل على وصول فوري لأكثر من 10 مليون IP سكنية في 190+ دولة.

¿Listo para empezar?

Accede a más de 50M de IPs residenciales en más de 148 países con filtrado impulsado por IA.

Ver preciosProxies residenciales
← Volver al Blog