Skip to main content

chrome-scraper

Stealth and stateful Chrome scraper, built to let AI agents easily grab up-to-date information in markdown.

Shared browser server with profile persistence, with pre-built CLI scrapers for Google and x.com that dump content in HTML + clean markdown.

chrome-scraper is a Python package built on Patchright (a maintained Playwright fork) that runs a single long-lived Chrome instance behind a FastAPI HTTP API. Clients can connect concurrently, share cookies/sessions via a persistent profile, and avoid the cold-start/teardown cost of per-task browser launch.

You can find some prebuild skills under .agents/skills

Install

# Core package
uv tool install chrome-scraper

# Or install from source
uv sync

Supports Python 3.10–3.13.

Quickstart

# 1. Start the shared browser server (keep running)
uv run browser-api [--headless]

# 2. In another terminal — render a URL to markdown
html-to-md https://example.com --output out/example.md

# 3. Search Google and download results
google-fetch --query "latest pydantic version" --max-results 2 --num-pages 1 --out-dir data/searches/latest-pydantic-version

# 4. Search x.com and download tweets
xcom-fetch --query "latest ai news" --max-results 10 --out-dir data/research/xcom-latest-ai-news

browser-api is a persistent server — start it once, run scrapers against it from multiple terminals or scripts. Chrome keeps cookies, logins, and profile state across sessions.

Architecture

┌──────────────────────────────────────────────────────┐
│                browser-api (port 9333)                │
│  ┌──────────────────────────────────────────────────┐ │
│  │  FastAPI server                                  │ │
│  │  /status /tabs /goto /eval /html /type /press │ │
│  └──────────┬───────────────────────────────────────┘ │
│             │ owns                                    │
│  ┌──────────▼───────────────────────────────────────┐ │
│  │  Chrome (Patchright persistent context)          │ │
│  │  Profile                                         │ │
│  │  Tabs: separate label-keyed sandboxes            │ │
│  └──────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
         ▲ HTTP                         ▲ HTTP
         │                              │
┌────────┴──────────┐    ┌──────────────┴──────────────┐
│  html-to-md       │    │  google-fetch / xcom-fetch  │
│  short-lived      │    │  short-lived CLIs           │
│  open tab →       │    │  open tab → search →        │
│  extract → close  │    │  fetch each result → close  │
└───────────────────┘    └─────────────────────────────┘

The server patches Patchright's crBrowser.js so new tabs open in background — Chrome never steals focus during concurrent scraping.

CLI reference

browser-api

Start, stop, and check status of the shared browser server.

uv run browser-api                       # start on :9333 (default)
uv run browser-api --port 8080           # custom port
uv run browser-api --headless            # headless mode
uv run browser-api --chrome-path /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome
uv run browser-api --profile-dir ~/my-chrome-profile
uv run browser-api --hide                # hide Chrome window (macOS)
uv run browser-api --proxy http://proxy:8080
uv run browser-api --browser-args="--disable-gpu --no-sandbox"
uv run browser-api status                # check if running
uv run browser-api stop                  # shut down

Stateful features:

  • Persistent profile — cookies, localStorage, and logins survive restarts. Profile lives at ~/Library/Application Support/thebase/playwright/profile/ on macOS.
  • Headless identity — when --headless is used, the server automatically probes a throwaway headless instance, strips the HeadlessChrome token from the User-Agent, and passes the clean UA to the real persistent context.
  • Hide macOS window — --hide runs osascript to hide Chrome from sight without quitting (non-headless only).

url-to-md

Render a live URL as layout-preserving markdown. Site-specific handlers are used automatically when available — for example, YouTube watch URLs are expanded and transcript markdown is prepended before the generic page render.

# Generic page
url-to-md https://example.com --output out/page.md

# Custom handler kicks in automatically for supported URL patterns
url-to-md "https://www.youtube.com/watch?v=JVTUtdzqeGI" --output out/video.md

# Print to stdout
url-to-md https://example.com --output -

Direct PDF URLs are downloaded and converted with Poppler's pdftotext. When writing to a file, url-to-md keeps the original PDF beside the extracted markdown. google-fetch likewise writes <name>.pdf plus <name>.md instead of HTML for PDF results. Poppler must be installed and pdftotext available on PATH.

html-to-md

Render any URL (or local HTML file) as layout-preserving markdown.

# Live URL
html-to-md https://example.com --output out/page.md

# Local HTML file
html-to-md --from-file page.html --output out/page.md

# Print to stdout
html-to-md https://example.com --output -

# Save raw text-node payload alongside markdown
html-to-md https://example.com --save-items

# Skip scroll pass (for already-scrolled SPAs)
html-to-md https://example.com --no-scroll

# Verbose layout diagnostics
html-to-md https://example.com -v

# Custom browser-api URL
html-to-md https://example.com --browser-api http://localhost:9333

Rendering preserves multi-column layout, code blocks, headings, links, inline code, and list structure. Sidebar content is separated by a --- rule. See docs/layout.md for details on the layout-to-markdown algorithm.

google-fetch

Search Google and download each result as HTML + markdown.

# Basic search, 1 page
google-fetch --query "quantum computing"

# Multi-page with output dir
google-fetch --query "machine learning transformers" \
  --num-pages 3 --out-dir data/research/transformers

# Filter by hostname
google-fetch --query "python typing" \
  --allowed-hosts docs.python.org peps.python.org

# Limit total results
google-fetch --query "Rust async" --max-results 5

# Custom browser-api server
google-fetch --query "agents" --browser-api http://localhost:9333

Output layout:

data/research/<query-slug>/<tag>/
├── results.json           # title + URL index
├── 01-introduction-to.html
├── 01-introduction-to.md   # frontmatter + rendered markdown
├── 02-advanced-topics.html
├── 02-advanced-topics.md
└── ...

xcom-fetch

Search x.com and download tweets as HTML + markdown.

# Keyword search
xcom-fetch --query "reinforcement learning"

# Restrict to one account
xcom-fetch --query "safety" --from "Anthropic"

# Limit results
xcom-fetch --query "alignment" --max-results 5

# Custom output dir
xcom-fetch --query "scaling laws" --out-dir data/tweets

Output layout:

data/research/xcom-<query>/
├── results.json           # permalink + author + text snippet
├── 01-anthropic-12345.html
├── 01-anthropic-12345.md   # frontmatter + rendered markdown
└── ...

yt-fetch

Fetch a YouTube video page with expanded description and transcript as HTML + markdown.

# Fetch video by URL
yt-fetch "https://www.youtube.com/watch?v=JVTUtdzqeGI"

# Short URL
yt-fetch "https://youtu.be/JVTUtdzqeGI"

# Custom output dir
yt-fetch "https://www.youtube.com/watch?v=JVTUtdzqeGI" --out-dir data/youtube/video

Output layout:

data/youtube/<video-id>/
├── <video-id>.html        # raw outerHTML with expanded transcript
├── <video-id>.md          # frontmatter + rendered markdown

insta-fetch

Search Instagram and download posts/reels as HTML + markdown.

Requires login — start browser-api non-headless once, log in at instagram.com. Cookies persist in the profile for future runs.

# Keyword search
insta-fetch --query "aurora borealis"

# Profile posts
insta-fetch --profile "nasa"

# Hashtag search
insta-fetch --hashtag "space"

# Single post by URL
insta-fetch --post "https://www.instagram.com/p/CxYZ12345/"

# Limit results
insta-fetch --query "machine learning" --max-results 5

Output layout:

data/research/insta-<query>/
├── results.json           # permalink + shortcode + type + caption snippet
├── 01-username-ABC123.html
├── 01-username-ABC123.md   # frontmatter + rendered markdown
└── ...

Python API

from pathlib import Path
from chrome_scraper.html_to_md import extract_from_url, render_page

# Extract text-node payload from a URL
payload = extract_from_url(
    "https://example.com",
    browser_api_url="http://localhost:9333",
    timeout=30.0,
    scroll=True,
)

# Render to layout-preserving markdown
items = payload.get("items", [])
page_width = (payload.get("viewport") or {}).get("scroll_w", 1280)
md = render_page(items, page_width)
Path("out/example.md").write_text(md, encoding="utf-8")

Or manage the browser lifecycle yourself:

from chrome_scraper.browser_api.client import BrowserAPIClient
from chrome_scraper.html_to_md.extract import extract_page

client = BrowserAPIClient(timeout=30.0)
with client.tab("my-tab"):
    payload = extract_page(
        "https://example.com",
        client,
        tab_ref="my-tab",
        timeout=30.0,
        scroll=True,
    )

At a glance

browser-api — shared Chrome behind HTTP:

  • Persistent profile with cookies/logins.
  • Label-keyed tabs for concurrent clients.
  • Tab lifecycle isolated per client (open → use → close).
  • Background-tab patch so Chrome stays out of the way.
  • Headless-mode UA cleaning (strips HeadlessChrome).
  • macOS hide support (--hide).

url-to-md — single live URL to markdown:

  • Uses the shared URL dispatcher and custom handlers automatically when a URL pattern is supported.
  • Falls back to generic layout-preserving extraction for unknown sites.
  • Downloads PDF pages and extracts their text with pdftotext.

html-to-md — layout-preserving markdown via Chrome CDP:

  • Extracts every rendered text node with position and styling.
  • Detects columns via x-start histogram peaks.
  • Splits main/sidebar content via widest vertical gutter.
  • Preserves code blocks, headings, links, inline code, lists.
  • Row boundaries computed from intersecting column gap sets — long main-column paragraphs stay intact regardless of sidebar density.

google-fetch — multi-page Google scraping:

  • Paginates through Google result pages.
  • Visits each result link, dumps outerHTML + rendered markdown.
  • Navigates back to search results after each fetch.
  • Optional hostname filtering and result count limits.

xcom-fetch — x.com tweet scraping:

  • Drives x.com's React search UI via native keyboard (Patchright).
  • Virtual list scrolling to populate results.
  • Visits each tweet permalink, dumps HTML + markdown.
  • SPA-safe navigation; falls back to direct URL navigation if anchor click fails.

Metadata

Release files for chrome-scraper 0.1.4

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

Source distribution (sdist)

Source distribution for chrome-scraper 0.1.4
File Size Uploaded
chrome_scraper-0.1.4.tar.gz 46.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for chrome-scraper 0.1.4
File Interpreter ABI Platform
chrome_scraper-0.1.4-py3-none-any.whl Python 3 none any Details

Total release size: 113.1 kB

Release files / chrome_scraper-0.1.4.tar.gz

Download URL chrome_scraper-0.1.4.tar.gz
Size 46.9 kB
Tags Source
SHA-256 checksum
How to use checksums
ad321c761e275fbb5b47ad4b65d7ee0f54ba688a5ae98d9dc53bb526d726b1c1
BLAKE2b-256 checksum
How to use checksums
2de9266a5d5234088639b6169aa9769121a7bd7a41178cf4845591b8ef205c63
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / chrome_scraper-0.1.4-py3-none-any.whl

Download URL chrome_scraper-0.1.4-py3-none-any.whl
Size 66.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
707df8d61a294e7d8ea0fe77b248e612d093123eafafdc2314326ac8881066d6
BLAKE2b-256 checksum
How to use checksums
cb8548ece58bd1284c2950b33899f6070acdc2892764f998678770cd351e677f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.1.4 This release

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release 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