nakalbrowser 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nakalbrowser-0.1.0/PKG-INFO +240 -0
- nakalbrowser-0.1.0/README.md +203 -0
- nakalbrowser-0.1.0/pyproject.toml +49 -0
- nakalbrowser-0.1.0/setup.cfg +4 -0
- nakalbrowser-0.1.0/src/nakalbrowser/__init__.py +119 -0
- nakalbrowser-0.1.0/src/nakalbrowser/__main__.py +3 -0
- nakalbrowser-0.1.0/src/nakalbrowser/ai/__init__.py +18 -0
- nakalbrowser-0.1.0/src/nakalbrowser/ai/captcha.py +124 -0
- nakalbrowser-0.1.0/src/nakalbrowser/ai/snapshot.py +256 -0
- nakalbrowser-0.1.0/src/nakalbrowser/boot/__init__.py +4 -0
- nakalbrowser-0.1.0/src/nakalbrowser/boot/boot.py +164 -0
- nakalbrowser-0.1.0/src/nakalbrowser/boot/ensure.py +272 -0
- nakalbrowser-0.1.0/src/nakalbrowser/boot/launch.py +364 -0
- nakalbrowser-0.1.0/src/nakalbrowser/boot/process.py +45 -0
- nakalbrowser-0.1.0/src/nakalbrowser/cli.py +110 -0
- nakalbrowser-0.1.0/src/nakalbrowser/cloak/__init__.py +4 -0
- nakalbrowser-0.1.0/src/nakalbrowser/cloak/face.py +451 -0
- nakalbrowser-0.1.0/src/nakalbrowser/cloak/veil.py +202 -0
- nakalbrowser-0.1.0/src/nakalbrowser/core/__init__.py +5 -0
- nakalbrowser-0.1.0/src/nakalbrowser/core/errors.py +131 -0
- nakalbrowser-0.1.0/src/nakalbrowser/core/knob.py +20 -0
- nakalbrowser-0.1.0/src/nakalbrowser/core/wire.py +198 -0
- nakalbrowser-0.1.0/src/nakalbrowser/dom/__init__.py +4 -0
- nakalbrowser-0.1.0/src/nakalbrowser/dom/chip.py +182 -0
- nakalbrowser-0.1.0/src/nakalbrowser/dom/ghost.py +48 -0
- nakalbrowser-0.1.0/src/nakalbrowser/dom/interact.py +129 -0
- nakalbrowser-0.1.0/src/nakalbrowser/dom/locator.py +514 -0
- nakalbrowser-0.1.0/src/nakalbrowser/dom/node.py +452 -0
- nakalbrowser-0.1.0/src/nakalbrowser/dom/shell.py +155 -0
- nakalbrowser-0.1.0/src/nakalbrowser/engine/__init__.py +11 -0
- nakalbrowser-0.1.0/src/nakalbrowser/engine/async_engine.py +149 -0
- nakalbrowser-0.1.0/src/nakalbrowser/engine/engine.py +837 -0
- nakalbrowser-0.1.0/src/nakalbrowser/fleet/__init__.py +11 -0
- nakalbrowser-0.1.0/src/nakalbrowser/fleet/farm.py +102 -0
- nakalbrowser-0.1.0/src/nakalbrowser/fleet/profile.py +195 -0
- nakalbrowser-0.1.0/src/nakalbrowser/hunt/__init__.py +4 -0
- nakalbrowser-0.1.0/src/nakalbrowser/hunt/parse.py +183 -0
- nakalbrowser-0.1.0/src/nakalbrowser/hunt/seek.py +127 -0
- nakalbrowser-0.1.0/src/nakalbrowser/net/__init__.py +18 -0
- nakalbrowser-0.1.0/src/nakalbrowser/net/download.py +169 -0
- nakalbrowser-0.1.0/src/nakalbrowser/net/har.py +101 -0
- nakalbrowser-0.1.0/src/nakalbrowser/net/screencast.py +96 -0
- nakalbrowser-0.1.0/src/nakalbrowser/net/sniff.py +512 -0
- nakalbrowser-0.1.0/src/nakalbrowser/net/state.py +142 -0
- nakalbrowser-0.1.0/src/nakalbrowser/page/__init__.py +4 -0
- nakalbrowser-0.1.0/src/nakalbrowser/page/page.py +499 -0
- nakalbrowser-0.1.0/src/nakalbrowser/page/pane.py +331 -0
- nakalbrowser-0.1.0/src/nakalbrowser/page/pulse.py +220 -0
- nakalbrowser-0.1.0/src/nakalbrowser/page/rush.py +883 -0
- nakalbrowser-0.1.0/src/nakalbrowser/proxy/__init__.py +3 -0
- nakalbrowser-0.1.0/src/nakalbrowser/proxy/proxy.py +352 -0
- nakalbrowser-0.1.0/src/nakalbrowser/py.typed +0 -0
- nakalbrowser-0.1.0/src/nakalbrowser/tune/__init__.py +3 -0
- nakalbrowser-0.1.0/src/nakalbrowser/tune/until.py +73 -0
- nakalbrowser-0.1.0/src/nakalbrowser/util/__init__.py +12 -0
- nakalbrowser-0.1.0/src/nakalbrowser/util/cleanup.py +128 -0
- nakalbrowser-0.1.0/src/nakalbrowser/util/geoip.py +102 -0
- nakalbrowser-0.1.0/src/nakalbrowser/version.py +1 -0
- nakalbrowser-0.1.0/src/nakalbrowser.egg-info/PKG-INFO +240 -0
- nakalbrowser-0.1.0/src/nakalbrowser.egg-info/SOURCES.txt +69 -0
- nakalbrowser-0.1.0/src/nakalbrowser.egg-info/dependency_links.txt +1 -0
- nakalbrowser-0.1.0/src/nakalbrowser.egg-info/entry_points.txt +2 -0
- nakalbrowser-0.1.0/src/nakalbrowser.egg-info/requires.txt +23 -0
- nakalbrowser-0.1.0/src/nakalbrowser.egg-info/top_level.txt +1 -0
- nakalbrowser-0.1.0/tests/test_advanced_features.py +332 -0
- nakalbrowser-0.1.0/tests/test_core_unit.py +97 -0
- nakalbrowser-0.1.0/tests/test_engine_smoke.py +84 -0
- nakalbrowser-0.1.0/tests/test_hardening.py +275 -0
- nakalbrowser-0.1.0/tests/test_production_advanced.py +313 -0
- nakalbrowser-0.1.0/tests/test_realworld.py +320 -0
- nakalbrowser-0.1.0/tests/test_realworld_full.py +243 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nakalbrowser
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Nakal Browser — light, powerful CDP browser automation for Python
|
|
5
|
+
Author: NakalBrowser
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/nakalbrowser/nakalbrowser
|
|
8
|
+
Project-URL: Documentation, https://github.com/nakalbrowser/nakalbrowser
|
|
9
|
+
Keywords: browser,automation,cdp,chrome,scraping,nakal
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: websocket-client>=1.6.0
|
|
22
|
+
Provides-Extra: http
|
|
23
|
+
Requires-Dist: curl_cffi>=0.6.0; extra == "http"
|
|
24
|
+
Provides-Extra: parse
|
|
25
|
+
Requires-Dist: selectolax>=0.3.21; extra == "parse"
|
|
26
|
+
Provides-Extra: proc
|
|
27
|
+
Requires-Dist: psutil>=5.9.0; extra == "proc"
|
|
28
|
+
Provides-Extra: cloak
|
|
29
|
+
Requires-Dist: cloakbrowser>=0.4.0; extra == "cloak"
|
|
30
|
+
Provides-Extra: captcha
|
|
31
|
+
Provides-Extra: all
|
|
32
|
+
Requires-Dist: nakalbrowser[cloak,http,parse,proc]; extra == "all"
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
36
|
+
Requires-Dist: nakalbrowser[http]; extra == "dev"
|
|
37
|
+
|
|
38
|
+
<p align="center">
|
|
39
|
+
<img src="BannerGithubBrowser.png" alt="Nakal Browser" width="100%">
|
|
40
|
+
</p>
|
|
41
|
+
|
|
42
|
+
<h1 align="center">Nakal Browser</h1>
|
|
43
|
+
|
|
44
|
+
<p align="center">
|
|
45
|
+
<b>Lightweight browser automation for Python — fast, stealth, production-ready</b>
|
|
46
|
+
</p>
|
|
47
|
+
|
|
48
|
+
<p align="center">
|
|
49
|
+
<img src="https://img.shields.io/badge/python-3.10+-blue?style=flat-square&logo=python&logoColor=white" alt="Python">
|
|
50
|
+
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="License">
|
|
51
|
+
<img src="https://img.shields.io/badge/CDP-pure-red?style=flat-square" alt="CDP">
|
|
52
|
+
<img src="https://img.shields.io/badge/version-0.1.0-orange?style=flat-square" alt="Version">
|
|
53
|
+
</p>
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Features
|
|
58
|
+
|
|
59
|
+
- **Pure CDP** — no chromedriver, no webdriver, direct Chrome DevTools Protocol
|
|
60
|
+
- **Rush** — HTTP requests + JavaScript rendering in one class
|
|
61
|
+
- **Playwright-style locators** — `bot.locator("h1").click()` with auto-wait
|
|
62
|
+
- **Stealth built-in** — fingerprint, humanize, proxy, multi-account
|
|
63
|
+
- **Any Chromium** — Chrome, Edge, CloakBrowser, or custom binary
|
|
64
|
+
- **Farm** — run multiple accounts in parallel
|
|
65
|
+
- **Network capture** — sniff requests, intercept, mock responses
|
|
66
|
+
- **AI Agent ready** — snapshot, extract, a11y for LLM integration
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Install
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install nakalbrowser
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
python -m nakalbrowser doctor # check environment
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Quick start
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from nakalbrowser import Engine
|
|
86
|
+
|
|
87
|
+
with Engine(headless=True) as bot:
|
|
88
|
+
bot.open("https://example.com")
|
|
89
|
+
print(bot.title)
|
|
90
|
+
print(bot.find("h1").text)
|
|
91
|
+
bot.find("a").click()
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Rush — HTTP + JS hybrid
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from nakalbrowser import Rush
|
|
100
|
+
|
|
101
|
+
# Pure HTTP (fast, no browser)
|
|
102
|
+
with Rush(impersonate="chrome") as r:
|
|
103
|
+
resp = r.get("https://api.example.com/data")
|
|
104
|
+
print(resp.json)
|
|
105
|
+
|
|
106
|
+
# Need JS? Render with browser
|
|
107
|
+
with Rush(headless=True) as r:
|
|
108
|
+
r.render("https://spa.example.com", wait_for=".content", wait_until="networkidle")
|
|
109
|
+
print(r.find("h1").text)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Locators
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
bot.locator("h1").click()
|
|
118
|
+
bot.locator("#email").fill("user@example.com")
|
|
119
|
+
bot.get_by_role("button", name="Submit").click()
|
|
120
|
+
bot.get_by_text("Welcome").expect().to_have_text("Welcome")
|
|
121
|
+
bot.locator("h1").expect().to_be_visible(timeout=5)
|
|
122
|
+
bot.locator("form").locator("input[type=text]").fill("x")
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Multi-account & Proxy
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from nakalbrowser import Engine, Face, Profile, Proxy
|
|
131
|
+
|
|
132
|
+
face = Face.from_seed("acc01", os="windows")
|
|
133
|
+
prof = Profile.create("acc01", solid=True, proxy="socks5://user:pass@host:1080")
|
|
134
|
+
with Engine(profile=prof, headless=True) as bot:
|
|
135
|
+
bot.open("https://example.com")
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Farm — parallel execution
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from nakalbrowser import Farm, Profile
|
|
144
|
+
|
|
145
|
+
profiles = [Profile.create(f"acc_{i}", solid=True) for i in range(10)]
|
|
146
|
+
farm = Farm(profiles=profiles, concurrency=3, headless=True)
|
|
147
|
+
results = farm.run(lambda bot, prof: bot.open("https://target.com") or bot.title)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Mouse & Keyboard
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
bot.mouse.click(100, 200)
|
|
156
|
+
bot.mouse.drag(0, 0, 500, 500)
|
|
157
|
+
bot.keyboard.type("Hello World")
|
|
158
|
+
bot.keyboard.hotkey("Control", "a")
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Network
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
# Capture
|
|
167
|
+
bot.sniff.start("**/api/**")
|
|
168
|
+
bot.open("https://example.com")
|
|
169
|
+
for c in bot.sniff.all():
|
|
170
|
+
print(c.method, c.url, c.status)
|
|
171
|
+
|
|
172
|
+
# Mock
|
|
173
|
+
bot.intercept.fulfill("*/api/data", body='{"ok": true}', content_type="application/json")
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## AI Agent
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
with Engine(headless=True) as bot:
|
|
182
|
+
bot.open("https://example.com")
|
|
183
|
+
state = bot.snapshot(markdown=True) # compact state for LLM
|
|
184
|
+
data = bot.extract({"title": "h1", "links": "a@href[]"})
|
|
185
|
+
bot.find("text:Learn more").click()
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## API
|
|
191
|
+
|
|
192
|
+
| Class | What it does |
|
|
193
|
+
|---|---|
|
|
194
|
+
| `Engine` | Main browser controller |
|
|
195
|
+
| `Rush` | HTTP + JS render |
|
|
196
|
+
| `Face` | Fingerprint (save/load) |
|
|
197
|
+
| `Profile` | Multi-account bundle |
|
|
198
|
+
| `Proxy` | Proxy parse + auth bridge |
|
|
199
|
+
| `Farm` | Concurrent multi-profile |
|
|
200
|
+
| `Locator` | Playwright-style finder |
|
|
201
|
+
| `Mouse` / `Keyboard` | Precise input |
|
|
202
|
+
| `Page` / `Node` / `Ghost` | Tab / element / missing |
|
|
203
|
+
| `Pane` / `Shell` | iframe / shadow DOM |
|
|
204
|
+
|
|
205
|
+
Plus: `sniff`, `intercept`, `until`, `download`, `save_state` / `load_state`
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## CLI
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
python -m nakalbrowser doctor
|
|
213
|
+
python -m nakalbrowser open https://example.com --headless
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Browser
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
Engine() # auto-detect
|
|
222
|
+
Engine(browser="chrome") # force Chrome
|
|
223
|
+
Engine(browser="edge") # force Edge
|
|
224
|
+
Engine(browser_path="...") # custom path
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Docs
|
|
230
|
+
|
|
231
|
+
| File | Content |
|
|
232
|
+
|---|---|
|
|
233
|
+
| [docs/USAGE.md](docs/USAGE.md) | Full guide |
|
|
234
|
+
| [docs/AGENTS.md](docs/AGENTS.md) | Project structure for AI agents |
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## License
|
|
239
|
+
|
|
240
|
+
MIT
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="BannerGithubBrowser.png" alt="Nakal Browser" width="100%">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">Nakal Browser</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<b>Lightweight browser automation for Python — fast, stealth, production-ready</b>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<img src="https://img.shields.io/badge/python-3.10+-blue?style=flat-square&logo=python&logoColor=white" alt="Python">
|
|
13
|
+
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="License">
|
|
14
|
+
<img src="https://img.shields.io/badge/CDP-pure-red?style=flat-square" alt="CDP">
|
|
15
|
+
<img src="https://img.shields.io/badge/version-0.1.0-orange?style=flat-square" alt="Version">
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- **Pure CDP** — no chromedriver, no webdriver, direct Chrome DevTools Protocol
|
|
23
|
+
- **Rush** — HTTP requests + JavaScript rendering in one class
|
|
24
|
+
- **Playwright-style locators** — `bot.locator("h1").click()` with auto-wait
|
|
25
|
+
- **Stealth built-in** — fingerprint, humanize, proxy, multi-account
|
|
26
|
+
- **Any Chromium** — Chrome, Edge, CloakBrowser, or custom binary
|
|
27
|
+
- **Farm** — run multiple accounts in parallel
|
|
28
|
+
- **Network capture** — sniff requests, intercept, mock responses
|
|
29
|
+
- **AI Agent ready** — snapshot, extract, a11y for LLM integration
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install nakalbrowser
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
python -m nakalbrowser doctor # check environment
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Quick start
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from nakalbrowser import Engine
|
|
49
|
+
|
|
50
|
+
with Engine(headless=True) as bot:
|
|
51
|
+
bot.open("https://example.com")
|
|
52
|
+
print(bot.title)
|
|
53
|
+
print(bot.find("h1").text)
|
|
54
|
+
bot.find("a").click()
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Rush — HTTP + JS hybrid
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from nakalbrowser import Rush
|
|
63
|
+
|
|
64
|
+
# Pure HTTP (fast, no browser)
|
|
65
|
+
with Rush(impersonate="chrome") as r:
|
|
66
|
+
resp = r.get("https://api.example.com/data")
|
|
67
|
+
print(resp.json)
|
|
68
|
+
|
|
69
|
+
# Need JS? Render with browser
|
|
70
|
+
with Rush(headless=True) as r:
|
|
71
|
+
r.render("https://spa.example.com", wait_for=".content", wait_until="networkidle")
|
|
72
|
+
print(r.find("h1").text)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Locators
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
bot.locator("h1").click()
|
|
81
|
+
bot.locator("#email").fill("user@example.com")
|
|
82
|
+
bot.get_by_role("button", name="Submit").click()
|
|
83
|
+
bot.get_by_text("Welcome").expect().to_have_text("Welcome")
|
|
84
|
+
bot.locator("h1").expect().to_be_visible(timeout=5)
|
|
85
|
+
bot.locator("form").locator("input[type=text]").fill("x")
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Multi-account & Proxy
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from nakalbrowser import Engine, Face, Profile, Proxy
|
|
94
|
+
|
|
95
|
+
face = Face.from_seed("acc01", os="windows")
|
|
96
|
+
prof = Profile.create("acc01", solid=True, proxy="socks5://user:pass@host:1080")
|
|
97
|
+
with Engine(profile=prof, headless=True) as bot:
|
|
98
|
+
bot.open("https://example.com")
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Farm — parallel execution
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from nakalbrowser import Farm, Profile
|
|
107
|
+
|
|
108
|
+
profiles = [Profile.create(f"acc_{i}", solid=True) for i in range(10)]
|
|
109
|
+
farm = Farm(profiles=profiles, concurrency=3, headless=True)
|
|
110
|
+
results = farm.run(lambda bot, prof: bot.open("https://target.com") or bot.title)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Mouse & Keyboard
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
bot.mouse.click(100, 200)
|
|
119
|
+
bot.mouse.drag(0, 0, 500, 500)
|
|
120
|
+
bot.keyboard.type("Hello World")
|
|
121
|
+
bot.keyboard.hotkey("Control", "a")
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Network
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
# Capture
|
|
130
|
+
bot.sniff.start("**/api/**")
|
|
131
|
+
bot.open("https://example.com")
|
|
132
|
+
for c in bot.sniff.all():
|
|
133
|
+
print(c.method, c.url, c.status)
|
|
134
|
+
|
|
135
|
+
# Mock
|
|
136
|
+
bot.intercept.fulfill("*/api/data", body='{"ok": true}', content_type="application/json")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## AI Agent
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
with Engine(headless=True) as bot:
|
|
145
|
+
bot.open("https://example.com")
|
|
146
|
+
state = bot.snapshot(markdown=True) # compact state for LLM
|
|
147
|
+
data = bot.extract({"title": "h1", "links": "a@href[]"})
|
|
148
|
+
bot.find("text:Learn more").click()
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## API
|
|
154
|
+
|
|
155
|
+
| Class | What it does |
|
|
156
|
+
|---|---|
|
|
157
|
+
| `Engine` | Main browser controller |
|
|
158
|
+
| `Rush` | HTTP + JS render |
|
|
159
|
+
| `Face` | Fingerprint (save/load) |
|
|
160
|
+
| `Profile` | Multi-account bundle |
|
|
161
|
+
| `Proxy` | Proxy parse + auth bridge |
|
|
162
|
+
| `Farm` | Concurrent multi-profile |
|
|
163
|
+
| `Locator` | Playwright-style finder |
|
|
164
|
+
| `Mouse` / `Keyboard` | Precise input |
|
|
165
|
+
| `Page` / `Node` / `Ghost` | Tab / element / missing |
|
|
166
|
+
| `Pane` / `Shell` | iframe / shadow DOM |
|
|
167
|
+
|
|
168
|
+
Plus: `sniff`, `intercept`, `until`, `download`, `save_state` / `load_state`
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## CLI
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
python -m nakalbrowser doctor
|
|
176
|
+
python -m nakalbrowser open https://example.com --headless
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Browser
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
Engine() # auto-detect
|
|
185
|
+
Engine(browser="chrome") # force Chrome
|
|
186
|
+
Engine(browser="edge") # force Edge
|
|
187
|
+
Engine(browser_path="...") # custom path
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Docs
|
|
193
|
+
|
|
194
|
+
| File | Content |
|
|
195
|
+
|---|---|
|
|
196
|
+
| [docs/USAGE.md](docs/USAGE.md) | Full guide |
|
|
197
|
+
| [docs/AGENTS.md](docs/AGENTS.md) | Project structure for AI agents |
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## License
|
|
202
|
+
|
|
203
|
+
MIT
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "nakalbrowser"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Nakal Browser — light, powerful CDP browser automation for Python"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "NakalBrowser" }]
|
|
13
|
+
keywords = ["browser", "automation", "cdp", "chrome", "scraping", "nakal"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Topic :: Internet :: WWW/HTTP :: Browsers",
|
|
23
|
+
"Topic :: Software Development :: Libraries",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"websocket-client>=1.6.0",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
http = ["curl_cffi>=0.6.0"]
|
|
31
|
+
parse = ["selectolax>=0.3.21"]
|
|
32
|
+
proc = ["psutil>=5.9.0"]
|
|
33
|
+
cloak = ["cloakbrowser>=0.4.0"]
|
|
34
|
+
captcha = [] # use env TWOCAPTCHA_API_KEY + built-in TwoCaptcha helper
|
|
35
|
+
all = ["nakalbrowser[http,parse,proc,cloak]"]
|
|
36
|
+
dev = ["pytest>=7.0", "pytest-asyncio>=0.23", "nakalbrowser[http]"]
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Homepage = "https://github.com/nakalbrowser/nakalbrowser"
|
|
40
|
+
Documentation = "https://github.com/nakalbrowser/nakalbrowser"
|
|
41
|
+
|
|
42
|
+
[project.scripts]
|
|
43
|
+
nakalbrowser = "nakalbrowser.cli:main"
|
|
44
|
+
|
|
45
|
+
[tool.setuptools.packages.find]
|
|
46
|
+
where = ["src"]
|
|
47
|
+
|
|
48
|
+
[tool.setuptools.package-data]
|
|
49
|
+
nakalbrowser = ["py.typed"]
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from .boot.boot import Boot
|
|
2
|
+
from .cloak.face import Face
|
|
3
|
+
from .core.errors import (
|
|
4
|
+
BrowserConnectError,
|
|
5
|
+
BrowserMissingError,
|
|
6
|
+
CDPError,
|
|
7
|
+
ClickError,
|
|
8
|
+
DownloadError,
|
|
9
|
+
ElementLostError,
|
|
10
|
+
ElementNotFoundError,
|
|
11
|
+
ExpectError,
|
|
12
|
+
FrameError,
|
|
13
|
+
InterceptError,
|
|
14
|
+
JSError,
|
|
15
|
+
LocatorError,
|
|
16
|
+
NakalError,
|
|
17
|
+
PageDisconnectedError,
|
|
18
|
+
WaitTimeoutError,
|
|
19
|
+
error_dict,
|
|
20
|
+
)
|
|
21
|
+
from .core.knob import Knob
|
|
22
|
+
from .dom.ghost import Ghost
|
|
23
|
+
from .dom.interact import Keyboard, Mouse
|
|
24
|
+
from .dom.locator import Locator
|
|
25
|
+
from .dom.node import Node
|
|
26
|
+
from .dom.shell import Shell
|
|
27
|
+
from .engine.engine import Engine
|
|
28
|
+
from .fleet.profile import Profile
|
|
29
|
+
from .net.download import Download, Loot
|
|
30
|
+
from .net.sniff import Catch, Intercept, Sniff
|
|
31
|
+
from .net.state import load_state, save_state
|
|
32
|
+
from .page.page import Page
|
|
33
|
+
from .page.pane import Pane
|
|
34
|
+
from .page.rush import Pulse, Rush
|
|
35
|
+
from .proxy.proxy import Proxy
|
|
36
|
+
from .tune.until import Until
|
|
37
|
+
from .util.cleanup import cleanup_path, cleanup_temp, list_temp_profiles
|
|
38
|
+
from .util.geoip import lookup_egress
|
|
39
|
+
from .version import __version__
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def __getattr__(name: str):
|
|
43
|
+
if name in ("Farm", "FarmResult"):
|
|
44
|
+
from .fleet.farm import Farm, FarmResult
|
|
45
|
+
|
|
46
|
+
return Farm if name == "Farm" else FarmResult
|
|
47
|
+
if name == "AsyncEngine":
|
|
48
|
+
from .engine.async_engine import AsyncEngine
|
|
49
|
+
|
|
50
|
+
return AsyncEngine
|
|
51
|
+
if name in ("TwoCaptcha", "CaptchaError", "inject_recaptcha_token", "inject_turnstile_token"):
|
|
52
|
+
from .ai import captcha as _c
|
|
53
|
+
|
|
54
|
+
return getattr(_c, name)
|
|
55
|
+
if name in ("snapshot", "a11y", "extract", "snapshot_markdown"):
|
|
56
|
+
from .ai import snapshot as _s
|
|
57
|
+
|
|
58
|
+
return getattr(_s, name)
|
|
59
|
+
raise AttributeError(f"module 'nakalbrowser' has no attribute {name}")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
__all__ = [
|
|
63
|
+
"Engine",
|
|
64
|
+
"Boot",
|
|
65
|
+
"Page",
|
|
66
|
+
"Node",
|
|
67
|
+
"Rush",
|
|
68
|
+
"Pulse",
|
|
69
|
+
"Ghost",
|
|
70
|
+
"Face",
|
|
71
|
+
"Profile",
|
|
72
|
+
"Proxy",
|
|
73
|
+
"Knob",
|
|
74
|
+
"Sniff",
|
|
75
|
+
"Intercept",
|
|
76
|
+
"Catch",
|
|
77
|
+
"Until",
|
|
78
|
+
"Download",
|
|
79
|
+
"Loot",
|
|
80
|
+
"Farm",
|
|
81
|
+
"FarmResult",
|
|
82
|
+
"AsyncEngine",
|
|
83
|
+
"Pane",
|
|
84
|
+
"Shell",
|
|
85
|
+
"Locator",
|
|
86
|
+
"Mouse",
|
|
87
|
+
"Keyboard",
|
|
88
|
+
"save_state",
|
|
89
|
+
"load_state",
|
|
90
|
+
"snapshot",
|
|
91
|
+
"snapshot_markdown",
|
|
92
|
+
"a11y",
|
|
93
|
+
"extract",
|
|
94
|
+
"TwoCaptcha",
|
|
95
|
+
"CaptchaError",
|
|
96
|
+
"inject_recaptcha_token",
|
|
97
|
+
"inject_turnstile_token",
|
|
98
|
+
"error_dict",
|
|
99
|
+
"NakalError",
|
|
100
|
+
"BrowserConnectError",
|
|
101
|
+
"BrowserMissingError",
|
|
102
|
+
"CDPError",
|
|
103
|
+
"PageDisconnectedError",
|
|
104
|
+
"ElementNotFoundError",
|
|
105
|
+
"ElementLostError",
|
|
106
|
+
"ClickError",
|
|
107
|
+
"WaitTimeoutError",
|
|
108
|
+
"LocatorError",
|
|
109
|
+
"JSError",
|
|
110
|
+
"ExpectError",
|
|
111
|
+
"InterceptError",
|
|
112
|
+
"DownloadError",
|
|
113
|
+
"FrameError",
|
|
114
|
+
"cleanup_temp",
|
|
115
|
+
"cleanup_path",
|
|
116
|
+
"list_temp_profiles",
|
|
117
|
+
"lookup_egress",
|
|
118
|
+
"__version__",
|
|
119
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from .captcha import (
|
|
2
|
+
CaptchaError,
|
|
3
|
+
TwoCaptcha,
|
|
4
|
+
inject_recaptcha_token,
|
|
5
|
+
inject_turnstile_token,
|
|
6
|
+
)
|
|
7
|
+
from .snapshot import a11y, extract, snapshot, snapshot_markdown
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"snapshot",
|
|
11
|
+
"snapshot_markdown",
|
|
12
|
+
"a11y",
|
|
13
|
+
"extract",
|
|
14
|
+
"TwoCaptcha",
|
|
15
|
+
"CaptchaError",
|
|
16
|
+
"inject_recaptcha_token",
|
|
17
|
+
"inject_turnstile_token",
|
|
18
|
+
]
|