كيف يعمل اكتشاف Puppeteer فعليًا: 12 إشارة تفضح البوتات
أطر الأتمتة ترث بصمة متصفح حقيقي لكنها تغيّرها بعشرات الطرق القابلة للرصد. دليل ميداني لـ12 إشارة على مستوى JavaScript والشبكة والسلوك يستخدمها المدافعون لكشف Puppeteer وPlaywright.
تتحكم أطر الأتمتة في متصفحات حقيقية، ما يعني أنها ترث بصمة المتصفح. فمتصفح Chrome المُقاد بواسطة Puppeteer يملك وكيل المستخدم نفسه، وإصدار Chrome نفسه، وبنية Chromium نفسها التي يملكها متصفح إنسان. ظاهريًا، يبدوان متطابقين.
لكن أطر الأتمتة تغيّر المتصفح بعشرات الطرق الدقيقة — معظمها قابل للرصد من JavaScript. الاكتشاف ليس سحرًا، بل قائمة تحقّق.
1. راية navigator.webdriver
أبسط الإشارات وأشهرها. عندما يعمل Chrome تحت إطار أتمتة، تُرجع navigator.webdriver القيمة true. وهي جزء من مواصفة W3C WebDriver، وينفّذها Chromium افتراضيًا.
يرقّع كل مشروع أتمتة جادّ هذه الراية في غضون ثوانٍ. فإضافة puppeteer-extra-stealth تتجاوز الـgetter. ومستخدمو Playwright يحقنون سكربتات Object.defineProperty على المستندات الجديدة. إخفاء الراية أمر تافه.
لكن كون إخفائها تافهًا هو بحد ذاته إشارة. متصفح Chrome الشرعي لا يحتاج إلى إخفاء أي شيء. فعندما تفحص صفحةٌ Object.getOwnPropertyDescriptor(Navigator.prototype, 'webdriver') ويبدو الواصف مختلفًا عن واصف متصفح أصلي — تُكشف الأتمتة حتى بعد ترقيع الراية.
2. شذوذ كائن chrome
يكشف Chrome الشرعي عن كائن عام window.chrome ذي بنية جوهرية — chrome.runtime وchrome.loadTimes وchrome.csi. أما Chrome بدون واجهة (headless) وإعدادات Puppeteer الأقدم فإما أن تحذف هذا الكائن كليًا أو تكشف نسخة منزوعة منه.
تعيد إضافات التخفّي إنشاء الكائن، لكن إعادة الإنشاء غير مكتملة. فقد توجد chrome.runtime.onConnect دون chrome.runtime.PlatformOs. وقد تُرجع توقيعات الدوال كائنات بدل undefined. كل عدم تطابق هو إشارة إيجابية.
3. تناقضات واجهة Permissions
تُرجع المتصفحات الحقيقية نتائج متماسكة عند الاستعلام عبر واجهة Permissions. فينبغي أن تُرجع navigator.permissions.query({name: 'notifications'}) القيمة 'default' إذا لم يمنح المستخدم الإذن أو يرفضه صراحةً.
يُرجع Chrome بدون واجهة القيمة 'denied' افتراضيًا، لأنه لا توجد واجهة لعرض مطالبة الإذن. ترقّع أطر الأتمتة هذا — لكن غالبًا بصورة خاطئة. ومن الدلائل الشائعة: أن يعيد الاستعلام عن إذن غير مألوف مثل 'clipboard-read' نتيجةً لا تطابق ما يفعله Chrome الحقيقي في الإصدار الحالي.
4. مصفوفات الإضافات وأنواع MIME
تُرجع navigator.plugins في متصفح حقيقي PluginArray تحوي مدخلات مثل عارض PDF وإضافة Chromium PDF. وفي الوضع بدون واجهة، تكون هذه المصفوفة فارغة.
تضيف إضافات التخفّي مدخلات مزيفة، لكن هذه المدخلات كثيرًا ما تحمل خصائص خاطئة — length مفقودة، أو حقول وصف خاطئة، أو كائنات إضافة لا تتصرف كأنها من صنف Plugin عند اختبارها بـinstanceof.
5. عدم تطابق اللغة والمحلّية
ينبغي أن تتطابق navigator.language وnavigator.languages مع ترويسة HTTP المسماة Accept-Language. كما ينبغي أن تتسق مع المنطقة الزمنية المُبلّغ عنها بواسطة Intl.DateTimeFormat().resolvedOptions().timeZone.
فبوت يدّعي أنه في كييف (منطقة Europe/Kyiv الزمنية) لكنه يرسل Accept-Language: en-US,en;q=0.9 ويُبلّغ أن navigator.language === 'en-US' أمرٌ ممكن لكنه غير معتاد إحصائيًا. وعند دمجه مع عنوان IP من مركز بيانات في فرانكفورت، تتضح الصورة.
6. سلاسل مُصيّر WebGL
تُرجع WebGL2RenderingContext.getParameter(WebGLDebugRendererInfo.UNMASKED_RENDERER_WEBGL) بائع بطاقة الرسوميات وطرازها. على جهاز حقيقي، قد تكون:
ANGLE (Intel, Intel(R) UHD Graphics 620 Direct3D11 vs_5_0 ps_5_0)
أما Chrome بدون واجهة العامل داخل حاوية فكثيرًا ما يُرجع:
ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero)), SwiftShader driver)
أي سلسلة رسوميات تحوي SwiftShader أو Subzero هي إشارة بوت قوية.
7. شذوذ الشاشة ونافذة العرض
نافذة المتصفح الحقيقية تملك window.outerWidth وwindow.outerHeight اللذين يحسبان حساب واجهة المتصفح — شريط العنوان، والتبويبات، وشريط الإشارات المرجعية. والفارق بين الأبعاد الخارجية والداخلية يكون عادةً بين 80 و140 بكسل رأسيًا.
المتصفحات بدون واجهة كثيرًا ما تملك outerHeight === innerHeight لأنه لا توجد واجهة لعرضها. ويصلح Puppeteer بوضع headless: false هذا الأمر، لكن كثيرًا من إعدادات الأتمتة لا تزال تعمل بأبعاد متساوية.
8. تعداد الخطوط
يملك المستخدمون الحقيقيون مئات الخطوط المثبّتة، تتفاوت بحسب نظام التشغيل واللغة والتطبيقات المثبّتة. فنظام Windows 11 مع تثبيت Office يملك أكثر من 500 خط. أما حاويات الأتمتة العاملة بنظام Alpine Linux فتملك 30 خطًا.
يكشف تعداد الخطوط عبر document.fonts.check() أو التصيير على لوحة خارج الشاشة هذا الأمر فورًا. فمتصفح يُبلّغ أنه Chrome على Windows 11 لكنه يكشف 30 خطًا فقط هو أتمتة شبه مؤكدة.
9. عشوائية حركة الفأرة
يحرّك المستخدمون الحقيقيون الفأرة باهتزاز. المنحنيات غير خطية. والسرعة متغيّرة. وبين نقرتين، توجد عادةً مئات من أحداث mousemove.
تصطنع أطر الأتمتة الحركة — إما بخطوط مستقيمة نحو الإحداثيات، أو بمنحنيات مبرمجة تفتقر إلى الارتعاشات الدقيقة ليد الإنسان. وحتى محاكاة منحنيات Bézier تميل إلى امتلاك ملامح تسارع ناعمة بصورة مريبة.
تجمع أنظمة اكتشاف البوتات آثار الحركة وتقيّمها مقابل نموذج للحركة البشرية. والآثار ذات السرعة المفرطة الاتساق، أو المنحنيات المفرطة النظافة، أو الأحداث الدقيقة المفقودة يُشار إليها كمشتبهٍ فيها.
10. بصمات التوقيت
تنفّذ أطر الأتمتة JavaScript بخصائص توقيت مختلفة عن المتصفحات المُقادة بمستخدم. فتوقيت performance.now() بين الأحداث، وبخاصة بين pointerdown وpointerup، يتبع توزيعًا مختلفًا.
أزمنة الضغط الحقيقية للنقر تتراوح بين 50 و150 ميلي ثانية بتباين عالٍ. أما زمن ضغط النقر الافتراضي في Puppeteer فقيمة ثابتة — غالبًا 30 أو 100 ميلي ثانية — بلا تباين تقريبًا. فمئة نقرة بأزمنة ضغط متطابقة هي أتمتة قاطعة.
11. عدم تطابق بصمة TLS
هذه الإشارة تتجاوز JavaScript كليًا. فعندما يتصل Puppeteer، يستخدم حزمة TLS الخاصة بـChromium — نفسها التي يستخدمها Chrome الحقيقي. لكن كاشطات البيانات القائمة على undici أو axios التي تتظاهر بأنها Chrome عبر تزييف وكيل المستخدم تستخدم حزمة TLS الخاصة بـNode.js، التي تحمل توقيع Client Hello مختلفًا بوضوح (تجزئة JA4).
فطلبٌ يدّعي User-Agent: Chrome/124.0.6367.60 لكنه يقدّم بصمة TLS خاصة بـNode.js هو تأكيد فوري لعميلٍ ليس متصفحًا.
12. تسرّب بروتوكول CDP
تتواصل Puppeteer وPlaywright مع Chrome عبر Chrome DevTools Protocol. وفي بعض الإعدادات، يترك هذا البروتوكول آثارًا قابلة للاكتشاف — خصائص إضافية على كائن Runtime، أو صيغ Error.stack مُغيّرة، أو مخرجات console.debug محددة لا تظهر إلا عند إرفاق عميل CDP.
فأمر Runtime.enable تحديدًا يغيّر طريقة تصيير آثار المكدس. والبوتات التي لا تُطلق أخطاءً أبدًا تتجنب هذه الإشارة، لكن أي مسار أتمتة يصطدم باستثناء (ومعظمها يفعل) يترك بصمات.
تراكب هذه الإشارات
لا توجد إشارة منفردة دليلاً قاطعًا على الأتمتة. فمستخدم حقيقي بمشغّل رسوميات غير مألوف قد يُرجع سلسلة WebGL غريبة. ومستخدم مهتم بالخصوصية قد يكون عدّل navigator.plugins.
تأتي قوة الاكتشاف الحديث من دمج الإشارات — اثنتا عشرة إيجابية ضعيفة أقوى من إيجابية قوية واحدة، لأن المستخدم الحقيقي نادرًا ما يُطلق أكثر من شذوذين أو ثلاثة في آنٍ واحد.
تسند أنظمة الاكتشاف لكل إشارة وزنًا وتحسب درجة. وفوق العتبة، يُعامَل الزائر كأتمتة. أما العتبة الدقيقة والأوزان فهي أصعب الأجزاء ضبطًا — الصرامة الزائدة تحجب المستخدمين الشرعيين؛ والتساهل الزائد يمرّر البوتات المتطورة.
الإشارات نفسها هي الجزء السهل. فكل إطار أتمتة تقريبًا يسرّب الاثنتي عشرة كلها. أما الهندسة الحقيقية فهي في تقرير أي التوليفات مهمة، وأيها إيجابيات كاذبة، وكيف يُحدَّث النموذج مع تكيّف أطر الأتمتة.