إذا حاولت جمع بيانات عامة من مواقع محمية بأنظمة مكافحة البوت باستخدام مكتبة requests العادية في بايثون، فمن المرجّح أنك رأيت استجابة 403 أو تحدي CAPTCHA خلال أول طلبات قليلة. السبب ليس دائماً عنوان IP الخاص بك — بل بصمة TLS التي يرسلها عميل بايثون في رسالة ClientHello. في هذا الدليل نشرح انتحال بصمة TLS باستخدام curl_cffi خطوة بخطوة: لماذا تكشف أنظمة الحماية عملاء بايثون بسرعة، وكيف يعيد curl_cffi بناء بصمة Chrome الحقيقية، ولماذا تبقى البروكسي السكنية إلزامية حتى مع بصمة TLS مثالية.
ما هو انتحال بصمة TLS باستخدام curl_cffi ولماذا يهمّك
انتحال بصمة TLS (TLS Impersonation) هو عملية جعل عميل HTTP يُصدر رسالة TLS ClientHello مطابقة لِما يُرسله متصفح حقيقي مثل Chrome أو Firefox، بدلاً من بصمة مكتبة بايثون الافتراضية. curl_cffi هي حزمة بايثون تُغلّف مشروع curl-impersonate، وتمنحك واجهة برمجية مشابهة لـ requests لكنها تستخدم BoringSSL المعدّل لإنتاج بصمة TLS شبه مطابقة للمتصفح.
لماذا يهمّ هذا؟ لأن أنظمة مكافحة البوت الحديثة (Cloudflare, Akamai, Datadome, PerimeterX) تقرأ بصمة TLS قبل أن تنظر حتى إلى رؤوس HTTP أو User-Agent. إذا كانت بصمتك تقول "أنا urllib3 2.x على بايثون"، فلن يصل طلبك إلى منطق التطبيق أصلاً.
لماذا تكشف بصمة requests/urllib3 بسرعة: تحليل JA3 و JA4
ما هي بصمة JA3
JA3 هي بصمة (fingerprint) ينتجها مبيعات Salesforce في عام 2017 وتعتمد على تجزئة (hash) سلسلة نصية تُبنى من: إصدار TLS، التشفيرات (ciphers) المدعومة بترتيبها، امتدادات TLS (extensions)، ومنحنياتElliptic المدعومة، وأشكال التوقيع. الخرج الناتج هو تجزئة MD5 بطول 32 حرفاً سداسياً عشرياً.
المشكلة أن ترتيب العناصر داخل كل حقل يهمّ. متصفح Chrome يضع TLS_AES_128_GCM_SHA256 أولاً ثم TLS_CHACHA20_POLY1305_SHA256 ثم TLS_AES_256_GCM_SHA384، بينما urllib3 قد يرتّبها بشكل مختلف أو يُضيف تشفيرات ضعيفة لا يستخدمها Chrome. هذا الاختلاف وحده كافٍ لتمييز العميل.
ما هي بصمة JA4 ولماذا صُمّمت لتكون مستقرة
JA4 هي بصمة أحدث طوّرها FoxIO بهدف أن تكون مستقرة ترتيبياً (order-stable) — أي أنها تُرتّب الحقول أبجدياً قبل التجزئة، بحيث لا يتغيّر الناتج إذا غيّر المتصفح ترتيب التشفيرات بين الإصدارات. هذا يجعل JA4 أكثر فائدة للمحلّلين، لكنه أيضاً يعني أن مجرد إعادة ترتيب التشفيرات لن يخدع نظاماً يعتمد JA4.
ابتداءً من Chrome 110+، بدأ Google بتطبيق تبديل عشوائي (permutation) لترتيب التشفيرات في ClientHello — أي أن نفس إصدار Chrome قد يُرسل ترتيباً مختلفاً في كل اتصال. هذا كسر JA3 عملياً (لأنه حساس للترتيب)، لكنه لا يكسر JA4 (لأنه يُرتّب الحقول أبجدياً). لذلك الأنظمة المتقدمة الآن تجمع JA3 و JA4 معاً، وتقارن التوزيع الإحصائي لِبصمات JA3 الواردة من نطاق IP مع التوزيع المتوقع من Chrome الحقيقي.
الإشارات الكاشفة في ClientHello لـ urllib3
عندما يُرسل requests طلب HTTPS، تظهر عدة إشارات تقول "لست متصفحاً":
- غياب GREASE: Chrome يُضيف قيم GREASE (مثل
0x0a0aفي extension_supported_versions) عشوائياً لتكسر التجزئة على الأجهزة الوسيطة. urllib3 لا يفعل ذلك. - ترتيب التشفيرات: OpenSSL (الذي تستخدمه urllib3) يرتّب التشفيرات حسب أولوية الخادم المُتفاوض عليها، بينما Chrome يرتّبها حسب أولوية العميل.
- غياب extension_application_settings (ALPS): Chrome يُرسل امتداد ALPS الخاص بـ HTTP/2، بينما urllib3 لا يعرفه.
- غياب امتداد encrypted_client_hello (ECH): Chrome 120+ يُرسل ECH حتى لو كان فارغاً، كإشارة على دعمه.
- شكل ClientHello في TLS 1.3: Chrome يُرسل
supported_versionsبقيم GREASE أولاً ثم0x0304(TLS 1.3) ثم0x0303(TLS 1.2 احتياطياً). urllib3 قد يُرسل ترتيباً مختلفاً.
النتيجة: بصمة JA3 لـ urllib3 2.x على بايثون 3.11 هي شيء مثل 3b5074b90b951c0b — قيمة معروفة جيداً في قواعد بيانات أنظمة مكافحة البوت، ومُصنّفة فوراً كـ "أتمتة".
كيف يعيد curl_cffi بناء بصمة Chrome: BoringSSL والإعدادات المسبقة
curl-impersonate و BoringSSL
مشروع curl-impersonate يأخذ كود مصدر curl ويُصحّحه لاستخدام BoringSSL (نسخة OpenSSL التي يستخدمها Chrome) بدلاً من OpenSSL العادي. ثم يُعيد بناء ترتيب التشفيرات، والامتدادات، ومنحنيات Elliptic، وقيم GREASE، وإطارات HTTP/2 SETTINGS لتطابق Chrome إصداراً بإصدار.
curl_cffi هي طبقة بايثون فوق ذلك، تُقدّم واجهة requests-مثل متزامنة وواجهة AsyncSession غير متزامنة، مع دعم impersonate="chrome" الذي يختار الإعداد المسبق المناسب تلقائياً.
الإعدادات المسبقة (presets)
عند تمرير impersonate="chrome"، يختار curl_cffi أحدث إعداد متاح (عادة chrome120 أو chrome131 حسب الإصدار). يمكنك تحديد إصدار دقيق:
from curl_cffi import requests # اختيار إصدار Chrome محدد r = requests.get( "https://example.com", impersonate="chrome131", ) print(r.status_code)الإعدادات المسبقة المتاحة تشمل:
chrome99,chrome100,chrome101,chrome104,chrome107,chrome110,chrome116,chrome119,chrome120,chrome123,chrome124,chrome131,edge99,edge101,safari15_3,safari15_5,safari17_0.تجاوز بصمة JA3 يدوياً
إذا أردت تخصيص بصمة JA3 بدلاً من استخدام إعداد مسبق، يُتيح curl_cffi تمرير
ja3كقاموس JSON يحتوي على الحقول:ssl_version,ciphers,extensions,curves,certificate_compression. هذا مفيد لاستنساخ بصمة متصفح نادر أو تطبيق جوال محدد.from curl_cffi import requests ja3 = { "ssl_version": "TLSVersion.TLSV1_3", "ciphers": [ "TLS_AES_128_GCM_SHA256", "TLS_CHACHA20_POLY1305_SHA256", "TLS_AES_256_GCM_SHA384", ], "extensions": [ "ExtensionType.SERVER_NAME", "ExtensionType.EXTENDED_MASTER_SECRET", "ExtensionType.SUPPORTED_GROUPS", "ExtensionType.SESSION_TICKET", "ExtensionType.APPLICATION_LAYER_PROTOCOL_NEGOTIATION", "ExtensionType.SUPPORTED_VERSIONS", "ExtensionType.PSK_KEY_EXCHANGE_MODES", "ExtensionType.KEY_SHARE", "ExtensionType.SIGNATURE_ALGORITHMS", "ExtensionType.PADDING", ], "curves": ["X25519", "P256", "P384"], } r = requests.get("https://example.com", ja3=ja3)تجاوز بصمة Akamai و extra_fp
بصمة Akamai تجمع بصمة TLS مع بصمة HTTP/2 (إعداد SETTINGS frame وترتيب الرؤوس). يُتيح curl_cffi تمرير
akamaiكقاموس يحتوي علىhttp2_settingsوhttp2_pseudo_header_orderوhttp2_header_order. كما يُتيحextra_fpلتخصيصtls_signature_algorithmsوtls_cert_compressionوغيرها.from curl_cffi import requests akamai = { "http2_settings": { "HEADER_TABLE_SIZE": 65536, "ENABLE_PUSH": 0, "INITIAL_WINDOW_SIZE": 6291456, "MAX_HEADER_LIST_SIZE": 262144, }, "http2_pseudo_header_order": [":method", ":path", ":authority", ":scheme"], "http2_header_order": [ "user-agent", "accept", "accept-encoding", "accept-language", ], } r = requests.get("https://example.com", akamai=akamai, impersonate="chrome131")لماذا تبقى البروكسي السكنية إلزامية: تسجيل سمعة IP
بصمة TLS مثالية تُمرّر الفلتر الأول (TLS fingerprint check)، لكن أنظمة مكافحة البوت لديها طبقة ثانية: تسجيل سمعة عنوان IP (IP reputation scoring). كل عنوان IP يُصنّف حسب مصدره:
نوع IP مصدر سمعة نموذجية احتمال تجاوز الفلتر سكني (Residential) ISP حقيقي، منزل/جوال عالية 85–95% جوال (Mobile) شبكة خلوية (4G/5G) عالية جداً 90–98% مركز بيانات (Datacenter) AWS, OVH, DigitalOcean منخفضة 10–30% عنوان IP من AWS (نطاق
54.x.x.x) أو DigitalOcean (نطاق159.x.x.x) يُعلّم فوراً في قواعد بيانات مثل IP2Location و MaxMind و قوائم Cloudflare الداخلية. حتى لو كانت بصمة TLS مطابقة لـ Chrome 131 بنسبة 100%، فإن طلباً من54.210.x.xسيُرفض أو يُوجّه إلى تحدي JS لأن السمعة تقول "هذا مركز بيانات، والمتصفح لا يعيش في مركز بيانات".هنا تأتي أهمية البروكسي السكنية: عنوان IP من ISP ألماني (مثل Deutsche Telekom نطاق
91.x.x.x) يحمل سمعة "مستخدم منزلي"، ويتجاوز فلتر السمعة بسهولة. يمكنك الاطلاع على مواقع ProxyHat المتاحة لمعرفة الدول والمدن المدعومة.مثال عملي: curl_cffi AsyncSession عبر ProxyHat السكنية
الآن نُجمع كل شيء: جلسة curl_cffi غير متزامنة مع
impersonate="chrome131"، موجهة عبر ProxyHat السكنية في ألمانيا، مع إدارة دوران الجلسات والمحاولات المتكررة.التثبيت
pip install curl_cffi aiohttpالكود الكامل
import asyncio from curl_cffi.requests import AsyncSession PROXYHAT_HOST = "gate.proxyhat.com" PROXYHAT_PORT = 8080 PROXYHAT_USER = "user-country-DE" PROXYHAT_PASS = "your_password_here" PROXY_URL = f"http://{PROXYHAT_USER}:{PROXYHAT_PASS}@{PROXYHAT_HOST}:{PROXYHAT_PORT}" TARGETS = [ "https://httpbin.org/headers", "https://httpbin.org/ip", "https://httpbin.org/user-agent", ] async def fetch(session: AsyncSession, url: str, attempt: int = 0): try: r = await session.get( url, impersonate="chrome131", proxy=PROXY_URL, timeout=30, ) print(f"[{url}] status={r.status_code} attempt={attempt}") return r except Exception as e: if attempt < 3: await asyncio.sleep(2 ** attempt) return await fetch(session, url, attempt + 1) print(f"[{url}] failed after {attempt} retries: {e}") return None async def main(): async with AsyncSession() as session: tasks = [fetch(session, url) for url in TARGETS] results = await asyncio.gather(*tasks) for r in results: if r: print(r.text[:200]) asyncio.run(main())توجيه عبر مدينة محددة وجلسة لاصقة
إذا أردت توجيه الطلبات عبر برلين تحديداً مع جلسة لاصقة (sticky session) للحفاظ على نفس IP عبر طلبات متعددة:
PROXYHAT_USER = "user-country-DE-city-berlin-session-myjob123" PROXY_URL = f"http://{PROXYHAT_USER}:{PROXYHAT_PASS}@{PROXYHAT_HOST}:{PROXYHAT_PORT}"هذا يضمن أن جميع الطلبات في الجلسة
myjob123تخرج من نفس عنوان IP في برلين — مفيد لتسجيل الدخول إلى موقع يحتاج تناسق IP.مقارنة مع ProxyHat SDK للدوران والمحاولات
إذا كنت تُدير آلاف الطلبات، يُفضّل استخدام ProxyHat SDK (إن كان متاحاً للغتك) أو بناء طبقة دوران خاصة. الفكرة: لكل طلب، أنشئ معرّف جلسة فريد (UUID) ليحصل على IP جديد، أو أعِد استخدام نفس معرّف الجلسة لـ N طلب ثم بدّله.
import uuid def get_proxy_url(sticky: bool = False, session_id: str = None): if sticky and session_id: user = f"user-country-DE-session-{session_id}" else: user = f"user-country-DE-session-{uuid.uuid4().hex[:12]}" return f"http://{user}:{PROXYHAT_PASS}@{PROXYHAT_HOST}:{PROXYHAT_PORT}" # دوران لكل طلب (IP مختلف) proxy_a = get_proxy_url() # جلسة لاصقة (نفس IP لـ 10 طلبات) my_session = uuid.uuid4().hex[:12] proxy_b = get_proxy_url(sticky=True, session_id=my_session)للاطلاع على أسعار البروكسي السكنية والخطط المتاحة، زر صفحة أسعار ProxyHat. ولمزيد من حالات الاستخدام في جمع البيانات، اطّلع على صفحة استخدامات جمع البيانات وصفحة تتبّع نتائج البحث.
الأخطاء الشائعة وحالات حدية
1. الاعتماد على impersonate فقط وتجاهل الرؤوس
بصمة TLS تُمرّر الفلتر الأول، لكن إذا أرسلت
User-Agent: python-requests/2.31.0مع بصمة Chrome 131، فأنت تُناقض نفسك. اجعل الرؤوس متسقة مع المتصفح الذي تنتحله:headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", "Accept-Language": "de-DE,de;q=0.9,en;q=0.8", "Accept-Encoding": "gzip, deflate, br", "Sec-Ch-Ua": '"Chromium";v="131", "Not_A Brand";v="24"', "Sec-Ch-Ua-Mobile": "?0", "Sec-Ch-Ua-Platform": '"Windows"', "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "none", "Sec-Fetch-User": "?1", "Upgrade-Insecure-Requests": "1", } r = await session.get(url, impersonate="chrome131", headers=headers, proxy=PROXY_URL)2. نسيان بصمة Canvas و WebGL
curl_cffi يحلّ مشكلة بصمة TLS فقط. إذا كان الموقع يُحمّل JavaScript يفحص بصمة Canvas أو WebGL أو AudioContext أو قائمة الخطوط، فإن curl_cffi لن ينفّذ JS أصلاً. في هذه الحالة، انتقل إلى متصفح حقيقي مقوّى (مثل Playwright مع
playwright-stealthأو undetected-chromedriver) موجه عبر ProxyHat السكنية.3. إرسال طلبات متزامنة كثيفة من نفس IP
حتى مع بصمة TLS مثالية وIP سكني، إرسال 50 طلباً في الثانية من نفس IP يُفعّل فلر معدل الطلبات (rate limiting). وزّع الطلبات: 1–3 طلبات في الثانية لكل IP، واستخدم دوران الجلسات لتبديل IP كل 20–50 طلباً.
4. استخدام إعداد مسبق قديم
إذا استخدمت
impersonate="chrome99"في عام 2026، فأنت تُرسل بصمة متصفح عمره 7 سنوات — وهذا في حد ذاته مُريب. استخدم أحدث إعداد متاح (chrome131أو أحدث).الحدود والأخلاقيات: ما لا يستطيع curl_cffi فعله
curl_cffi لا يحلّ تحديات JavaScript. إذا واجهتك صفحة Cloudflare Turnstile أو Datadome CAPTCHA، فإن curl_cffi سيستقبل HTML التحدي ولن يستطيع تجاوزه. في هذه الحالة، الخيار الواقعي هو متصفح حقيقي مقوّى (Playwright + stealth) أو خدمة حلّ CAPTCHA متخصصة، مع ProxyHat السكنية كطبقة شبكة.
الاستخدام المصرّح به فقط. انتحال بصمة TLS أداة محايدة تقنياً — تُستخدم لأغراض مشروعة مثل:
- أبحاث أمنية مصرّح بها (authorized pentesting).
- جمع بيانات عامة لا تحميها شروط استخدام صريحة.
- مراقبة أسعار المنافسين في نطاق القانون.
- اختبار أداء واجهات برمجية خاصة بك.
تنبيه قانوني: في الولايات المتحدة، قد يُعرّف القانون CFAA (18 U.S.C. § 1030) تجاوز ضوابط تقنية بأنه "وصول غير مصرّح" حتى لو كانت البيانات عامة. وفي الاتحاد الأوروبي، ينظّم GDPR جمع البيانات الشخصية. راجع شروط استخدام الموقع المستهدف، واحترم robots.txt، واستشر مستشاراً قانونياً عند الشك. ProxyHat لا تشجّع استخدام أدوات انتحال البصمة لانتهاك شروط الخدمة أو جمع بيانات شخصية دون أساس قانوني.
للاطلاع على التوثيق التقني الكامل لـ ProxyHat، زر docs.proxyhat.com.
النقاط الرئيسية
الخلاصة العملية:
- بصمة TLS هي الفلتر الأول الذي يواجهه طلبك — قبل الرؤوس وقبل IP.
- urllib3/requests يُنتج JA3 معروفاً ومُصنّفاً كأتمتة في قواعد بيانات أنظمة الحماية.
- curl_cffi مع
impersonate="chrome131"يُعيد بناء بصمة Chrome الحقيقية عبر BoringSSL.- JA4 مُصمّم ليكون مستقراً ترتيبياً، لذا مجرد إعادة ترتيب التشفيرات لا يكفي — يجب مطابقة البصمة كاملة.
- بصمة TLS مثالية + IP مركز بيانات = فشل في طبقة السمعة. البروكسي السكنية إلزامية.
- curl_cffi لا يحلّ تحديات JS — استخدم متصفحاً حقيقياً مقوّى هناك.
- وزّع الطلبات: 1–3 طلبات/ثانية لكل IP، وبدّل الجلسة كل 20–50 طلباً.
الأسئلة الشائعة
ما هو انتحال بصمة TLS باستخدام curl_cffi؟
انتحال بصمة TLS باستخدام curl_cffi هو استخدام مكتبة بايثون curl_cffi (المبنية على curl-impersonate و BoringSSL) لإرسال رسالة TLS ClientHello مطابقة لِما يُرسله متصفح حقيقي مثل Chrome. بدلاً من بصمة urllib3 الافتراضية التي تكشفها أنظمة مكافحة البوت فوراً، يُنتج curl_cffi بصمة JA3/JA4 شبه مطابقة للمتصفح، مما يسمح للطلب بالمرور عبر فلتر TLS الأول.
لماذا يهمّ انتحال بصمة TLS لمستخدمي البروكسي؟
لأن أنظمة مكافحة البوت تتحقق من بصمة TLS قبل التحقق من عنوان IP أو الرؤوس. حتى لو استخدمت بروكسي سكنية ممتازة، فإن طلباً بصمة urllib3 سيُرفض لأنه يُكشف كأتمتة. انتحال بصمة Chrome يجعل الطلب يبدو كأنه من متصفح حقيقي، ويُكمل دور البروكسي السكنية بتوفير سمعة IP عالية. الاثنان معاً يرفعان احتمال التجاوز من 10–30% إلى 85–95%.
أي نوع من البروكسي يعمل أفضل مع انتحال بصمة TLS في curl_cffi؟
البروكسي السكنية (Residential) هي الخيار الأمثل لأنها تُقدّم عناوين IP من ISP حقيقية بسمعة عالية. البروكسي الجوالية (Mobile) ممتازة أيضاً وتحمل سمعة أعلى. البروكسي المركزية (Datacenter) غير مناسبة لأن عناوينها مُعلّمة في قوائم أنظمة الحماية بسمعة منخفضة، وستفشل حتى مع بصمة TLS مثالية. يُفضّل توجيه الطلبات عبر ProxyHat السكنية على gate.proxyhat.com:8080 مع تحديد الدولة المناسبة.
كيف تتجنّب الحظر عند تنفيذ انتحال بصمة TLS باستخدام curl_cffi؟
اجعل الرؤوس متسقة مع المتصفح الذي تنتحله (User-Agent, Sec-Ch-Ua, Accept-Language)، واستخدم أحدث إعداد مسبق (مثل chrome131)، ووزّع الطلبات بحدود 1–3 طلبات في الثانية لكل IP، وبدّل الجلسة كل 20–50 طلباً للحصول على IP جديد. استخدم البروكسي السكنية لسمعة IP، وتجنّب الإفراط في التزامن. إذا واجهت تحدي JS، انتقل إلى متصفح حقيقي مقوّى لأن curl_cffi لا ينفّذ JavaScript.
هل curl_cffi كافٍ لتجاوز Cloudflare و Datadome؟
يعتمد على مستوى الحماية. للمواقع التي تعتمد على فلتر TLS الأساسي وسمعة IP، نعم — curl_cffi مع ProxyHat السكنية يكفي غالباً. لكن للمواقع التي تُحمّل JavaScript لفحص بصمة Canvas أو WebGL أو Turnstile CAPTCHA، لا يكفي curl_cffi لأنه لا ينفّذ JS. في هذه الحالة استخدم Playwright مع إضافات stealth موجه عبر ProxyHat السكنية، أو اقبل أن بعض المواقع ستتطلب طبقة متصفح كاملة.






