scrapling-turnstile
Solve Cloudflare Turnstile inside a Scrapling StealthyFetcher fetch. It reads the sitekey off the page, gets a token from Peak, writes it into cf-turnstile-response, and fires the widget callback — so the fetch clears the Turnstile/403 wall and you parse the page you actually wanted.
from scrapling.fetchers import StealthyFetcher
from scrapling_turnstile import turnstile_action
page = StealthyFetcher.fetch(url, page_action=turnstile_action(api_key="pk_your_api_key"))
print(page.css_first("h1::text"))
Why this exists
StealthyFetcher runs Camoufox, a fingerprint-patched Firefox. That fingerprint is real enough that plenty of low-risk Turnstile widgets pass on their own, and this package leans on that first: it checks whether cf-turnstile-response is already filled and, if it is, hands you that token for free.
The wall shows up when the widget scores the session as risky. Turnstile weighs the IP and the session history, not the checkbox. From a datacenter IP with no trusted past, a fetch gets held at the "Verifying..." spinner or looped through an invisible challenge that never resolves — and Scrapling hands you a 403 or the challenge HTML instead of the page. Waiting longer does nothing, because the token you need is minted by Cloudflare's risk engine, not by the interaction.
So when the native pass stalls, this reads the sitekey, sends it to Peak, and injects the token Cloudflare's siteverify will accept. Drop in an API key and the blocked fetch keeps moving.
Install
pip install scrapling scrapling-turnstile
scrapling install # one-time: pull the Camoufox browser Scrapling drives
The package itself has no runtime dependencies — it drives the Scrapling page you already have. scrapling is the fetcher you run it against.
Quickstart
turnstile_action() returns a page_action you hand straight to StealthyFetcher.fetch. Scrapling calls it with the live page, the action solves and injects, and you get a parsed response back.
import os
from scrapling.fetchers import StealthyFetcher
from scrapling_turnstile import turnstile_action
page = StealthyFetcher.fetch(
"https://protected.example/login",
headless=True,
wait_selector=".cf-turnstile", # let the widget render first
wait_selector_state="attached",
page_action=turnstile_action(
api_key=os.environ["PEAK_API_KEY"],
),
)
print("status:", page.status)
print("title:", page.css_first("title::text"))
Set the key once and let it read from the environment:
export PEAK_API_KEY=pk_your_api_key # Windows: set PEAK_API_KEY=pk_your_api_key
page_action=turnstile_action() # picks up PEAK_API_KEY
Prefer to drive the page yourself? Call the solver inside your own action:
from scrapling_turnstile import solve_turnstile
def action(page):
page.wait_for_timeout(2500) # let the widget settle
solve_turnstile(page, proxy="http://user:pass@host:port")
page.click("button[type=submit]")
return page
page = StealthyFetcher.fetch(url, page_action=action)
A full runnable script is in examples/scrapling_example.py.
Async
StealthyFetcher.async_fetch gets an async page, so use the async twins:
from scrapling.fetchers import StealthyFetcher
from scrapling_turnstile import async_turnstile_action
page = await StealthyFetcher.async_fetch(
url,
page_action=async_turnstile_action(api_key="pk_your_api_key"),
)
Native pass vs. an API solve
Scrapling's stealth and Peak cover different failure modes. Use them together.
- Let Scrapling try first.
StealthyFetcheron a clean fingerprint clears most non-interactive widgets with no solve at all. Give it a real locale and — for anything that matters — a residential or ISPproxy.turnstile_actionreturns that free token when it finds one, so you pay nothing on the pages that were never going to block you. - Solve through Peak when it stalls. Datacenter egress, a high-risk sitekey, or an aggressive site will hold the widget open no matter how good the browser is. That is the case Peak handles: it returns a token minted against the sitekey, independent of your fetch's browser session.
- Match the egress. If you pass a
proxyto Peak, use the same IP the fetch goes out on. A token solved from one network and replayed from another is more likely to be rejected server-side.
What it does not do
- Not the full-page interstitial. The Cloudflare "Just a moment" page that gates a whole domain and sets
cf_clearanceis a different challenge from an embedded Turnstile widget. This package handles the widget — the one that renders acf-turnstileelement and acf-turnstile-responsefield. For the interstitial, Peak has a separatecloudflare5stask; see the docs. - Not reCAPTCHA or hCaptcha. Turnstile only.
- No magic on a banned IP. If the target already blocked your IP range, get a better egress first. A valid token on a burned network still gets refused.
API
turnstile_action(api_key=None, proxy=None, sitekey=None, timeout=180.0, use_existing=True)
Build a page_action for StealthyFetcher.fetch(page_action=...). Solves, injects, and returns the page — the contract Scrapling expects. Keyword arguments match solve_turnstile.
solve_turnstile(page, api_key=None, proxy=None, sitekey=None, timeout=180.0, use_existing=True)
Get a valid Turnstile token onto a page and inject it. Returns the token. Call it from inside your own page_action when you want to do more than solve.
page— the sync Playwright page handed to your action byStealthyFetcher.fetch, on a Turnstile-gated URL.api_key— Peak key (pk_...). Falls back toPEAK_API_KEY.proxy— optional proxy handed to Peak, e.g.http://user:pass@host:port. Use the same egress as the fetch.sitekey— skip the DOM read and use this sitekey.timeout— seconds to wait on Peak.use_existing— ifTrue(default), return a token Scrapling already earned instead of paying for a solve.
read_sitekey(page)
Return the Turnstile sitekey rendered on the page (from .cf-turnstile[data-sitekey], any [data-sitekey], or the challenges.cloudflare.com iframe src), or None.
existing_token(page)
Return the token in cf-turnstile-response if Scrapling already passed the widget, else None.
inject_token(page, token)
Write token into the response field(s) and fire the widget's data-callback. Returns True if a callback was found and called.
request_token(sitekey, url, api_key=None, proxy=None, timeout=180.0)
The raw, blocking Peak call, in case you want the token without a page. Returns the token string or raises PeakError.
Async twins: async_turnstile_action, async_solve_turnstile, async_read_sitekey, async_existing_token, async_inject_token — same signatures, for StealthyFetcher.async_fetch.
How it works
- If
use_existing, readcf-turnstile-response; a filled field means Scrapling already passed, so return that token and skip the solve. - Read the sitekey from the widget, or scrape it from the Cloudflare iframe
srcwhen the widget rendered implicitly. - Call Peak
POST https://api.peak.fo/solvewithtask_type: "turnstiletask", thesitekey,url: page.url, and your optionalproxy— overX-API-Key. Peak returns{"success": true, "data": {"token": "..."}}. - Inject the token into every
cf-turnstile-responsefield (creating a hidden input if the widget hasn't rendered one) and invoke the widget'sdata-callback, so the host page's success handler runs.
The sync path calls Peak inline, which is fine inside a synchronous fetch action. The async path runs the blocking call in a worker thread so the browser's event loop keeps ticking.
Powered by Peak
This uses Peak, a Cloudflare specialist that returns a Turnstile token in about a second.
- $0.90 per 1,000 successful Turnstile solves, down to $0.35 at volume.
- Pay only for successes — a failed solve is not billed.
- About 1,000 free solves to start, no card.
→ Get a free API key · Turnstile docs
Other Peak wrappers
Same solve, wired into other stacks:
- camoufox-turnstile — Turnstile in Camoufox (the browser under Scrapling's
StealthyFetcher). - playwright-turnstile — token injection for Playwright.
- selenium-turnstile — a valid token in Selenium, no physical click.
- scrapy-turnstile — Scrapy middleware that keeps blocked spiders running.
- puppeteer-extra-plugin-turnstile — a
puppeteer-extraplugin. - turnstile-curl — solve from
curl_cffi, no browser. - cloudscraper-turnstile — a
cloudscraperdrop-in. - crawl4ai-turnstile — Turnstile solving for Crawl4AI.
More on the list: awesome-turnstile-solvers.
Legitimate use
Built for automation, QA, and scraping public data. Respect each site's Terms of Service and robots.txt, and don't point it at credential-stuffing or other abuse. How you use it is on you.
License
MIT — see LICENSE.
Metadata
Release files for scrapling-turnstile 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| scrapling_turnstile-0.1.1.tar.gz | 10.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| scrapling_turnstile-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 22.1 kB
Release files / scrapling_turnstile-0.1.1.tar.gz
| Download URL | scrapling_turnstile-0.1.1.tar.gz |
|---|---|
| Size | 10.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
196fc0cdd7a49d8bee7dd7f4ad47b4af99d20061e752d437f8413943a7135908
|
|
BLAKE2b-256 checksum How to use checksums |
5849c61a9de4c0e1cb020286e99fbd4a606c0ae343cbd8d75eddd2d839c65ae9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.0
|
Release files / scrapling_turnstile-0.1.1-py3-none-any.whl
| Download URL | scrapling_turnstile-0.1.1-py3-none-any.whl |
|---|---|
| Size | 11.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
aaccc0e7a680ffa34de2841cf635b02bd93b8a93a4cdd9f78a20158dc307e76b
|
|
BLAKE2b-256 checksum How to use checksums |
114ff7d366eeea2b0b70648e2a0842b1d21be02474dfd3577103ea6b741c12ec
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.0
|