Was this helpful?
How to bypass reCAPTCHA v2 with Playwright
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
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
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
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
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
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
# 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
page.click("button[type='submit']")
page.wait_for_load_state("networkidle")
browser.close()
Complete Working Example (Python)
python
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.
Useful Links
- 2Captcha API documentation for reCAPTCHA v2: https://2captcha.com/api-docs/recaptcha-v2
- reCAPTCHA v2 demo page: https://2captcha.com/demo/recaptcha-v2
- 2Captcha Python SDK on GitHub: https://github.com/2captcha/2captcha-python