A powerful CLI for DrissionPage — browser automation, structured data extraction, network listening and more.
Project description
dp-cli
A powerful CLI for DrissionPage — browser automation, structured data extraction, network listening and more.
Features
- Anti-detection by default — not based on webdriver,
navigator.webdriverisfalse - Reuse your own browser —
--auto-connect(Chrome/Edge 144+, no CLI flag needed) or--port - Hybrid snapshot — a11y tree + Vimium-style clickable detection, catches icon-only buttons
and custom menu items the a11y tree misses; every element gets an
[N]ref with confidence markers (⚡medium,?low) dp scan— fast Vimium-style listing of interactive elements (viewport-only mode available)- Powerful locator syntax — descriptive strings stable across navigation, plus
Playwright-style
pw:role=button[name="Submit"] >> nth=2chains - Structured data extraction —
extract+query+snapshotfor scraping list pages - Network listening — capture XHR/Fetch requests and response bodies
- Stealth patches —
dp stealthbypasses common automation detections - Dual mode — browser control + pure HTTP requests
- Shadow-root / iframe — traverse directly without switching context
- JSON output — all commands output JSON, AI-friendly
Installation
pip install dp-cli
dp --help
Quick Start
# Auto-managed browser
dp open https://example.com
dp snapshot
dp click "text:Login"
dp fill "@name=username" admin
dp press Enter
dp close
# Connect to your own logged-in browser
google-chrome --remote-debugging-port=9222
dp open https://example.com --port 9222
dp snapshot
Connect to a Normally-Launched Chrome/Edge (Chrome/Edge 144+)
No --remote-debugging-port required. Chrome/Edge 144+ exposes opt-in remote debugging
via chrome://inspect (Chrome) or edge://inspect/#devices (Edge):
- Open your Chrome or Edge as usual (no special flags)
- Visit
chrome://inspect/#remote-debugging(Chrome) oredge://inspect/#devices(Edge) - Check "Allow remote debugging for this browser instance"
- Run
dp open --auto-connect
dp open --auto-connect # auto channel: sniffs Chrome stable → Edge stable
dp open --auto-connect --channel edge # force Edge stable
dp open --auto-connect --channel beta # Chrome beta
dp open --auto-connect --probe-dir ~/my-profile # custom user-data-dir
How it works
Chrome/Edge 144+ in this mode exposes only a browser-level WebSocket and omits the HTTP
REST API (/json, /json/version, ...) that DrissionPage / puppeteer / Playwright
depend on. dp-cli transparently handles this:
- Reads
DevToolsActivePortfrom the user-data-dir → real CDP port - Probes the port — if
/json/versionis missing, identifies this as inspect mode - Spawns a local bridge (
python -m dp_cli.bridge) that:- Synthesizes the missing HTTP endpoints from CDP calls
- Multiplexes page-level CDP traffic over a single browser-level WebSocket
via
Target.attachToTarget(flatten=True)
- Points DrissionPage at the bridge. Subsequent
dpcommands reuse the same bridge.
The bridge subprocess and its port are tracked in the session file; dp close stops
the bridge automatically and never quits your browser (it's your browser, not dp's).
Caveats
- Chrome/Edge always shows an "Allow remote debugging" dialog per new WebSocket client.
Since bridge maintains one WebSocket and dp commands share it, you confirm at most
once per
dp open --auto-connect. - Works with whatever profile Chrome/Edge is actually using — same cookies, logins, history.
- Classic
--remote-debugging-port=9222mode still works unchanged viadp open --port 9222.
Hybrid Snapshot (a11y + Vimium-style)
The default dp snapshot fuses two element-discovery paths:
- Browser a11y tree via CDP — the structural skeleton (headings, lists, form roles,
explicit
<a>/<button>, anyrole="..."element). - Vimium-style clickable detection — a JS probe that flags icon-only buttons,
<div onclick>,[tabindex>=0],aria-selected,cursor:pointerelements, etc. that the a11y tree misses.
Results are deduplicated by backendNodeId and rendered with confidence markers:
| Marker | Confidence | Triggers |
|---|---|---|
| none | high | <a href>, <button>, <input>, role=button/link/..., contenteditable |
⚡ |
medium | onclick / jsaction / tabindex>=0 / aria-selected / <audio>/<video>, or cursor:pointer + heuristic (aria-label / icon child / small square / class keyword) — catches most React/Vue icon buttons |
? |
low | bare cursor:pointer / class keyword only (no other signals); hidden unless --include-low |
Output includes helpful context:
@top-left,@top-right,@center,@bottom… — position in the 9-region viewport grid(icon)— icon-only button (no visible label, has<svg>/<img>child)- Shadow DOM is traversed automatically (open shadow roots)
Every element gets an [N] ref usable in any command: dp click "ref:5".
dp snapshot # a11y + clickable (default); high + medium markers
dp snapshot --viewport-only # clickable probe limited to viewport (faster)
dp snapshot --include-low # also surface `?` low-confidence heuristics
dp snapshot --no-clickables # a11y tree only (legacy behavior)
dp scan — fast clickable-only listing
When you only need "what can I click next?" without the full a11y tree:
dp scan # full page, high+medium
dp scan --viewport # only elements currently in viewport
dp scan --confidence all # include low-confidence heuristics
dp scan --confidence high # only the sure-thing clickables
Both snapshot and scan share the same [N] ref numbering per session, so
dp click "ref:N" works regardless of which one produced the snapshot.
Playwright-style locators (pw: prefix)
Need semantic, role-based targeting on a fresh page (no snapshot required)?
Use the pw: prefix. Syntax mirrors Playwright, and chains with >>:
# By ARIA role (with accessible name — exact / substring / regex)
dp click 'pw:role=button[name="Submit"]'
dp click 'pw:role=button[name=/^Sign/i]'
dp click 'pw:role=link[name=More]' # substring
# By visible text (exact / substring / regex)
dp click 'pw:text="Login"' # exact
dp click 'pw:text=Login' # substring (case-insensitive)
dp click 'pw:text=/^log/i' # regex
# By form affordances
dp fill 'pw:placeholder=Search…' "chatgpt"
dp fill 'pw:label="Email"' "a@b.com"
dp click 'pw:alt="Logo"'
dp click 'pw:title="Close"'
dp click 'pw:testid=submit-btn' # data-testid / data-test-id / data-test
# Chain with >> (each step narrows the scope)
dp click 'pw:css=.sidebar >> role=listitem[name="Chat"] >> nth=2'
dp click 'pw:css=li >> has-text="Python"'
dp click 'pw:role=list >> nth=-1' # negative index = from end
# Raw css/xpath chunks mix freely
dp click 'pw:xpath=//nav >> role=link[name=Docs]'
Matchers: role / text / label / placeholder / alt / title /
testid / css / xpath / nth / has-text / visible
Value forms: bare = substring, "quoted" = exact, /pattern/flags = regex
Visibility: role / text / has-text automatically skip elements hidden
via display:none, visibility:hidden, hidden attribute, or
aria-hidden="true" (matches Playwright semantics).
Shadow DOM: open shadow roots are traversed automatically.
Under the hood the matcher chain is evaluated in-page as JS, the target element
is tagged with a one-shot data-dp-ref attribute, and DrissionPage resolves it
by that attribute — bypassing stale classes / CSS Modules / dynamic XPath.
Anti-Detection (stealth)
Bypass navigator.webdriver, HeadlessChrome UA, empty plugins, SwiftShader WebGL,
chrome.runtime missing, and other common automation fingerprints.
# One-shot: connect + apply full stealth patches
dp open --port 9322 --stealth
dp goto https://bot.sannysoft.com/
# Or apply manually on an existing session (full preset by default)
dp stealth
dp stealth --preset mild # webdriver + UA only
dp stealth --ua "Mozilla/5.0 ..." # custom UA
dp stealth --feature webdriver --feature webgl # fine-grained
Recommended VPS Chrome flags (when connecting via SSH tunnel)
google-chrome --headless=new --remote-debugging-port=9222 \
--no-sandbox --disable-dev-shm-usage \
--disable-blink-features=AutomationControlled \
--user-data-dir=~/.config/google-chrome
# Then on local:
ssh -NL 9322:127.0.0.1:9222 vps
dp open --port 9322 --stealth
Patched features (full preset): webdriver, UA, chrome.runtime, permissions,
plugins, languages, WebGL VENDOR/RENDERER, window.outerWidth/Height.
Patches are injected via Page.addScriptToEvaluateOnNewDocument — they persist across
navigations and frames. Advanced fingerprints (Canvas/Audio/font list) require a real
GPU or Xvfb environment.
Data Extraction (3-step workflow)
# 1. Discover CSS class names via noise-filtered content tree
dp snapshot --mode content --max-text 40
# 2. Verify field selectors
dp query "css:.item-title" --fields "text,loc"
# 3. Batch extract to CSV
dp extract "css:.item-card" \
'{"title":"css:.item-title",
"price":"css:.item-price",
"tags":{"selector":"css:.tag","multi":true},
"url":{"selector":"css:a","attr":"href"}}' \
--limit 100 --output csv --filename result.csv
Project Structure
dp_cli/
├── main.py # CLI entry point (~47 lines)
├── session.py # Browser session management + auto-connect bridge glue
├── bridge.py # chrome://inspect mode CDP bridge (python -m dp_cli.bridge)
├── bridge_manager.py # Bridge subprocess lifecycle + inspect-mode detection
├── stealth.py # Anti-detection JS patches (applied via CDP)
├── snapshot/ # a11y-tree snapshot & data extraction engine
├── output.py # JSON output helpers
└── commands/
├── _utils.py # Shared decorators & helpers
├── browser.py # open / goto / reload / close / list / stealth
├── snapshot_cmd.py # snapshot / extract / query / find / inspect
├── element.py # click / fill / select / hover / drag / check / upload / count
├── keyboard.py # press / type / scroll / scroll-to / autoscroll
├── page.py # screenshot / pdf / eval / wait (idle/loaded/url/title) / dialog
├── tab.py # tab-list / tab-new / tab-select / tab-close
├── storage.py # cookie-* / localstorage-* / sessionstorage-*
├── network.py # listen / listen-stop / http-get / http-post
└── misc.py # resize / maximize / state-save / state-load / config-set
Documentation
See skills/SKILL.md for full workflow guide and skills/references/commands.md for complete command reference.
License
BSD-3-Clause
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 dp_cli-0.6.0.tar.gz.
File metadata
- Download URL: dp_cli-0.6.0.tar.gz
- Upload date:
- Size: 102.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e75dc3cdfc00ce1a80b1016bc9c3ba9a4048e273210ed0ba06967d0b51eec439
|
|
| MD5 |
6470af9095610d9fc2e94204ed023bff
|
|
| BLAKE2b-256 |
29f09cc128ea9fc79223131e9f2d05b4b5ddb5a074c75b763a614dc5881dde6a
|
File details
Details for the file dp_cli-0.6.0-py3-none-any.whl.
File metadata
- Download URL: dp_cli-0.6.0-py3-none-any.whl
- Upload date:
- Size: 102.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0797150c5f2c510ec650ac3f9635614f377307dc5e371d1289e7f698cb6080eb
|
|
| MD5 |
6ebc718b58012d0b1c9b2fae5f7c457d
|
|
| BLAKE2b-256 |
9db336b4591aafe4dad7967405966942b6afa720256c20526525229ca8e2ebaf
|