Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

Lithium

Version – 1.0b1

An HTTP-first, high stealth web runtime for Python. The simplicity of requests + the interaction model of Selenium, backed by a tiny native Go runtime — no browser to install, no drivers, no config to get going.

from lithium import Client

client = Client()                      # zero-config, undetected out of the box
page = client.get("https://example.com/login")

page.fill("#username", "alice")
page.fill("#password", "secret")
page.click("button[type=submit]")
page.wait_for("#dashboard")

print(page.text)
page.screenshot("dashboard.png")

Fun fact #1: the name comes from lithium — the lightest solid element — because the whole runtime binary is only ~19 MB. Fun fact #2: Python talks to the Go runtime over a Unix socket using MessagePack. No browser process, no WebDriver, no Chromium. Just a runtime. Fun fact #3: by default the runtime speaks a Chrome TLS (JA3) fingerprint over uTLS, so Client() is already impersonating a real browser — you don't have to turn anything on.


Installation

pip install lithium-web

That's it.


Quickstart — major features, individually

Requests-style HTTP

from lithium import Client

client = Client()
r = client.get("https://api.example.com/users?limit=10")
print(r.status_code)          # 200
print(r.ok)                   # True
print(r.json())               # parsed body

# POST with JSON
r = client.post("https://api.example.com/users", json={"name": "Ada"})

# POST with a form body (auto urlencoded)
r = client.post("https://httpbin.org/post", data={"q": "lithium"})

# POST multipart upload
r = client.post("https://httpbin.org/post",
                data={"note": "hi"},
                files={"file": ("report.pdf", b"%PDF-1.4 ...")})

Selenium-style page interaction

from lithium import Client

page = client.get("https://example.com/login")
page.fill("#username", "alice")
page.fill("#password", "secret")
page.click("button[type=submit]")     # navigates; `page` updates in place
page.wait_for("#dashboard")           # waits for the element to appear
print(page.text)                      # text of the current page

Execute JavaScript

page = client.get("https://example.com")
page.execute_script("document.body.dataset.loaded = 'true'")
print(page.dom.query("body").get_attr("data-loaded"))   # 'true'

Query the DOM

page = client.get("https://example.com/products")
items = page.dom.query_all(".product")
for el in items:
    print(el.text, el.get_attr("data-price"))

Screenshots

page = client.get("https://example.com")
page.screenshot("page.png")          # writes a PNG, returns the path
png_bytes = page.screenshot()        # or get raw bytes

Async client

import asyncio
from lithium import AsyncClient

async def main():
    async with AsyncClient() as client:
        r = await client.get("https://example.com")
        print(r.status_code)

asyncio.run(main())

Streaming downloads

client = Client()
client.stream_to_file("https://example.com/bigfile.bin", "bigfile.bin")

Proxies & rotating proxies

# single proxy
client = Client(proxy="socks5://user:pass@host:1080")

# rotating proxy pool (round-robin per request)
client = Client(proxies=[
    "socks5://u:p@proxy1:1080",
    "socks5://u:p@proxy2:1080",
])

Custom CA & mutual TLS

client = Client(
    ca_cert="/path/to/ca.pem",
    client_cert="/path/to/client.crt",
    client_key="/path/to/client.key",
)

Geo / locale auto-detection

client = Client(auto_location=True)     # timezone + currency + country
r = client.get("https://example.com")
print(r.timezone, r.currency, r.country_code)

Network inspection

page = client.get("https://example.com")
for req in page.network.requests:        # every HTTP + in-page fetch/XHR
    print(req.method, req.status, req.url)

Custom browser profile / fingerprint

# pick a coherent identity (UA + JA3 + navigator + GPU all agree)
client = Client(platform="windows", browser="chrome", fingerprint="my-identity")

One script that uses everything

import asyncio, json
from lithium import Client, AsyncClient

# 1. Requests-style client, undetected by default
client = Client(proxies=["socks5://u:p@h1:1080", "socks5://u:p@h2:1080"])
client.get("https://example.com/set-session")

# 2. A page we interact with, like Selenium
page = client.get("https://example.com/login")
page.fill("#username", "alice")
page.fill("#password", "secret")
page.check("#terms")
page.click("button[type=submit]")
page.wait_for("#dashboard")
print("landed:", page.status_code)

# 3. DOM + JS
rows = page.dom.query_all("table tbody tr")
page.execute_script("document.title = 'hello from js'")

# 4. Run JS that does real network (in-page fetch)
page.execute_script(
    "fetch('/api/data').then(r=>r.json())"
    ".then(d=>document.body.setAttribute('data-h', d.hello));"
)

# 5. Rich media + screenshot
print("media:", page.media)            # discovered video/audio/img sources
page.screenshot("dashboard.png")

# 6. Inspect every request the session made (HTTP + JS-driven)
for req in page.network.requests:
    print("  net:", req.method, req.status, req.url)

# 7. Large download, streamed to disk
client.stream_to_file("https://example.com/db.sqlite", "db.sqlite")

# 8. Plain HTTP / JSON when you don't need a page
r = client.post("https://example.com/api", json={"a": 1})
print(r.json())

# 9. Async when you need concurrency
async def main():
    async with AsyncClient() as ac:
        results = await asyncio.gather(*[ac.get("https://example.com") for _ in range(5)])
        print([r.status_code for r in results])
asyncio.run(main())

Config reference

All options work as direct keyword args to Client(...) or via a Config/JSON file.

Option Default What it does
profile "chrome-windows" Coherent browser identity (UA + headers + JA3 + navigator).
fingerprint None Persistent identity name; same name = same profile across runs.
platform None Force windows / macos / linux / android / ios.
browser None Force chrome / firefox / opera / brave.
proxy None Single proxy URL (http/https/socks4/socks4a/socks5/socks5h).
proxies None List of proxy URLs, rotated round-robin per request.
verify_ssl True Verify server certificates.
ca_cert, client_cert, client_key None Custom CA bundle and client certificate (mTLS).
trust_env True Respect HTTP_PROXY/HTTPS_PROXY/NO_PROXY env vars.
tls_spoof None Explicitly enable/disable TLS (JA3) impersonation.
http3 False Try HTTP/3 (QUIC) with graceful h1/h2 fallback.
execute_scripts False Auto-run inline <script> on page load.
js_timeout 10.0 Max seconds a JS call may run.
timeout 30.0 Default request timeout (seconds).
max_redirects 0 Redirect limit (0=Go default 10, -1=don't follow).
max_idle_conns, max_idle_conns_per_host 100, 10 HTTP connection-pool sizing.
viewport_width, viewport_height platform default Screen/inner size (screen, innerWidth, matchMedia).
device_scale_factor platform default devicePixelRatio (mobile auto-sets to 2).
screen_color_depth 24 screen.colorDepth / pixelDepth.
hardware_concurrency, device_memory from fingerprint navigator.hardwareConcurrency / .deviceMemory.
max_touch_points 0 navigator.maxTouchPoints.
do_not_track None navigator.doNotTrack.
audio_sample_rate 44100 AudioContext().sampleRate.
screenshot_width, screenshot_height 1280, 800 PNG size for page.screenshot().
virtual_gpu True Enable virtual WebGL context.
canvas_noise True Make toDataURL vary per identity.
auto_location, auto_timezone, auto_currency, auto_language None Auto-detect locale from your IP (via geo_url).
geo_url ip-api.com Custom geo lookup endpoint.
storage_file None Persist localStorage across runs/sessions.
socket_timeout None Seconds to wait for a reply from the runtime.
shared True Reuse one runtime process across clients.
binary auto Path to the runtime binary (override).
verbose False Print go-side logs to stderr.
solution False Attach live DNS/TCP/TLS diagnostics to errors.

Why lithium (comparison)

Capability lithium primp tls_client curl_cffi
TLS (JA3) impersonation
Coherent multi-layer identity (UA↔headers↔navigator↔GPU) partial partial partial
Selenium-style interaction (click/fill/wait_for/select)
Full DOM querying (query/query_all)
JavaScript execution
In-page fetch()/XHR → real HTTP
localStorage/indexedDB/WebSocket/WebCrypto
Screenshots
Network inspection (page.network.requests)
Zero-config browser identity (no libs/browser install)
Async
HTTP/2 fingerprint (Chrome SETTINGS) partial
HTTP/3 opt-in
Streaming downloads
Proxy rotation / mTLS / CA

High stealth: Sannysoft proof

The runtime drives the official bot.sannysoft.com page and reads its own detection table: 8 / 8 passed, 0 failed. Run it yourself:

import json, re
from lithium import Client

with Client(execute_scripts=False, browser="chrome", platform="windows") as client:
    page = client.get("https://bot.sannysoft.com/")
    html = page.text

    # the site's own detection needs lodash (it uses _.has on navigator.webdriver)
    page.execute_script(
        client.get("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js", timeout=15).text
    )

    # run the site's OWN detection script (fills the real main result table)
    for s in re.findall(r'<script(?![^>]*src)[^>]*>(.*?)</script>', html, re.S):
        if "runBotDetection" in s:
            try:
                page.execute_script(s)   # the site's real detection code
            except Exception:
                pass                     # harmless canvas-hash noise; table already filled
            break

    counts = json.loads(page.execute_script(
        "JSON.stringify({passed: document.querySelectorAll('.result.passed').length,"
        " failed: document.querySelectorAll('.result.failed').length})"))
    print(f"OFFICIAL sannysoft: {counts['passed']} passed, {counts['failed']} failed")

Output: OFFICIAL sannysoft: 8 passed, 0 failed.

Proof of undetected: the default Client() also speaks a Chrome JA3 (e.g. 771,4865-4866-4867-49195-49199-...) verified against https://tls.peet.ws/api/all, and the JA3 version matches the UA version.


Every feature at a glance

HTTP

  • get / post / put / patch / delete / head / options
  • Bodies: plain bytes/str, JSON (json=), urlencoded (data=dict), multipart (files=)
  • Cookies persisted per session automatically
  • Redirect policy (max_redirects)
  • Rotating proxy pools (proxies=[...])
  • Connection pooling tuning (max_idle_conns*)
  • Streaming downloads (stream_to_file)
  • Custom CA + mTLS (ca_cert / client_cert / client_key)
  • Geo / locale auto-detect (auto_location + friends, custom geo_url)
  • Network inspection (client.network, page.network.requests)
  • Async client (AsyncClient)
  • HTTP trailers (resp.trailers)

Browser runtime

  • DOM: page.dom.query / query_all, attributes, text, html, events
  • Interaction: fill, type, click, submit, select_option, check, uncheck, clear, wait_for, goto
  • JavaScript: page.execute_script, auto-run inline scripts
  • In-page fetch() / XMLHttpRequest → real HTTP
  • Storage: localStorage, sessionStorage (optionally persisted)
  • APIs: WebCrypto, MutationObserver, History/location, WebSocket, IndexedDB, matchMedia, getComputedStyle, performance, permissions, getBattery
  • Virtual graphics: Canvas 2D, WebGL, AudioContext
  • Screenshots (page.screenshot)
  • Media discovery + download (page.media, download_media)
  • Coherent fingerprints (UA ↔ JA3 ↔ navigator ↔ GPU ↔ screen)

API reference — unique functions

Client

  • client.get/post/put/patch/delete/head/options(...)
  • client.download(url, path) — fetch and write to disk
  • client.stream_to_file(url, path) — stream response straight to disk
  • client.networkNetworkLog of every request (iterable, .requests, .responses)
  • client.close() / context manager

Response (a.k.a. the page)

  • status_code, headers, trailers, url, elapsed_ms, ok, text, json(), content
  • timezone, currency, country_code (when auto-location on)
  • dom → query/query_all/wait_for
  • execute_script(js)
  • fill, type, click, submit, select_option, check, uncheck, clear
  • wait_for(selector, timeout=), goto(url) (both update the page in place)
  • screenshot(path=None) → writes/returns PNG
  • networkNetworkLog
  • media → discovered <video>/<audio>/<img> sources
  • download(url, path), download_media(dir, kinds=None)
  • stream(chunk_size=) — chunked iterator

Element

  • text, html, get_attr(name, default=None), set_attr(name, value), remove()
  • add_event_listener(event, cb), dispatch_event(event, detail=None)
  • click(), fill(v), type(text, delay=), select_option(v), clear(), check(), uncheck(), submit()

AsyncClient

  • await versions of get/post/put/patch/delete/head/options/stream_to_file/download, plus network.

Made by Blaze, available on discord & github

feel free to contact me regarding any issues, suggestions or queries

License & note

lithium-web is under active development. The name, PyPI, and trademark availability should be confirmed before any public release, since similarly-named projects already exist.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lithium_web-1.0b1.tar.gz (37.5 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lithium_web-1.0b1-py3-none-any.whl (37.7 MB view details)

Uploaded Python 3

File details

Details for the file lithium_web-1.0b1.tar.gz.

File metadata

  • Download URL: lithium_web-1.0b1.tar.gz
  • Upload date:
  • Size: 37.5 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.4

File hashes

Hashes for lithium_web-1.0b1.tar.gz
Algorithm Hash digest
SHA256 2b60b7433b29d479c6d2c22bc8ce41e558efee78811e331e2b70e67e225ba28a
MD5 671227eef5bc2c27ae949d78f61564ac
BLAKE2b-256 fb81950c44584a1b87cb434406492128326b602aca2e4cc606c8ae9cde697ca5

See more details on using hashes here.

File details

Details for the file lithium_web-1.0b1-py3-none-any.whl.

File metadata

  • Download URL: lithium_web-1.0b1-py3-none-any.whl
  • Upload date:
  • Size: 37.7 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.4

File hashes

Hashes for lithium_web-1.0b1-py3-none-any.whl
Algorithm Hash digest
SHA256 473167c66e7a3db5b0c6d6c5182c4cd7a36ab4fbde964f9587354cc3b2b4492c
MD5 9e7ddd27f686363dc50d93b875d5e9c6
BLAKE2b-256 0cdceca6b1713ae6c0b7ecbe7319a9a4a7ba4ae34cfe3e93e0e621a975aa8a3e

See more details on using hashes here.

Release history Release notifications | RSS feed

1.0

1 file

This release

1.0b1 This release

2 files

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