Skip to main content

captcha-detector for Python

Does this web page have a captcha? The Python version of captcha-detector: the same 69 named captcha and bot-check products plus catch-all rules, the same answers, zero dependencies.

  • Check what your scraper got back: pass a requests, httpx, aiohttp, curl_cffi, Scrapy or urllib response, or the JSON a scraper API sent, and find out whether you got the real page or a captcha, a bot check or a block page.
  • Every kind: checkbox widgets, invisible/score captchas, puzzles and sliders, proof-of-work, image captchas, math and text questions, full-page bot checks ("Just a moment…", "Press & Hold"), and access-denied pages.
  • Real browsers too: Playwright (sync and async) and Selenium, cross-origin frames included, with visible telling you whether the captcha is on screen.
pip install captcha-detector

Check a response

import requests
from captcha_detector import detect_response

url = "https://shop.example/product/42"
res = requests.get(url)
r = detect_response(res)

if r.captcha or r.challenge_page or r.blocked:
    print("Not the real page:", r.summary)   # e.g. "Captcha found: DataDome captcha (slider) [visible]"
else:
    parse(res.text)

detect_response() takes:

Input Example
requests / httpx / curl_cffi / niquests response detect_response(requests.get(url))
Scrapy response detect_response(response) inside parse()
urllib.request / urllib3 response detect_response(urlopen(url))
aiohttp response await detect_response_async(resp)
HTML as str or bytes detect_response(html, url=url)
A scraper API's JSON (str, dict or list) detect_response(api_res.json(), url=target)

Results from scraper APIs

HTML wrapped in JSON ({"html": …}, {"result": {"content": …}} and the like) is found on its own, along with the status code, headers and URL next to it.

target = "https://shop.example/product/42"
res = requests.get("https://scraper-api.example/v1", params={"url": target, "key": KEY})
r = detect_response(res, url=target)
if r.captcha or r.challenge_page or r.blocked:
    ...  # retry with another proxy or provider, don't parse or store this one

Pass url, the page you asked the API to scrape, not the API's own address. Most scraper APIs answer 200 whatever the site did; that's fine, the page itself gives a captcha away. If your API reports the site's real status or headers somewhere else, pass them as status= and headers=.

Check HTML

from captcha_detector import detect_html

r = detect_html(html, url="https://shop.example/", status=403, headers={"server": "cloudflare"})

In a real browser

Raw HTML only shows what the server sent, and can't tell what is on screen. In a browser the detector sees what scripts added and whether the captcha is visible.

from playwright.sync_api import sync_playwright
from captcha_detector import detect_in_page, wait_for_captcha

with sync_playwright() as p:
    page = p.chromium.launch().new_page()
    response = page.goto("https://www.google.com/recaptcha/api2/demo")
    r = detect_in_page(page, response=response)      # checks every frame, cross-origin ones too
    print(r.summary)                                  # Captcha found: reCAPTCHA (v2-checkbox) [visible]

    r = wait_for_captcha(page, timeout=10, visible=True)   # wait until one is on screen

Async: await detect_in_page_async(page) and await wait_for_captcha_async(page).

Selenium:

from selenium import webdriver
from captcha_detector import detect_selenium

driver = webdriver.Chrome()
driver.get("https://www.google.com/recaptcha/api2/demo")
r = detect_selenium(driver)    # switches into each frame and back

Anything else that runs JavaScript can load captcha_detector.BROWSER_SCRIPT (it defines window.CaptchaDetector) or evaluate captcha_detector.expression(), then convert the result with captcha_detector.from_js().

The result

A dict (so json.dumps works) whose keys can also be read as attributes:

{
  "captcha": True,          # a captcha or bot check is on the page (shown, invisible or not rendered yet)
  "visible": None,          # on screen right now: True/False in a browser, None for raw HTML
  "challenge_page": False,  # the whole page is a bot check
  "blocked": False,         # an access-denied page
  "detections": [{
      "id": "recaptcha", "name": "reCAPTCHA", "vendor": "Google",
      "kind": "widget",     # widget | invisible | image | question | challenge-page | block-page | protection
      "subtype": "v2-checkbox", "confidence": 0.99, "visible": None,
      "evidence": [{"type": "scripts", "value": "https://www.google.com/recaptcha/api.js"},
                   {"type": "elements", "value": ".g-recaptcha"}],
      "site": "https://developers.google.com/recaptcha",
  }],
  "summary": "Captcha found: reCAPTCHA (v2-checkbox)",
  "url": "https://shop.example/", "mode": "html", "version": "1.0.0",
}

kind: "protection" means a bot-protection service is loaded (DataDome, Akamai, PerimeterX…) but no captcha is shown; it does not set captcha. When the reCAPTCHA or hCaptcha image challenge is open, details["challenge_open"] is True.

Options for every function: min_confidence (0..1, default 0.5) and signatures (your own list; SIGNATURES + mine adds to the built-in ones).

Command line

captcha-detector https://www.google.com/recaptcha/api2/demo
captcha-detector https://example.com --browser --json      # needs: pip install playwright
curl -s https://example.com | captcha-detector -
curl -s "https://scraper-api.example/v1?url=..." | captcha-detector -

Options: --browser, --headful, --wait MS, --json, --fail (exit code 1 when a captcha is found), --min 0..1. Also python -m captcha_detector.

Add your own signature

from captcha_detector import SIGNATURES, detect_html

acme = {
    "id": "acme-check", "name": "ACME bot check", "kind": "challenge-page",
    "title": [(r"^acme says hold on", 0.9)],                  # regex, or (regex, weight)
    "scripts": [r"cdn\.acme-shield\.example/"],
    "elements": ["#acme-check", {"sel": "form", "attr": ["action"], "re": r"/acme/verify", "w": 0.9}],
    "refine": lambda d, x: d if x.short else None,             # optional: adjust or drop a match
}
r = detect_html(html, signatures=SIGNATURES + [acme])

Matchers: scripts, frames, elements (CSS: tag, #id, .class, [attr op "v" i], :not()), inline (inline script text), urls, title, text (short pages only, unless (re, w, "any")), cookies, headers ((name, re, w)), status. Matches combine into a confidence; the default threshold is 0.5.

To add a product for everyone, add it to src/signatures.js in the repository: the JavaScript and Python packages share one signature database.

How it stays in step with the JavaScript package

The signature database (signatures.json) is generated from the JavaScript source, and the test suite checks that the Python engine gives exactly the same result as the JavaScript engine on every test page.

License

MIT

Metadata

Release files for captcha-detector 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for captcha-detector 1.0.0
File Size Uploaded Provenance Actions
captcha_detector-1.0.0.tar.gz 70.0 kB Without provenance Details for captcha_detector-1.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for captcha-detector 1.0.0
File Interpreter ABI Platform Actions
captcha_detector-1.0.0-py3-none-any.whl Python 3 none any Details for captcha_detector-1.0.0-py3-none-any.whl

Total release size: 132.7 kB

Release files / captcha_detector-1.0.0.tar.gz

Download URL captcha_detector-1.0.0.tar.gz
Size 70.0 kB
Tags Source
SHA-256 checksum
How to use checksums
5be88b44b6e6ca1c4a218e3b7ad8ea492902c8d1641c17120c42e450275d0b76
BLAKE2b-256 checksum
How to use checksums
da6ac941f1fe28a73f75a6315c0c091657bab11c33515e5c709c06b2d5413d38
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.9.13

Release files / captcha_detector-1.0.0-py3-none-any.whl

Download URL captcha_detector-1.0.0-py3-none-any.whl
Size 62.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
707ef07883517657fd97b50b8d11ce31d04a627b140f3498d08063d9102edce4
BLAKE2b-256 checksum
How to use checksums
8f9e4f5161049fe5a6cfbd95e27f3522e13bf0115799a9b691fab0c52fbd52f4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.9.13

Security for captcha-detector 1.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page