تفشل معظم الـ scrapers قبل قراءة header واحد حتى.
ينظر الخادم إلى مصافحة TLS (ترتيب cipher suite، وترتيب الـ extensions، وتفضيلات المنحنيات) ويقرر ما إذا كنت متصفحاً أم مكتبة عميل (client library) تتظاهر بأنها متصفح. مكتبة requests في Python، و net/http في Go، و curl العادي: كلها تقدم بصمة مميزة بمجرد بدء الاتصال. المواقع التي تهتم بهذه التفاصيل (مثل Datadome و Akamai و Imperva والجانب المدار من Cloudflare) تقطع الاتصال أو تعرض لك صفحة تحدٍ (challenge page) قبل أن يكون لسلسلة User-Agent أي أهمية.
هذا هو بالضبط ما تحله علامة unblocker: true على FourA. في الشهر الماضي، حددنا الأجزاء التي تجعلها تعمل بشكل موثوق.
ما الجديد
تعد unblocker: true علامة واحدة في أي استدعاء لـ /api/single. عند تفعيلها، نقوم بثلاثة أشياء: حقن مجموعة headers المتصفح، وإرسال الـ request عبر curl-impersonate ببصمة TLS لمتصفح حقيقي، وفك ضغط أي شيء يعيده الخادم (gzip و brotli و deflate). كان الميزتان الأوليان متاحتين منذ المرحلة التجريبية (beta). أما الميزة الثالثة (فك الضغط التلقائي لـ brotli) فقد تم إطلاقها في 25 مارس، ووصل عمل تثبيت إصدار المتصفح في اليوم التالي للحفاظ على تزامن الـ headers والـ TLS تماماً.
كيف يعمل
إليك كيف يبدو الـ request:
curl -X POST "https://api.foura.ai/api/single" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"url": "https://example.com/products",
"method": "GET",
"unblocker": true
}'
تعمل ثلاث طبقات في الخلفية.
حقن الـ headers. نقوم بتعيين حزمة headers المتصفح الكاملة: User-Agent، و Sec-Ch-Ua، و Sec-Ch-Ua-Platform، و Sec-Fetch-Site، و Sec-Fetch-Mode، و Sec-Fetch-Dest، و Accept، و Accept-Language، و Accept-Encoding. الترتيب مهم هنا، حيث ترسل المتصفحات الحقيقية هذه القيم بتسلسل معين، وتتحقق مكتبات الكشف من ذلك.
بصمة TLS. يقوم curl-impersonate 0.8.2 بتجميع libcurl مع BoringSSL وإعادة ترتيب TLS extensions لتطابق ما يرسله إصدار المتصفح المستهدف بالفعل عبر الشبكة. تظهر هاشات JA3 و JA4 الخاصة بك مطابقة تماماً لجلسة متصفح حقيقية. في المقابل، ينتج curl القياسي، ومكتبة requests في Python، و net/http في Go بصمات يتم تصنيفها تلقائياً كأدوات برمجية خلال أجزاء من الثانية على البنى التحتية المحمية.
فك الضغط التلقائي. عند تفعيل unblocker، نقوم بتعيين Accept-Encoding إلى gzip, deflate, br وندع libcurl يتولى فك حزمة الـ body. ستحصل على سلسلة نصية مفكوكة الترميز (أو Buffer إذا قمت بتمرير returnBuffer: true). لا حاجة للتعامل اليدوي مع brotli، ولا وجود لعدم التطابق بين الـ headers والـ body عندما يختار الموقع deflate بدلاً من gzip.
لماذا يهم تثبيت الإصدار (Version Pinning)
بصمات TLS مقفلة بإصدارات معينة. ترتيب الـ ciphers للمتصفح هذا الشهر ليس هو نفسه الشهر الماضي، والموقع الذي يفحص البصمات بدقة سيلاحظ هذا الانحراف. يشحن curl-impersonate ملفات تعريف (profiles) لإصدارات متصفح معينة، ونحن نثبت ملف المتصفح الثنائي (browser binary) الحقيقي لدينا على أي إصدار يستهدفه curl-impersonate حالياً. وبالتالي، تبلغ الـ headers وكائنات navigator والـ TLS جميعها عن نفس الإصدار.
إذا كان هذا يبدو معقداً ودقيقاً، فهو كذلك بالفعل. لقد واجهنا مشكلة بسبب عدم التطابق أثناء عملية ترحيل المستودع الموحد (monorepo migration) في مارس عندما تم تحديث المتصفح تلقائياً وانحرفت الـ headers لتصبح غير متزامنة مع curl-impersonate. تمثّل الحل في التزامين (two commits): تثبيت ملف المتصفح الثنائي، وعدم الثقة مطلقاً بمدير الحزم للحفاظ على محاذاتهما نيابة عنك.
الأثر
في الاختبارات الداخلية ضد الأهداف التي تفحص البصمات بكثافة (التمويل، السفر، التجارة الإلكترونية المحمية)، فإن الفرق بين unblocker: false و unblocker: true هو الفرق بين صفحة التحدي واستجابة برمز الحالة 200. إن استخدام curl العادي للوصول إلى Cloudflare المدار يؤدي إلى خطأ 403 من المحاولة الأولى. أما نفس الـ URL مع تفعيل unblocker: true فيمر بنجاح لأن رسالة TLS hello تبدو تماماً كمصافحة متصفح حقيقي.
ولكن بالنسبة للمواقع التي لا تفحص البصمات (معظم الـ APIs العامة، قوالب CMS القديمة، أي شيء يقتصر حظره على الـ IP rate limits فقط)، فإن ترك unblocker معطلاً أمر جيد ويوفر بضع أجزاء من الثانية من وقت تفاوض TLS. استخدمه فقط عند الحاجة إليه.
للمستخدمين المتقدمين
بعض الأنماط التي تستحق المعرفة.
قم بربط unblocker مع proxy سكني (residential proxy) عندما يتحقق الهدف أيضاً من سمعة عنوان الـ IP. عناوين IP الخاصة بمراكز البيانات (Datacenter IPs) مع مصافحة TLS مثالية ستظل تثير الشبهات على شبكات الـ ASNs التي أدرجها الموقع في القائمة السوداء. يقوم الـ proxy endpoint الخاص بنا (/api/proxy) بالتدوير حسب النطاق المستهدف، لذا فإن إضافة "proxy": "residential" إلى الـ request تكون كافية عادةً.
تجاوز استخدام unblocker عند استدعاء JSON APIs التي لا تهتم بالمتصفحات. يمكن أن تبدو الـ headers الإضافية مريبة لـ API يتوقع عميلاً برمجياً، على سبيل المثال عندما يستدعي backend خدمته المصغرة (microservice) الخاصة به.
إذا كان الموقع يشغل نظام حماية من البوتات يعتمد على JavaScript (مثل تحديات Turnstile التفاعلية، أو Perimeter X في أقصى درجات صرامته، أو Akamai Bot Manager مع تفعيل الكشف السلوكي المتقدم)، فلن يكون unblocker بمفرده كافياً. ستحتاج إلى browser endpoint، الذي يشغل Chromium حقيقياً ويمكنه تنفيذ التحدي. هذا منتج مختلف بتسعير رصيد مختلف، وقد شرحناه بالتفصيل في Browser Tasks: كيفية كشط المواقع التي تعتمد بكثافة على JavaScript.
ويمكنك دمج unblocker مع كتلة validate لرفض الاستجابات التي تعيد تقنياً رمز الحالة 200 ولكنها تحتوي على صفحة تحدٍ:
{
"url": "https://example.com/products",
"method": "GET",
"unblocker": true,
"validate": {
"data": { "fail": ["captcha", "Access Denied"] }
}
}
يحول هذا الفشل الصامت إلى فشل مصنف، وهو أمر مهم لتتبع معدل النجاح الخاص بك في لوحة التحكم.
ما الخطوة التالية
تطلق المتصفحات إصداراً مستقراً جديداً كل أربعة أسابيع. وعادةً ما يواكب مطور curl-impersonate ذلك بعد شهر، ونقوم بتحديث بنيتنا البرمجية (stack) عندما يفعلون ذلك. لا تحتاج إلى تعديل أي شيء من جانبك: تستمر unblocker: true في الإشارة إلى أي إصدار متصفح قمنا بالتحقق منه بالكامل (end-to-end).
العمل الأصعب ينتظرنا في المستقبل. بدأ فحص بصمات HTTP/3 يظهر بالفعل في أنظمة مكافحة البوتات المدارة، كما أن محاكاة بروتوكول QUIC أكثر تعقيداً من TLS 1.3، وقد بدأت عملية الانتقال من حزم الـ headers الثابتة إلى المحاكاة الديناميكية الحقيقية. انتقلت المواقع المحمية إلى التحقق من ترتيب إطارات HTTP/2 ومتغيرات JA4+، وستضيق الفجوة بين "curl الذي يشبه المتصفح" و"المتصفح الحقيقي" من كلا الطرفين. سنكتب عن هذا الموضوع عندما نقوم بإطلاقه.