cloudscraper-go

module
v0.0.0-...-33f0207 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 5, 2026 License: MIT

README ¶

cloudscraper-go

ci go license

A native Go library and CLI to fetch anti-bot–protected pages — TLS/JA3 fingerprint spoofing via uTLS, no Python, no headless browser for the common cases. A single static binary.

Status: the full roadmap (M1–M5) is implemented and CI-green — a browser TLS fingerprint (verifiable against tls.peet.ws), hot sessions with retries + proxy, a bounded concurrent crawler, a server daemon, and an MCP server so AI agents can call it as a tool. Scope limits are documented, not hidden.

Problem

Cloudflare, Akamai and friends increasingly block by how you connect, not who you say you are. Go's net/http has a distinctive TLS ClientHello (JA3/JA4) that these systems flag instantly — so a plain http.Get gets 403 / Access Denied on sites a real browser loads fine. cloudscraper-go makes the Go client's TLS handshake indistinguishable from a real browser's, which clears the most common "blocked by fingerprint" cases without spinning up Chrome.

How it works

The core is a custom http.RoundTripper that performs the TLS handshake with a browser's ClientHello (via uTLS), then speaks HTTP/2 or HTTP/1.1 over that connection. Cookies and redirects are handled by the standard net/http.Client.

flowchart LR
  App["Your code / CLI"] -->|http.Client| RT["uTLS RoundTripper"]
  RT -->|"browser ClientHello (JA3/JA4)"| TLS["TLS handshake"]
  TLS -->|ALPN| H{h2?}
  H -->|yes| H2["golang.org/x/net/http2"]
  H -->|no| H1["HTTP/1.1"]
  H2 --> Site[(Protected site)]
  H1 --> Site

Quick start

CLI
go install github.com/maarkN/cloudscraper-go/cmd/cloudscraper@latest

# Fetch a page with a Chrome fingerprint (body -> stdout, status/headers -> stderr)
cloudscraper fetch https://example.com --dump-headers

# Prove the fingerprint: what JA3/JA4 does the server actually see?
cloudscraper fingerprint --profile chrome

fingerprint output (real):

profile:       chrome
http_version:  h2
ja3_hash:      96f0044b9649e05e8e079d698cc65a77
ja4:           t13d1516h2_8daaf6152771_d8a2da3f94cd
user_agent:    Mozilla/5.0 (...) Chrome/131.0.0.0 Safari/537.36

Switch --profile firefox and the JA3/JA4 changes to Firefox's — the cipher and extension ordering is genuinely different, not just the User-Agent.

CLI reference

Run cloudscraper <command> --help for the authoritative list. All fetching commands take a browser --profile and can send extra request headers.

cloudscraper fetch <url> — GET one URL. The body goes to stdout; status and headers go to stderr (so you can pipe the body cleanly).

Flag Default What it does
-p, --profile chrome Browser fingerprint profile (chrome, firefox).
-H, --header Extra request header "Name: Value", repeatable.
-t, --timeout 30s Overall request timeout.
--retries 2 Retries on transient failures (network / 429 / 5xx), with backoff + jitter.
--proxy Proxy URL: http://host:port or socks5://host:port (Basic auth in the URL is supported).
--dump-headers false Print the response headers to stderr.
--no-redirect false Do not follow redirects.
--insecure false Skip TLS certificate verification (testing only).

cloudscraper crawl <url>... — fetch many URLs concurrently.

Flag Default What it does
-p, --profile chrome Browser fingerprint profile.
-H, --header Extra request header "Name: Value", applied to every fetch.
-c, --concurrency NumCPU*2 Max concurrent fetches.
--rps 0 Per-host requests per second (0 = unlimited).
-t, --timeout 30s Per-request timeout.

cloudscraper fingerprint — probe tls.peet.ws and print the JA3/JA4 the server sees. Flag: -p, --profile.

cloudscraper mcp — run the MCP server over stdio (see the AI agents section below). Flag: -p, --profile.

Custom headers (Authorization, X-Api-Key, anything)

Pass -H "Name: Value" as many times as you need. It works on fetch and crawl:

# A bearer token, an API key and an Accept header on a single fetch
cloudscraper fetch https://api.example.com/v1/me \
  -H "Referer: https://api.example.com/" \
  -H "Authorization: Bearer eyJhbGciOi..." \
  -H "X-Api-Key: 1234567890" \
  -H "Accept: application/json"

# The same auth header applied to every URL in a concurrent crawl
cloudscraper crawl -c 8 -H "Authorization: Bearer $TOKEN" \
  https://api.example.com/a https://api.example.com/b

From the library it is cloudscraper.WithHeader("Authorization", "Bearer ..."), and you can pass it as many times as you like when constructing the client.

When headers are enough, and when they aren't

The right headers matter as much as the fingerprint. A lot of Cloudflare rules block on request shape, not TLS: a missing Referer (hotlink / anti-scraping rules), a missing API auth header, or a non-browser Accept / Sec-Fetch-*. Those are exactly what -H fixes. A common path from 403 Sorry, you have been blocked to 200 looks like:

cloudscraper fetch "https://api.example.com/v1/search?q=..." --profile firefox \
  -H "Referer: https://api.example.com/" \        # clears a Referer-gated Cloudflare rule
  -H "Authorization: Guest" \                     # some public APIs still want an auth header
  -H "Accept: application/json, text/plain, */*"

It is enough when the block is header- or IP-based: a browser fingerprint plus the right -H headers get you through, and the response is the real content (check the body, not just the status).

It is not enough when the site serves a JavaScript challenge ("Just a moment...") or keys on the HTTP/2 frame fingerprint, which is still Go's (see Scope & limitations below). Pure Go cannot clear those, and the tool says so rather than pretending.

Library
package main

import (
	"context"
	"fmt"

	"github.com/maarkN/cloudscraper-go/pkg/cloudscraper"
)

func main() {
	client, _ := cloudscraper.New(cloudscraper.WithProfile("chrome"))
	resp, err := client.Get(context.Background(), "https://example.com")
	if err != nil {
		panic(err)
	}
	fmt.Println(resp.Proto, resp.StatusCode, len(resp.Body))
}

Options: WithProfile, WithTimeout, WithoutRedirects, WithHeader, WithProxy, WithRetries.

Sessions, retries & proxy (M2)

A Client is a hot session: its cookie jar persists across calls, so a login or challenge cookie set on one request rides along on the next.

client, _ := cloudscraper.New(
	cloudscraper.WithProfile("chrome"),
	cloudscraper.WithRetries(3),                       // network / 429 / 5xx, exp. backoff + jitter
	cloudscraper.WithProxy("http://user:pass@host:8080"), // http CONNECT or socks5://
)
a, _ := client.Get(ctx, "https://site/login")   // sets a cookie
b, _ := client.Get(ctx, "https://site/account") // cookie is reused automatically

Retries honour Retry-After and the request context; they only replay requests whose body can be rewound. From the CLI: --retries and --proxy.

Concurrent crawling (M3)

Fetch many URLs with a bounded worker pool, per-host rate limiting and optional Prometheus metrics — cancellable and with backpressure:

import "github.com/maarkN/cloudscraper-go/internal/crawl"

crawler := crawl.New(fetcher, crawl.Options{
	Concurrency: 8,    // bounded in-flight fetches (semaphore)
	PerHostRPS:  2,    // token-bucket rate limit per host
	Metrics:     crawl.NewMetrics(prometheus.DefaultRegisterer),
})
results, err := crawler.Crawl(ctx, urls) // results keep input order; per-URL errors captured

From the CLI:

cloudscraper crawl -c 8 --rps 2 https://a.example https://b.example https://c.example

A single failing URL never aborts the others; a cancelled context stops the whole crawl. Fetcher is a one-method interface, so the crawler is unit-tested with a fake — no network.

Server mode (M4)

Run it as a daemon that keeps solved sessions hot — each ?session= id maps to its own long-lived, cookie-warm client:

go run ./cmd/server -addr :8080 -idle-ttl 10m
Endpoint Purpose
GET /healthz liveness
GET /fetch?url=…&session=…&profile=… fetch via the (reused) hot session
DELETE /sessions/{id} drop a session
GET /metrics Prometheus (requests, duration, in-flight, active sessions)
curl "localhost:8080/fetch?url=https://example.com&session=a"   # solves once
curl "localhost:8080/fetch?url=https://example.com/about&session=a" # reuses it

Idle sessions are evicted by a background janitor; SIGINT/SIGTERM triggers a graceful drain. Handlers depend on a small Doer/Factory pair, so they're unit-tested without network.

🤖 Use it from an AI agent (MCP)

cloudscraper mcp runs a Model Context Protocol server over stdio (built on the official modelcontextprotocol/go-sdk), so any MCP client — Claude Desktop, IDEs — can read anti-bot–protected pages as a tool. Point the client at the binary:

{
  "mcpServers": {
    "cloudscraper": { "command": "cloudscraper", "args": ["mcp"] }
  }
}

Tools exposed:

Tool Does
fetch_protected_url Fetch a URL with a browser TLS fingerprint, returning clean Markdown (default) or raw HTML — what an LLM actually wants to read.
get_cookies Solve the challenge for a URL and return the resulting cookies.

This is the native Go counterpart to the MCP server in cloudscraper.js — same idea, single static binary, no Python.

Scope & limitations (read this)

Being explicit about what pure Go can and can't do is a feature, not a caveat.

Protection layer Handled in pure Go?
TLS ClientHello fingerprint (JA3 / JA4) ✅ Yes — via uTLS. Done (M1).
Browser-like request headers & User-Agent âś… Values yes; exact on-wire header order not yet (see below).
HTTP/2 frame fingerprint (Akamai h2 hash) ⚠️ Not yet — currently Go's x/net/http2 SETTINGS, not the browser's.
Legacy JS challenge (IUAM) ⚠️ Partial — needs a JS engine (goja); planned via a pluggable solver.
Turnstile / modern Managed Challenge ❌ Needs a headless browser or a solving service (planned pluggable fallback).

What this means today: the JA3/JA4 layer matches a real browser, which clears the common "blocked by IP/TLS fingerprint" cases. Two known gaps are documented rather than hidden:

  • HTTP/2 frame fingerprint — cloudscraper fingerprint still reports Go's akamai_fingerprint, because we use the standard x/net/http2. Matching Chrome's h2 SETTINGS/frame order needs a customized HTTP/2 layer (roadmap).
  • Header order — net/http reorders headers on the wire, so the byte-exact browser header order isn't reproduced yet.

For heavy JS challenges, the plan is a pluggable fallback (headless via chromedp, or an external solver) — deliberately out of the fast path.

Design decisions & trade-offs

  • uTLS over a headless browser. For the fingerprint layer, uTLS is orders of magnitude cheaper than driving Chrome, and ships as one static binary. Headless is reserved as an opt-in fallback for challenges Go can't evaluate.
  • Compose http.RoundTripper, don't fork net/http. Cookies, redirects and the Client API come for free; we only replace the transport. The cost is the two gaps above (h2 frames, header order), which a fork would fix — a considered trade-off for M1, revisited later.
  • Dial per request (for now). Simple and correct; session reuse / pooling is M2/M3, where it belongs alongside rate limiting and backpressure.
  • Fail honestly. https-only today; non-https returns a clear error rather than pretending.

Benchmarks

Measured on loopback (a local TLS server) so the numbers isolate the library's own cost — uTLS handshake + HTTP + gzip inflate — with no network variance, and are reproducible:

go test -run '^$' -bench BenchmarkGet -benchmem ./pkg/cloudscraper
Benchmark per request throughput allocs/op
Get (sequential) ~8.4 ms ~119 req/s 1,227
Get (parallel, 8 threads) ~3.5 ms ~282 req/s 1,239

Intel i7-4850HQ (4C/8T), Go 1.26. Concurrency yields ~2.4× throughput. Each request currently opens a fresh TLS connection, so the uTLS handshake dominates the per-request time — connection pooling is the clear next optimization. (Hot sessions already avoid re-solving the anti-bot challenge; pooling would additionally avoid re-handshaking.)

For context, the Python approach this replaces — the original cloudscraper.js — spawned a python process per request (interpreter cold-start + a freshly created scraper), a cost measured in hundreds of milliseconds to seconds. cloudscraper-go is a single static binary with no per-request process spawn.

Roadmap

  • M1 — uTLS transport: Chrome/Firefox ClientHello, verified against tls.peet.ws. CLI fetch + fingerprint.
  • M2 — Sessions: cookie reuse across requests, retries with exponential backoff + jitter (honouring Retry-After), and http/socks5 proxy support.
  • M3 — Concurrent crawler: bounded worker pool (errgroup + semaphore), per-host token-bucket rate limiting, cancellable context + backpressure, and Prometheus metrics. CLI crawl.
  • M4 — Server mode: HTTP daemon that keeps sessions hot (per-session clients), with /fetch, session close, Prometheus /metrics, idle eviction and graceful shutdown.
  • M5 — Agent bridge: an MCP server (cloudscraper mcp) exposing fetch_protected_url (clean Markdown) and get_cookies — the native Go counterpart to cloudscraper.js's agent story.

Development

make build       # build both binaries into ./bin
make test        # go test -race -short ./...   (offline, deterministic)
make test-net    # full suite incl. network fingerprint tests
make lint        # golangci-lint
make bench       # network benchmark
make fingerprint # build + run `cloudscraper fingerprint`

License

MIT — see LICENSE.

Credits

TLS fingerprinting by refraction-networking/utls. Inspired by the Python cloudscraper and the author's cloudscraper.js.

Directories ¶

Path Synopsis
cmd
cloudscraper command
Command cloudscraper is the CLI for the cloudscraper-go library: fetch an anti-bot–protected URL with a browser TLS fingerprint, or probe what fingerprint a server actually sees.
Command cloudscraper is the CLI for the cloudscraper-go library: fetch an anti-bot–protected URL with a browser TLS fingerprint, or probe what fingerprint a server actually sees.
server command
Command server runs cloudscraper-go as an HTTP daemon (milestone M4).
Command server runs cloudscraper-go as an HTTP daemon (milestone M4).
examples
basic command
Example: fetch a page with a Chrome TLS fingerprint and print a summary.
Example: fetch a page with a Chrome TLS fingerprint and print a summary.
internal
agent
Package agent exposes cloudscraper-go to AI agents as an MCP server: tools an LLM can call to fetch anti-bot–protected pages (as clean Markdown) and read their cookies.
Package agent exposes cloudscraper-go to AI agents as an MCP server: tools an LLM can call to fetch anti-bot–protected pages (as clean Markdown) and read their cookies.
crawl
Package crawl fetches many URLs concurrently with a bounded worker pool, per-host rate limiting, cancellation and backpressure.
Package crawl fetches many URLs concurrently with a bounded worker pool, per-host rate limiting, cancellation and backpressure.
fingerprint
Package fingerprint holds the browser profiles cloudscraper-go can impersonate: the uTLS ClientHello that shapes the TLS/JA3 fingerprint, plus the matching User-Agent and default request headers.
Package fingerprint holds the browser profiles cloudscraper-go can impersonate: the uTLS ClientHello that shapes the TLS/JA3 fingerprint, plus the matching User-Agent and default request headers.
retry
Package retry provides an http.RoundTripper that transparently retries transient failures — network errors and configurable status codes — with exponential backoff and full jitter, honouring the Retry-After header and the request's context deadline/cancellation.
Package retry provides an http.RoundTripper that transparently retries transient failures — network errors and configurable status codes — with exponential backoff and full jitter, honouring the Retry-After header and the request's context deadline/cancellation.
server
Package server exposes cloudscraper-go over HTTP as a daemon that keeps solved sessions hot: each session id maps to its own long-lived fetcher (cookies stay warm across requests).
Package server exposes cloudscraper-go over HTTP as a daemon that keeps solved sessions hot: each session id maps to its own long-lived fetcher (cookies stay warm across requests).
transport
Package transport implements an http.RoundTripper that performs the TLS handshake with a real browser's ClientHello (via uTLS), so the connection's JA3/JA4 fingerprint matches that browser instead of Go's default net/http fingerprint — which anti-bot systems (Cloudflare, Akamai) flag on sight.
Package transport implements an http.RoundTripper that performs the TLS handshake with a real browser's ClientHello (via uTLS), so the connection's JA3/JA4 fingerprint matches that browser instead of Go's default net/http fingerprint — which anti-bot systems (Cloudflare, Akamai) flag on sight.
pkg
cloudscraper
Package cloudscraper is the public API: an HTTP client that fetches anti-bot–protected pages using a real browser's TLS fingerprint (via uTLS), with cookie persistence and redirect handling provided by net/http.
Package cloudscraper is the public API: an HTTP client that fetches anti-bot–protected pages using a real browser's TLS fingerprint (via uTLS), with cookie persistence and redirect handling provided by net/http.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL