إذا كنت تكتب سكربتات بلغة Python لجمع البيانات العامة، فمن المرجح أنك صدمت بحظر مفاجئ رغم أن كودك نظيف ولا يرسل أي طلب مشبوه ظاهريًا. المشكلة نادرًا ما تكون في منطق تطبيقك؛ بل في الطريقة التي يصافح بها عميلك خادم الوجهة على طبقة النقل. أنظمة مكافحة البوت الحديثة تقرأ بصمة ClientHello قبل أن ترسل بايت واحد من HTTP، وهنا يأتي دور انتحال بصمة TLS باستخدام curl_cffi كحل عملي يجعل عميلك يبدو كمتصفح Chrome حقيقي بدل مكتبة Python.
ما هو انتحال بصمة TLS باستخدام curl_cffi؟
انتحال بصمة TLS (TLS Impersonation) هو إعادة إنتاج بصمة المصافحة التي يرسلها متصفح حقيقي — ترتيب الشفرات (cipher order)، الامتدادات، منحنيات الإهليلجي، قيم GREASE، وشكل ClientHello الخاص بـ TLS 1.3 — بحيث لا يستطيع خادم الوجهة التمييز بين عميلك ومتصفح Chrome أو Firefox. curl_cffi هو ربط بلغة Python لمشروع curl-impersonate الذي يستبدل OpenSSL بـ BoringSSL ويعيد بناء libcurl لإنتاج بصمة مطابقة للمتصفح، مع واجهة API شبيهة بـ requests ودعم غير متزامن عبر AsyncSession.
الفكرة المركزية: أنظمة مثل Akamai Bot Manager و Cloudflare و Datadome لا تكتفي بفحص User-Agent. بل تحسب JA3 وJA4 وبصمة HTTP/2 (Akamai fingerprint) وبصمة السلوك، وتطابقها مع قاعدة بيانات بصرمات المتصفحات المعروفة. أي تباين — ولو في ترتيب امتداد واحد — يرفع درجة المخاطرة. curl_cffi يحل هذه المشكلة عبر إعدادات مسبقة مثل impersonate="chrome" و impersonate="chrome110" التي تغلف كل هذه التفاصيل.
لماذا تكشف بصمة Python requests/urllib3؟ السياق التقني
عندما يفتح requests أو urllib3 اتصال TLS، يستخدم في الغالب OpenSSL أو NSS عبر ssl module. ClientHello الناتج يحمل بصمة ثابتة تختلف جذريًا عن متصفح Chrome. الأسباب تتلخص في خمسة محاور:
1. ترتيب الشفرات (Cipher Suite Order)
OpenSSL يرتب الشفرات حسب أولوية المكتبة، بينما Chrome يستخدم ترتيبًا محددًا بدقة يعكس تفضيلات BoringSSL. مثلاً، يضع TLS_AES_128_GCM_SHA256 قبل TLS_AES_256_GCM_SHA384 في TLS 1.3، ويرتب شفرات ECDHE بطريقة معينة. أي اختلاف في الترتيب يغير JA3 تمامًا لأن JA3 يعتمد على ترتيب الـ bytes.
2. الامتدادات (Extensions)
قائمة امتدادات TLS وترتيبها تكشف الهوية. OpenSSL يرسل عادةً امتدادات مثل server_name، ec_point_formats، signature_algorithms بترتيب مختلف عن Chrome. Chrome يضيف امتدادات مثل signed_certificate_timestamp، application_layer_protocol_negotiation (ALPN مع h2 و http/1.1)، key_share، و encrypted_client_hello (ECH) بترتيب ثابت. غياب أي منها أو إضافة امتداد غير متوقع يرفع علمًا أحمر.
3. المنحنيات الإهليلجية (Supported Curves)
Chrome يعلن عن منحنيات مثل x25519، secp256r1، secp384r1 بترتيب محدد. OpenSSL قد يضيف secp521r1 أو منحنيات أخرى، أو يرتبها بشكل مختلف. حسب RFC 8446، ترتيب منحنيات supported_groups يؤثر على التفاوض، ويؤثر أيضًا على البصمة.
4. قيم GREASE
GREASE (RFC 8701) هي قيم وهمية تُدرجها المتصفحات لمنع تعطل الوسطاء. Chrome يدرج قيم GREASE في أماكن محددة (cipher suites، extensions، supported groups) بنمط معروف. OpenSSL لا يفعل ذلك افتراضيًا، وغياب GREASE بنفس الترتيب والقيم يكشف أن العميل ليس متصفحًا. RFC 8701 يحدد آلية GREASE رسميًا.
5. شكل ClientHello الخاص بـ TLS 1.3
Chrome 110+ أضاف تغييرًا جوهريًا: ترتيب امتدادات key_share و pre_shared_key و session_ticket أصبح يتبع نمطًا تبادليًا (permutation) يعتمد على جلسة سابقة، مما يجعل JA3 غير مستقر لكل طلب. هذا هو السبب الذي دفع فريق FoxIO إلى تصميم JA4 ليكون مستقرًا في الترتيب (order-stable) — يفرز الشفرات والامتدادات أبجديًا قبل الهاش، بحيث لا يتغير الهاش بسبب التبديل العشوائي الذي يقدمه Chrome.
الخلاصة: JA3 يعاقبك على الترتيب، بينما JA4 يفرز ثم يهاش. كلاهما يكشف Python requests، لكن JA4 أقوى لأنه يتجاهل التبديل الذي يقدمه Chrome نفسه.
كيف يعمل curl_cffi: BoringSSL وإعادة بناء libcurl
الحل الذي يقدمه curl_cffi ليس سحرًا، بل هندسة دقيقة. المشروع يبني libcurl مدمجًا مع BoringSSL (مكتبة Google التي يستخدمها Chrome) بدل OpenSSL، ثم يعدل شفرة المصدر ليرسل ClientHello مطابقًا للمتصفح. النتيجة: بصمة JA3/JA4 وبصمة HTTP/2 تتطابق مع Chrome حتى مستوى بايت.
الإعدادات المسبقة (impersonate presets)
أبسط طريقة للاستخدام هي تمرير impersonate:
from curl_cffi import requests
r = requests.get(
"https://nowsecure.nl",
impersonate="chrome110",
proxies={"https": "http://user-country-DE:pass@gate.proxyhat.com:8080"},
)
print(r.status_code)
الإعدادات المسبقة المتاحة تشمل chrome99، chrome100، chrome101، chrome104، chrome107، chrome110، chrome116، chrome119، chrome120، chrome124، chrome131، safari15_3، safari15_5، safari17_0، edge99، edge101. كل إعداد يحدد بصمة TLS وبصمة HTTP/2 معًا.
تجاوز JA3 و Akamai و extra_fp
للحالات المتقدمة، يسمح curl_cffi بتمرير بصمة مخصصة:
from curl_cffi import requests
r = requests.get(
"https://example.com",
impersonate="chrome120",
ja3="771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-10-11,29-23-24,0",
akamai="1:65536;2:0;3:10000;4:6291456;6:262144|15663105|0|m,a,s,p",
extra_fp={"tls_cert_compression": "brotli"},
)
هذه المعاملات تتيح ضبط JA3 يدويًا، وتعديل إطار HTTP/2 SETTINGS (بصمة Akamai)، وإضافة حقول إضافية. لكن احذر: تخصيص مفرط قد يخلق بصمة فريدة لا تطابق أي متصفح معروف، وهو أسوأ من استخدام الإعداد المسبق مباشرة.
تبديل ClientHello في Chrome 110+ ولماذا صُمم JA4 ليكون مستقرًا
ابتداءً من Chrome 110، بدأ المتصفح بتبديل ترتيب امتدادات معينة في ClientHello بشكل شبه عشوائي لكل جلسة. الهدف المعلن: جعل JA3 غير موثوق كأداة تعريف، وبالتالي إجبار أنظمة الكشف على عدم الاعتماد عليه. التأثير العملي: متصفح Chrome حقيقي ينتج JA3 مختلفًا عبر الطلبات، بينما عميل Python ثابت ينتج JA3 واحدًا — وهذا التناقض نفسه يصبح إشارة كشف.
JA4 صُمم ليعالج هذه المشكلة. بدل الهاش المباشر للترتيب الأصلي، يقوم JA4 بـ:
- فرز قيم cipher suites أبجديًا قبل الهاش.
- فرز امتدادات TLS أبجديًا.
- فصل حقلين: JA4 (الأساسي) و JA4_r (الخام الذي يحفظ الترتيب الأصلي).
النتيجة: بصمة مستقرة عبر الطلبات للمتصفح نفسه، وأكثر صعوبة في الانتحال لأنها تلتقط الخصائص الهيكلية لا الترتيب العشوائي. curl_cffi يدعم JA4 عبر إعداداته المسبقة لأنه ينتج ClientHello مطابق للمتصفح، وبالتالي JA4 المستقر يطابق Chrome.
لماذا تبقى البروكسي السكنية إلزامية؟
بصمة TLS مثالية لا تكفي. أنظمة مكافحة البوت تجمع بين عدة طبقات: بصمة TLS، سمعة IP، بصمة HTTP/2، السلوك، و CAPTCHA. حتى لو قدمت ClientHello مطابقًا لـ Chrome 131، فإن طلبك من IP مركز بيانات معروف (ASN مستضاف) يرفع درجة المخاطرة فورًا. أنظمة مثل Cloudflare تحتفظ بقوائم ASN لمزودي الاستضافة، وتعامل أي طلب منها باعتباره آليًا افتراضيًا حتى يثبت العكس.
هنا تظهر قيمة البروكسي السكنية: عنوان IP مسجل لدى مزود إنترنت منزلي (ISP) يحمل سمعة عالية لأنه يبدو كمستخدم حقيقي. الجمع بين بصمة TLS متصفح + IP سكني هو المعادلة الناجحة. البروكسي السكنية من ProxyHat توفر مخرجات في أكثر من 195 دولة، مع إمكانية استهداف المدينة، وجلسات لاصقة (sticky sessions) للحفاظ على نفس IP عبر عدة طلبات.
| الطبقة | بدون انتحال TLS | مع curl_cffi فقط | curl_cffi + ProxyHat سكني |
|---|---|---|---|
| بصمة JA3/JA4 | تكشف Python | مطابقة لـ Chrome | مطابقة لـ Chrome |
| سمعة IP | ضعيفة (مركز بيانات) | ضعيفة (مركز بيانات) | عالية (ISP سكني) |
| بصمة HTTP/2 | غير متصفح | مطابقة | مطابقة |
| معدل النجاح التقديري | أقل من 30% | 50-70% | 90%+ |
مثال عملي: AsyncSession مع curl_cffi عبر ProxyHat
إليك مثالًا كاملًا قابلًا للتشغيل يستخدم AsyncSession من curl_cffi مع انتحال Chrome، موجهًا عبر مخرجات ProxyHat السكنية في ألمانيا. الكود يتضمن إعادة المحاولة والتدوير:
import asyncio
from curl_cffi import AsyncSession
gateway = "http://user-country-DE:pass@gate.proxyhat.com:8080"
async def fetch(session, url, session_id):
proxy = f"http://user-country-DE-session-{session_id}:pass@gate.proxyhat.com:8080"
for attempt in range(3):
try:
r = await session.get(
url,
impersonate="chrome131",
proxies={"http": proxy, "https": proxy},
timeout=20,
)
if r.status_code == 200:
return r.text
elif r.status_code in (403, 429):
await asyncio.sleep(2 ** attempt)
continue
except Exception as e:
await asyncio.sleep(2 ** attempt)
return None
async def main():
urls = ["https://example.com/page1", "https://example.com/page2"]
async with AsyncSession() as session:
tasks = [fetch(session, u, f"s{i}") for i, u in enumerate(urls)]
results = await asyncio.gather(*tasks)
for r in results:
print(len(r) if r else "failed")
asyncio.run(main())
لاحظ استخدام session-{id} في اسم المستخدم: هذا يطلب جلسة لاصقة من ProxyHat تحافظ على نفس IP للجلسة. لتدوير لكل طلب، احذف علم الجلسة أو غيّره لكل طلب. راجع وثائق ProxyHat لتفاصيل علم الجلسة والاستهداف الجغرافي.
مقارنة مع SDK الرسمي للتدوير وإعادة المحاولة
بدل إدارة التدوير يدويًا، يمكنك الاعتماد على نمط اسم المستخدم في ProxyHat للتدوير التلقائي:
# تدوير لكل طلب: غيّر اسم الجلسة عشوائيًا
import secrets
proxy = f"http://user-country-DE-session-{secrets.token_hex(4)}:pass@gate.proxyhat.com:8080"
هذا النهج يوزع الطلبات عبر مئات آلاف IPs السكنية، مما يقلل خطر التقييد. للتكلفة والخطط، راجع صفحة التسعير. للاطلاع على الدول المتاحة، زر صفحة المواقع.
الأخطاء الشائعة وحالات الحافة
1. نسيان User-Agent المطابق
curl_cffi ينتحل بصمة TLS، لكنه لا يضبط User-Agent تلقائيًا. إذا أرسلت بصمة Chrome 131 مع User-Agent يقول python-requests/2.31، فالنتيجة تناقض صارخ يكشف التزييف. أضف دائمًا:
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"}
2. عدم تطابق إصدار المتصفح مع بصمة HTTP/2
الإعداد المسبق impersonate="chrome120" ينتج بصمة TLS وبصمة HTTP/2 لـ Chrome 120. إذا ضبطت User-Agent على Chrome 131، يحدث تباين. حافظ على اتساق الإصدارات عبر كل البصمات.
3. الاعتماد على curl_cffi لتحديات JS
curl_cffi لا ينفذ JavaScript. إذا كان الموقع يقدم تحدي Cloudflare Turnstile أو Datadome captcha، فلن يحلها curl_cffi. في هذه الحالة، استخدم متصفحًا حقيقيًا (Playwright مع إضافة stealth) أو خدمة حل CAPTCHA متخصصة. لمزيد حول سيناريوهات السكربت، راجع صفحة استخدامات السكربت.
4. تجاهل بصمة السلوك
بصمة TLS مثالية + IP سكني + User-Agent صحيح لا يكفي إذا كان سلوكك آليًا: 100 طلب في الثانية من نفس IP، أو نمط تنقل غير منطقي. اضبط معدل الطلبات (rate limiting) وأضف تأخيرات عشوائية بين 1-3 ثوانٍ، ووزع الطلبات عبر جلسات متعددة.
5. استخدام SOCKS5 للسرعة
إذا كنت تتعامل مع بروتوكولات غير HTTP أو تريد أداءً أعلى قليلًا، يدعم ProxyHat SOCKS5 على المنفذ 1080:
proxy = "socks5://user-country-DE:pass@gate.proxyhat.com:1080"
الحدود والأخلاقيات
curl_cffi أداة قوية لكنها ليست عصا سحرية. حدودها:
- لا يحل تحديات JS/CAPTCHA: استخدم متصفحًا حقيقيًا لهذه الحالات.
- لا يخفي بصمة Canvas/WebGL: هذه بصمات متصفح، لا تظهر في curl_cffi لأنه لا يوجد DOM.
- قد يتخلف عن إصدارات Chrome: تابع تحديثات المشروع لمواكبة بصمات Chrome الجديدة.
من الناحية الأخلاقية والقانونية: استخدم هذه الأدوات للوصول إلى البيانات العامة المصرح بها فقط. احترم robots.txt وشروط الخدمة للمواقع. في الولايات المتحدة، قد ينطبق Computer Fraud and Abuse Act (CFAA) على الوصول غير المصرح به. في الاتحاد الأوروبي، يفرض GDPR قيودًا على معالجة البيانات الشخصية. السكربت على بيانات عامة لا يتطلب تسجيل دخول هو الاستخدام المقبول؛ تجاوز أنظمة الحماية للوصول إلى محتوى محمي قد يكون غير قانوني. راجع صفحة تتبع نتائج البحث لحالات استخدام مشروعة.
القاعدة الذهبية: إذا كانت البيانات متاحة公开ًا دون تسجيل دخول، والسكربت يحترم معدل الطلبات ولا يضر بالبنية التحتية، فالاستخدام غالبًا مشروع. استشر خبيرًا قانونيًا للحالات الغامضة.
النقاط الرئيسية
- بصمة TLS هي الخط الأول للكشف: Python requests/urllib3 يكشف عبر JA3/JA4 قبل أي HTTP.
- curl_cffi يحل بصمة TLS و HTTP/2 عبر BoringSSL والإعدادات المسبقة مثل
impersonate="chrome131". - البروكسي السكنية إلزامية: بصمة متصفح مثالية على IP مركز بيانات لا تمر عبر سمعة IP.
- اتساق البصمات: طابق إصدار Chrome في TLS مع User-Agent وبصمة HTTP/2.
- curl_cffi لا يحل CAPTCHA/JS: استخدم متصفحًا حقيقيًا لتلك الحالات.
- الأخلاقيات: بيانات عامة فقط، احترم CFAA و GDPR و شروط الخدمة.
ابدأ بدمج curl_cffi مع ProxyHat اليوم: سجل في صفحة التسعير، احصل على بيانات اعتمادك، ووجّه طلباتك عبر gate.proxyhat.com:8080 مع انتحال Chrome للحصول على معدل نجاح يتجاوز 90% على المواقع المحمية. لمزيد من المواقع المتاحة، راجع صفحة المواقع.






