Skip to main content

lightpanda-python

Lightpanda for Python: scrape and automate the web with pip install lightpanda. Browser included, no separate download.

Lightpanda is an open-source headless browser built for web scraping, automation, and AI agents. It executes JavaScript and renders pages like a full browser, but starts in milliseconds and uses an order of magnitude less memory than Chrome stacks. The wheel bundles the browser binary; there is no second install step.

uv add lightpanda        # or: uv pip install lightpanda / pip install lightpanda

example.py:

from lightpanda import Browser

with Browser() as b:
    page = b.new_session()
    page.goto(url="https://example.com")
    data = page.extract(schema={"title": "h1"})
    print(data)

Run it like any Python script — no driver to install, no browser to download:

$ python example.py
{'title': 'Example Domain'}

The same API is available for asyncio — AsyncBrowser spawns the browser on first use and runs sessions concurrently. async_example.py:

import asyncio

from lightpanda import AsyncBrowser


async def main():
    async with AsyncBrowser() as b:
        page = await b.new_session()
        await page.goto(url="https://example.com")
        data = await page.extract(schema={"title": "h1"})
        print(data)


asyncio.run(main())
python async_example.py

The package also puts the full lightpanda CLI on PATH — agent REPL, fetch, serve, and the rest: see the command reference.

Drive it with Playwright or Puppeteer

Lightpanda has its own Chrome DevTools Protocol server, so existing Playwright/Puppeteer code works against it without Chromium. CDPServer starts lightpanda serve on a free localhost port and hands you the endpoint:

from lightpanda import CDPServer
from playwright.sync_api import sync_playwright

with CDPServer() as server, sync_playwright() as p:
    browser = p.chromium.connect_over_cdp(server.ws_endpoint)
    page = browser.new_context().new_page()
    page.goto("https://example.com")
    print(page.title())

AsyncCDPServer is the asyncio twin (async with AsyncCDPServer() as server). server.ws_endpoint is a plain CDP WebSocket, so Node clients connect to it too: Puppeteer with puppeteer.connect({ browserWSEndpoint }) and Playwright with chromium.connectOverCDP(...). Pass port= to pin the port, and args= for lightpanda serve flags such as --cdp-max-connections or --http-proxy. This is a separate process from Browser (the binary cannot serve MCP and CDP from the same one). The package itself needs only Python's standard library; Playwright is a dev-only test dependency and connect_over_cdp never downloads a browser.

Drive it with Selenium (WebDriver)

The browser also speaks WebDriver, both classic and BiDi. WebDriverServer starts lightpanda serve --protocol webdriver and hands you the URL Selenium's webdriver.Remote takes as command_executor, so no chromedriver is involved:

from lightpanda import WebDriverServer
from selenium import webdriver
from selenium.webdriver.common.by import By

with WebDriverServer() as server:
    # Selenium requires an options object; Lightpanda accepts any browser's.
    driver = webdriver.Remote(command_executor=server.http_endpoint, options=webdriver.ChromeOptions())
    driver.get("https://example.com")
    print(driver.find_element(By.CSS_SELECTOR, "h1").text)
    driver.quit()

In a pytest suite, start the server once in a session-scoped fixture and open a driver per test; with pytest-xdist each worker gets its own server on its own free port.

Set options.web_socket_url = True to also get a WebDriver BiDi session and use driver.browsing_context / driver.script. server.bidi_endpoint (ws://127.0.0.1:<port>/session) is the raw BiDi WebSocket for clients that speak the protocol directly. AsyncWebDriverServer is the asyncio twin, and args=["--protocol", "cdp"] serves CDP on the same port as well.

Respecting robots.txt

The browser can enforce robots.txt for you. It is off by default, matching the lightpanda binary's own default, and every wrapper takes browser flags through args=:

Browser(args=["--obey-robots"])                    # also AsyncBrowser
CDPServer(args=["--obey-robots"])                  # also WebDriverServer, and the async twins
run_script("saved.js", args=["--obey-robots"])

A request the site disallows then fails rather than being sent: a tool call raises ToolError: navigation failed: RobotsBlocked, and run_script raises ScriptError.

One thing to know before turning it on, because it is easy to mistake for a bug: the rule is applied to every request, not just the page you asked for. Plenty of sites disallow the directory their own assets live in, so a page you are allowed to fetch can load with its scripts and styles missing, and render blank or empty. That is robots.txt being honoured, not a failure — the site is asking you not to fetch those files. If a page comes back strangely empty under --obey-robots, read the site's robots.txt before assuming otherwise.

Whether a given scrape is acceptable is not a question robots.txt alone answers: a site's terms of service can forbid what its robots.txt permits.

How the bindings work

Every browser tool is a Session method, typed and documented in your IDE. The methods are not written by hand: they are generated from the bundled browser's MCP tool schemas (scripts/generate_methods.py → lightpanda/_methods.py), so signatures and docstrings come straight from the binary, with tool and parameter names in snake_case (waitForSelector → wait_for_selector, backendNodeId → backend_node_id). Session.call is the escape hatch that takes the raw tool and parameter names as the MCP server declares them. The full API reference is published at lightpanda.io/docs/reference/python.

The bindings follow Lightpanda's development and the package version tracks browser releases — there is no backwards-compatibility guarantee: when the browser's tools change, the Python methods change with them.

A tool returning JSON gives you the parsed value — extract a dict, links a list. goto and the actions answer with a sentence describing what they did, which also carries where it left the page, so a 404 is a field rather than something to find in prose:

r = page.goto(url="https://example.com/missing")
print(r)          # Navigated successfully. HTTP 404 Not Found.
if r.http_status >= 400:
    raise SystemExit(f"{r.url} is an error page, not content")

find_element and interactive_elements return elements that act on themselves: each is still the dict the browser sent, and every action that takes a backend_node_id is also a method on it:

found = page.find_element(role="button", name="Accept")
if found:
    found[0].click()        # also fill(value=...), hover(), markdown(), ...

Examples

examples/ scrapes a JavaScript-rendered site that requests cannot read, analyses the result with pandas/matplotlib, measures the same job against Selenium + headless Chrome (100 quotes in ~3 s / 35 MB vs ~6 s / 1.2 GB, median of 5 runs), and reads five years of NeurIPS — 19,219 paper abstracts that never appear in the HTML at all — then clusters them to find what the field started working on. Each script runs standalone: uv run examples/quotes_analysis.py.

License

This client library is Apache-2.0. The bundled Lightpanda browser binary is licensed separately under AGPL-3.0 — see lightpanda-io/browser.

Development

The runtime binary is resolved from LIGHTPANDA_BIN, the package directory, then PATH. For development, clone lightpanda-io/browser as a sibling checkout and build it (zig build) — or point LIGHTPANDA_BIN at any lightpanda binary. Then:

uv run --group dev pytest tests

The dev group includes playwright and selenium as clients for the CDP and BiDi tests (their pip packages only; no browser or driver download). Those tests skip when the client is absent.

Regenerate the tool methods (lightpanda/_methods.py) and the API docs:

uv run --no-project python scripts/generate_methods.py
uv run --no-project --with pdoc python scripts/build_docs.py   # writes docs/

Building a wheel

With a binary available (LIGHTPANDA_BIN, or the sibling checkout built ReleaseFast):

uv build --wheel                                    # -> dist/lightpanda-*-py3-none-<plat>.whl
LIGHTPANDA_PLAT=manylinux_2_35_x86_64 uv build --wheel   # CI: tag for the release runner's glibc

The wheel is platform-specific (it carries the binary) but works on every Python ≥3.10 — hence the py3-none-<plat> tag. Release wheels must bundle a ReleaseFast binary; a debug build works but is several times larger and slower.

Releasing

CI (.github/workflows/wheels.yml) builds and tests wheels for all four platforms on every pull request and push to main, bundling the browser's nightly release.

Publishing is automatic: when the browser repo builds a version release, its release workflow (the update-python-package job in browser's release.yml) dispatches the wheels workflow here with that tag. The run builds from that browser release, derives the wheel version from the tag, tests on all platforms, and publishes to PyPI via trusted publishing — after a maintainer approves the pypi environment deployment on the run page. Once published, the run's release job records the matching GitHub release here.

That job is what creates the release, rather than the browser repo doing it directly: the browser repo authenticates with a GitHub App that can start workflows here but deliberately has no write access to repository contents, so the release is created in-repo with the run's own GITHUB_TOKEN. A release created that way triggers no workflow, so it cannot loop back into the release: published build.

A release can also be triggered by hand: run the workflow from the Actions tab (any browser tag, publish to TestPyPI or PyPI) for dry runs, or create a GitHub release here whose tag matches a browser release tag — the release: published trigger is kept as an escape hatch and does everything the dispatch does.

To ship a packaging or client-side fix without waiting for a new browser release, dispatch the workflow with the browser tag and a post number, e.g. browser 0.4.0 with post 1: it bundles browser 0.4.0 again and publishes as the PEP 440 post-release 0.4.0.post1. Bare pip install lightpanda, >= and ~= requirements pick post-releases up; an exact ==0.4.0 pin deliberately does not, so pin with ~=0.4.0 to receive them. Creating a release tagged 0.4.0-1 (or 0.4.0.post1) by hand works too.

The API reference at lightpanda.io/docs/reference/python is regenerated daily from this repository's main branch by the docs repo's python-reference workflow, so a merged docstring or signature change shows up there with no step on this side.

Metadata

Release files for lightpanda 1.0.2

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

Built distributions (wheels)

Table of built distributions (wheels) for lightpanda 1.0.2
File
lightpanda-1.0.2-py3-none-manylinux_2_35_x86_64.whl Python 3 none Linux glibc 2.35+ x86-64 Details
lightpanda-1.0.2-py3-none-manylinux_2_35_aarch64.whl Python 3 none Linux glibc 2.35+ ARM64 Details
lightpanda-1.0.2-py3-none-macosx_14_0_arm64.whl Python 3 none macOS 14.0+ ARM64 Details
lightpanda-1.0.2-py3-none-macosx_13_0_x86_64.whl Python 3 none macOS 13.0+ x86-64 Details

Total release size: 115.0 MB

Release files / lightpanda-1.0.2-py3-none-manylinux_2_35_x86_64.whl

Download URL lightpanda-1.0.2-py3-none-manylinux_2_35_x86_64.whl
Size 28.6 MB
Tags Linux glibc 2.35+ x86-64 Python 3
SHA-256 checksum
How to use checksums
8f1e71951664130a7b8a4165ef763b802f549d6e60e3dff62102d89f8aea87df
BLAKE2b-256 checksum
How to use checksums
caa197e9e561081fe5c2e879da2f254f70a6b16637ba07cc572facc785eac027
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / lightpanda-1.0.2-py3-none-manylinux_2_35_aarch64.whl

Download URL lightpanda-1.0.2-py3-none-manylinux_2_35_aarch64.whl
Size 27.2 MB
Tags Linux glibc 2.35+ ARM64 Python 3
SHA-256 checksum
How to use checksums
0a45ffcc1b0a9d1f0868ad524bcb7fc35a1f979957855c71802bfa99c959a50f
BLAKE2b-256 checksum
How to use checksums
9395e9636b774de644d07785fad1294f49e753b1e0aa4b96e8376df06c52bf24
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / lightpanda-1.0.2-py3-none-macosx_14_0_arm64.whl

Download URL lightpanda-1.0.2-py3-none-macosx_14_0_arm64.whl
Size 28.7 MB
Tags Python 3 macOS 14.0+ ARM64
SHA-256 checksum
How to use checksums
314a530b8a0cb5b5fb06e75014894750483139d6e29387c78e68eb015bca1842
BLAKE2b-256 checksum
How to use checksums
e831c8972ad7cbed2efe4ecf01647516fa4fa562399690cce890bc6b839221c1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / lightpanda-1.0.2-py3-none-macosx_13_0_x86_64.whl

Download URL lightpanda-1.0.2-py3-none-macosx_13_0_x86_64.whl
Size 30.5 MB
Tags Python 3 macOS 13.0+ x86-64
SHA-256 checksum
How to use checksums
edc24e0c5e7ac28fbbe70cde0efbb9c227aeb0c7cd40c9ba6750da695f50b5b3
BLAKE2b-256 checksum
How to use checksums
384eea887f06170917279488fa672e81c702049188ff39a13a9ff14277123ca0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for lightpanda 1.0.2

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.

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