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.
@@ -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.