Python Requests में TLS फ़िंगरप्रिंटिंग ब्लॉक कैसे ठीक करें

अगर आपके headers बिल्कुल सही हैं और फिर भी साइट आपको ब्लॉक कर रही है, तो यह ब्लॉक आपका पहला header पहुँचने से पहले ही हो चुका था। TLS फ़िंगरप्रिंटिंग आपके HTTP client की पहचान उसके TLS handshake के आकार से करती है, और Python की requests लाइब्रेरी का handshake ऐसा है जो धरती पर कोई भी ब्राउज़र पैदा नहीं करता। इसे किसी user agent string की नक़ल करके ठीक नहीं किया जा सकता। इसे ठीक करने के लिए handshake को ख़ुद एक ब्राउज़र जैसा दिखाना पड़ता है। यह पोस्ट आपको दिखाती है कि अपना फ़िंगरप्रिंट कैसे पढ़ें, इसे कैसे बदलें, और इस समस्या को उन दो समस्याओं से कैसे अलग पहचानें जिनसे यह अक्सर उलझ जाती है।
आपको क्या चाहिए
- Python 3.10 या नया। उदाहरण पहले समस्या दिखाने के लिए requests लाइब्रेरी इस्तेमाल करते हैं, फिर उसे ठीक करने के लिए curl_cffi।
- एक terminal और लगभग दस मिनट। diagnostic स्टेप में दो requests लगती हैं और कोड में कोई बदलाव नहीं करना पड़ता।
- आख़िरी सेक्शन के लिए सिर्फ़ CapSkip चलता हुआ चाहिए, चाहे Local mode में loopback पते पर हो या Server mode में किसी ऐसी मशीन पर जहाँ तक आपके workers पहुँच सकें। दोनों मोड का विवरण यहाँ मिलता है: कनेक्शन सेटिंग्स, इसलिए शुरू करने से पहले एक चुन लें।
TLS फ़िंगरप्रिंटिंग असल में क्या पढ़ती है
हर TLS कनेक्शन एक ClientHello मैसेज से शुरू होता है। यह मैसेज उन TLS वर्ज़न को सूचीबद्ध करता है जिन्हें आप सपोर्ट करते हैं, उन cipher suites को जो आप ऑफ़र करते हैं, उन extensions को जो आप भेजते हैं, उन elliptic curves को जिन्हें आप स्वीकार करते हैं, और वह क्रम जिसमें आप इन सबको रखते हैं। इनमें से कुछ भी गुप्त नहीं है और इनमें से कुछ भी प्रति-request कॉन्फ़िगर करने योग्य नहीं है। यह इस बात से तय होता है कि आपका HTTP client किस TLS लाइब्रेरी के आधार पर बनाया गया था।
उस सूची को hash करें और आपको client software के लिए एक स्थिर पहचानकर्ता मिल जाता है। JA3 उस hash का पहला व्यापक रूप से इस्तेमाल किया गया वर्ज़न था। JA4 मौजूदा वर्ज़न है, और यह hash करने से पहले ClientHello extensions को क्रमबद्ध करता है ताकि एक ही ब्राउज़र हर कनेक्शन पर अलग फ़िंगरप्रिंट न बनाए। Cloudflare दोनों को इस तरह वर्णित करता है: TLS clients की पहचान इस आधार पर करना कि वे कनेक्शन कैसे शुरू करते हैं, और इस मान को firewall rules, analytics और Workers तक उपलब्ध कराता है।
यही वजह है कि यह आपके सेट किए किसी भी header से ज़्यादा मायने रखता है। Python जिस तरह OpenSSL भेजता है, Chrome जिस तरह BoringSSL भेजता है, और Firefox जिस तरह NSS भेजता है, ये सभी अलग-अलग ClientHello मैसेज भेजते हैं। इसलिए Chrome होने का दावा करने वाली लेकिन Python जैसा handshake करने वाली request कोई सूक्ष्म संकेत नहीं है जिसे पकड़ने के लिए चालाक पहचान चाहिए। यह दो ऐसे फ़ील्ड हैं जो आपस में मेल नहीं खाते, और एक bot नियम इन्हें एक ही लाइन में तुलना कर सकता है।
स्टेप 1: कुछ भी बदलने से पहले अपना फ़िंगरप्रिंट पढ़ें
यह अंदाज़ा मत लगाइए कि TLS फ़िंगरप्रिंटिंग आपकी समस्या है या नहीं, क्योंकि आप इसे मापी सकते हैं। एक सार्वजनिक endpoint है जो आपके कनेक्शन पर देखे गए JA3 hash को वापस भेज देता है, और इसके ख़िलाफ़ दो requests की तुलना करने में एक मिनट से भी कम समय लगता है।
# pip install requests
import requests
# The endpoint echoes back the handshake it received from you.
r = requests.get("https://tls.browserleaks.com/json", timeout=30)
seen = r.json()
print(seen["ja3_hash"]) # stable per TLS library, not per user agent
print(seen["ja3_text"]) # the raw cipher and extension listअब headers में ब्राउज़र वाला user agent सेट करके वही कॉल फिर से चलाएँ, और hash को दूसरी बार पढ़ें। यह टस से मस नहीं होगा। एक ही प्रयोग में पूरा सबक़ यही है: user agent एक header में रहता है, फ़िंगरप्रिंट handshake में रहता है, और पहले को बदलने से दूसरे पर कोई असर नहीं पड़ता। अगर आप किसी ब्लॉक से बचने के लिए user agents को रोटेट करते रहे हैं, तो यही वजह है कि यह काम नहीं आया।
स्टेप 2: curl_cffi के साथ असली ब्राउज़र जैसा handshake करें
आप स्टैंडर्ड Python TLS स्टैक से Chrome जैसा ClientHello नहीं बनवा सकते, क्योंकि extension सेट और उसका क्रम सेटिंग्स के रूप में उपलब्ध नहीं होते बल्कि लाइब्रेरी बिल्ड में पहले से तय होते हैं। आप जो कर सकते हैं वह है किसी दूसरी लाइब्रेरी का इस्तेमाल करना। curl_cffi पैकेज curl के ऐसे बिल्ड से जुड़ता है जो विशेष ब्राउज़र handshakes को दोहराता है, और यह requests जैसा दिखने वाला API देता है ताकि आपके बाक़ी कोड में मुश्किल से कोई बदलाव करना पड़े।
# One dependency, no browser and no driver involved. pip install curl_cffi --upgrade
जो आर्गुमेंट यह काम करता है वह है impersonate। यह तय करता है कि handshake किस ब्राउज़र बिल्ड की नक़ल करे, और जब कोई साइट मौजूदा वर्ज़न को लेकर नुक़्ताचीनी करने लगे तो आप कोई ख़ास वर्ज़न पिन कर सकते हैं।
# pip install curl_cffi
import curl_cffi
# Same call as before, but the handshake now matches Chrome.
r = curl_cffi.get("https://tls.browserleaks.com/json", impersonate="chrome")
print(r.json()["ja3_hash"]) # a different hash from the requests run above
# Pin a version when a site rejects the current default.
r = curl_cffi.get("https://example.com/", impersonate="chrome124")
print(r.status_code)दोनों hashes को साथ-साथ पढ़ें। अगर वे अलग हैं, तो impersonation काम कर रहा है, और यही एकमात्र पुष्टि है जिसकी ज़रूरत है। यह प्रोजेक्ट समर्थित targets का दस्तावेज़ीकरण करता है, जिसमें Chrome के साथ-साथ Safari और iOS Safari बिल्ड भी शामिल हैं। हाथ से लिखी JA3 strings भी स्वीकार की जाती हैं, हालाँकि लगभग हर मामले में एक मेंटेन किया गया preset उनसे बेहतर होता है, क्योंकि जब ब्राउज़र अपडेट होते हैं तो presets भी अपडेट हो जाते हैं।
एक बार यह काम करने लगे तो session पर चले जाएँ। आपको connection reuse और एक cookie jar चाहिए, उन्हीं वजहों से जिनके लिए आप इन्हें requests के साथ चाहते थे।
# pip install curl_cffi
import curl_cffi
# One session, one handshake profile, cookies carried across calls.
s = curl_cffi.Session(impersonate="chrome")
s.get("https://example.com/login")
r = s.get("https://example.com/dashboard")
print(r.status_code, len(s.cookies))स्टेप 3: क्लाइंट के बाक़ी हिस्से को एकसमान रखें
साफ़ तौर पर स्क्रिप्टेड request के साथ जुड़ा ब्राउज़र handshake अपने आप में एक तरह की बेमेल स्थिति है, और एक संकेत ठीक करके बाक़ी को ज़ोर से बजने देना ही इसके ग़लत होने का सबसे आम तरीक़ा है। तीन चीज़ों को आपके चुने गए profile से मेल खाना चाहिए।
आपके user agent को उसी ब्राउज़र परिवार का नाम लेना चाहिए और impersonation टारगेट के लगभग उसी वर्ज़न का। Chrome जैसा handshake करते हुए Firefox होने का दावा करना, कुछ भी दावा न करने से भी बुरा है, क्योंकि इससे एक कमज़ोर संकेत एक भरोसेमंद संकेत बन जाता है।
आपके headers का क्रम मायने रखता है, और आपका HTTP/2 व्यवहार भी। ब्राउज़र एक स्थिर header क्रम, HTTP/2 settings frames का एक स्थिर सेट, और एक स्थिर pseudo header क्रम भेजते हैं, और ये सभी उतनी ही साफ़ तरह से फ़िंगरप्रिंट होते हैं जितना TLS लेयर होती है। headers को जिस भी क्रम में आपका dictionary iterate कर दे उसी क्रम में भेजने से आपका अभी किया गया काम बेकार हो जाता है। यह एक मेंटेन किए गए impersonation preset के पक्ष में एक और तर्क है, जो HTTP/2 लेयर को आपके लिए संभाल लेता है, बजाय इसके कि इसे आपके client के डिफ़ॉल्ट पर छोड़ दिया जाए।
आपका IP उस ट्रैफ़िक के हिसाब से सही लगना चाहिए जो आप भेज रहे हैं। मशीन की रफ़्तार से ब्राउज़र जैसी requests भेजने वाला datacentre पता एक अलग संकेत है, और कोई भी handshake इसे ठीक नहीं करता। इन विकल्पों का पूरा विवरण इस पोस्ट में मिलता है: कैप्चा प्रॉक्सी रोटेशन, जो बताती है कि worker pool के भीतर रोटेशन कहाँ फ़िट बैठता है।
संकेतों की रैंकिंग, ताकि आप उन्हें सही क्रम में ठीक करें
| सिग्नल | यह कहाँ पढ़ा जाता है | क्या आप इसे बदल सकते हैं |
|---|---|---|
| TLS ClientHello, जिसे JA3 या JA4 के रूप में hash किया जाता है | किसी भी HTTP डेटा भेजे जाने से पहले | हाँ, TLS लाइब्रेरी बदलकर |
| HTTP/2 settings और pseudo header क्रम | कनेक्शन के पहले frames | हाँ, और एक अच्छा preset यह आपके लिए कर देता है |
| Header के नाम, मान और क्रम | ख़ुद request | हाँ, और इसमें ग़लती होना सबसे आसान है |
| IP प्रतिष्ठा और पते का प्रकार | कनेक्शन का स्रोत | सिर्फ़ यह बदलकर कि आप कहाँ से कनेक्ट करते हैं |
| canvas और WebGL जैसे ब्राउज़र runtime संकेत | एक असली पेज के भीतर, JavaScript चलने के बाद | जब आप ब्राउज़र नहीं चला रहे हों तो लागू नहीं होता |
| Request दर और नेविगेशन पैटर्न | कई requests में | हाँ, रफ़्तार धीमी करके और जिन paths पर आप जाते हैं उन्हें बदलकर |
उस टेबल को आर-पार पढ़ने के बजाय ऊपर से नीचे पढ़ें। TLS फ़िंगरप्रिंटिंग कनेक्शन में सबसे पहले होती है, इसलिए यह किसी defender के लिए फ़ैसला लेने की सबसे सस्ती जगह है, और यही वह पहली जगह है जहाँ आपका समाधान पहुँचना चाहिए।
इनमें से कोई भी चीज़ जो ठीक नहीं करती
एक सही handshake कोई प्रवेश टिकट नहीं है। यह आप पर अविश्वास करने की एक वजह हटा देता है, जिसका मतलब है कि साइट अब आपको दरवाज़े पर ही रोकने के बजाय आपके बाक़ी ट्रैफ़िक का मूल्यांकन करती है। फ़िंगरप्रिंट ठीक करने के बाद भी बहुत सी साइटें आपको चुनौती दिखाती रहेंगी, और यह अपेक्षित नतीजा है, न कि इस बात का संकेत कि काम विफल हो गया।
यही ईमानदार सीमा है, और इसे साफ़ तौर पर कहना ज़रूरी है। CapSkip आपका TLS फ़िंगरप्रिंट नहीं बदलता और यह कोई stealth लेयर नहीं है। यह उस CAPTCHA को हल करता है जो आपको दिखाया गया, जो इस समस्या का वह हिस्सा है जिसके आख़िर में एक token मिलता है। अगर कोई Turnstile widget या चुनौती पेज आपके और response body के बीच खड़ा है, तो सॉल्वर token बनाता है और आपका मौजूदा client उसे सबमिट करता है।
# pip install capskip
from capskip import CapSkip
solver = CapSkip(host="127.0.0.1", port=8080)
# The challenge you were served, solved on your own machine.
result = solver.turnstile(
sitekey="YOUR_SITEKEY",
url="https://example.com/protected",
)
token = result["code"]
agent = result["userAgent"] # send this exact user agent with the tokenवह लौटाया गया user agent सजावट नहीं है। Turnstile token को उस ब्राउज़र पहचान से बाँध देता है जिसने उसे बनाया, इसलिए किसी अलग user agent के तहत एक बिल्कुल सही token सबमिट करना उसे अस्वीकार करवाने का पक्का तरीक़ा है। दोनों को साथ भेजें तो सबमिशन टिकता है। Cloudflare Turnstile सॉल्वर पेज widget वाले मामले और चुनौती पेज वाले मामले को अलग-अलग कवर करता है, क्योंकि इन दोनों को अलग-अलग इनपुट चाहिए।
सॉल्वर को उसकी जगह सर्वर पर चलाना
फ़िंगरप्रिंट का काम आमतौर पर एक स्क्रिप्ट के बजाय worker pool में दिखता है, और एक pool कोई loopback पता साझा नहीं करता। कनेक्शन सेटिंग्स दोनों रूपों को कवर करती हैं:
| मोड | किस पर सुनता है | कब इस्तेमाल करें |
|---|---|---|
| लोकल | 127.0.0.1, केवल उसी डिवाइस पर | आपका स्क्रैपर और सॉल्वर एक ही मशीन पर चलते हैं |
| सर्वर | आपका नेटवर्क पता या पब्लिक IP | Workers, कोई VPS या होस्टेड प्लेटफ़ॉर्म API के ज़रिए कॉल करते हैं |
SDK के host को सॉल्वर मशीन की ओर इंगित करें और आपके कोड में और कुछ नहीं बदलता, इसलिए पूरा fleet एक ही instance साझा कर सकता है। जब callers आपके अपने नेटवर्क से बाहर हों तो एक static public IP की सलाह दी जाती है। जानकारी यहाँ मिलती है: कनेक्शन सेटिंग्स, और Server mode फिर भी आपका ही hardware है और फिर भी अनमीटर्ड है: यह सिर्फ़ यह बदलता है कि सॉल्वर कहाँ चलता है, कभी यह नहीं कि इसका मालिक कौन है।
FAQ
क्या मैं ख़ुद requests लाइब्रेरी का TLS फ़िंगरप्रिंट बदल सकता हूँ?
किसी काम के तरीक़े से नहीं। आप underlying SSL context के ज़रिए cipher suites का क्रम बदल सकते हैं और hash को हिला सकते हैं, लेकिन इस तरीक़े से आप ब्राउज़र जैसा ClientHello नहीं बना सकते, क्योंकि extension सेट और उसका क्रम कॉन्फ़िगरेशन से नहीं बल्कि लाइब्रेरी बिल्ड से आते हैं। आख़िर में आपको ऐसा फ़िंगरप्रिंट मिलता है जो किसी से भी मेल नहीं खाता, जो Python से मेल खाने से भी बुरी स्थिति है। इसके बजाय लाइब्रेरी बदल दें।
क्या असली ब्राउज़र चलाने से यह समस्या ख़त्म हो जाती है?
TLS लेयर के लिए हाँ, क्योंकि एक असली Chrome एक असली Chrome handshake भेजता है। लेकिन इसमें प्रति worker सैकड़ों megabytes और एक बहुत धीमी request की क़ीमत चुकानी पड़ती है, इसलिए यह एक संकरी समस्या का भारी-भरकम जवाब है। जब आपको सच में पेज पर JavaScript चलवाना हो तो ब्राउज़र का इस्तेमाल करें, और जब आपको सिर्फ़ response body चाहिए हो तो एक impersonating HTTP client का इस्तेमाल करें।
मुझे कैसे पता चलेगा कि ब्लॉक फ़िंगरप्रिंटिंग की वजह से था, rate limiting की वजह से नहीं?
रफ़्तार पूरी तरह धीमी करें और उसी client से दोबारा कोशिश करें। rate limit इंतज़ार करने पर ढीली पड़ जाती है, आमतौर पर बताती है कि कितनी देर इंतज़ार करना है, और बाद में सफ़ाई से लौट आती है। TLS फ़िंगरप्रिंटिंग ब्लॉक को समय से कोई मतलब ही नहीं होता, इसलिए दिन की पहली request बिल्कुल सौवीं की तरह विफल होती है। अगर कोई cold client अपनी बिल्कुल पहली request पर ही विफल हो जाए, तो इंतज़ार करना आपका समाधान नहीं है।
क्या CapSkip मेरा JA3 या JA4 फ़िंगरप्रिंट बदलता है?
नहीं, और इसे बदलना भी नहीं चाहिए। CapSkip एक सॉल्वर है: आप इसे एक चुनौती सौंपते हैं और यह आपको एक token लौटाता है, आपके अपने hardware पर और बिना किसी प्रति-हल शुल्क के। आपका फ़िंगरप्रिंट उस client का है जो request भेजता है, इसलिए वह हिस्सा आपका ही काम बना रहता है। व्यवहार में ये दोनों हिस्से अच्छी तरह फ़िट बैठते हैं, क्योंकि ब्राउज़र जैसे दिखने वाले client को सीधे इनकार के बजाय एक हल करने योग्य चुनौती दी जाती है।
सबसे छोटा जवाब
पहले अपना फ़िंगरप्रिंट पढ़ें, क्योंकि इसमें सिर्फ़ एक request लगती है और बहस निपट जाती है। अगर user agents रोटेट करते समय भी hash टस से मस न हो, तो समस्या handshake में है और कोई भी header इसे ठीक नहीं करेगा। एक impersonating client पर स्विच करें, जो भी impersonate किया है उसके अनुसार user agent, header क्रम और HTTP/2 profile को एकसमान रखें, फिर अपना IP देखें। इसके बाद कोई चुनौती आना विफलता नहीं बल्कि प्रगति है, और एक लोकल captcha सॉल्वर वह हिस्सा है जो इसे token में बदल देता है। इसे worker pool में जोड़ने के नोट्स यहाँ मिलते हैं: वेब स्क्रैपिंग के लिए कैप्चा सॉल्वर। जब आप ख़ुद कोड तक पहुँचें, तो Python कैप्चा सॉल्वर पेज में SDK की वे जानकारियाँ हैं जो आपको आगे चाहिए होंगी।
