إيباي هو أحد أكبر أسواق التجارة الإلكترونية عالمياً — أكثر من 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 API | Browse API | كشط HTML |
|---|---|---|---|
| حد الطلبات اليومي | 5,000 | حسب المستوى | غير محدود (مع بروكسي) |
| نتائج لكل طلب | 100 | 50–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+ دولة.






