owl-browser 1.0.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.
- owl_browser-1.0.0/PKG-INFO +784 -0
- owl_browser-1.0.0/README.md +748 -0
- owl_browser-1.0.0/owl_browser/__init__.py +284 -0
- owl_browser-1.0.0/owl_browser/async_browser.py +691 -0
- owl_browser-1.0.0/owl_browser/browser.py +511 -0
- owl_browser-1.0.0/owl_browser/context.py +1314 -0
- owl_browser-1.0.0/owl_browser/core.py +852 -0
- owl_browser-1.0.0/owl_browser/exceptions.py +252 -0
- owl_browser-1.0.0/owl_browser/http_client.py +796 -0
- owl_browser-1.0.0/owl_browser/jwt.py +451 -0
- owl_browser-1.0.0/owl_browser/py.typed +2 -0
- owl_browser-1.0.0/owl_browser/types.py +695 -0
- owl_browser-1.0.0/owl_browser/ws_client.py +705 -0
- owl_browser-1.0.0/owl_browser.egg-info/PKG-INFO +784 -0
- owl_browser-1.0.0/owl_browser.egg-info/SOURCES.txt +18 -0
- owl_browser-1.0.0/owl_browser.egg-info/dependency_links.txt +1 -0
- owl_browser-1.0.0/owl_browser.egg-info/requires.txt +8 -0
- owl_browser-1.0.0/owl_browser.egg-info/top_level.txt +1 -0
- owl_browser-1.0.0/pyproject.toml +98 -0
- owl_browser-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,784 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: owl-browser
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: AI-first browser automation SDK with on-device vision model and natural language selectors
|
|
5
|
+
Author-email: Olib AI <hello@olib.ai>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://www.owlbrowser.net
|
|
8
|
+
Project-URL: Documentation, https://github.com/Olib-AI/owl-browser#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/Olib-AI/owl-browser
|
|
10
|
+
Project-URL: Issues, https://github.com/Olib-AI/owl-browser/issues
|
|
11
|
+
Keywords: browser,automation,scraping,ai,llm,vision,selenium-alternative,playwright-alternative,puppeteer-alternative,web-scraping,headless-browser,stealth,anti-detection,owl-browser
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Classifier: Topic :: Software Development :: Testing
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.8
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
32
|
+
Requires-Dist: black>=23.0; extra == "dev"
|
|
33
|
+
Requires-Dist: isort>=5.0; extra == "dev"
|
|
34
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
35
|
+
Requires-Dist: ruff>=0.1; extra == "dev"
|
|
36
|
+
|
|
37
|
+
# Owl Browser SDK for Python
|
|
38
|
+
|
|
39
|
+
AI-first browser automation SDK with on-device vision model, natural language selectors, and comprehensive stealth features.
|
|
40
|
+
|
|
41
|
+
## Features
|
|
42
|
+
|
|
43
|
+
- **Natural Language Selectors** - Click, type, and interact using descriptions like "search button" or "login form"
|
|
44
|
+
- **On-Device Vision Model** - Built-in Qwen 3 model for page understanding and CAPTCHA solving
|
|
45
|
+
- **Stealth Mode** - Proxy support with timezone spoofing, WebRTC blocking, and anti-detection
|
|
46
|
+
- **Thread-Safe** - Designed for concurrent usage with multiple pages
|
|
47
|
+
- **Simple API** - Minimal code required for common tasks
|
|
48
|
+
- **Dual Mode** - Connect to local browser binary OR remote HTTP server
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install owl-browser
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Note:** You must also build the Owl Browser binary. See the main project README for build instructions.
|
|
57
|
+
|
|
58
|
+
## Quick Start
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from owl_browser import Browser
|
|
62
|
+
|
|
63
|
+
# Simple usage with context manager
|
|
64
|
+
with Browser() as browser:
|
|
65
|
+
page = browser.new_page()
|
|
66
|
+
page.goto("https://example.com")
|
|
67
|
+
|
|
68
|
+
# Natural language selectors
|
|
69
|
+
page.click("search button")
|
|
70
|
+
page.type("search input", "hello world")
|
|
71
|
+
|
|
72
|
+
# Take screenshot
|
|
73
|
+
page.screenshot("screenshot.png")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Remote Mode (HTTP Server)
|
|
77
|
+
|
|
78
|
+
The SDK supports connecting to a remote Owl Browser HTTP server, enabling:
|
|
79
|
+
|
|
80
|
+
- **Cloud deployment** - Run browser on remote servers
|
|
81
|
+
- **Distributed scraping** - Connect multiple clients to one browser
|
|
82
|
+
- **Resource optimization** - Share browser resources across applications
|
|
83
|
+
|
|
84
|
+
### Basic Remote Usage
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from owl_browser import Browser, RemoteConfig
|
|
88
|
+
|
|
89
|
+
# Connect to remote browser server
|
|
90
|
+
browser = Browser(remote=RemoteConfig(
|
|
91
|
+
url="http://192.168.1.100:8080",
|
|
92
|
+
token="your-secret-token"
|
|
93
|
+
))
|
|
94
|
+
browser.launch()
|
|
95
|
+
|
|
96
|
+
# API is identical to local mode!
|
|
97
|
+
page = browser.new_page()
|
|
98
|
+
page.goto("https://example.com")
|
|
99
|
+
page.click("search button")
|
|
100
|
+
page.type("search input", "hello world")
|
|
101
|
+
page.screenshot("screenshot.png")
|
|
102
|
+
|
|
103
|
+
browser.close()
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Remote with Context Manager
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from owl_browser import Browser, RemoteConfig
|
|
110
|
+
|
|
111
|
+
with Browser(remote=RemoteConfig(
|
|
112
|
+
url="http://localhost:8080",
|
|
113
|
+
token="secret-token"
|
|
114
|
+
)) as browser:
|
|
115
|
+
page = browser.new_page()
|
|
116
|
+
page.goto("https://example.com")
|
|
117
|
+
page.screenshot("screenshot.png")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Async Remote Usage
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
import asyncio
|
|
124
|
+
from owl_browser import AsyncBrowser, RemoteConfig
|
|
125
|
+
|
|
126
|
+
async def main():
|
|
127
|
+
async with AsyncBrowser(remote=RemoteConfig(
|
|
128
|
+
url="http://192.168.1.100:8080",
|
|
129
|
+
token="your-secret-token"
|
|
130
|
+
)) as browser:
|
|
131
|
+
page = await browser.new_page()
|
|
132
|
+
await page.goto("https://example.com")
|
|
133
|
+
await page.screenshot("screenshot.png")
|
|
134
|
+
|
|
135
|
+
asyncio.run(main())
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### JWT Authentication
|
|
139
|
+
|
|
140
|
+
For enhanced security, the SDK supports JWT (JSON Web Token) authentication with RSA signing. The SDK can automatically generate and refresh JWT tokens using your private key:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from owl_browser import Browser, RemoteConfig, JWTConfig, AuthMode
|
|
144
|
+
|
|
145
|
+
# Connect with JWT authentication (auto-generated tokens)
|
|
146
|
+
browser = Browser(remote=RemoteConfig(
|
|
147
|
+
url="http://192.168.1.100:8080",
|
|
148
|
+
auth_mode=AuthMode.JWT,
|
|
149
|
+
jwt=JWTConfig(
|
|
150
|
+
private_key="/path/to/private.pem", # RSA private key
|
|
151
|
+
expires_in=3600, # Token validity (1 hour)
|
|
152
|
+
refresh_threshold=300, # Refresh 5 min before expiry
|
|
153
|
+
issuer="my-app", # Optional claims
|
|
154
|
+
subject="user-123"
|
|
155
|
+
)
|
|
156
|
+
))
|
|
157
|
+
browser.launch()
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
You can also use the JWT utilities directly:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from owl_browser import generate_jwt, decode_jwt, JWTManager, generate_key_pair
|
|
164
|
+
|
|
165
|
+
# Generate a single token
|
|
166
|
+
token = generate_jwt('/path/to/private.pem', expires_in=7200, issuer='my-app')
|
|
167
|
+
|
|
168
|
+
# Decode a token (without verification)
|
|
169
|
+
decoded = decode_jwt(token)
|
|
170
|
+
print(f"Expires at: {decoded['payload']['exp']}")
|
|
171
|
+
|
|
172
|
+
# Use JWTManager for auto-refresh
|
|
173
|
+
jwt_manager = JWTManager('/path/to/private.pem', expires_in=3600, refresh_threshold=300)
|
|
174
|
+
token = jwt_manager.get_token() # Auto-refreshes when needed
|
|
175
|
+
|
|
176
|
+
# Generate new RSA key pair
|
|
177
|
+
private_key, public_key = generate_key_pair()
|
|
178
|
+
with open('private.pem', 'w') as f:
|
|
179
|
+
f.write(private_key)
|
|
180
|
+
with open('public.pem', 'w') as f:
|
|
181
|
+
f.write(public_key)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### WebSocket Transport
|
|
185
|
+
|
|
186
|
+
For lower latency and persistent connections, use WebSocket transport instead of HTTP:
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
from owl_browser import Browser, RemoteConfig, TransportMode, ReconnectConfig
|
|
190
|
+
|
|
191
|
+
# WebSocket mode - real-time communication
|
|
192
|
+
browser = Browser(remote=RemoteConfig(
|
|
193
|
+
url="http://192.168.1.100:8080",
|
|
194
|
+
token="your-secret-token",
|
|
195
|
+
transport=TransportMode.WEBSOCKET, # Use WebSocket instead of HTTP
|
|
196
|
+
reconnect=ReconnectConfig(
|
|
197
|
+
enabled=True,
|
|
198
|
+
max_attempts=5,
|
|
199
|
+
initial_delay_ms=1000,
|
|
200
|
+
max_delay_ms=30000
|
|
201
|
+
)
|
|
202
|
+
))
|
|
203
|
+
browser.launch()
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### High-Performance Configuration
|
|
207
|
+
|
|
208
|
+
For high-concurrency workloads, configure retry, and concurrency limits:
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
from owl_browser import Browser, RemoteConfig, RetryConfig, ConcurrencyConfig
|
|
212
|
+
|
|
213
|
+
# High-performance HTTP configuration
|
|
214
|
+
browser = Browser(remote=RemoteConfig(
|
|
215
|
+
url="http://192.168.1.100:8080",
|
|
216
|
+
token="your-secret-token",
|
|
217
|
+
timeout=30000,
|
|
218
|
+
# Retry configuration with exponential backoff
|
|
219
|
+
retry=RetryConfig(
|
|
220
|
+
max_retries=5,
|
|
221
|
+
initial_delay_ms=100,
|
|
222
|
+
max_delay_ms=10000,
|
|
223
|
+
backoff_multiplier=2.0,
|
|
224
|
+
jitter_factor=0.1
|
|
225
|
+
),
|
|
226
|
+
# Concurrency limiting
|
|
227
|
+
concurrency=ConcurrencyConfig(
|
|
228
|
+
max_concurrent=50
|
|
229
|
+
)
|
|
230
|
+
))
|
|
231
|
+
browser.launch()
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### RemoteConfig Options
|
|
235
|
+
|
|
236
|
+
| Option | Type | Default | Description |
|
|
237
|
+
|--------|------|---------|-------------|
|
|
238
|
+
| `url` | str | *required* | Base URL of HTTP server (e.g., `http://localhost:8080`) |
|
|
239
|
+
| `token` | str | - | Bearer token (required for TOKEN mode) |
|
|
240
|
+
| `auth_mode` | AuthMode | TOKEN | Authentication mode (TOKEN or JWT) |
|
|
241
|
+
| `jwt` | JWTConfig | - | JWT configuration (required for JWT mode) |
|
|
242
|
+
| `transport` | TransportMode | HTTP | Transport mode (HTTP or WEBSOCKET) |
|
|
243
|
+
| `timeout` | int | 30000 | Request timeout in milliseconds |
|
|
244
|
+
| `verify_ssl` | bool | True | Verify SSL certificates |
|
|
245
|
+
| `retry` | RetryConfig | - | Retry configuration for HTTP transport |
|
|
246
|
+
| `reconnect` | ReconnectConfig | - | Reconnection config for WebSocket |
|
|
247
|
+
| `concurrency` | ConcurrencyConfig | - | Concurrency limiting config |
|
|
248
|
+
|
|
249
|
+
### JWTConfig Options
|
|
250
|
+
|
|
251
|
+
| Option | Type | Default | Description |
|
|
252
|
+
|--------|------|---------|-------------|
|
|
253
|
+
| `private_key` | str | *required* | Path to RSA private key or PEM string |
|
|
254
|
+
| `expires_in` | int | 3600 | Token validity in seconds |
|
|
255
|
+
| `refresh_threshold` | int | 300 | Seconds before expiry to refresh |
|
|
256
|
+
| `issuer` | str | - | Issuer claim (iss) |
|
|
257
|
+
| `subject` | str | - | Subject claim (sub) |
|
|
258
|
+
| `audience` | str | - | Audience claim (aud) |
|
|
259
|
+
| `claims` | dict | - | Additional custom claims |
|
|
260
|
+
|
|
261
|
+
### RetryConfig Options
|
|
262
|
+
|
|
263
|
+
| Option | Type | Default | Description |
|
|
264
|
+
|--------|------|---------|-------------|
|
|
265
|
+
| `max_retries` | int | 3 | Maximum number of retry attempts |
|
|
266
|
+
| `initial_delay_ms` | int | 100 | Initial delay in milliseconds |
|
|
267
|
+
| `max_delay_ms` | int | 10000 | Maximum delay cap in milliseconds |
|
|
268
|
+
| `backoff_multiplier` | float | 2.0 | Multiplier for exponential backoff |
|
|
269
|
+
| `jitter_factor` | float | 0.1 | Random jitter factor (0-1) |
|
|
270
|
+
|
|
271
|
+
### ReconnectConfig Options
|
|
272
|
+
|
|
273
|
+
| Option | Type | Default | Description |
|
|
274
|
+
|--------|------|---------|-------------|
|
|
275
|
+
| `enabled` | bool | True | Whether auto-reconnection is enabled |
|
|
276
|
+
| `max_attempts` | int | 5 | Maximum reconnection attempts (0 = infinite) |
|
|
277
|
+
| `initial_delay_ms` | int | 1000 | Initial delay in milliseconds |
|
|
278
|
+
| `max_delay_ms` | int | 30000 | Maximum delay cap in milliseconds |
|
|
279
|
+
| `backoff_multiplier` | float | 2.0 | Multiplier for exponential backoff |
|
|
280
|
+
| `jitter_factor` | float | 0.1 | Random jitter factor (0-1) |
|
|
281
|
+
|
|
282
|
+
### ConcurrencyConfig Options
|
|
283
|
+
|
|
284
|
+
| Option | Type | Default | Description |
|
|
285
|
+
|--------|------|---------|-------------|
|
|
286
|
+
| `max_concurrent` | int | 10 | Maximum concurrent requests |
|
|
287
|
+
|
|
288
|
+
### Checking Connection Mode
|
|
289
|
+
|
|
290
|
+
```python
|
|
291
|
+
from owl_browser import Browser, RemoteConfig, ConnectionMode
|
|
292
|
+
|
|
293
|
+
browser = Browser(remote=RemoteConfig(url="...", token="..."))
|
|
294
|
+
browser.launch()
|
|
295
|
+
|
|
296
|
+
# Check the connection mode
|
|
297
|
+
print(f"Mode: {browser.mode}") # ConnectionMode.REMOTE
|
|
298
|
+
print(f"Is Remote: {browser.is_remote}") # True
|
|
299
|
+
|
|
300
|
+
browser.close()
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Setting Up the HTTP Server
|
|
304
|
+
|
|
305
|
+
See `http-server/README.md` for instructions on deploying the Owl Browser HTTP server.
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
# Start the HTTP server
|
|
309
|
+
./owl_browser --http --port 8080 --token "your-secret-token"
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Usage Examples
|
|
313
|
+
|
|
314
|
+
### Basic Navigation and Interaction
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
from owl_browser import Browser
|
|
318
|
+
|
|
319
|
+
browser = Browser()
|
|
320
|
+
browser.launch()
|
|
321
|
+
|
|
322
|
+
page = browser.new_page()
|
|
323
|
+
page.goto("https://example.com")
|
|
324
|
+
|
|
325
|
+
# Click using various selector types
|
|
326
|
+
page.click("#submit") # CSS selector
|
|
327
|
+
page.click("100x200") # Coordinates
|
|
328
|
+
page.click("login button") # Natural language
|
|
329
|
+
|
|
330
|
+
# Type into inputs
|
|
331
|
+
page.type("#email", "user@example.com")
|
|
332
|
+
page.type("password field", "secret123")
|
|
333
|
+
|
|
334
|
+
# Select from dropdowns
|
|
335
|
+
page.pick("country dropdown", "United States")
|
|
336
|
+
|
|
337
|
+
# Press special keys
|
|
338
|
+
from owl_browser import KeyName
|
|
339
|
+
page.press_key(KeyName.ENTER)
|
|
340
|
+
|
|
341
|
+
browser.close()
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### AI-Powered Features
|
|
345
|
+
|
|
346
|
+
```python
|
|
347
|
+
from owl_browser import Browser
|
|
348
|
+
|
|
349
|
+
with Browser() as browser:
|
|
350
|
+
page = browser.new_page()
|
|
351
|
+
page.goto("https://news.example.com")
|
|
352
|
+
|
|
353
|
+
# Query the page using LLM
|
|
354
|
+
summary = page.query_page("What are the main headlines?")
|
|
355
|
+
print(summary)
|
|
356
|
+
|
|
357
|
+
# Get structured page summary
|
|
358
|
+
summary = page.summarize_page()
|
|
359
|
+
print(summary)
|
|
360
|
+
|
|
361
|
+
# Execute natural language commands
|
|
362
|
+
page.execute_nla("scroll down and click the first article")
|
|
363
|
+
|
|
364
|
+
# Auto-solve CAPTCHAs
|
|
365
|
+
result = page.solve_captcha()
|
|
366
|
+
if result.get("success"):
|
|
367
|
+
print("CAPTCHA solved!")
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Concurrent Scraping
|
|
371
|
+
|
|
372
|
+
```python
|
|
373
|
+
from owl_browser import Browser
|
|
374
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
375
|
+
|
|
376
|
+
browser = Browser()
|
|
377
|
+
browser.launch()
|
|
378
|
+
|
|
379
|
+
def scrape_url(url):
|
|
380
|
+
"""Scrape a single URL - each call gets its own isolated page."""
|
|
381
|
+
page = browser.new_page()
|
|
382
|
+
try:
|
|
383
|
+
page.goto(url)
|
|
384
|
+
return {
|
|
385
|
+
"url": url,
|
|
386
|
+
"title": page.get_title(),
|
|
387
|
+
"text": page.extract_text("main content")
|
|
388
|
+
}
|
|
389
|
+
finally:
|
|
390
|
+
page.close()
|
|
391
|
+
|
|
392
|
+
urls = [
|
|
393
|
+
"https://example1.com",
|
|
394
|
+
"https://example2.com",
|
|
395
|
+
"https://example3.com",
|
|
396
|
+
]
|
|
397
|
+
|
|
398
|
+
# Scrape 5 pages concurrently
|
|
399
|
+
with ThreadPoolExecutor(max_workers=5) as executor:
|
|
400
|
+
results = list(executor.map(scrape_url, urls))
|
|
401
|
+
|
|
402
|
+
browser.close()
|
|
403
|
+
|
|
404
|
+
for result in results:
|
|
405
|
+
print(f"{result['title']}: {result['text'][:100]}...")
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Proxy with Stealth
|
|
409
|
+
|
|
410
|
+
```python
|
|
411
|
+
from owl_browser import Browser, ProxyConfig, ProxyType
|
|
412
|
+
|
|
413
|
+
with Browser() as browser:
|
|
414
|
+
# Create page with proxy and timezone spoofing
|
|
415
|
+
page = browser.new_page(proxy=ProxyConfig(
|
|
416
|
+
type=ProxyType.SOCKS5H, # SOCKS5 with remote DNS
|
|
417
|
+
host="proxy.example.com",
|
|
418
|
+
port=1080,
|
|
419
|
+
username="user",
|
|
420
|
+
password="pass",
|
|
421
|
+
stealth=True, # Block WebRTC leaks
|
|
422
|
+
timezone_override="America/New_York" # Match proxy location
|
|
423
|
+
))
|
|
424
|
+
|
|
425
|
+
page.goto("https://whatismyip.com")
|
|
426
|
+
page.screenshot("proxy-test.png")
|
|
427
|
+
|
|
428
|
+
# Check proxy status
|
|
429
|
+
status = page.get_proxy_status()
|
|
430
|
+
print(f"Proxy connected: {status.connected}")
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Cookie Management
|
|
434
|
+
|
|
435
|
+
```python
|
|
436
|
+
from owl_browser import Browser
|
|
437
|
+
|
|
438
|
+
with Browser() as browser:
|
|
439
|
+
page = browser.new_page()
|
|
440
|
+
page.goto("https://example.com")
|
|
441
|
+
|
|
442
|
+
# Get all cookies
|
|
443
|
+
cookies = page.get_cookies()
|
|
444
|
+
for cookie in cookies:
|
|
445
|
+
print(f"{cookie.name}: {cookie.value}")
|
|
446
|
+
|
|
447
|
+
# Set a cookie
|
|
448
|
+
page.set_cookie(
|
|
449
|
+
url="https://example.com",
|
|
450
|
+
name="session",
|
|
451
|
+
value="abc123",
|
|
452
|
+
secure=True,
|
|
453
|
+
http_only=True
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
# Delete specific cookie
|
|
457
|
+
page.delete_cookies("https://example.com", "session")
|
|
458
|
+
|
|
459
|
+
# Delete all cookies
|
|
460
|
+
page.delete_cookies()
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### Video Recording
|
|
464
|
+
|
|
465
|
+
```python
|
|
466
|
+
from owl_browser import Browser
|
|
467
|
+
|
|
468
|
+
with Browser() as browser:
|
|
469
|
+
page = browser.new_page()
|
|
470
|
+
|
|
471
|
+
# Start recording
|
|
472
|
+
page.start_video_recording(fps=30)
|
|
473
|
+
|
|
474
|
+
page.goto("https://example.com")
|
|
475
|
+
page.click("some button")
|
|
476
|
+
page.type("input field", "test data")
|
|
477
|
+
|
|
478
|
+
# Stop and get video path
|
|
479
|
+
video_path = page.stop_video_recording()
|
|
480
|
+
print(f"Video saved to: {video_path}")
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Content Extraction
|
|
484
|
+
|
|
485
|
+
```python
|
|
486
|
+
from owl_browser import Browser, CleanLevel, ExtractionTemplate
|
|
487
|
+
|
|
488
|
+
with Browser() as browser:
|
|
489
|
+
page = browser.new_page()
|
|
490
|
+
page.goto("https://example.com")
|
|
491
|
+
|
|
492
|
+
# Extract text
|
|
493
|
+
text = page.extract_text()
|
|
494
|
+
article = page.extract_text("main article")
|
|
495
|
+
|
|
496
|
+
# Get as Markdown
|
|
497
|
+
markdown = page.get_markdown(include_links=True, include_images=False)
|
|
498
|
+
|
|
499
|
+
# Get clean HTML
|
|
500
|
+
html = page.get_html(CleanLevel.AGGRESSIVE)
|
|
501
|
+
|
|
502
|
+
# Extract structured JSON (auto-detects template)
|
|
503
|
+
data = page.extract_json()
|
|
504
|
+
|
|
505
|
+
# Use specific template
|
|
506
|
+
data = page.extract_json(ExtractionTemplate.GOOGLE_SEARCH)
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Test Execution
|
|
510
|
+
|
|
511
|
+
Run tests exported from the Developer Playground:
|
|
512
|
+
|
|
513
|
+
```python
|
|
514
|
+
from owl_browser import Browser
|
|
515
|
+
|
|
516
|
+
with Browser() as browser:
|
|
517
|
+
page = browser.new_page()
|
|
518
|
+
|
|
519
|
+
# Run test from JSON file
|
|
520
|
+
result = page.run_test("my-test.json", verbose=True)
|
|
521
|
+
|
|
522
|
+
# Or define inline
|
|
523
|
+
result = page.run_test({
|
|
524
|
+
"name": "Login Test",
|
|
525
|
+
"steps": [
|
|
526
|
+
{"type": "navigate", "url": "https://example.com/login"},
|
|
527
|
+
{"type": "type", "selector": "#email", "text": "user@example.com"},
|
|
528
|
+
{"type": "type", "selector": "#password", "text": "password123"},
|
|
529
|
+
{"type": "click", "selector": "button[type='submit']"},
|
|
530
|
+
{"type": "wait", "duration": 2000},
|
|
531
|
+
{"type": "screenshot", "filename": "logged-in.png"}
|
|
532
|
+
]
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
print(f"Test: {result.test_name}")
|
|
536
|
+
print(f"Success: {result.successful_steps}/{result.total_steps}")
|
|
537
|
+
print(f"Time: {result.execution_time}ms")
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
### Quick Utilities
|
|
541
|
+
|
|
542
|
+
For simple one-off operations:
|
|
543
|
+
|
|
544
|
+
```python
|
|
545
|
+
from owl_browser import quick_screenshot, quick_extract, quick_query
|
|
546
|
+
|
|
547
|
+
# Take a quick screenshot
|
|
548
|
+
quick_screenshot("https://example.com", "example.png")
|
|
549
|
+
|
|
550
|
+
# Extract text quickly
|
|
551
|
+
text = quick_extract("https://example.com", "main content")
|
|
552
|
+
|
|
553
|
+
# Query a page
|
|
554
|
+
answer = quick_query("https://news.com", "What is the top headline?")
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### Async/Await Usage
|
|
558
|
+
|
|
559
|
+
For asyncio-based applications:
|
|
560
|
+
|
|
561
|
+
```python
|
|
562
|
+
import asyncio
|
|
563
|
+
from owl_browser import AsyncBrowser
|
|
564
|
+
|
|
565
|
+
async def main():
|
|
566
|
+
async with AsyncBrowser() as browser:
|
|
567
|
+
page = await browser.new_page()
|
|
568
|
+
await page.goto("https://example.com")
|
|
569
|
+
|
|
570
|
+
# All methods are async
|
|
571
|
+
await page.click("search button")
|
|
572
|
+
await page.type("search input", "hello world")
|
|
573
|
+
|
|
574
|
+
text = await page.extract_text()
|
|
575
|
+
await page.screenshot("screenshot.png")
|
|
576
|
+
|
|
577
|
+
asyncio.run(main())
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
#### Concurrent Async Scraping
|
|
581
|
+
|
|
582
|
+
```python
|
|
583
|
+
import asyncio
|
|
584
|
+
from owl_browser import AsyncBrowser, ProxyConfig, ProxyType
|
|
585
|
+
|
|
586
|
+
async def scrape_url(browser, url):
|
|
587
|
+
"""Scrape a single URL."""
|
|
588
|
+
page = await browser.new_page()
|
|
589
|
+
try:
|
|
590
|
+
await page.goto(url)
|
|
591
|
+
return {
|
|
592
|
+
"url": url,
|
|
593
|
+
"title": await page.get_title(),
|
|
594
|
+
"text": await page.extract_text("main")
|
|
595
|
+
}
|
|
596
|
+
finally:
|
|
597
|
+
await page.close()
|
|
598
|
+
|
|
599
|
+
async def main():
|
|
600
|
+
urls = [
|
|
601
|
+
"https://example1.com",
|
|
602
|
+
"https://example2.com",
|
|
603
|
+
"https://example3.com",
|
|
604
|
+
]
|
|
605
|
+
|
|
606
|
+
async with AsyncBrowser() as browser:
|
|
607
|
+
# Scrape all URLs concurrently
|
|
608
|
+
results = await asyncio.gather(*[
|
|
609
|
+
scrape_url(browser, url) for url in urls
|
|
610
|
+
])
|
|
611
|
+
|
|
612
|
+
for result in results:
|
|
613
|
+
print(f"{result['title']}: {result['text'][:50]}...")
|
|
614
|
+
|
|
615
|
+
asyncio.run(main())
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
#### Quick Async Utilities
|
|
619
|
+
|
|
620
|
+
```python
|
|
621
|
+
import asyncio
|
|
622
|
+
from owl_browser import async_screenshot, async_extract, async_query
|
|
623
|
+
|
|
624
|
+
async def main():
|
|
625
|
+
# Quick screenshot
|
|
626
|
+
await async_screenshot("https://example.com", "example.png")
|
|
627
|
+
|
|
628
|
+
# Quick extraction
|
|
629
|
+
text = await async_extract("https://example.com", "main content")
|
|
630
|
+
|
|
631
|
+
# Quick LLM query
|
|
632
|
+
answer = await async_query("https://news.com", "What is the top headline?")
|
|
633
|
+
|
|
634
|
+
asyncio.run(main())
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
## API Reference
|
|
638
|
+
|
|
639
|
+
### Browser
|
|
640
|
+
|
|
641
|
+
| Method | Description |
|
|
642
|
+
|--------|-------------|
|
|
643
|
+
| `launch()` | Start the browser process |
|
|
644
|
+
| `new_page(proxy?, llm?)` | Create a new page (context) |
|
|
645
|
+
| `pages()` | Get all active pages |
|
|
646
|
+
| `get_llm_status()` | Check LLM availability |
|
|
647
|
+
| `get_demographics()` | Get location, time, weather |
|
|
648
|
+
| `close()` | Close browser and all pages |
|
|
649
|
+
|
|
650
|
+
### BrowserContext (Page)
|
|
651
|
+
|
|
652
|
+
#### Navigation
|
|
653
|
+
| Method | Description |
|
|
654
|
+
|--------|-------------|
|
|
655
|
+
| `goto(url)` | Navigate to URL |
|
|
656
|
+
| `reload(ignore_cache?)` | Reload page |
|
|
657
|
+
| `go_back()` | Navigate back |
|
|
658
|
+
| `go_forward()` | Navigate forward |
|
|
659
|
+
|
|
660
|
+
#### Interaction
|
|
661
|
+
| Method | Description |
|
|
662
|
+
|--------|-------------|
|
|
663
|
+
| `click(selector)` | Click element |
|
|
664
|
+
| `type(selector, text)` | Type into input |
|
|
665
|
+
| `pick(selector, value)` | Select from dropdown |
|
|
666
|
+
| `press_key(key)` | Press special key |
|
|
667
|
+
| `submit_form()` | Submit focused form |
|
|
668
|
+
| `highlight(selector)` | Highlight element |
|
|
669
|
+
|
|
670
|
+
#### Content
|
|
671
|
+
| Method | Description |
|
|
672
|
+
|--------|-------------|
|
|
673
|
+
| `extract_text(selector?)` | Extract text content |
|
|
674
|
+
| `get_html(clean_level?)` | Get HTML |
|
|
675
|
+
| `get_markdown(...)` | Get as Markdown |
|
|
676
|
+
| `extract_json(template?)` | Extract structured JSON |
|
|
677
|
+
| `summarize_page()` | Get LLM page summary |
|
|
678
|
+
|
|
679
|
+
#### AI Features
|
|
680
|
+
| Method | Description |
|
|
681
|
+
|--------|-------------|
|
|
682
|
+
| `query_page(query)` | Ask LLM about page |
|
|
683
|
+
| `execute_nla(command)` | Execute NL command |
|
|
684
|
+
| `solve_captcha()` | Auto-solve CAPTCHA |
|
|
685
|
+
|
|
686
|
+
#### Screenshot & Video
|
|
687
|
+
| Method | Description |
|
|
688
|
+
|--------|-------------|
|
|
689
|
+
| `screenshot(path?)` | Take screenshot |
|
|
690
|
+
| `start_video_recording(fps?)` | Start recording |
|
|
691
|
+
| `stop_video_recording()` | Stop and save video |
|
|
692
|
+
|
|
693
|
+
#### Cookies & Proxy
|
|
694
|
+
| Method | Description |
|
|
695
|
+
|--------|-------------|
|
|
696
|
+
| `get_cookies(url?)` | Get cookies |
|
|
697
|
+
| `set_cookie(...)` | Set a cookie |
|
|
698
|
+
| `delete_cookies(...)` | Delete cookies |
|
|
699
|
+
| `set_proxy(config)` | Configure proxy |
|
|
700
|
+
| `get_proxy_status()` | Get proxy status |
|
|
701
|
+
| `connect_proxy()` | Enable proxy |
|
|
702
|
+
| `disconnect_proxy()` | Disable proxy |
|
|
703
|
+
|
|
704
|
+
## Error Handling
|
|
705
|
+
|
|
706
|
+
The SDK provides specific exception types for different error scenarios:
|
|
707
|
+
|
|
708
|
+
```python
|
|
709
|
+
from owl_browser import (
|
|
710
|
+
Browser, RemoteConfig, JWTConfig, AuthMode,
|
|
711
|
+
AuthenticationError, RateLimitError, IPBlockedError,
|
|
712
|
+
LicenseError, BrowserInitializationError
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
try:
|
|
716
|
+
browser = Browser(remote=RemoteConfig(
|
|
717
|
+
url="http://localhost:8080",
|
|
718
|
+
auth_mode=AuthMode.JWT,
|
|
719
|
+
jwt=JWTConfig(private_key="/path/to/private.pem")
|
|
720
|
+
))
|
|
721
|
+
browser.launch()
|
|
722
|
+
|
|
723
|
+
page = browser.new_page()
|
|
724
|
+
page.goto("https://example.com")
|
|
725
|
+
|
|
726
|
+
except AuthenticationError as e:
|
|
727
|
+
# 401 - Invalid or expired token
|
|
728
|
+
print(f"Auth failed: {e.message}")
|
|
729
|
+
print(f"Reason: {e.reason}")
|
|
730
|
+
|
|
731
|
+
except RateLimitError as e:
|
|
732
|
+
# 429 - Too many requests
|
|
733
|
+
print(f"Rate limited. Retry after: {e.retry_after} seconds")
|
|
734
|
+
|
|
735
|
+
except IPBlockedError as e:
|
|
736
|
+
# 403 - IP not whitelisted
|
|
737
|
+
print(f"IP blocked: {e.ip_address}")
|
|
738
|
+
|
|
739
|
+
except LicenseError as e:
|
|
740
|
+
# License validation failed
|
|
741
|
+
print(f"License error: {e.status}")
|
|
742
|
+
|
|
743
|
+
except BrowserInitializationError as e:
|
|
744
|
+
print(f"Failed to start browser: {e}")
|
|
745
|
+
|
|
746
|
+
finally:
|
|
747
|
+
browser.close()
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
### Exception Types
|
|
751
|
+
|
|
752
|
+
| Exception | HTTP Code | Description |
|
|
753
|
+
|-----------|-----------|-------------|
|
|
754
|
+
| `AuthenticationError` | 401 | Invalid/expired token or JWT signature mismatch |
|
|
755
|
+
| `RateLimitError` | 429 | Too many requests, includes `retry_after` in seconds |
|
|
756
|
+
| `IPBlockedError` | 403 | Client IP not in whitelist |
|
|
757
|
+
| `LicenseError` | 503 | Browser license validation failed |
|
|
758
|
+
| `BrowserInitializationError` | - | Failed to start/connect to browser |
|
|
759
|
+
| `CommandTimeoutError` | - | Operation timed out |
|
|
760
|
+
| `ElementNotFoundError` | - | Element not found on page |
|
|
761
|
+
|
|
762
|
+
## Thread Safety
|
|
763
|
+
|
|
764
|
+
The SDK is designed for concurrent usage:
|
|
765
|
+
|
|
766
|
+
- **Browser** instance can be shared across threads
|
|
767
|
+
- Each **BrowserContext** (page) is isolated
|
|
768
|
+
- Multiple pages can run operations simultaneously
|
|
769
|
+
- IPC communication is thread-safe with proper locking
|
|
770
|
+
|
|
771
|
+
Best practice for concurrent scraping:
|
|
772
|
+
1. Create one `Browser` instance
|
|
773
|
+
2. Create separate pages for each concurrent task
|
|
774
|
+
3. Close pages when done to free resources
|
|
775
|
+
|
|
776
|
+
## Requirements
|
|
777
|
+
|
|
778
|
+
- Python 3.8+
|
|
779
|
+
- macOS or Linux
|
|
780
|
+
- Built Owl Browser binary
|
|
781
|
+
|
|
782
|
+
## License
|
|
783
|
+
|
|
784
|
+
MIT License - see the main project for details.
|