Symfony Panther में reCAPTCHA कैसे हल करें (PHP SDK)

Symfony Panther में कैप्चा हल करने के लिए, Panther में खुले पेज से sitekey पढ़ें और उसे मौजूदा URL के साथ CapSkip PHP SDK के recaptcha method को पास करें। फिर executeScript से token को छिपे हुए g-recaptcha-response textarea में लिखें, और फॉर्म के अपने बटन पर क्लिक करके सबमिट करें। Panther कोड आमतौर पर token लिखने वाले इसी हिस्से पर टूटता है। reCAPTCHA उस textarea को display:none पर रखता है, इसलिए उसे भरने वाला Panther का हर helper, चाहे Form object हो या sendKeys, ElementNotInteractableException फेंकता है। जो पेज reCAPTCHA callback से सबमिट होते हैं, उन्हें एक और चरण चाहिए, क्योंकि callback चलने तक वे उस फ़ील्ड को अनदेखा करते हैं। यह गाइड Windows पर सेटअप, हल, token लिखना, दोनों तरह के सबमिट, और वे इंतज़ार कवर करती है जिनसे रन भरोसेमंद रहता है।
आपको क्या चाहिए
- किसी Windows मशीन पर चल रहा CapSkip। Local मोड में वह केवल उसी डिवाइस के लिए 127.0.0.1 पर जवाब देता है; Server मोड में वह आपके नेटवर्क पते या पब्लिक IP पर सुनता है, ताकि किसी दूसरी मशीन पर चल रहा Panther API के ज़रिए उसे कॉल कर सके। दोनों यहाँ सेट किए जाते हैं: कनेक्शन सेटिंग्स.
- PHP 8.1 या उसके बाद का संस्करण, जिसमें php.ini में curl और zip extensions चालू हों। Panther 2.4 को 8.1 चाहिए, zip के बिना Composer php-webdriver इंस्टॉल नहीं करेगा, और PHP के Windows builds में ये दोनों extensions बंद आते हैं।
- Chrome, और उसी major वर्ज़न वाला chromedriver। नीचे दिया गया bdi टूल सही वाला डाउनलोड कर देता है।
- reCAPTCHA v2 widget वाला एक पेज, चाहे checkbox हो या invisible।
# PHP 8.1+; bdi fetches the chromedriver that matches your Chrome composer require symfony/panther capskip/capskip composer require --dev dbrekelmans/bdi vendor\bin\bdi detect drivers
आख़िरी लाइन कमांड का Windows रूप है; Linux या macOS पर यह vendor/bin/bdi है। यह आपके प्रोजेक्ट के अंदर एक drivers फ़ोल्डर में chromedriver.exe सेव करता है। Panther उस फ़ोल्डर में अपने आप देखता है, लेकिन Windows पर यह खोज एक जाल है। वह फ़ाइल ढूँढ लेता है, फिर relative path से उसे शुरू करने में फेल हो जाता है। error में "Could not start chrome" लिखा आता है, और उसके बाद cmd.exe का एक मैसेज कि कोई कमांड पहचानी नहीं गई। driver का पूरा path createChromeClient को पास करें, जैसा यहाँ का हर सैंपल करता है, या drivers फ़ोल्डर को PATH में डालें।
चरण 1: पेज खोलें और sitekey पढ़ें
Panther, chromedriver के ज़रिए एक headless Chrome शुरू करता है और पेज लोड करता है। फिर reCAPTCHA का api.js widget बनाता है और g-recaptcha-response id वाला एक textarea जोड़ता है, इसलिए उस element का इंतज़ार करना इस बात का भरोसेमंद संकेत है कि widget तैयार है। उसके दिखने का इंतज़ार न करें: वह कभी दिखता ही नहीं, और उस पर waitForVisibility का time out हो जाता है।
<?php
// composer require symfony/panther capskip/capskip
require __DIR__ . '/vendor/autoload.php';
use Symfony\Component\Panther\Client;
// The full path: on Windows, Panther finds drivers\chromedriver.exe on
// its own but then fails to start it from the relative path.
$client = Client::createChromeClient(__DIR__ . '/drivers/chromedriver.exe');
$client->request('GET', 'https://example.com/signup');
// api.js adds the hidden textarea once the widget has rendered.
$client->waitFor('#g-recaptcha-response', 20);
$crawler = $client->getCrawler();
$sitekey = $crawler->filter('[data-sitekey]')->attr('data-sitekey');ज़्यादातर पेज widget को ऐसे div से घोषित करते हैं जिस पर data-sitekey attribute होता है। जो पेज उसे अपनी script से grecaptcha.render के साथ बनाता है, उसमें ऐसा कोई attribute नहीं होता, लेकिन key फिर भी पेज पर होती है, widget के iframe के पते में। ऐसे पेजों पर चरण 1 की आख़िरी लाइन की जगह यह block इस्तेमाल करें:
// grecaptcha.render() pages have no data-sitekey; the key is the
// k= parameter of the widget's iframe.
$src = $crawler->filter('iframe[title="reCAPTCHA"]')->attr('src');
parse_str((string) parse_url($src, PHP_URL_QUERY), $query);
$sitekey = $query['k'] ?? '';चरण 2: PHP SDK से हल करें
CapSkip PHP SDK को sitekey और उस पेज का URL चाहिए जिस पर widget चलता है। URL अपने वेरिएबल से नहीं, Panther से लें: redirect के बाद Panther में जो पेज खुला है, token उसी के लिए बनना चाहिए।
use CapSkip\CapSkip; $solver = new CapSkip(['host' => '127.0.0.1', 'port' => 8080]); // The page Panther has open, after any redirect, so the token is minted // for the URL the form really lives on. $result = $solver->recaptcha($sitekey, $client->getCurrentURL()); $token = $result['code']; // the result holds captchaId and code
invisible widget पेज पर दो में से किसी एक रूप में मिलता है: invisible पर सेट data-size attribute वाला एक div, या खुद submit बटन पर data-sitekey। दोनों ही मामलों में, तीसरे argument के रूप में एक options array पास करें, जिसमें invisible को 1 पर सेट किया गया हो। असली CapSkip के ज़रिए Google की सार्वजनिक टेस्ट sitekey के एक टेस्ट हल में लगभग 13 सेकंड लगे, और 1,678 कैरेक्टर का token मिला।
PHP SDK synchronous है, इसलिए token वापस आने तक script रुकी रहती है, और इसमें कोई दिक्कत नहीं। Panther का ब्राउज़र बस पेज पर इंतज़ार करता है, क्योंकि php-webdriver के timeouts हर कमांड पर लागू होते हैं, कमांड के बीच के अंतराल पर नहीं। 103 सेकंड के हल के दौरान खुला रखा गया session उसके बाद भी token सामान्य रूप से डाल पाया और सबमिट कर पाया। SDK ज़्यादा से ज़्यादा अपने recaptchaTimeout तक पोल करता है, जो डिफ़ॉल्ट रूप से 300 सेकंड है, और options array में timeout key एक कॉल के लिए इसे बदल देती है। जिसका समय सच में खत्म होता है, वह token है। reCAPTCHA token जारी होने के बाद लगभग दो मिनट तक ही वैध रहता है, इसलिए उसे तुरंत लिखें और सबमिट करें।
चरण 3: executeScript से token लिखें
यही वह चरण है जो तय करता है कि Symfony Panther में कैप्चा वाला रन काम करेगा या नहीं। सबसे सीधा तरीका फ़ील्ड को Panther के Form object से सेट करना लगता है, और वह फेल हो जाता है। Panther फ़ील्ड वैसे ही भरता है जैसे कोई यूज़र भरता, यानी WebDriver के ज़रिए उन्हें खाली करके और उनमें टाइप करके, और WebDriver ऐसे element में टाइप करने से मना कर देता है जिसे कोई देख नहीं सकता। Form पर फ़ील्ड सेट करना, setValues कॉल करना, उसे submit को पास करना या element पर sendKeys कॉल करना, सबका अंत उसी ElementNotInteractableException में होता है। इसके बजाय वैल्यू को पेज के अंदर सेट करें:
// Throws ElementNotInteractableException: the textarea is display:none.
// $form['g-recaptcha-response'] = $token;
$client->executeScript(
'document.getElementById("g-recaptcha-response").value = arguments[0];',
[$token]
);executeScript, PHP array के हर item को script में arguments[0], arguments[1] वगैरह के रूप में पास करता है, इसलिए token को कभी JavaScript स्ट्रिंग के अंदर escape नहीं करना पड़ता। ईमेल पते जैसे दिखने वाले फ़ील्ड अब भी Panther की सामान्य form API से भरे जाते हैं।
चरण 4: असली बटन पर क्लिक करके सबमिट करें
आप कैसे सबमिट करते हैं, इससे बदलता है कि पेज को क्या दिखता है। फॉर्म को उसके बटन से बनाएँ, तो Panther उसी बटन पर क्लिक करके उसे सबमिट करता है, ठीक वैसे ही जैसे कोई विज़िटर करता है:
// Built from its button, the form is sent by clicking that button, so the
// page's own submit handlers and HTML5 validation run as for a visitor.
$form = $client->getCrawler()->selectButton('Sign up')->form();
$form['email'] = 'YOUR_EMAIL';
$client->submit($form);
// Wait for something only the next page has; getCrawler() can lag.
$client->waitFor('#welcome', 15);
echo $client->getTitle(), PHP_EOL;सबमिट करने के बाकी तरीके कुछ चीज़ें छोड़ देते हैं, और यह उन पेजों पर मायने रखता है जिनकी अपनी script सबमिट के समय कोई फ़ील्ड जोड़ती है या फॉर्म की जाँच करती है:
| फॉर्म कैसे भेजा जाता है | पेज के submit handlers चलते हैं | HTML5 वैलिडेशन | बटन का name और value पोस्ट होते हैं |
|---|---|---|---|
| बटन से बने फॉर्म पर Panther का submit | हां | हां | हां |
| form element से बने फॉर्म पर Panther का submit | हां | नहीं | नहीं |
| executeScript के ज़रिए चलाया गया form.submit() | नहीं | नहीं | नहीं |
वैलिडेशन दोधारी तलवार है। खाली छोड़ा गया कोई required फ़ील्ड, या ऐसी वैल्यू जिसे ईमेल फ़ील्ड स्वीकार न करे, PHP में कोई error दिए बिना क्लिक को रोक देती है, और कुछ भी पोस्ट नहीं होता। यही एक वजह है कि ऊपर वाला block ऐसे element का इंतज़ार करता है जो सिर्फ़ अगले पेज पर होता है। दूसरी वजह यह है कि सबमिट के बाद कुछ पल तक Panther का crawler पुराने पेज का ही ब्योरा दे सकता है, इसलिए तुरंत getCrawler() पढ़ने वाले कोड को फॉर्म फिर से दिख सकता है।
चरण 5: callback से सबमिट होने वाले पेज
कुछ साइटों पर सामान्य सबमिट होता ही नहीं। widget पर data-callback attribute होता है, या पेज grecaptcha.render को एक callback पास करता है, और reCAPTCHA के token सौंपते ही वह function फॉर्म सबमिट कर देता है। invisible widgets लगभग हमेशा इसी तरह काम करते हैं। ऐसे पेजों पर, callback चलने तक textarea में रखा token कुछ नहीं करता, इसलिए उसे खुद कॉल करें। पहले textarea लिखें, क्योंकि callback आमतौर पर फॉर्म पोस्ट करता है और सर्वर token उसी फ़ील्ड से पढ़ता है। नीचे की script दोनों काम करती है, और कई widgets वाले पेज पर वह सबसे पहले मिलने वाला callback कॉल करती है:
// Writes the token into every reCAPTCHA textarea, then calls the widget's
// callback if it has one. ___grecaptcha_cfg is undocumented, so the walk
// looks for the object holding the sitekey instead of fixed key names.
const SUBMIT_TOKEN_JS = <<<'JS'
var token = arguments[0], found = null, seen = [];
document.querySelectorAll('textarea[name="g-recaptcha-response"]')
.forEach(function (t) { t.value = token; });
function walk(o, depth) {
if (found || !o || typeof o !== 'object' || depth > 6 || seen.indexOf(o) !== -1) return;
if (o instanceof Node || o === window) return;
seen.push(o);
if (Object.prototype.hasOwnProperty.call(o, 'sitekey') && o.callback) { found = o.callback; return; }
for (var k in o) {
if (Object.prototype.hasOwnProperty.call(o, k)) { try { walk(o[k], depth + 1); } catch (e) {} }
}
}
if (window.___grecaptcha_cfg) {
Object.keys(___grecaptcha_cfg.clients).forEach(function (id) { walk(___grecaptcha_cfg.clients[id], 0); });
}
if (typeof found === 'string') found = window[found];
if (typeof found !== 'function') return 'no callback';
found(token);
return 'callback called';
JS;
echo $client->executeScript(SUBMIT_TOKEN_JS, [$token]), PHP_EOL;reCAPTCHA हर widget की सेटिंग्स ___grecaptcha_cfg नाम के एक global object में रखता है। data-callback attribute वहाँ function के नाम के रूप में पहुँचता है, और grecaptcha.render को पास किया गया callback खुद function के रूप में, इसलिए script दोनों को संभालती है। यह object undocumented है और इसके अंदर की keys के नाम minified हैं, इसीलिए script किसी तय path पर चलने के बजाय सेटिंग्स को खोजती है। अगर Google कभी इसकी संरचना बदल दे, तो script कोई callback नहीं लौटाएगी, और data-callback वाले पेज पर आप फिर भी attribute पढ़कर उस नाम से function कॉल कर सकते हैं। textarea लिखे बिना callback कॉल करना ही वह एक गलती है जिससे बचना है: फॉर्म खाली g-recaptcha-response के साथ चला जाता है।
पूरा चलने वाला उदाहरण
यह रहा Symfony Panther में कैप्चा का पूरा रन, एक ही script में। यह दोनों तरह के पेज से key पढ़ती है, हल करती है, token लिखती है, और callback हो तो उसे सबमिट करने देती है, न हो तो बटन पर क्लिक करती है।
<?php
// composer require symfony/panther capskip/capskip
// signup.php: run from the project folder with php signup.php
require __DIR__ . '/vendor/autoload.php';
use CapSkip\CapSkip;
use CapSkip\Exceptions\CapSkipError;
use CapSkip\Exceptions\ValidationException;
use Symfony\Component\Panther\Client;
const PAGE_URL = 'https://example.com/signup';
// Writes the token into every reCAPTCHA textarea, then calls the widget's
// callback if it has one. ___grecaptcha_cfg is undocumented, so the walk
// looks for the object holding the sitekey instead of fixed key names.
const SUBMIT_TOKEN_JS = <<<'JS'
var token = arguments[0], found = null, seen = [];
document.querySelectorAll('textarea[name="g-recaptcha-response"]')
.forEach(function (t) { t.value = token; });
function walk(o, depth) {
if (found || !o || typeof o !== 'object' || depth > 6 || seen.indexOf(o) !== -1) return;
if (o instanceof Node || o === window) return;
seen.push(o);
if (Object.prototype.hasOwnProperty.call(o, 'sitekey') && o.callback) { found = o.callback; return; }
for (var k in o) {
if (Object.prototype.hasOwnProperty.call(o, k)) { try { walk(o[k], depth + 1); } catch (e) {} }
}
}
if (window.___grecaptcha_cfg) {
Object.keys(___grecaptcha_cfg.clients).forEach(function (id) { walk(___grecaptcha_cfg.clients[id], 0); });
}
if (typeof found === 'string') found = window[found];
if (typeof found !== 'function') return 'no callback';
found(token);
return 'callback called';
JS;
$solver = new CapSkip([
'apiKey' => getenv('CAPSKIP_API_KEY') ?: 'capskip',
'host' => getenv('CAPSKIP_HOST') ?: '127.0.0.1',
'port' => (int) (getenv('CAPSKIP_PORT') ?: 8080),
]);
// The full path: on Windows, Panther finds drivers\chromedriver.exe on
// its own but then fails to start it from the relative path.
$client = Client::createChromeClient(__DIR__ . '/drivers/chromedriver.exe');
try {
$client->request('GET', PAGE_URL);
// api.js adds the hidden textarea once the widget has rendered.
$client->waitFor('#g-recaptcha-response', 20);
$crawler = $client->getCrawler();
$box = $crawler->filter('[data-sitekey]');
if ($box->count() > 0) {
$sitekey = $box->attr('data-sitekey');
} else {
// grecaptcha.render() pages: the key is the k= parameter of the widget iframe.
$src = $crawler->filter('iframe[title="reCAPTCHA"]')->attr('src');
parse_str((string) parse_url($src, PHP_URL_QUERY), $query);
$sitekey = $query['k'] ?? '';
}
// Visible fields take typing as usual.
$form = $crawler->selectButton('Sign up')->form();
$form['email'] = 'YOUR_EMAIL';
$token = $solver->recaptcha($sitekey, $client->getCurrentURL())['code'];
if ($client->executeScript(SUBMIT_TOKEN_JS, [$token]) !== 'callback called') {
// No callback: click the real button, as a visitor would.
$client->submit($form);
}
// Wait for something only the next page has; getCrawler() can lag.
$client->waitFor('#welcome', 15);
echo 'Submitted: ', $client->getTitle(), PHP_EOL;
} catch (ValidationException $e) {
echo 'Not sent: ', $e->getMessage(), PHP_EOL;
} catch (CapSkipError $e) {
echo 'Solve failed: ', get_class($e), ': ', $e->getMessage(), PHP_EOL;
} finally {
$client->quit();
}बटन का label, ईमेल फ़ील्ड और जिस selector का आप इंतज़ार करते हैं, उन्हें अपने पेज के मुताबिक़ बदलें। invisible widget के लिए, चरण 2 वाला options array recaptcha कॉल में जोड़ें। बिना किसी बटन वाले callback पेज पर, चरण 1, 2 और 5 ही पूरा काम हैं। अगर क्लिक के बाद waitFor हार मान ले, तो token को दोष देने से पहले फॉर्म देखें: खाली छोड़ा गया required फ़ील्ड बिना किसी error के सबमिट रोक देता है। SDK वाले पहलू पर ज़्यादा जानकारी के लिए, invisible और Enterprise ऑप्शन समेत, PHP में reCAPTCHA v2 हल करना पर लिखी गाइड बिना ब्राउज़र के recaptcha कॉल को कवर करती है।
Panther और CapSkip को अलग-अलग मशीनों पर चलाना
Panther suites अक्सर किसी CI runner या Linux मशीन पर चलते हैं, जबकि CapSkip एक Windows एप्लिकेशन है। यह बिल्कुल ठीक काम करता है। CapSkip को Server मोड पर कर दें और वह आपके नेटवर्क पते या पब्लिक IP पर सुनने लगता है, ताकि किसी भी मशीन पर चल रहा Panther उसी API के ज़रिए उसे कॉल कर सके। जब रास्ता इंटरनेट से होकर जाए तो स्टैटिक पब्लिक IP इस्तेमाल करें, API key validation चालू करें, और Windows Firewall नियम से पोर्ट को सिर्फ़ अपेक्षित पतों तक सीमित रखें। सॉल्वर अब भी आपकी अपनी Windows मशीन है, और हल अब भी बिना मीटर वाले हैं।
PHP SDK एनवायरनमेंट वेरिएबल खुद नहीं पढ़ता। CAPSKIP_HOST, CAPSKIP_PORT और CAPSKIP_API_KEY को अपने कोड में पढ़ें और उन्हें constructor को पास करें, जैसा पूरा उदाहरण करता है, ताकि वही script आपकी डेस्क पर भी चले और CI में भी। जब host गलत हो या CapSkip चल न रहा हो, तो पहली कॉल एक NetworkException के साथ फेल होती है, जो बताता है कि कनेक्ट नहीं हो सका। जब पोर्ट पर कुछ भी न सुन रहा हो, तो इसमें लगभग दो सेकंड लगते हैं, और जब कोई फ़ायरवॉल कनेक्शन ड्रॉप कर दे, तो 30 सेकंड तक, जो SDK का connect timeout है।
आम errors और उनका मतलब
| आप जो देखते हैं | कारण | फिक्स |
|---|---|---|
| ElementNotInteractableException: element not interactable | Panther ने token को छिपे हुए g-recaptcha-response textarea में टाइप करने की कोशिश की | चरण 3 की तरह, वैल्यू executeScript से लिखें |
| Could not start chrome, साथ में cmd.exe का मैसेज कि कोई कमांड पहचानी नहीं गई | Windows: Panther को drivers फ़ोल्डर में chromedriver मिल गया, पर वह उसे relative path से शुरू नहीं कर सका | driver का पूरा path createChromeClient को पास करें, या drivers फ़ोल्डर को PATH में जोड़ें |
| Could not start chrome, साथ में मैसेज कि सिस्टम को path नहीं मिल रहा | createChromeClient को पास किए गए path पर कोई chromedriver.exe नहीं है | प्रोजेक्ट फ़ोल्डर में bdi detect drivers चलाएँ, या path ठीक करें |
| SessionNotCreatedException: This version of ChromeDriver only supports Chrome version N | Chrome ने खुद को अपडेट कर लिया, पर driver अपडेट नहीं हुआ | bdi detect drivers फिर से चलाएँ |
| RuntimeException: The port 9515 is already in use. | chromedriver का डिफ़ॉल्ट पोर्ट पहले से किसी दूसरे Panther client या script के पास है | createChromeClient के options array में हर client को उसका अपना पोर्ट दें |
| token लिख दिया गया, पर कुछ नहीं होता | पेज reCAPTCHA callback से सबमिट होता है | चरण 5 वाली script चलाएँ, जो फ़ील्ड लिखती है और callback कॉल करती है |
| क्लिक के बाद waitFor का time out हो जाता है और कुछ भी पोस्ट नहीं हुआ | कोई required फ़ील्ड खाली था, या कोई वैल्यू फ़ील्ड के वैलिडेशन में फेल हो गई | सबमिट करने से पहले हर required फ़ील्ड में मान्य वैल्यू भरें |
| NetworkException: Failed to connect to 127.0.0.1 port 8080 | CapSkip चल नहीं रहा, या host और port गलत हैं | CapSkip शुरू करें, फिर देखें कि उसे Local मोड में होना चाहिए या Server मोड में |
| हल के दौरान Maximum execution time exceeded | Panther एक वेब रिक्वेस्ट के अंदर चलता है, और Windows पर वह सीमा इंतज़ार में बीता समय भी गिनती है | Panther को command line से चलाएँ, जहाँ यह सीमा बंद रहती है, या किसी queue worker से |
FAQ
क्या headless Chrome से कैप्चा पास करना मुश्किल हो जाता है?
इस तरीके में नहीं। challenge CapSkip के अपने ब्राउज़र में हल होता है, उस Chrome में नहीं जिसे Panther चलाता है, इसलिए Panther के ब्राउज़र को उसे कभी पास नहीं करना पड़ता। Panther बस तैयार token को फॉर्म तक ले जाता है। रन को देखने के लिए, PHP शुरू करने से पहले environment में PANTHER_NO_HEADLESS को 1 पर सेट करें। Panther इसे $_SERVER से पढ़ता है, इसलिए script के अंदर putenv() कॉल का कोई असर नहीं होता।
क्या मेरे अपने ऐप के Panther tests में सॉल्वर इस्तेमाल करना चाहिए?
आमतौर पर नहीं। जिस ऐप पर आपका नियंत्रण है, उसके test environment को Google की प्रकाशित reCAPTCHA टेस्ट keys से कॉन्फ़िगर करें, जो हमेशा verification पास करती हैं, इसलिए tests में कुछ भी हल करने की ज़रूरत ही नहीं पड़ती। सॉल्वर तब काम का है जब Panther ऐसे पेज चलाता है जिन पर आपका नियंत्रण नहीं है, या कोई ऐसी staging साइट जिसे अपनी असली keys रखनी ही हैं।
क्या मैं एक साथ कई Panther ब्राउज़र चला सकता हूँ?
हाँ, हर प्रोसेस में एक या एक ही प्रोसेस में कई, बशर्ते हर client को अपना अलग chromedriver पोर्ट मिले। जब तक आप createChromeClient के options में कोई दूसरा पोर्ट पास न करें, हर client पोर्ट 9515 पर अपना chromedriver शुरू करता है, और उसी पोर्ट पर दूसरा client अपनी पहली रिक्वेस्ट पर ही फेल हो जाता है। PHP SDK synchronous है, इसलिए हर प्रोसेस एक बार में एक कैप्चा हल करता है, और CapSkip खुद डिफ़ॉल्ट रूप से एक साथ 10 reCAPTCHA हल चलाता है।
क्या यही तरीका Cloudflare Turnstile के लिए भी काम करता है?
ढाँचा वही है। Turnstile अपना token cf-turnstile-response नाम के एक hidden input में रखता है। Panther उसे भी नहीं भर सकता: Form object एक invalid element state की रिपोर्ट करता है, sendKeys बताता है कि element interactable नहीं है, और executeScript उसे सेट कर देता है। इसे recaptcha के बजाय SDK के turnstile method से हल करें, और sitekey को cf-turnstile div के data-sitekey attribute से पढ़ें। चरण 5 वाली script सिर्फ़ reCAPTCHA callbacks संभालती है।
संक्षेप में
Symfony Panther में कैप्चा के लिए, Windows पर driver का पूरा path createChromeClient को पास करें, पेज लोड करें और g-recaptcha-response textarea का इंतज़ार करें, फिर sitekey को data-sitekey से या widget के iframe से पढ़ें। Panther के मौजूदा URL के साथ PHP SDK से हल करें। token को executeScript से लिखें, Form object से कभी नहीं, फिर असली बटन पर क्लिक करें, या जिन पेजों पर callback है, वहाँ callback कॉल करें। अगला पेज पढ़ने से पहले उसका इंतज़ार करें, और जब Panther कहीं और चले, तो CapSkip को Server मोड पर कर दें।
- कैप्चा के वे सभी टाइप जिन्हें PHP पैकेज संभालता है: PHP कैप्चा सॉल्वर पेज.
- checkbox और invisible reCAPTCHA v2 कैसे हल होते हैं: reCAPTCHA v2 सॉल्वर पेज.
हर पेज पर widget से सामना करने वाले Panther स्क्रैपर को कुछ भी अतिरिक्त नहीं चुकाना पड़ता, जब कैप्चा बायपास आपकी अपनी Windows मशीन पर चलता है। किसी पेज को सिर्फ़ इसलिए छोड़ने की कोई वजह नहीं कि उस पर कोई challenge है।
