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-evalrestrictions 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)
- Open Chrome →
chrome://extensions/ - Enable Developer mode
- Click Load unpacked → select the
extension/directory from this repo - 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:
xlsxwriteroropenpyxlfor Excel export
License
MIT
Metadata
Release files for zschrome 0.3.10
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| zschrome-0.3.10.tar.gz | 33.2 kB | Without provenance | Details for zschrome-0.3.10.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| zschrome-0.3.10-py3-none-any.whl | Python 3 | none | any | Details for zschrome-0.3.10-py3-none-any.whl |
Total release size: 61.6 kB
Release files / zschrome-0.3.10.tar.gz
| Download URL | zschrome-0.3.10.tar.gz |
|---|---|
| Size | 33.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d3dd0d75abfdf8b236947e9b5472ce213c48f39c77f12bd88d1d27abc92e7a26
|
|
BLAKE2b-256 checksum How to use checksums |
a02a1f5e59c05db667441f4a0fac2968df569e0ba2a81007e73f8e702c0a1c1f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.3
|
Release files / zschrome-0.3.10-py3-none-any.whl
| Download URL | zschrome-0.3.10-py3-none-any.whl |
|---|---|
| Size | 28.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
df3a77d3de80a6b0dfec4a608cdc011a5396c1b5475d00ea38c78c09cd764411
|
|
BLAKE2b-256 checksum How to use checksums |
250bae74389b3ab9938c40b959a65ade9b1c8944f8ca519d8069a6b1f4665f5a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.3
|
Security for zschrome 0.3.10
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.