يبدأ الجميع ببناء أداة سحب بيانات المجمّعات أولا. Cars.com و CarGurus و AutoTrader: ثلاثة مواقع، ومحلل بيانات واحد لكل منها، وبحلول نهاية الأسبوع يكون لديك تدفق بيانات يشبه سوق السيارات المستعملة.
ثم يسأل أحدهم عن بقية البيانات.
التحدي
تحصي NADA عدد 16,972 من وكلاء المركبات الخفيفة الحاصلين على امتياز في الولايات المتحدة وفقا لتقريرها في منتصف عام 2025، وذلك قبل احتساب المعارض المستقلة، والتي لا يحصيها أحد بشكل متسق. تتراوح التقارير الثانوية لنفس رقم NADA بين 15,720 و 16,990، وهو ما يوضح لك مدى دقة قياس هذا السوق.
يدير كل من هؤلاء الوكلاء موقعه الإلكتروني الخاص. المخزون المعروض عليه هو مخزون ذلك الوكيل، ومسعّر اليوم، قبل أيام من وصول أي جزء منه إلى موقع قوائم تابع لطرف ثالث. إذا كنت تسعّر المركبات المستعملة، أو تتوقع القيم المتبقية، أو تبيع أداة تنافسية للوكلاء، فإن موقع الوكيل نفسه هو البيانات التي تريدها. المجمّعات ليست سوى نسخة متأخرة ومفلترة منها.
لذلك تستهدف الفرق مواقع الوكلاء المباشرة، وتكتشف ثلاثة أمور بالترتيب.
إنها ليست فريدة من نوعها. تعمل جميعها تقريبا على مجموعة صغيرة من منصات مواقع الوكلاء: Dealer.com و DealerOn و Dealer Inspire و CDK و Reynolds و Sincro و Lotlinx (تعتمد القائمة الدقيقة على جهة الإحصاء). كل من يبيع هذه البيانات يبني محللا واحدا لكل منصة، وليس لكل وكيل. يوضح ساحب مخزون مواقع الوكلاء من Apify ذلك صراحة: حدد المنصة أولا، ثم استخرج البيانات. حفنة من القوالب تغطي عشرات الآلاف من مواقع الوكلاء. هذا هو الجزء الإيجابي في القصة.
السعر عادة لا يكون موجودا في HTML الذي جلبته. تعرض هذه المنصات كتلة التسعير من جانب العميل، وغالبا ما تصل تقديرات الدفع والحوافز في استدعاء ثان بعد ذلك. يمنحك جلب HTTP البسيط معلومات مثل السنة، والشركة المصنعة، والطراز، والمسافة المقطوعة، ورقم VIN. لكن حقل السعر يعود فارغا.
ثم يأتي الجزء الذي يفسد مجموعات البيانات بصمت: في موقع الوكيل، يبدو الفشل تماما كأنه حقيقة. تجيب خدمة كشف البوتات على أي request مشبوه برمز HTTP 200 وصفحة بينية. وكتلة التسعير التي لم يتم تصييرها تترك "اتصل لمعرفة السعر" في DOM، وهو أمر يكتبه الوكلاء أيضا عن قصد. يصل كلا الصفين إلى مستودع البيانات الخاص بك ويبدوان نظيفين بنفس القدر.
لقد غطينا هذا النمط في قطاع مختلف، حيث يبدو الحظر كنقطة بيانات. قطاع السيارات هو النسخة الأكثر صعوبة، لأن حالة "لا يوجد سعر" هي حالة تجارية مشروعة وليست شذوذا واضحا.
ما الذي يتغير عند التقسيم حسب التكلفة
خطوط معالجة البيانات التي تصمد لا تنظم نفسها حسب الموقع. إنها تنظم نفسها حسب تكلفة كل request.
الـ request المكلف هو الأول ضد موقع الوكيل: وهو الذي يتطلب تشغيل متصفح، وتجاوز ما تضعه منصة الوكيل أمامه، والعودة بـ session صالحة. كل ما يأتي بعد ذلك هو استدعاء HTTP منخفض التكلفة يعيد استخدام ما حققه الاستدعاء الأول.
import requests
FOURA = "https://api.foura.ai/api"
AUTH = {"Authorization": "Bearer pk_live_..."}
# First page of a rooftop: render it, clear the defense, keep the session.
first = requests.post(f"{FOURA}/browser", headers=AUTH, json={
"url": "https://example-motors.com/used-inventory/index.htm",
"unblocker": True,
"timeout_ms": 45000,
}).json()
listings = first["body"]
jar = "; ".join(f'{c["name"]}={c["value"]}' for c in first["cookies"])
agent = first["userAgent"]
exit_id = first["proxy"] # opaque proxy ID, send it back to stay on the same exit
ثم تصفح بقية مخزون هذا التاجر دون أن تدفع ثمن متصفح مرة أخرى:
page = requests.post(f"{FOURA}/single", headers=AUTH, json={
"method": "GET",
"url": "https://example-motors.com/used-inventory/index.htm?start=20",
"proxy": exit_id,
"headers": [["Cookie", jar], ["User-Agent", agent]],
"validate": {
"status": {"accept": [200]},
"data": {
"accept": ["vehicle-card"],
"fail": ["Just a moment", "Access Denied"]
}
}
}).json()
هناك تفصيلان يحملان أهمية أكبر مما يبدوان عليه.
تنتقل الجلسة كوحدة واحدة. يرتبط clearance cookie بنقطة الخروج التي حصلت عليه وبالـ User-Agent الذي تم الحصول عليه باستخدامه. أعد تشغيله من مكان آخر، أو تحت User-Agent مختلف، وسيعيدك الموقع إلى نقطة البداية عند اختبار التحقق (challenge). لهذا السبب تتحرك cookie jar وسلسلة User-Agent ومعرّف الـ proxy معاً. هذا هو التفصيل الأكثر شيوعاً الذي نرى المطورين يخطئون فيه: يحتفظون بالـ cookie، ويتخلون عن نقطة الخروج، ثم يتساءلون لماذا توقف المسار الاقتصادي عن كونه اقتصادياً.
كتلة validate هي ما يقضي على مشكلة الفشل الصامت. إنها طريقة تحديد الـ request لما تبدو عليه الصفحة الحقيقية: قبول علامة تظهر فقط بعد تصيير (render) شبكة القوائم، والفشل عند ظهور نصوص الصفحات البينية (interstitial strings). أي response لا يطابق هذه القواعد لا يُعد صفاً بقيمة سعر فارغة (null). بل هو فشل، ويُصنف على هذا النحو، ولا يُحسب كنجاح. في قطاع السيارات، اكتب العلامة الإيجابية بالإضافة إلى القائمة السلبية، لأن عبارة "اتصل لمعرفة السعر" تحتمل اللبس حقاً، بينما "بطاقة المركبة لم تُصيَّر أبداً" لا تحتمل أي لبس.
عندما لا تعرف بعد المسار الذي تحتاجه منصة معينة، فإن وضع Auto سيكتشف ذلك في استدعاء واحد ويعيد الجلسة التي نجحت. تعامل مع ذلك كعملية استكشاف، وليس كمسار بيئة الإنتاج. بمجرد أن تعرف أن منصة معينة تحتاج إلى متصفح بينما لا تحتاجه المنصات المجاورة لها، ثبّت كل منصة على المحرك المباشر وتوقف عن الدفع لمنسق (orchestrator) لإعادة اكتشاف الإجابة نفسها كل ليلة.
النتائج
قم بإجراء الحسابات على مهمة متوسطة الحجم (سيناريو توضيحي يعتمد على معايير المجال، وليس لعميل محدد): 4000 معرض، يحتوي كل منها على حوالي 180 سيارة مستعملة، ويتم التحديث ليلياً.
- 4000 صفحة مُصيَّرة بدلاً من 720000. تصيير واحد لكل معرض يفتح الجلسة؛ بينما تمر الـ 716000 صفحة الأخرى عبر المسار الاقتصادي على الجلسة نفسها. هذه النسبة، وليس الـ parser، هي ما يحدد ما إذا كانت التغطية الليلية مجدية من حيث التكلفة.
- نوعان من الأسعار المفقودة، في جدولين مختلفين. مع تطبيق قواعد التحقق (validate)، يتوقف "المعرض لا ينشر أي سعر" و"لم نحصل على الصفحة إطلاقاً" عن مشاركة نفس هيكل الصف في قاعدة البيانات. يرى نموذجك النوع الأول فقط.
- محلل واحد (parser) لكل منصة، وليس لكل وكيل. اكتشف المنصة من الـ response، ومرر الـ HTML إلى الـ parser المسؤول عنها. إضافة معرض جديد على منصة مغطاة مسبقاً لا تكلف شيئاً في الإعداد.
- الوكيل الذي يغير منصته يفشل بشكل صريح وواضح. يفشل اكتشاف المنصة، ولا يُكتب الصف أبداً، ويتم إنشاء تذكرة دعم لشخص ما بدلاً من جمع أسعار خاطئة بصمت لمدة ستة أسابيع.
أين تصبح الأمور أقل بساطة: تدير مجموعات الوكلاء الكبيرة مواقع مخصصة خارج المنصات القياسية بشكل متزايد، وتلك المواقع لا تزال بحاجة إلى parsers مبنية يدوياً مع كل أعباء الصيانة الناتجة عن ذلك. كما أن حداثة بيانات المعارض ليست متطابقة. تقوم بعض المنصات بالتخزين المؤقت (caching) لصفحات المخزون بشكل مكثف، لذا قد يكون "سعر اليوم" قديماً بيوم كامل بغض النظر عن معدل التجميع. إذا كان نموذجك يتعامل مع كل طابع زمني للمعرض على أنه محدث ومباشر، فهو خاطئ بطريقة لن تصلحها أي بنية تحتية للتجميع مهما بلغت.
الخلاصة الأساسية
لم يكن الجزء الصعب في بيانات السيارات يكمن في المجمّعات الثلاثة الكبرى التي يقيس الجميع أداءهم مقارنة بها. الصعوبة الحقيقية تكمن في 17 ألف موقع صغير لم يكن أي منها يستحق أداة كشط مخصصة بمفرده، بينما تكتسب معاً قيمة هائلة.
يتكرر هذا النمط في قطاعات تتجاوز السيارات بكثير. الصيدليات، وموزعو المعدات، ومتاجر البقالة الإقليمية، وشبكات الامتياز التجاري بمختلف أنواعها: لا يبدو الذيل الطويل مكلفاً إلا عندما تتعامل مع كل موقع فيه كحالة فريدة. وهو في الغالب ليس كذلك. اضبط الـ request الأول بشكل صحيح، واجعل الـ session قابلة لإعادة الاستخدام، ولن يعود ما تبقى مشكلة جمع بيانات بل يتحول إلى مشكلة parsing، وهي الأقل تكلفة بفارق كبير.