Skip to main content

ZSChrome — RPA browser automation toolkit for Chrome. Control your own browser with Python via Chrome Extension + WebSocket.

Project description

ZSChrome

Python RPA toolkit for Chrome — control your own browser via a Chrome Extension + WebSocket. No WebDriver, no --remote-debugging-port. Your cookies, logins, and sessions stay intact.

Architecture

Python script  ←→  WebSocket (ws://127.0.0.1:9527)  ←→  Chrome Extension (MV3)
                                                         ├─ Content Script (DOM read/write)
                                                         └─ CDP (Input.dispatch* → trusted events)
  • Content Script handles DOM queries, locators, data extraction (bypasses CSP unsafe-eval restrictions on most pages)
  • CDP (Chrome DevTools Protocol) dispatches trusted OS-level mouse/keyboard events — sites like Taobao that ignore synthetic events still respond

Quick Start

1. Install

pip install zschrome        # core
pip install zschrome[excel]  # with Excel export support

2. Load the Chrome Extension (one-time)

  1. Open Chrome → chrome://extensions/
  2. Enable Developer mode
  3. Click Load unpacked → select the extension/ directory from this repo
  4. The extension badge shows "未连接" — it will connect when your script runs

3. Your first script

import asyncio
from zschrome import Browser

async def main():
    browser = await Browser.connect_or_launch("https://www.baidu.com")
    page = browser.current_page

    await page.wait_for_selector("input#kw")
    await page.globalclick("input#kw")
    await page.globaltype("旺旺")
    await page.globalkey("Enter")

    await page.wait_for_selector("#content_left")
    results = await page.eval(
        "document.querySelectorAll('#content_left .result').length"
    )
    print(f"找到 {results} 条结果")
    await browser.close()

asyncio.run(main())

Core API

Browser lifecycle

# Auto-launch Chrome + connect (recommended)
browser = await Browser.connect_or_launch("https://www.ctrip.com")

# Or connect to an already-running Chrome
browser = await Browser.connect()

page = browser.current_page
await browser.close()

CDP trusted events

Use these on sites that block synthetic DOM events (Taobao, Ctrip, etc.):

# Click by CSS or XPath (auto-detected)
await page.globalclick("input#q")
await page.globalclick('//*[@id="search-btn"]')

# Type text character-by-character
await page.globaltype("机械键盘")

# Press named keys
await page.globalkey("Enter")
await page.globalkey("Tab")

Locator API (Playwright-style)

# Fill an input
await page.locator("#hotels-destination").fill("上海神旺大酒店")

# Click by text
await page.get_by_text("搜索").click()

# Read attributes
val = await page.locator("#checkIn").get_attribute("value")

# Get all text contents in one round-trip
texts = await page.locator(".day-cell").all_text_contents()

# Nth element
await page.locator(".item").nth(2).click()

# Wait for visibility
await page.locator(".results").wait_for("visible", timeout=30)

Waiting & navigation

await page.wait_for_selector("input#kw", timeout=30)
await page.wait_for_selector('//*[@id="results"]', timeout=60)
await page.wait_for_load(timeout=30)
await page.navigate("https://example.com")

Bulk extraction: extract_cards

Quick product/card extraction without manual selector setup:

products = await page.extract_cards()
# → [{"title": "...", "price": "...", "shop": "...", "sales": "...", "link": "..."}, ...]

# Or with custom selectors
products = await page.extract_cards(
    card_selectors=["div[class*='item']", "li[class*='product']"],
    title_selectors=["[class*='title']", "h3"],
    price_selectors=["[class*='price']", "strong"],
)

Bulk extraction: query_all + Field

Precision extraction with container + sub-selector pattern:

from zschrome.selector import Selector
from zschrome.field import Field

fields = {
    "name":     Field.text(sub_selector=".hotel-name span"),
    "star":     Field.html(sub_selector=".star-rating"),
    "room":     Field.text(sub_selector=".room-type"),
    "location": Field.text(sub_selector=".location span"),
    "price_col": Field.text(sub_selector=".price-column"),
}

hotels = await page.query_all(Selector.css("#hotel-list div[id]"), fields)
for h in hotels:
    print(h["name"], h["price_col"])

Tab management

# List all open tabs
tabs = await page.list_tabs()
# → [{"id": 123, "url": "...", "title": "...", "active": true}, ...]

# Switch to a tab
await page.switch_to_tab(tab_id)

# Find a tab by URL
tab_id = await page.find_tab("search")

Scrolling

await page.scroll_to(y=3000)
await page.scroll_to(x=0, y=0)  # back to top

Keyboard

await page.keyboard.press("Enter")
await page.keyboard.press("Control+A")
await page.keyboard.type("hello")
await page.keyboard.insert_text("paste this value")

JavaScript eval (when CSP allows)

# Simple expression
count = await page.eval("document.querySelectorAll('.item').length")

# Arbitrary code
data = await page.eval_raw('''
    const cards = document.querySelectorAll('.card');
    return Array.from(cards).map(c => ({
        title: c.querySelector('.title')?.innerText,
        price: c.querySelector('.price')?.innerText,
    }));
''')

Note: eval/eval_raw are blocked on sites with strict CSP (e.g., Ctrip). Use query_all, extract_cards, or the Locator API instead.

Manual mode

await page.wait_manual("Please solve the captcha")
# Shows a prompt in terminal, user presses Enter when ready

Or just use Python's input():

print("请在浏览器中完成验证码后按 Enter")
input()

Complete examples

Baidu search

import asyncio
from zschrome import Browser

async def main():
    browser = await Browser.connect_or_launch("https://www.baidu.com")
    page = browser.current_page

    await page.wait_for_selector("input#kw", timeout=15)
    await page.globalclick("input#kw")
    await page.globaltype("旺旺")
    await page.globalkey("Enter")

    await page.wait_for_selector("#content_left", timeout=15)
    count = await page.eval(
        "document.querySelectorAll('#content_left .result').length"
    )
    print(f"找到 {count} 条结果")

    await browser.close()

asyncio.run(main())

Taobao product crawling with Excel export

import asyncio
from zschrome import Browser

async def main():
    browser = await Browser.connect_or_launch("https://www.taobao.com")
    page = browser.current_page

    keyword = "水神 次氯酸水"
    await page.globalclick("input#q")
    await page.globaltype(keyword)
    await page.globalkey("Enter")
    await asyncio.sleep(3)

    products = await page.extract_cards()
    print(f"提取到 {len(products)} 个商品")

    # Export with openpyxl or xlsxwriter...
    await browser.close()

asyncio.run(main())

Ctrip hotel search with date picker

import asyncio
from zschrome import Browser, Selector, Field

CHECKIN = "2026-05-25"
CHECKOUT = "2026-05-27"
KEYWORD = "上海神旺大酒店"

async def main():
    browser = await Browser.connect_or_launch("https://www.ctrip.com/")
    page = browser.current_page

    # Select dates: click checkIn → calendar popup → click day cells
    await page.globalclick("//*[@id='checkIn']")
    await asyncio.sleep(1.5)
    # ... click calendar day for CHECKIN, then CHECKOUT

    # Search
    await page.wait_for_selector('//*[@id="hotels-destination"]', timeout=60)
    await page.globalclick('//*[@id="hotels-destination"]')
    await page.globaltype(KEYWORD)
    await page.globalclick('//*[@id="kakxi"]/li[6]/div')

    # Extract
    await page.wait_for_selector('//*[@id="hp_container"]/div[2]/div[2]/div[2]/div[3]/div[1]', timeout=60)
    hotels = await page.query_all(
        Selector.css("#hp_container div[id]"),
        {
            "name": Field.text(sub_selector="div:nth-of-type(2) > div:nth-of-type(1) > div:nth-of-type(1) > div > span"),
            "location": Field.text(sub_selector="div:nth-of-type(2) > div:nth-of-type(1) > div:nth-of-type(3) > span:nth-of-type(1)"),
        }
    )
    for h in hotels:
        name = (h.get("name") or "").strip()
        if name:
            print(name)

    await browser.close()

asyncio.run(main())

Project structure

zschrome/
├── __init__.py      # Public API exports
├── browser.py       # Browser launch + WebSocket connect
├── page.py          # Page object (all actions)
├── transport.py     # WebSocket server
├── selector.py      # CSS/XPath Selector
├── field.py         # Field extraction definitions
├── locator.py       # Playwright-style Locator
├── keyboard.py      # Keyboard actions
├── errors.py        # Exception hierarchy
└── protocol.py      # Request/response serialization

extension/           # Chrome Extension (MV3), load unpacked

sample/              # Working example scripts
├── search_baidu.py
├── crawl_taobao.py
└── query_hotel.py

Requirements

  • Python ≥ 3.10
  • Chrome or Chromium
  • Chrome Extension loaded in Developer Mode (extension/ folder)
  • websockets ≥ 12.0 (installed automatically)
  • Optional: xlsxwriter or openpyxl for Excel export

License

MIT

Project details


Download files

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

Source Distribution

zschrome-0.2.1.tar.gz (25.2 kB view details)

Uploaded Source

Built Distribution

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

zschrome-0.2.1-py3-none-any.whl (21.1 kB view details)

Uploaded Python 3

File details

Details for the file zschrome-0.2.1.tar.gz.

File metadata

  • Download URL: zschrome-0.2.1.tar.gz
  • Upload date:
  • Size: 25.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.3

File hashes

Hashes for zschrome-0.2.1.tar.gz
Algorithm Hash digest
SHA256 efe71c030de29fb45fce4ac2bf918989552e583f314446a7a7ebf2b3a60c97ce
MD5 7bbf3057b185137af879b6eb1f16f311
BLAKE2b-256 e3698c6e98f3517429e00cfb0f583b2f6065cd085f6c726d9e5b67af550bd060

See more details on using hashes here.

File details

Details for the file zschrome-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: zschrome-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 21.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.3

File hashes

Hashes for zschrome-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 20c2325eb39f1a8070a15ffea28fd0be4280b0af5bb0e56895ab182bf950758d
MD5 6f8ebc71abca109cbb327c3e30714d73
BLAKE2b-256 d66ad42b99f261a907d36da749788100726d796b5f2afe7263d1398439414dca

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page