Logo of «2Captcha»To home page
Captcha bypass tutorials

Was this helpful?

How to bypass reCAPTCHA v2 with Playwright

Gregory Fisher
Gregory Fisher

Technical engineer

reCAPTCHA v2 (the famous "I'm not a robot" checkbox and the subsequent image grid) remains one of the main obstacles in web scraping, automated testing, and form filling.

In this guide, we will walk through the complete cycle of bypassing reCAPTCHA v2 in Playwright with Python: extract the sitekey from the page, obtain a solved token via the 2Captcha API, and correctly inject it into the DOM so the form recognizes the action as performed by a real user.

Note: reCAPTCHA v2 comes in two variants: Visible (with a visible checkbox) and Invisible (works in the background, triggering verification only on suspicious behavior). The method described below is universal and works for both variants, as the token generation and validation mechanism is identical.

What You Will Need

  • Python 3.7 or higher
  • Installed Playwright
  • 2Captcha account and API key
  • Target page URL
  • Optional: proxy if the target site blocks requests from datacenter IPs

Step 1. Installing Dependencies

Install the required libraries and Playwright browsers:

bash Copy
pip install playwright 2captcha-python
playwright install chromium

Step 2. Browser Initialization and Page Navigation

For debugging, always start with visible mode headless=False. This allows you to visually monitor the widget appearance and token injection process.

python Copy
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    page = browser.new_page()
    
    # Navigate to the target page
    page.goto("https://2captcha.com/demo/recaptcha-v2")
    
    # Wait for the reCAPTCHA container to appear
    page.wait_for_selector(".g-recaptcha")

Step 3. Extracting the Sitekey

The unique site key sitekey is usually stored in the data-sitekey attribute of the widget container.

python Copy
sitekey = page.locator(".g-recaptcha").get_attribute("data-sitekey")
page_url = page.url

print(f"Found sitekey: {sitekey}")

Important: if the widget is rendered dynamically via grecaptcha.render(), the data-sitekey attribute may not be present in the DOM. In this case, open DevTools, go to the Network tab, and find the request to google.com/recaptcha/api2/anchor. The sitekey will be passed there as the query parameter k.

Step 4. Sending the Task to the API and Getting the Token

The simplest approach is to use the official Python SDK. It automatically creates a task and polls the server until a result is obtained (this usually takes 15–30 seconds).

python Copy
from twocaptcha import TwoCaptcha

solver = TwoCaptcha('YOUR_API_KEY')

result = solver.recaptcha(
    sitekey=sitekey,
    url=page_url
)

token = result['code']
print(f"Token received: {token[:20]}...")

Alternative (direct HTTP request):
If you don't use the SDK, you can send a request directly via curl or the requests library:

bash Copy
curl -X POST https://api.2captcha.com/createTask \
  -H "Content-Type: application/json" \
  -d '{
    "clientKey": "YOUR_API_KEY",
    "task": {
      "type": "RecaptchaV2TaskProxyless",
      "websiteURL": "https://2captcha.com/demo/recaptcha-v2",
      "websiteKey": "6LfD3PIbAAAAAJs_eEHvoOl75_83eXSqpPSRFJ_u"
    }
  }'

If the site requires binding to a specific IP, use the RecaptchaV2Task task type and add proxy parameters: proxyType, proxyAddress, proxyPort, proxyLogin, proxyPassword.

Step 5. Injecting the Token into the Page (Critical Step)

This is where many make mistakes. Unlike regular input fields, reCAPTCHA v2 expects the token in a hidden textarea element with id g-recaptcha-response.

Additionally, many sites use a callback function that triggers upon successful solving and unlocks the form submission button. We need to simulate both of these actions.

python Copy
# 1. Inject the token into the hidden field
page.evaluate(f"""
    const responseField = document.getElementById('g-recaptcha-response');
    if (responseField) {{
        responseField.value = "{token}";
        responseField.style.display = 'block'; // Make visible for reliability
    }}
""")

# 2. Simulate the callback function call (if it exists)
# Replace recaptchaCallback with the actual function name from the data-callback attribute if different
page.evaluate(f"""
    if (typeof recaptchaCallback === 'function') {{
        recaptchaCallback("{token}");
    }}
""")

Step 6. Submitting the Form

After successfully injecting the token, the form should consider itself valid.

python Copy
page.click("button[type='submit']")
page.wait_for_load_state("networkidle")

browser.close()

Complete Working Example (Python)

python Copy
from playwright.sync_api import sync_playwright
from twocaptcha import TwoCaptcha

API_KEY = "YOUR_API_KEY"
TARGET_URL = "https://2captcha.com/demo/recaptcha-v2"

def solve_recaptcha():
    solver = TwoCaptcha(API_KEY)

    with sync_playwright() as p:
        browser = p.chromium.launch(headless=False)
        page = browser.new_page()
        
        try:
            page.goto(TARGET_URL)
            page.wait_for_selector(".g-recaptcha")

            # 1. Extract the sitekey
            sitekey = page.locator(".g-recaptcha").get_attribute("data-sitekey")
            print(f"Sitekey: {sitekey}")

            # 2. Solve the captcha via API
            print("Sending task to API...")
            result = solver.recaptcha(sitekey=sitekey, url=TARGET_URL)
            token = result['code']
            print("Token received!")

            # 3. Inject the token and call the callback
            page.evaluate(f"""
                const responseField = document.getElementById('g-recaptcha-response');
                if (responseField) {{
                    responseField.value = "{token}";
                }}
                if (typeof recaptchaCallback === 'function') {{
                    recaptchaCallback("{token}");
                }}
            """)

            # 4. Submit the form
            page.click("button[type='submit']")
            page.wait_for_load_state("networkidle")
            
            print("Form submitted successfully!")
            page.wait_for_timeout(3000) # Pause for visual verification

        except Exception as e:
            print(f"Error: {e}")
        finally:
            browser.close()

if __name__ == "__main__":
    solve_recaptcha()

Troubleshooting Common Issues

  • Form doesn't submit after token injection: the site expects the callback function to trigger. Check the data-callback attribute of the .g-recaptcha container and call this function via page.evaluate as shown in Step 5.
  • Sitekey not found (returns empty): the widget is loaded inside a dynamic iframe. Wait for the iframe to appear or intercept the network request to google.com/recaptcha/api2/anchor and extract the k parameter from the URL.
  • Additional verification appears (images): you've encountered a low trust level trigger. Solution: use high-quality residential or mobile proxies when creating the task in the API and add a realistic User-Agent.
  • Script works in visible mode but fails in headless: this is headless mode detection by the anti-bot system, not a captcha error. Solution: use masking libraries (e.g., playwright-stealth) or pass proxies to the API of the same type as your server.

Conclusion

Automating reCAPTCHA v2 bypass no longer needs to be a bottleneck in your workflows. Using the Playwright and 2Captcha API combination, you get a reliable and scalable tool that handles all the complexity of interacting with Google's protection.

The key to consistent success lies not only in obtaining a valid token but also in its correct integration. Injecting the value into the hidden g-recaptcha-response field and properly calling the callback function programmatically fully simulates real user behavior at the browser level. Following this guide, you'll be able to bypass checks predictably and efficiently while maintaining high script performance for scraping, testing, or data parsing, regardless of which reCAPTCHA v2 variant you encounter.