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-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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
efe71c030de29fb45fce4ac2bf918989552e583f314446a7a7ebf2b3a60c97ce
|
|
| MD5 |
7bbf3057b185137af879b6eb1f16f311
|
|
| BLAKE2b-256 |
e3698c6e98f3517429e00cfb0f583b2f6065cd085f6c726d9e5b67af550bd060
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
20c2325eb39f1a8070a15ffea28fd0be4280b0af5bb0e56895ab182bf950758d
|
|
| MD5 |
6f8ebc71abca109cbb327c3e30714d73
|
|
| BLAKE2b-256 |
d66ad42b99f261a907d36da749788100726d796b5f2afe7263d1398439414dca
|