chore: import upstream snapshot with attribution
Test Browser Use CLI Install / uv pip install (ubuntu-latest) (push) Failing after 1s
Test Browser Use CLI Install / uvx browser-use from local wheel (push) Failing after 1s
Test Browser Use CLI Install / uvx browser-use[cli] from PyPI (push) Failing after 1s
package / pip-install-on-macos-latest-py-3.11 (push) Has been skipped
package / pip-install-on-macos-latest-py-3.13 (push) Has been skipped
package / pip-install-on-ubuntu-latest-py-3.11 (push) Has been skipped
package / pip-install-on-windows-latest-py-3.13 (push) Has been skipped
cloud_evals / trigger_cloud_eval_image_build (push) Failing after 1s
docker / build_publish_image (push) Failing after 1s
Test Browser Use CLI Install / browser-use skill sync (push) Failing after 1s
lint / code-style (push) Failing after 0s
lint / type-checker (push) Failing after 1s
package / pip-build (push) Failing after 1s
lint / syntax-errors (push) Failing after 3s
package / pip-install-on-ubuntu-latest-py-3.13 (push) Has been skipped
package / pip-install-on-windows-latest-py-3.11 (push) Has been skipped
test / ${{ matrix.test_filename }} (push) Has been skipped
test / evaluate-tasks (push) Has been skipped
test / setup-chromium (push) Failing after 2s
test / find_tests (push) Failing after 2s
Test Browser Use CLI Install / uv pip install (windows-latest) (push) Has been cancelled
Test Browser Use CLI Install / uv pip install (macos-latest) (push) Has been cancelled
Test Browser Use CLI Install / uv pip install (ubuntu-latest) (push) Failing after 1s
Test Browser Use CLI Install / uvx browser-use from local wheel (push) Failing after 1s
Test Browser Use CLI Install / uvx browser-use[cli] from PyPI (push) Failing after 1s
package / pip-install-on-macos-latest-py-3.11 (push) Has been skipped
package / pip-install-on-macos-latest-py-3.13 (push) Has been skipped
package / pip-install-on-ubuntu-latest-py-3.11 (push) Has been skipped
package / pip-install-on-windows-latest-py-3.13 (push) Has been skipped
cloud_evals / trigger_cloud_eval_image_build (push) Failing after 1s
docker / build_publish_image (push) Failing after 1s
Test Browser Use CLI Install / browser-use skill sync (push) Failing after 1s
lint / code-style (push) Failing after 0s
lint / type-checker (push) Failing after 1s
package / pip-build (push) Failing after 1s
lint / syntax-errors (push) Failing after 3s
package / pip-install-on-ubuntu-latest-py-3.13 (push) Has been skipped
package / pip-install-on-windows-latest-py-3.11 (push) Has been skipped
test / ${{ matrix.test_filename }} (push) Has been skipped
test / evaluate-tasks (push) Has been skipped
test / setup-chromium (push) Failing after 2s
test / find_tests (push) Failing after 2s
Test Browser Use CLI Install / uv pip install (windows-latest) (push) Has been cancelled
Test Browser Use CLI Install / uv pip install (macos-latest) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
"""
|
||||
Cloud Example 1: Your First Browser Use Cloud Task
|
||||
==================================================
|
||||
|
||||
This example demonstrates the most basic Browser Use Cloud functionality:
|
||||
- Create a simple automation task
|
||||
- Get the task ID
|
||||
- Monitor completion
|
||||
- Retrieve results
|
||||
|
||||
Perfect for first-time cloud users to understand the API basics.
|
||||
|
||||
Cost: ~$0.04 (1 task + 3 steps with GPT-4.1 mini)
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
# Configuration
|
||||
API_KEY = os.getenv('BROWSER_USE_API_KEY')
|
||||
if not API_KEY:
|
||||
raise ValueError(
|
||||
'Please set BROWSER_USE_API_KEY environment variable. You can also create an API key at https://cloud.browser-use.com/new-api-key'
|
||||
)
|
||||
|
||||
BASE_URL = os.getenv('BROWSER_USE_BASE_URL', 'https://api.browser-use.com/api/v1')
|
||||
TIMEOUT = int(os.getenv('BROWSER_USE_TIMEOUT', '30'))
|
||||
HEADERS = {'Authorization': f'Bearer {API_KEY}', 'Content-Type': 'application/json'}
|
||||
|
||||
|
||||
def _request_with_retry(method: str, url: str, **kwargs) -> requests.Response:
|
||||
"""Make HTTP request with timeout and retry logic."""
|
||||
kwargs.setdefault('timeout', TIMEOUT)
|
||||
|
||||
for attempt in range(3):
|
||||
try:
|
||||
response = requests.request(method, url, **kwargs)
|
||||
response.raise_for_status()
|
||||
return response
|
||||
except RequestException as e:
|
||||
if attempt == 2: # Last attempt
|
||||
raise
|
||||
sleep_time = 2**attempt
|
||||
print(f'⚠️ Request failed (attempt {attempt + 1}/3), retrying in {sleep_time}s: {e}')
|
||||
time.sleep(sleep_time)
|
||||
|
||||
# This line should never be reached, but satisfies type checker
|
||||
raise RuntimeError('Unexpected error in retry logic')
|
||||
|
||||
|
||||
def create_task(instructions: str) -> str:
|
||||
"""
|
||||
Create a new browser automation task.
|
||||
|
||||
Args:
|
||||
instructions: Natural language description of what the agent should do
|
||||
|
||||
Returns:
|
||||
task_id: Unique identifier for the created task
|
||||
"""
|
||||
print(f'📝 Creating task: {instructions}')
|
||||
|
||||
payload = {
|
||||
'task': instructions,
|
||||
'llm_model': 'gpt-4.1-mini', # Cost-effective model
|
||||
'max_agent_steps': 10, # Prevent runaway costs
|
||||
'enable_public_share': True, # Enable shareable execution URLs
|
||||
}
|
||||
|
||||
response = _request_with_retry('post', f'{BASE_URL}/run-task', headers=HEADERS, json=payload)
|
||||
|
||||
task_id = response.json()['id']
|
||||
print(f'✅ Task created with ID: {task_id}')
|
||||
return task_id
|
||||
|
||||
|
||||
def get_task_status(task_id: str) -> dict[str, Any]:
|
||||
"""Get the current status of a task."""
|
||||
response = _request_with_retry('get', f'{BASE_URL}/task/{task_id}/status', headers=HEADERS)
|
||||
return response.json()
|
||||
|
||||
|
||||
def get_task_details(task_id: str) -> dict[str, Any]:
|
||||
"""Get full task details including steps and output."""
|
||||
response = _request_with_retry('get', f'{BASE_URL}/task/{task_id}', headers=HEADERS)
|
||||
return response.json()
|
||||
|
||||
|
||||
def wait_for_completion(task_id: str, poll_interval: int = 3) -> dict[str, Any]:
|
||||
"""
|
||||
Wait for task completion and show progress.
|
||||
|
||||
Args:
|
||||
task_id: The task to monitor
|
||||
poll_interval: How often to check status (seconds)
|
||||
|
||||
Returns:
|
||||
Complete task details with output
|
||||
"""
|
||||
print(f'⏳ Monitoring task {task_id}...')
|
||||
|
||||
step_count = 0
|
||||
start_time = time.time()
|
||||
|
||||
while True:
|
||||
details = get_task_details(task_id)
|
||||
status = details['status']
|
||||
current_steps = len(details.get('steps', []))
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
# Clear line and show current progress
|
||||
if current_steps > step_count:
|
||||
step_count = current_steps
|
||||
|
||||
# Build status message
|
||||
if status == 'running':
|
||||
if current_steps > 0:
|
||||
status_msg = f'🔄 Step {current_steps} | ⏱️ {elapsed:.0f}s | 🤖 Agent working...'
|
||||
else:
|
||||
status_msg = f'🤖 Agent starting... | ⏱️ {elapsed:.0f}s'
|
||||
else:
|
||||
status_msg = f'🔄 Step {current_steps} | ⏱️ {elapsed:.0f}s | Status: {status}'
|
||||
|
||||
# Clear line and print status
|
||||
print(f'\r{status_msg:<80}', end='', flush=True)
|
||||
|
||||
# Check if finished
|
||||
if status == 'finished':
|
||||
print(f'\r✅ Task completed successfully! ({current_steps} steps in {elapsed:.1f}s)' + ' ' * 20)
|
||||
return details
|
||||
elif status in ['failed', 'stopped']:
|
||||
print(f'\r❌ Task {status} after {current_steps} steps' + ' ' * 30)
|
||||
return details
|
||||
|
||||
time.sleep(poll_interval)
|
||||
|
||||
|
||||
def main():
|
||||
"""Run a basic cloud automation task."""
|
||||
print('🚀 Browser Use Cloud - Basic Task Example')
|
||||
print('=' * 50)
|
||||
|
||||
# Define a simple search task (using DuckDuckGo to avoid captchas)
|
||||
task_description = (
|
||||
"Go to DuckDuckGo and search for 'browser automation tools'. Tell me the top 3 results with their titles and URLs."
|
||||
)
|
||||
|
||||
try:
|
||||
# Step 1: Create the task
|
||||
task_id = create_task(task_description)
|
||||
|
||||
# Step 2: Wait for completion
|
||||
result = wait_for_completion(task_id)
|
||||
|
||||
# Step 3: Display results
|
||||
print('\n📊 Results:')
|
||||
print('-' * 30)
|
||||
print(f'Status: {result["status"]}')
|
||||
print(f'Steps taken: {len(result.get("steps", []))}')
|
||||
|
||||
if result.get('output'):
|
||||
print(f'Output: {result["output"]}')
|
||||
else:
|
||||
print('No output available')
|
||||
|
||||
# Show share URLs for viewing execution
|
||||
if result.get('live_url'):
|
||||
print(f'\n🔗 Live Preview: {result["live_url"]}')
|
||||
if result.get('public_share_url'):
|
||||
print(f'🌐 Share URL: {result["public_share_url"]}')
|
||||
elif result.get('share_url'):
|
||||
print(f'🌐 Share URL: {result["share_url"]}')
|
||||
|
||||
if not result.get('live_url') and not result.get('public_share_url') and not result.get('share_url'):
|
||||
print("\n💡 Tip: Add 'enable_public_share': True to task payload to get shareable URLs")
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f'❌ API Error: {e}')
|
||||
except Exception as e:
|
||||
print(f'❌ Error: {e}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,265 @@
|
||||
"""
|
||||
Cloud Example 2: Ultra-Fast Mode with Gemini Flash ⚡
|
||||
====================================================
|
||||
|
||||
This example demonstrates the fastest and most cost-effective configuration:
|
||||
- Gemini 2.5 Flash model ($0.01 per step)
|
||||
- No proxy (faster execution, but no captcha solving)
|
||||
- No element highlighting (better performance)
|
||||
- Optimized viewport size
|
||||
- Maximum speed configuration
|
||||
|
||||
Perfect for: Quick content generation, humor tasks, fast web scraping
|
||||
|
||||
Cost: ~$0.03 (1 task + 2-3 steps with Gemini Flash)
|
||||
Speed: 2-3x faster than default configuration
|
||||
Fun Factor: 💯 (Creates hilarious tech commentary)
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
# Configuration
|
||||
API_KEY = os.getenv('BROWSER_USE_API_KEY')
|
||||
if not API_KEY:
|
||||
raise ValueError(
|
||||
'Please set BROWSER_USE_API_KEY environment variable. You can also create an API key at https://cloud.browser-use.com/new-api-key'
|
||||
)
|
||||
|
||||
BASE_URL = os.getenv('BROWSER_USE_BASE_URL', 'https://api.browser-use.com/api/v1')
|
||||
TIMEOUT = int(os.getenv('BROWSER_USE_TIMEOUT', '30'))
|
||||
HEADERS = {'Authorization': f'Bearer {API_KEY}', 'Content-Type': 'application/json'}
|
||||
|
||||
|
||||
def _request_with_retry(method: str, url: str, **kwargs) -> requests.Response:
|
||||
"""Make HTTP request with timeout and retry logic."""
|
||||
kwargs.setdefault('timeout', TIMEOUT)
|
||||
|
||||
for attempt in range(3):
|
||||
try:
|
||||
response = requests.request(method, url, **kwargs)
|
||||
response.raise_for_status()
|
||||
return response
|
||||
except RequestException as e:
|
||||
if attempt == 2: # Last attempt
|
||||
raise
|
||||
sleep_time = 2**attempt
|
||||
print(f'⚠️ Request failed (attempt {attempt + 1}/3), retrying in {sleep_time}s: {e}')
|
||||
time.sleep(sleep_time)
|
||||
|
||||
raise RuntimeError('Unexpected error in retry logic')
|
||||
|
||||
|
||||
def create_fast_task(instructions: str) -> str:
|
||||
"""
|
||||
Create a browser automation task optimized for speed and cost.
|
||||
|
||||
Args:
|
||||
instructions: Natural language description of what the agent should do
|
||||
|
||||
Returns:
|
||||
task_id: Unique identifier for the created task
|
||||
"""
|
||||
print(f'⚡ Creating FAST task: {instructions}')
|
||||
|
||||
# Ultra-fast configuration
|
||||
payload = {
|
||||
'task': instructions,
|
||||
# Model: Fastest and cheapest
|
||||
'llm_model': 'gemini-2.5-flash',
|
||||
# Performance optimizations
|
||||
'use_proxy': False, # No proxy = faster execution
|
||||
'highlight_elements': False, # No highlighting = better performance
|
||||
'use_adblock': True, # Block ads for faster loading
|
||||
# Viewport optimization (smaller = faster)
|
||||
'browser_viewport_width': 1024,
|
||||
'browser_viewport_height': 768,
|
||||
# Cost control
|
||||
'max_agent_steps': 25, # Reasonable limit for fast tasks
|
||||
# Enable sharing for viewing execution
|
||||
'enable_public_share': True, # Get shareable URLs
|
||||
# Optional: Speed up with domain restrictions
|
||||
# "allowed_domains": ["google.com", "*.google.com"]
|
||||
}
|
||||
|
||||
response = _request_with_retry('post', f'{BASE_URL}/run-task', headers=HEADERS, json=payload)
|
||||
|
||||
task_id = response.json()['id']
|
||||
print(f'✅ Fast task created with ID: {task_id}')
|
||||
print('⚡ Configuration: Gemini Flash + No Proxy + No Highlighting')
|
||||
return task_id
|
||||
|
||||
|
||||
def monitor_fast_task(task_id: str) -> dict[str, Any]:
|
||||
"""
|
||||
Monitor task with optimized polling for fast execution.
|
||||
|
||||
Args:
|
||||
task_id: The task to monitor
|
||||
|
||||
Returns:
|
||||
Complete task details with output
|
||||
"""
|
||||
print(f'🚀 Fast monitoring task {task_id}...')
|
||||
|
||||
start_time = time.time()
|
||||
step_count = 0
|
||||
last_step_time = start_time
|
||||
|
||||
# Faster polling for quick tasks
|
||||
poll_interval = 1 # Check every second for fast tasks
|
||||
|
||||
while True:
|
||||
response = _request_with_retry('get', f'{BASE_URL}/task/{task_id}', headers=HEADERS)
|
||||
details = response.json()
|
||||
status = details['status']
|
||||
|
||||
# Show progress with timing
|
||||
current_steps = len(details.get('steps', []))
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
# Build status message
|
||||
if current_steps > step_count:
|
||||
step_time = time.time() - last_step_time
|
||||
last_step_time = time.time()
|
||||
step_count = current_steps
|
||||
step_msg = f'🔥 Step {current_steps} | ⚡ {step_time:.1f}s | Total: {elapsed:.1f}s'
|
||||
else:
|
||||
if status == 'running':
|
||||
step_msg = f'🚀 Step {current_steps} | ⏱️ {elapsed:.1f}s | Fast processing...'
|
||||
else:
|
||||
step_msg = f'🚀 Step {current_steps} | ⏱️ {elapsed:.1f}s | Status: {status}'
|
||||
|
||||
# Clear line and show progress
|
||||
print(f'\r{step_msg:<80}', end='', flush=True)
|
||||
|
||||
# Check completion
|
||||
if status == 'finished':
|
||||
total_time = time.time() - start_time
|
||||
if current_steps > 0:
|
||||
avg_msg = f'⚡ Average: {total_time / current_steps:.1f}s per step'
|
||||
else:
|
||||
avg_msg = '⚡ No steps recorded'
|
||||
print(f'\r🏁 Task completed in {total_time:.1f}s! {avg_msg}' + ' ' * 20)
|
||||
return details
|
||||
|
||||
elif status in ['failed', 'stopped']:
|
||||
print(f'\r❌ Task {status} after {elapsed:.1f}s' + ' ' * 30)
|
||||
return details
|
||||
|
||||
time.sleep(poll_interval)
|
||||
|
||||
|
||||
def run_speed_comparison():
|
||||
"""Run multiple tasks to compare speed vs accuracy."""
|
||||
print('\n🏃♂️ Speed Comparison Demo')
|
||||
print('=' * 40)
|
||||
|
||||
tasks = [
|
||||
'Go to ProductHunt and roast the top product like a sarcastic tech reviewer',
|
||||
'Visit Reddit r/ProgrammerHumor and summarize the top post as a dramatic news story',
|
||||
"Check GitHub trending and write a conspiracy theory about why everyone's switching to Rust",
|
||||
]
|
||||
|
||||
results = []
|
||||
|
||||
for i, task in enumerate(tasks, 1):
|
||||
print(f'\n📝 Fast Task {i}/{len(tasks)}')
|
||||
print(f'Task: {task}')
|
||||
|
||||
start = time.time()
|
||||
task_id = create_fast_task(task)
|
||||
result = monitor_fast_task(task_id)
|
||||
end = time.time()
|
||||
|
||||
results.append(
|
||||
{
|
||||
'task': task,
|
||||
'duration': end - start,
|
||||
'steps': len(result.get('steps', [])),
|
||||
'status': result['status'],
|
||||
'output': result.get('output', '')[:100] + '...' if result.get('output') else 'No output',
|
||||
}
|
||||
)
|
||||
|
||||
# Summary
|
||||
print('\n📊 Speed Summary')
|
||||
print('=' * 50)
|
||||
total_time = sum(r['duration'] for r in results)
|
||||
total_steps = sum(r['steps'] for r in results)
|
||||
|
||||
for i, result in enumerate(results, 1):
|
||||
print(f'Task {i}: {result["duration"]:.1f}s ({result["steps"]} steps) - {result["status"]}')
|
||||
|
||||
print(f'\n⚡ Total time: {total_time:.1f}s')
|
||||
print(f'🔥 Average per task: {total_time / len(results):.1f}s')
|
||||
if total_steps > 0:
|
||||
print(f'💨 Average per step: {total_time / total_steps:.1f}s')
|
||||
else:
|
||||
print('💨 Average per step: N/A (no steps recorded)')
|
||||
|
||||
|
||||
def main():
|
||||
"""Demonstrate ultra-fast cloud automation."""
|
||||
print('⚡ Browser Use Cloud - Ultra-Fast Mode with Gemini Flash')
|
||||
print('=' * 60)
|
||||
|
||||
print('🎯 Configuration Benefits:')
|
||||
print('• Gemini Flash: $0.01 per step (cheapest)')
|
||||
print('• No proxy: 30% faster execution')
|
||||
print('• No highlighting: Better performance')
|
||||
print('• Optimized viewport: Faster rendering')
|
||||
|
||||
try:
|
||||
# Single fast task
|
||||
print('\n🚀 Single Fast Task Demo')
|
||||
print('-' * 30)
|
||||
|
||||
task = """
|
||||
Go to Hacker News (news.ycombinator.com) and get the top 3 articles from the front page.
|
||||
|
||||
Then, write a funny tech news segment in the style of Fireship YouTube channel:
|
||||
- Be sarcastic and witty about tech trends
|
||||
- Use developer humor and memes
|
||||
- Make fun of common programming struggles
|
||||
- Include phrases like "And yes, it runs on JavaScript" or "Plot twist: it's written in Rust"
|
||||
- Keep it under 250 words but make it entertaining
|
||||
- Structure it like a news anchor delivering breaking tech news
|
||||
|
||||
Make each story sound dramatic but also hilarious, like you're reporting on the most important events in human history.
|
||||
"""
|
||||
task_id = create_fast_task(task)
|
||||
result = monitor_fast_task(task_id)
|
||||
|
||||
print(f'\n📊 Result: {result.get("output", "No output")}')
|
||||
|
||||
# Show execution URLs
|
||||
if result.get('live_url'):
|
||||
print(f'\n🔗 Live Preview: {result["live_url"]}')
|
||||
if result.get('public_share_url'):
|
||||
print(f'🌐 Share URL: {result["public_share_url"]}')
|
||||
elif result.get('share_url'):
|
||||
print(f'🌐 Share URL: {result["share_url"]}')
|
||||
|
||||
# Optional: Run speed comparison with --compare flag
|
||||
parser = argparse.ArgumentParser(description='Fast mode demo with Gemini Flash')
|
||||
parser.add_argument('--compare', action='store_true', help='Run speed comparison with 3 tasks')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.compare:
|
||||
print('\n🏃♂️ Running speed comparison...')
|
||||
run_speed_comparison()
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f'❌ API Error: {e}')
|
||||
except Exception as e:
|
||||
print(f'❌ Error: {e}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,362 @@
|
||||
"""
|
||||
Cloud Example 3: Structured JSON Output 📋
|
||||
==========================================
|
||||
|
||||
This example demonstrates how to get structured, validated JSON output:
|
||||
- Define Pydantic schemas for type safety
|
||||
- Extract structured data from websites
|
||||
- Validate and parse JSON responses
|
||||
- Handle different data types and nested structures
|
||||
|
||||
Perfect for: Data extraction, API integration, structured analysis
|
||||
|
||||
Cost: ~$0.06 (1 task + 5-6 steps with GPT-4.1 mini)
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
from pydantic import BaseModel, Field, ValidationError
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
# Configuration
|
||||
API_KEY = os.getenv('BROWSER_USE_API_KEY')
|
||||
if not API_KEY:
|
||||
raise ValueError(
|
||||
'Please set BROWSER_USE_API_KEY environment variable. You can also create an API key at https://cloud.browser-use.com/new-api-key'
|
||||
)
|
||||
|
||||
BASE_URL = os.getenv('BROWSER_USE_BASE_URL', 'https://api.browser-use.com/api/v1')
|
||||
TIMEOUT = int(os.getenv('BROWSER_USE_TIMEOUT', '30'))
|
||||
HEADERS = {'Authorization': f'Bearer {API_KEY}', 'Content-Type': 'application/json'}
|
||||
|
||||
|
||||
def _request_with_retry(method: str, url: str, **kwargs) -> requests.Response:
|
||||
"""Make HTTP request with timeout and retry logic."""
|
||||
kwargs.setdefault('timeout', TIMEOUT)
|
||||
|
||||
for attempt in range(3):
|
||||
try:
|
||||
response = requests.request(method, url, **kwargs)
|
||||
response.raise_for_status()
|
||||
return response
|
||||
except RequestException as e:
|
||||
if attempt == 2: # Last attempt
|
||||
raise
|
||||
sleep_time = 2**attempt
|
||||
print(f'⚠️ Request failed (attempt {attempt + 1}/3), retrying in {sleep_time}s: {e}')
|
||||
time.sleep(sleep_time)
|
||||
|
||||
raise RuntimeError('Unexpected error in retry logic')
|
||||
|
||||
|
||||
# Define structured output schemas using Pydantic
|
||||
class NewsArticle(BaseModel):
|
||||
"""Schema for a news article."""
|
||||
|
||||
title: str = Field(description='The headline of the article')
|
||||
summary: str = Field(description='Brief summary of the article')
|
||||
url: str = Field(description='Direct link to the article')
|
||||
published_date: str | None = Field(description='Publication date if available')
|
||||
category: str | None = Field(description='Article category/section')
|
||||
|
||||
|
||||
class NewsResponse(BaseModel):
|
||||
"""Schema for multiple news articles."""
|
||||
|
||||
articles: list[NewsArticle] = Field(description='List of news articles')
|
||||
source_website: str = Field(description='The website where articles were found')
|
||||
extracted_at: str = Field(description='When the data was extracted')
|
||||
|
||||
|
||||
class ProductInfo(BaseModel):
|
||||
"""Schema for product information."""
|
||||
|
||||
name: str = Field(description='Product name')
|
||||
price: float = Field(description='Product price in USD')
|
||||
rating: float | None = Field(description='Average rating (0-5 scale)')
|
||||
availability: str = Field(description='Stock status (in stock, out of stock, etc.)')
|
||||
description: str = Field(description='Product description')
|
||||
|
||||
|
||||
class CompanyInfo(BaseModel):
|
||||
"""Schema for company information."""
|
||||
|
||||
name: str = Field(description='Company name')
|
||||
stock_symbol: str | None = Field(description='Stock ticker symbol')
|
||||
market_cap: str | None = Field(description='Market capitalization')
|
||||
industry: str = Field(description='Primary industry')
|
||||
headquarters: str = Field(description='Headquarters location')
|
||||
founded_year: int | None = Field(description='Year founded')
|
||||
|
||||
|
||||
def create_structured_task(instructions: str, schema_model: type[BaseModel], **kwargs) -> str:
|
||||
"""
|
||||
Create a task that returns structured JSON output.
|
||||
|
||||
Args:
|
||||
instructions: Task description
|
||||
schema_model: Pydantic model defining the expected output structure
|
||||
**kwargs: Additional task parameters
|
||||
|
||||
Returns:
|
||||
task_id: Unique identifier for the created task
|
||||
"""
|
||||
print(f'📝 Creating structured task: {instructions}')
|
||||
print(f'🏗️ Expected schema: {schema_model.__name__}')
|
||||
|
||||
# Generate JSON schema from Pydantic model
|
||||
json_schema = schema_model.model_json_schema()
|
||||
|
||||
payload = {
|
||||
'task': instructions,
|
||||
'structured_output_json': json.dumps(json_schema),
|
||||
'llm_model': 'gpt-4.1-mini',
|
||||
'max_agent_steps': 15,
|
||||
'enable_public_share': True, # Enable shareable execution URLs
|
||||
**kwargs,
|
||||
}
|
||||
|
||||
response = _request_with_retry('post', f'{BASE_URL}/run-task', headers=HEADERS, json=payload)
|
||||
|
||||
task_id = response.json()['id']
|
||||
print(f'✅ Structured task created: {task_id}')
|
||||
return task_id
|
||||
|
||||
|
||||
def wait_for_structured_completion(task_id: str, max_wait_time: int = 300) -> dict[str, Any]:
|
||||
"""Wait for task completion and return the result."""
|
||||
print(f'⏳ Waiting for structured output (max {max_wait_time}s)...')
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
while True:
|
||||
response = _request_with_retry('get', f'{BASE_URL}/task/{task_id}/status', headers=HEADERS)
|
||||
status = response.json()
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
# Check for timeout
|
||||
if elapsed > max_wait_time:
|
||||
print(f'\r⏰ Task timeout after {max_wait_time}s - stopping wait' + ' ' * 30)
|
||||
# Get final details before timeout
|
||||
details_response = _request_with_retry('get', f'{BASE_URL}/task/{task_id}', headers=HEADERS)
|
||||
details = details_response.json()
|
||||
return details
|
||||
|
||||
# Get step count from full details for better progress tracking
|
||||
details_response = _request_with_retry('get', f'{BASE_URL}/task/{task_id}', headers=HEADERS)
|
||||
details = details_response.json()
|
||||
steps = len(details.get('steps', []))
|
||||
|
||||
# Build status message
|
||||
if status == 'running':
|
||||
status_msg = f'📋 Structured task | Step {steps} | ⏱️ {elapsed:.0f}s | 🔄 Extracting...'
|
||||
else:
|
||||
status_msg = f'📋 Structured task | Step {steps} | ⏱️ {elapsed:.0f}s | Status: {status}'
|
||||
|
||||
# Clear line and show status
|
||||
print(f'\r{status_msg:<80}', end='', flush=True)
|
||||
|
||||
if status == 'finished':
|
||||
print(f'\r✅ Structured data extracted! ({steps} steps in {elapsed:.1f}s)' + ' ' * 20)
|
||||
return details
|
||||
|
||||
elif status in ['failed', 'stopped']:
|
||||
print(f'\r❌ Task {status} after {steps} steps' + ' ' * 30)
|
||||
return details
|
||||
|
||||
time.sleep(3)
|
||||
|
||||
|
||||
def validate_and_display_output(output: str, schema_model: type[BaseModel]):
|
||||
"""
|
||||
Validate the JSON output against the schema and display results.
|
||||
|
||||
Args:
|
||||
output: Raw JSON string from the task
|
||||
schema_model: Pydantic model for validation
|
||||
"""
|
||||
print('\n📊 Structured Output Analysis')
|
||||
print('=' * 40)
|
||||
|
||||
try:
|
||||
# Parse and validate the JSON
|
||||
parsed_data = schema_model.model_validate_json(output)
|
||||
print('✅ JSON validation successful!')
|
||||
|
||||
# Pretty print the structured data
|
||||
print('\n📋 Parsed Data:')
|
||||
print('-' * 20)
|
||||
print(parsed_data.model_dump_json(indent=2))
|
||||
|
||||
# Display specific fields based on model type
|
||||
if isinstance(parsed_data, NewsResponse):
|
||||
print(f'\n📰 Found {len(parsed_data.articles)} articles from {parsed_data.source_website}')
|
||||
for i, article in enumerate(parsed_data.articles[:3], 1):
|
||||
print(f'\n{i}. {article.title}')
|
||||
print(f' Summary: {article.summary[:100]}...')
|
||||
print(f' URL: {article.url}')
|
||||
|
||||
elif isinstance(parsed_data, ProductInfo):
|
||||
print(f'\n🛍️ Product: {parsed_data.name}')
|
||||
print(f' Price: ${parsed_data.price}')
|
||||
print(f' Rating: {parsed_data.rating}/5' if parsed_data.rating else ' Rating: N/A')
|
||||
print(f' Status: {parsed_data.availability}')
|
||||
|
||||
elif isinstance(parsed_data, CompanyInfo):
|
||||
print(f'\n🏢 Company: {parsed_data.name}')
|
||||
print(f' Industry: {parsed_data.industry}')
|
||||
print(f' Headquarters: {parsed_data.headquarters}')
|
||||
if parsed_data.founded_year:
|
||||
print(f' Founded: {parsed_data.founded_year}')
|
||||
|
||||
return parsed_data
|
||||
|
||||
except ValidationError as e:
|
||||
print('❌ JSON validation failed!')
|
||||
print(f'Errors: {e}')
|
||||
print(f'\nRaw output: {output[:500]}...')
|
||||
return None
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
print('❌ Invalid JSON format!')
|
||||
print(f'Error: {e}')
|
||||
print(f'\nRaw output: {output[:500]}...')
|
||||
return None
|
||||
|
||||
|
||||
def demo_news_extraction():
|
||||
"""Demo: Extract structured news data."""
|
||||
print('\n📰 Demo 1: News Article Extraction')
|
||||
print('-' * 40)
|
||||
|
||||
task = """
|
||||
Go to a major news website (like BBC, CNN, or Reuters) and extract information
|
||||
about the top 3 news articles. For each article, get the title, summary, URL,
|
||||
and any other available metadata.
|
||||
"""
|
||||
|
||||
task_id = create_structured_task(task, NewsResponse)
|
||||
result = wait_for_structured_completion(task_id)
|
||||
|
||||
if result.get('output'):
|
||||
parsed_result = validate_and_display_output(result['output'], NewsResponse)
|
||||
|
||||
# Show execution URLs
|
||||
if result.get('live_url'):
|
||||
print(f'\n🔗 Live Preview: {result["live_url"]}')
|
||||
if result.get('public_share_url'):
|
||||
print(f'🌐 Share URL: {result["public_share_url"]}')
|
||||
elif result.get('share_url'):
|
||||
print(f'🌐 Share URL: {result["share_url"]}')
|
||||
|
||||
return parsed_result
|
||||
else:
|
||||
print('❌ No structured output received')
|
||||
return None
|
||||
|
||||
|
||||
def demo_product_extraction():
|
||||
"""Demo: Extract structured product data."""
|
||||
print('\n🛍️ Demo 2: Product Information Extraction')
|
||||
print('-' * 40)
|
||||
|
||||
task = """
|
||||
Go to Amazon and search for 'wireless headphones'. Find the first product result
|
||||
and extract detailed information including name, price, rating, availability,
|
||||
and description.
|
||||
"""
|
||||
|
||||
task_id = create_structured_task(task, ProductInfo)
|
||||
result = wait_for_structured_completion(task_id)
|
||||
|
||||
if result.get('output'):
|
||||
parsed_result = validate_and_display_output(result['output'], ProductInfo)
|
||||
|
||||
# Show execution URLs
|
||||
if result.get('live_url'):
|
||||
print(f'\n🔗 Live Preview: {result["live_url"]}')
|
||||
if result.get('public_share_url'):
|
||||
print(f'🌐 Share URL: {result["public_share_url"]}')
|
||||
elif result.get('share_url'):
|
||||
print(f'🌐 Share URL: {result["share_url"]}')
|
||||
|
||||
return parsed_result
|
||||
else:
|
||||
print('❌ No structured output received')
|
||||
return None
|
||||
|
||||
|
||||
def demo_company_extraction():
|
||||
"""Demo: Extract structured company data."""
|
||||
print('\n🏢 Demo 3: Company Information Extraction')
|
||||
print('-' * 40)
|
||||
|
||||
task = """
|
||||
Go to a financial website and look up information about Apple Inc.
|
||||
Extract company details including name, stock symbol, market cap,
|
||||
industry, headquarters, and founding year.
|
||||
"""
|
||||
|
||||
task_id = create_structured_task(task, CompanyInfo)
|
||||
result = wait_for_structured_completion(task_id)
|
||||
|
||||
if result.get('output'):
|
||||
parsed_result = validate_and_display_output(result['output'], CompanyInfo)
|
||||
|
||||
# Show execution URLs
|
||||
if result.get('live_url'):
|
||||
print(f'\n🔗 Live Preview: {result["live_url"]}')
|
||||
if result.get('public_share_url'):
|
||||
print(f'🌐 Share URL: {result["public_share_url"]}')
|
||||
elif result.get('share_url'):
|
||||
print(f'🌐 Share URL: {result["share_url"]}')
|
||||
|
||||
return parsed_result
|
||||
else:
|
||||
print('❌ No structured output received')
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
"""Demonstrate structured output extraction."""
|
||||
print('📋 Browser Use Cloud - Structured JSON Output')
|
||||
print('=' * 50)
|
||||
|
||||
print('🎯 Features:')
|
||||
print('• Type-safe Pydantic schemas')
|
||||
print('• Automatic JSON validation')
|
||||
print('• Structured data extraction')
|
||||
print('• Multiple output formats')
|
||||
|
||||
try:
|
||||
# Parse command line arguments
|
||||
parser = argparse.ArgumentParser(description='Structured output extraction demo')
|
||||
parser.add_argument('--demo', choices=['news', 'product', 'company', 'all'], default='news', help='Which demo to run')
|
||||
args = parser.parse_args()
|
||||
|
||||
print(f'\n🔍 Running {args.demo} demo(s)...')
|
||||
|
||||
if args.demo == 'news':
|
||||
demo_news_extraction()
|
||||
elif args.demo == 'product':
|
||||
demo_product_extraction()
|
||||
elif args.demo == 'company':
|
||||
demo_company_extraction()
|
||||
elif args.demo == 'all':
|
||||
demo_news_extraction()
|
||||
demo_product_extraction()
|
||||
demo_company_extraction()
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f'❌ API Error: {e}')
|
||||
except Exception as e:
|
||||
print(f'❌ Error: {e}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,331 @@
|
||||
"""
|
||||
Cloud Example 4: Proxy Usage 🌍
|
||||
===============================
|
||||
|
||||
This example demonstrates reliable proxy usage scenarios:
|
||||
- Different country proxies for geo-restrictions
|
||||
- IP address and location verification
|
||||
- Region-specific content access (streaming, news)
|
||||
- Search result localization by country
|
||||
- Mobile/residential proxy benefits
|
||||
|
||||
Perfect for: Geo-restricted content, location testing, regional analysis
|
||||
|
||||
Cost: ~$0.08 (1 task + 6-8 steps with proxy enabled)
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
# Configuration
|
||||
API_KEY = os.getenv('BROWSER_USE_API_KEY')
|
||||
if not API_KEY:
|
||||
raise ValueError(
|
||||
'Please set BROWSER_USE_API_KEY environment variable. You can also create an API key at https://cloud.browser-use.com/new-api-key'
|
||||
)
|
||||
|
||||
BASE_URL = os.getenv('BROWSER_USE_BASE_URL', 'https://api.browser-use.com/api/v1')
|
||||
TIMEOUT = int(os.getenv('BROWSER_USE_TIMEOUT', '30'))
|
||||
HEADERS = {'Authorization': f'Bearer {API_KEY}', 'Content-Type': 'application/json'}
|
||||
|
||||
|
||||
def _request_with_retry(method: str, url: str, **kwargs) -> requests.Response:
|
||||
"""Make HTTP request with timeout and retry logic."""
|
||||
kwargs.setdefault('timeout', TIMEOUT)
|
||||
|
||||
for attempt in range(3):
|
||||
try:
|
||||
response = requests.request(method, url, **kwargs)
|
||||
response.raise_for_status()
|
||||
return response
|
||||
except RequestException as e:
|
||||
if attempt == 2: # Last attempt
|
||||
raise
|
||||
sleep_time = 2**attempt
|
||||
print(f'⚠️ Request failed (attempt {attempt + 1}/3), retrying in {sleep_time}s: {e}')
|
||||
time.sleep(sleep_time)
|
||||
|
||||
raise RuntimeError('Unexpected error in retry logic')
|
||||
|
||||
|
||||
def create_task_with_proxy(instructions: str, country_code: str = 'us') -> str:
|
||||
"""
|
||||
Create a task with proxy enabled from a specific country.
|
||||
|
||||
Args:
|
||||
instructions: Task description
|
||||
country_code: Proxy country ('us', 'fr', 'it', 'jp', 'au', 'de', 'fi', 'ca')
|
||||
|
||||
Returns:
|
||||
task_id: Unique identifier for the created task
|
||||
"""
|
||||
print(f'🌍 Creating task with {country_code.upper()} proxy')
|
||||
print(f'📝 Task: {instructions}')
|
||||
|
||||
payload = {
|
||||
'task': instructions,
|
||||
'llm_model': 'gpt-4.1-mini',
|
||||
# Proxy configuration
|
||||
'use_proxy': True, # Required for captcha solving
|
||||
'proxy_country_code': country_code, # Choose proxy location
|
||||
# Standard settings
|
||||
'use_adblock': True, # Block ads for faster loading
|
||||
'highlight_elements': True, # Keep highlighting for visibility
|
||||
'max_agent_steps': 15,
|
||||
# Enable sharing for viewing execution
|
||||
'enable_public_share': True, # Get shareable URLs
|
||||
}
|
||||
|
||||
response = _request_with_retry('post', f'{BASE_URL}/run-task', headers=HEADERS, json=payload)
|
||||
|
||||
task_id = response.json()['id']
|
||||
print(f'✅ Task created with {country_code.upper()} proxy: {task_id}')
|
||||
return task_id
|
||||
|
||||
|
||||
def test_ip_location(country_code: str) -> dict[str, Any]:
|
||||
"""Test IP address and location detection with proxy."""
|
||||
task = """
|
||||
Go to whatismyipaddress.com and tell me:
|
||||
1. The detected IP address
|
||||
2. The detected country/location
|
||||
3. The ISP/organization
|
||||
4. Any other location details shown
|
||||
|
||||
Please be specific about what you see on the page.
|
||||
"""
|
||||
|
||||
task_id = create_task_with_proxy(task, country_code)
|
||||
return wait_for_completion(task_id)
|
||||
|
||||
|
||||
def test_geo_restricted_content(country_code: str) -> dict[str, Any]:
|
||||
"""Test access to geo-restricted content."""
|
||||
task = """
|
||||
Go to a major news website (like BBC, CNN, or local news) and check:
|
||||
1. What content is available
|
||||
2. Any geo-restriction messages
|
||||
3. Local/regional content differences
|
||||
4. Language or currency preferences shown
|
||||
|
||||
Note any differences from what you might expect.
|
||||
"""
|
||||
|
||||
task_id = create_task_with_proxy(task, country_code)
|
||||
return wait_for_completion(task_id)
|
||||
|
||||
|
||||
def test_streaming_service_access(country_code: str) -> dict[str, Any]:
|
||||
"""Test access to region-specific streaming content."""
|
||||
task = """
|
||||
Go to a major streaming service website (like Netflix, YouTube, or BBC iPlayer)
|
||||
and check what content or messaging appears.
|
||||
|
||||
Report:
|
||||
1. What homepage content is shown
|
||||
2. Any geo-restriction messages or content differences
|
||||
3. Available content regions or language options
|
||||
4. Any pricing or availability differences
|
||||
|
||||
Note: Don't try to log in, just observe the publicly available content.
|
||||
"""
|
||||
|
||||
task_id = create_task_with_proxy(task, country_code)
|
||||
return wait_for_completion(task_id)
|
||||
|
||||
|
||||
def test_search_results_by_location(country_code: str) -> dict[str, Any]:
|
||||
"""Test how search results vary by location."""
|
||||
task = """
|
||||
Go to Google and search for "best restaurants near me" or "local news".
|
||||
|
||||
Report:
|
||||
1. What local results appear
|
||||
2. The detected location in search results
|
||||
3. Any location-specific content or ads
|
||||
4. Language preferences
|
||||
|
||||
This will show how search results change based on proxy location.
|
||||
"""
|
||||
|
||||
task_id = create_task_with_proxy(task, country_code)
|
||||
return wait_for_completion(task_id)
|
||||
|
||||
|
||||
def wait_for_completion(task_id: str) -> dict[str, Any]:
|
||||
"""Wait for task completion and return results."""
|
||||
print(f'⏳ Waiting for task {task_id} to complete...')
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
while True:
|
||||
response = _request_with_retry('get', f'{BASE_URL}/task/{task_id}', headers=HEADERS)
|
||||
details = response.json()
|
||||
|
||||
status = details['status']
|
||||
steps = len(details.get('steps', []))
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
# Build status message
|
||||
if status == 'running':
|
||||
status_msg = f'🌍 Proxy task | Step {steps} | ⏱️ {elapsed:.0f}s | 🤖 Processing...'
|
||||
else:
|
||||
status_msg = f'🌍 Proxy task | Step {steps} | ⏱️ {elapsed:.0f}s | Status: {status}'
|
||||
|
||||
# Clear line and show status
|
||||
print(f'\r{status_msg:<80}', end='', flush=True)
|
||||
|
||||
if status == 'finished':
|
||||
print(f'\r✅ Task completed in {steps} steps! ({elapsed:.1f}s total)' + ' ' * 20)
|
||||
return details
|
||||
|
||||
elif status in ['failed', 'stopped']:
|
||||
print(f'\r❌ Task {status} after {steps} steps' + ' ' * 30)
|
||||
return details
|
||||
|
||||
time.sleep(3)
|
||||
|
||||
|
||||
def demo_proxy_countries():
|
||||
"""Demonstrate proxy usage across different countries."""
|
||||
print('\n🌍 Demo 1: Proxy Countries Comparison')
|
||||
print('-' * 45)
|
||||
|
||||
countries = [('us', 'United States'), ('de', 'Germany'), ('jp', 'Japan'), ('au', 'Australia')]
|
||||
|
||||
results = {}
|
||||
|
||||
for code, name in countries:
|
||||
print(f'\n🌍 Testing {name} ({code.upper()}) proxy:')
|
||||
print('=' * 40)
|
||||
|
||||
result = test_ip_location(code)
|
||||
results[code] = result
|
||||
|
||||
if result.get('output'):
|
||||
print(f'📍 Location Result: {result["output"][:200]}...')
|
||||
|
||||
# Show execution URLs
|
||||
if result.get('live_url'):
|
||||
print(f'🔗 Live Preview: {result["live_url"]}')
|
||||
if result.get('public_share_url'):
|
||||
print(f'🌐 Share URL: {result["public_share_url"]}')
|
||||
elif result.get('share_url'):
|
||||
print(f'🌐 Share URL: {result["share_url"]}')
|
||||
|
||||
print('-' * 40)
|
||||
time.sleep(2) # Brief pause between tests
|
||||
|
||||
# Summary comparison
|
||||
print('\n📊 Proxy Location Summary:')
|
||||
print('=' * 30)
|
||||
for code, result in results.items():
|
||||
status = result.get('status', 'unknown')
|
||||
print(f'{code.upper()}: {status}')
|
||||
|
||||
|
||||
def demo_geo_restrictions():
|
||||
"""Demonstrate geo-restriction bypass."""
|
||||
print('\n🚫 Demo 2: Geo-Restriction Testing')
|
||||
print('-' * 40)
|
||||
|
||||
# Test from different locations
|
||||
locations = [('us', 'US content'), ('de', 'European content')]
|
||||
|
||||
for code, description in locations:
|
||||
print(f'\n🌍 Testing {description} with {code.upper()} proxy:')
|
||||
result = test_geo_restricted_content(code)
|
||||
|
||||
if result.get('output'):
|
||||
print(f'📰 Content Access: {result["output"][:200]}...')
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
|
||||
def demo_streaming_access():
|
||||
"""Demonstrate streaming service access with different proxies."""
|
||||
print('\n📺 Demo 3: Streaming Service Access')
|
||||
print('-' * 40)
|
||||
|
||||
locations = [('us', 'US'), ('de', 'Germany')]
|
||||
|
||||
for code, name in locations:
|
||||
print(f'\n🌍 Testing streaming access from {name}:')
|
||||
result = test_streaming_service_access(code)
|
||||
|
||||
if result.get('output'):
|
||||
print(f'📺 Access Result: {result["output"][:200]}...')
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
|
||||
def demo_search_localization():
|
||||
"""Demonstrate search result localization."""
|
||||
print('\n🔍 Demo 4: Search Localization')
|
||||
print('-' * 35)
|
||||
|
||||
locations = [('us', 'US'), ('de', 'Germany')]
|
||||
|
||||
for code, name in locations:
|
||||
print(f'\n🌍 Testing search results from {name}:')
|
||||
result = test_search_results_by_location(code)
|
||||
|
||||
if result.get('output'):
|
||||
print(f'🔍 Search Results: {result["output"][:200]}...')
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
|
||||
def main():
|
||||
"""Demonstrate comprehensive proxy usage."""
|
||||
print('🌍 Browser Use Cloud - Proxy Usage Examples')
|
||||
print('=' * 50)
|
||||
|
||||
print('🎯 Proxy Benefits:')
|
||||
print('• Bypass geo-restrictions')
|
||||
print('• Test location-specific content')
|
||||
print('• Access region-locked websites')
|
||||
print('• Mobile/residential IP addresses')
|
||||
print('• Verify IP geolocation')
|
||||
|
||||
print('\n🌐 Available Countries:')
|
||||
countries = ['🇺🇸 US', '🇫🇷 France', '🇮🇹 Italy', '🇯🇵 Japan', '🇦🇺 Australia', '🇩🇪 Germany', '🇫🇮 Finland', '🇨🇦 Canada']
|
||||
print(' • '.join(countries))
|
||||
|
||||
try:
|
||||
# Parse command line arguments
|
||||
parser = argparse.ArgumentParser(description='Proxy usage examples')
|
||||
parser.add_argument(
|
||||
'--demo', choices=['countries', 'geo', 'streaming', 'search', 'all'], default='countries', help='Which demo to run'
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
print(f'\n🔍 Running {args.demo} demo(s)...')
|
||||
|
||||
if args.demo == 'countries':
|
||||
demo_proxy_countries()
|
||||
elif args.demo == 'geo':
|
||||
demo_geo_restrictions()
|
||||
elif args.demo == 'streaming':
|
||||
demo_streaming_access()
|
||||
elif args.demo == 'search':
|
||||
demo_search_localization()
|
||||
elif args.demo == 'all':
|
||||
demo_proxy_countries()
|
||||
demo_geo_restrictions()
|
||||
demo_streaming_access()
|
||||
demo_search_localization()
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f'❌ API Error: {e}')
|
||||
except Exception as e:
|
||||
print(f'❌ Error: {e}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,355 @@
|
||||
"""
|
||||
Cloud Example 5: Search API (Beta) 🔍
|
||||
=====================================
|
||||
|
||||
This example demonstrates the Browser Use Search API (BETA):
|
||||
- Simple search: Search Google and extract from multiple results
|
||||
- URL search: Extract specific content from a target URL
|
||||
- Deep navigation through websites (depth parameter)
|
||||
- Real-time content extraction vs cached results
|
||||
|
||||
Perfect for: Content extraction, research, competitive analysis
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
import aiohttp
|
||||
|
||||
# Configuration
|
||||
API_KEY = os.getenv('BROWSER_USE_API_KEY')
|
||||
if not API_KEY:
|
||||
raise ValueError(
|
||||
'Please set BROWSER_USE_API_KEY environment variable. You can also create an API key at https://cloud.browser-use.com/new-api-key'
|
||||
)
|
||||
|
||||
BASE_URL = os.getenv('BROWSER_USE_BASE_URL', 'https://api.browser-use.com/api/v1')
|
||||
TIMEOUT = int(os.getenv('BROWSER_USE_TIMEOUT', '30'))
|
||||
HEADERS = {'Authorization': f'Bearer {API_KEY}', 'Content-Type': 'application/json'}
|
||||
|
||||
|
||||
async def simple_search(query: str, max_websites: int = 5, depth: int = 2) -> dict[str, Any]:
|
||||
"""
|
||||
Search Google and extract content from multiple top results.
|
||||
|
||||
Args:
|
||||
query: Search query to process
|
||||
max_websites: Number of websites to process (1-10)
|
||||
depth: How deep to navigate (2-5)
|
||||
|
||||
Returns:
|
||||
Dictionary with results from multiple websites
|
||||
"""
|
||||
# Validate input parameters
|
||||
max_websites = max(1, min(max_websites, 10)) # Clamp to 1-10
|
||||
depth = max(2, min(depth, 5)) # Clamp to 2-5
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
print(f"🔍 Simple Search: '{query}'")
|
||||
print(f'📊 Processing {max_websites} websites at depth {depth}')
|
||||
print(f'💰 Estimated cost: {depth * max_websites}¢')
|
||||
|
||||
payload = {'query': query, 'max_websites': max_websites, 'depth': depth}
|
||||
|
||||
timeout = aiohttp.ClientTimeout(total=TIMEOUT)
|
||||
connector = aiohttp.TCPConnector(limit=10) # Limit concurrent connections
|
||||
|
||||
async with aiohttp.ClientSession(timeout=timeout, connector=connector) as session:
|
||||
async with session.post(f'{BASE_URL}/simple-search', json=payload, headers=HEADERS) as response:
|
||||
elapsed = time.time() - start_time
|
||||
if response.status == 200:
|
||||
try:
|
||||
result = await response.json()
|
||||
print(f'✅ Found results from {len(result.get("results", []))} websites in {elapsed:.1f}s')
|
||||
return result
|
||||
except (aiohttp.ContentTypeError, json.JSONDecodeError) as e:
|
||||
error_text = await response.text()
|
||||
print(f'❌ Invalid JSON response: {e} (after {elapsed:.1f}s)')
|
||||
return {'error': 'Invalid JSON', 'details': error_text}
|
||||
else:
|
||||
error_text = await response.text()
|
||||
print(f'❌ Search failed: {response.status} - {error_text} (after {elapsed:.1f}s)')
|
||||
return {'error': f'HTTP {response.status}', 'details': error_text}
|
||||
|
||||
|
||||
async def search_url(url: str, query: str, depth: int = 2) -> dict[str, Any]:
|
||||
"""
|
||||
Extract specific content from a target URL.
|
||||
|
||||
Args:
|
||||
url: Target URL to extract from
|
||||
query: What specific content to look for
|
||||
depth: How deep to navigate (2-5)
|
||||
|
||||
Returns:
|
||||
Dictionary with extracted content
|
||||
"""
|
||||
# Validate input parameters
|
||||
depth = max(2, min(depth, 5)) # Clamp to 2-5
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
print(f'🎯 URL Search: {url}')
|
||||
print(f"🔍 Looking for: '{query}'")
|
||||
print(f'📊 Navigation depth: {depth}')
|
||||
print(f'💰 Estimated cost: {depth}¢')
|
||||
|
||||
payload = {'url': url, 'query': query, 'depth': depth}
|
||||
|
||||
timeout = aiohttp.ClientTimeout(total=TIMEOUT)
|
||||
connector = aiohttp.TCPConnector(limit=10) # Limit concurrent connections
|
||||
|
||||
async with aiohttp.ClientSession(timeout=timeout, connector=connector) as session:
|
||||
async with session.post(f'{BASE_URL}/search-url', json=payload, headers=HEADERS) as response:
|
||||
elapsed = time.time() - start_time
|
||||
if response.status == 200:
|
||||
try:
|
||||
result = await response.json()
|
||||
print(f'✅ Extracted content from {result.get("url", "website")} in {elapsed:.1f}s')
|
||||
return result
|
||||
except (aiohttp.ContentTypeError, json.JSONDecodeError) as e:
|
||||
error_text = await response.text()
|
||||
print(f'❌ Invalid JSON response: {e} (after {elapsed:.1f}s)')
|
||||
return {'error': 'Invalid JSON', 'details': error_text}
|
||||
else:
|
||||
error_text = await response.text()
|
||||
print(f'❌ URL search failed: {response.status} - {error_text} (after {elapsed:.1f}s)')
|
||||
return {'error': f'HTTP {response.status}', 'details': error_text}
|
||||
|
||||
|
||||
def display_simple_search_results(results: dict[str, Any]):
|
||||
"""Display simple search results in a readable format."""
|
||||
if 'error' in results:
|
||||
print(f'❌ Error: {results["error"]}')
|
||||
return
|
||||
|
||||
websites = results.get('results', [])
|
||||
|
||||
print(f'\n📋 Search Results ({len(websites)} websites)')
|
||||
print('=' * 50)
|
||||
|
||||
for i, site in enumerate(websites, 1):
|
||||
url = site.get('url', 'Unknown URL')
|
||||
content = site.get('content', 'No content')
|
||||
|
||||
print(f'\n{i}. 🌐 {url}')
|
||||
print('-' * 40)
|
||||
|
||||
# Show first 300 chars of content
|
||||
if len(content) > 300:
|
||||
print(f'{content[:300]}...')
|
||||
print(f'[Content truncated - {len(content)} total characters]')
|
||||
else:
|
||||
print(content)
|
||||
|
||||
# Show execution URLs if available
|
||||
if results.get('live_url'):
|
||||
print(f'\n🔗 Live Preview: {results["live_url"]}')
|
||||
if results.get('public_share_url'):
|
||||
print(f'🌐 Share URL: {results["public_share_url"]}')
|
||||
elif results.get('share_url'):
|
||||
print(f'🌐 Share URL: {results["share_url"]}')
|
||||
|
||||
|
||||
def display_url_search_results(results: dict[str, Any]):
|
||||
"""Display URL search results in a readable format."""
|
||||
if 'error' in results:
|
||||
print(f'❌ Error: {results["error"]}')
|
||||
return
|
||||
|
||||
url = results.get('url', 'Unknown URL')
|
||||
content = results.get('content', 'No content')
|
||||
|
||||
print(f'\n📄 Extracted Content from: {url}')
|
||||
print('=' * 60)
|
||||
print(content)
|
||||
|
||||
# Show execution URLs if available
|
||||
if results.get('live_url'):
|
||||
print(f'\n🔗 Live Preview: {results["live_url"]}')
|
||||
if results.get('public_share_url'):
|
||||
print(f'🌐 Share URL: {results["public_share_url"]}')
|
||||
elif results.get('share_url'):
|
||||
print(f'🌐 Share URL: {results["share_url"]}')
|
||||
|
||||
|
||||
async def demo_news_search():
|
||||
"""Demo: Search for latest news across multiple sources."""
|
||||
print('\n📰 Demo 1: Latest News Search')
|
||||
print('-' * 35)
|
||||
|
||||
demo_start = time.time()
|
||||
query = 'latest developments in artificial intelligence 2024'
|
||||
results = await simple_search(query, max_websites=4, depth=2)
|
||||
demo_elapsed = time.time() - demo_start
|
||||
|
||||
display_simple_search_results(results)
|
||||
print(f'\n⏱️ Total demo time: {demo_elapsed:.1f}s')
|
||||
|
||||
return results
|
||||
|
||||
|
||||
async def demo_competitive_analysis():
|
||||
"""Demo: Analyze competitor websites."""
|
||||
print('\n🏢 Demo 2: Competitive Analysis')
|
||||
print('-' * 35)
|
||||
|
||||
query = 'browser automation tools comparison features pricing'
|
||||
results = await simple_search(query, max_websites=3, depth=3)
|
||||
display_simple_search_results(results)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
async def demo_deep_website_analysis():
|
||||
"""Demo: Deep analysis of a specific website."""
|
||||
print('\n🎯 Demo 3: Deep Website Analysis')
|
||||
print('-' * 35)
|
||||
|
||||
demo_start = time.time()
|
||||
url = 'https://docs.browser-use.com'
|
||||
query = 'Browser Use features, pricing, and API capabilities'
|
||||
results = await search_url(url, query, depth=3)
|
||||
demo_elapsed = time.time() - demo_start
|
||||
|
||||
display_url_search_results(results)
|
||||
print(f'\n⏱️ Total demo time: {demo_elapsed:.1f}s')
|
||||
|
||||
return results
|
||||
|
||||
|
||||
async def demo_product_research():
|
||||
"""Demo: Product research and comparison."""
|
||||
print('\n🛍️ Demo 4: Product Research')
|
||||
print('-' * 30)
|
||||
|
||||
query = 'best wireless headphones 2024 reviews comparison'
|
||||
results = await simple_search(query, max_websites=5, depth=2)
|
||||
display_simple_search_results(results)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
async def demo_real_time_vs_cached():
|
||||
"""Demo: Show difference between real-time and cached results."""
|
||||
print('\n⚡ Demo 5: Real-time vs Cached Data')
|
||||
print('-' * 40)
|
||||
|
||||
print('🔄 Browser Use Search API benefits:')
|
||||
print('• Actually browses websites like a human')
|
||||
print('• Gets live, current data (not cached)')
|
||||
print('• Navigates deep into sites via clicks')
|
||||
print('• Handles JavaScript and dynamic content')
|
||||
print('• Accesses pages requiring navigation')
|
||||
|
||||
# Example with live data
|
||||
query = 'current Bitcoin price USD live'
|
||||
results = await simple_search(query, max_websites=3, depth=2)
|
||||
|
||||
print('\n💰 Live Bitcoin Price Search Results:')
|
||||
display_simple_search_results(results)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
async def demo_search_depth_comparison():
|
||||
"""Demo: Compare different search depths."""
|
||||
print('\n📊 Demo 6: Search Depth Comparison')
|
||||
print('-' * 40)
|
||||
|
||||
url = 'https://news.ycombinator.com'
|
||||
query = 'trending technology discussions'
|
||||
|
||||
depths = [2, 3, 4]
|
||||
results = {}
|
||||
|
||||
for depth in depths:
|
||||
print(f'\n🔍 Testing depth {depth}:')
|
||||
result = await search_url(url, query, depth)
|
||||
results[depth] = result
|
||||
|
||||
if 'content' in result:
|
||||
content_length = len(result['content'])
|
||||
print(f'📏 Content length: {content_length} characters')
|
||||
|
||||
# Brief pause between requests
|
||||
await asyncio.sleep(1)
|
||||
|
||||
# Summary
|
||||
print('\n📊 Depth Comparison Summary:')
|
||||
print('-' * 30)
|
||||
for depth, result in results.items():
|
||||
if 'content' in result:
|
||||
length = len(result['content'])
|
||||
print(f'Depth {depth}: {length} characters')
|
||||
else:
|
||||
print(f'Depth {depth}: Error or no content')
|
||||
|
||||
return results
|
||||
|
||||
|
||||
async def main():
|
||||
"""Demonstrate comprehensive Search API usage."""
|
||||
print('🔍 Browser Use Cloud - Search API (BETA)')
|
||||
print('=' * 45)
|
||||
|
||||
print('⚠️ Note: This API is in BETA and may change')
|
||||
print()
|
||||
print('🎯 Search API Features:')
|
||||
print('• Real-time website browsing (not cached)')
|
||||
print('• Deep navigation through multiple pages')
|
||||
print('• Dynamic content and JavaScript handling')
|
||||
print('• Multiple result aggregation')
|
||||
print('• Cost-effective content extraction')
|
||||
|
||||
print('\n💰 Pricing:')
|
||||
print('• Simple Search: 1¢ × depth × websites')
|
||||
print('• URL Search: 1¢ × depth')
|
||||
print('• Example: depth=2, 5 websites = 10¢')
|
||||
|
||||
try:
|
||||
# Parse command line arguments
|
||||
parser = argparse.ArgumentParser(description='Search API (BETA) examples')
|
||||
parser.add_argument(
|
||||
'--demo',
|
||||
choices=['news', 'competitive', 'deep', 'product', 'realtime', 'depth', 'all'],
|
||||
default='news',
|
||||
help='Which demo to run',
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
print(f'\n🔍 Running {args.demo} demo(s)...')
|
||||
|
||||
if args.demo == 'news':
|
||||
await demo_news_search()
|
||||
elif args.demo == 'competitive':
|
||||
await demo_competitive_analysis()
|
||||
elif args.demo == 'deep':
|
||||
await demo_deep_website_analysis()
|
||||
elif args.demo == 'product':
|
||||
await demo_product_research()
|
||||
elif args.demo == 'realtime':
|
||||
await demo_real_time_vs_cached()
|
||||
elif args.demo == 'depth':
|
||||
await demo_search_depth_comparison()
|
||||
elif args.demo == 'all':
|
||||
await demo_news_search()
|
||||
await demo_competitive_analysis()
|
||||
await demo_deep_website_analysis()
|
||||
await demo_product_research()
|
||||
await demo_real_time_vs_cached()
|
||||
await demo_search_depth_comparison()
|
||||
|
||||
except aiohttp.ClientError as e:
|
||||
print(f'❌ Network Error: {e}')
|
||||
except Exception as e:
|
||||
print(f'❌ Error: {e}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,137 @@
|
||||
# Browser Use Cloud Examples 🚀
|
||||
|
||||
Welcome to the Browser Use Cloud examples! This folder contains progressively complex examples to help you get started with the Browser Use Cloud API quickly and efficiently.
|
||||
|
||||
## 📋 Prerequisites
|
||||
|
||||
1. **API Key**: Get your API key from [cloud.browser-use.com](https://cloud.browser-use.com/new-api-key)
|
||||
2. **Python Environment**: Python 3.11+ with dependencies
|
||||
3. **Environment Variables**: Configure your API settings
|
||||
|
||||
### Quick Setup
|
||||
|
||||
```bash
|
||||
# Create virtual environment and install dependencies (from project root)
|
||||
uv venv --python 3.11
|
||||
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
||||
uv sync
|
||||
|
||||
# Set environment variables
|
||||
export BROWSER_USE_API_KEY="your_api_key_here"
|
||||
export BROWSER_USE_BASE_URL="https://api.browser-use.com/api/v1" # Optional
|
||||
export BROWSER_USE_TIMEOUT="30" # Optional: request timeout in seconds
|
||||
|
||||
# Or use .env file (recommended)
|
||||
cp examples/cloud/env.example .env
|
||||
# Edit .env with your values
|
||||
|
||||
# Run examples from project root
|
||||
python examples/cloud/01_basic_task.py
|
||||
```
|
||||
|
||||
## 🎯 Examples Overview
|
||||
|
||||
### 🚀 Easy Cloud Setup Examples
|
||||
|
||||
- **[01_basic_task.py](./01_basic_task.py)** - Your first cloud task (start here!)
|
||||
- **[02_fast_mode_gemini.py](./02_fast_mode_gemini.py)** - ⚡ Ultra-fast mode with Gemini Flash & Fireship humor
|
||||
- **[03_structured_output.py](./03_structured_output.py)** - Get structured JSON responses
|
||||
- **[04_proxy_usage.py](./04_proxy_usage.py)** - 🌍 Proxy for geo-restrictions & captcha solving
|
||||
- **[05_search_api.py](./05_search_api.py)** - 🔍 Search API for content extraction (BETA)
|
||||
|
||||
## 💰 Cost Optimization Tips
|
||||
|
||||
1. **Use Gemini Flash** for fastest/cheapest execution ($0.01/step)
|
||||
2. **Disable proxy** when not needed for captcha solving
|
||||
3. **Disable element highlighting** for better performance
|
||||
4. **Set max_agent_steps** to prevent runaway costs
|
||||
5. **Use structured output** to reduce parsing overhead
|
||||
6. **Add timeouts and retries** for reliability in production
|
||||
7. **Use domain restrictions** when working with secrets
|
||||
|
||||
## 🎨 Fast Mode Configuration
|
||||
|
||||
For maximum speed and cost efficiency:
|
||||
|
||||
```python
|
||||
{
|
||||
"llm_model": "gemini-2.5-flash",
|
||||
"use_proxy": False,
|
||||
"highlight_elements": False,
|
||||
"use_adblock": True,
|
||||
"max_agent_steps": 50
|
||||
}
|
||||
```
|
||||
|
||||
## 🔐 Security & Advanced Features
|
||||
|
||||
### Using Proxy
|
||||
```python
|
||||
{
|
||||
"use_proxy": True,
|
||||
"proxy_country_code": "us", # 'us', 'fr', 'it', 'jp', 'au', 'de', 'fi', 'ca'
|
||||
}
|
||||
```
|
||||
|
||||
### Passing Secrets Securely
|
||||
```python
|
||||
{
|
||||
"secrets": {
|
||||
"username": "your_username",
|
||||
"password": "your_password",
|
||||
"api_key": "your_api_key"
|
||||
},
|
||||
"allowed_domains": ["*.yoursite.com"] # Recommended with secrets
|
||||
}
|
||||
```
|
||||
|
||||
## 🔍 Search API (BETA)
|
||||
|
||||
The Search API extracts content by actually browsing websites (not cached results):
|
||||
|
||||
### Simple Search (Multi-site)
|
||||
```python
|
||||
# Cost: 1¢ × depth × websites
|
||||
{
|
||||
"query": "latest AI news",
|
||||
"max_websites": 5,
|
||||
"depth": 2
|
||||
}
|
||||
```
|
||||
|
||||
### URL Search (Single site)
|
||||
```python
|
||||
# Cost: 1¢ × depth
|
||||
{
|
||||
"url": "https://example.com",
|
||||
"query": "pricing information",
|
||||
"depth": 3
|
||||
}
|
||||
```
|
||||
|
||||
## 🔗 Quick Links
|
||||
|
||||
- [Cloud API Documentation](https://docs.browser-use.com/cloud)
|
||||
- [API Reference](https://docs.browser-use.com/api-reference)
|
||||
- [Pricing](https://cloud.browser-use.com/billing)
|
||||
- [Discord Community](https://link.browser-use.com/discord)
|
||||
|
||||
## 🔧 Production Best Practices
|
||||
|
||||
- **Timeouts**: All examples include 30-second timeouts with retry logic
|
||||
- **Error Handling**: Comprehensive error catching and status code validation
|
||||
- **Security**: Use environment variables, domain restrictions with secrets
|
||||
- **Reliability**: Built-in retries for network issues and rate limits
|
||||
- **Automation**: CLI arguments instead of interactive prompts for CI/CD
|
||||
|
||||
## 🆘 Support
|
||||
|
||||
Need help?
|
||||
|
||||
- 📧 Email: support@browser-use.com
|
||||
- 💬 Discord: [Join our community](https://link.browser-use.com/discord)
|
||||
- 📖 Docs: <https://docs.browser-use.com>
|
||||
|
||||
---
|
||||
|
||||
**💡 Pro Tip**: Start with `01_basic_task.py` and work your way up. Each example builds on the previous ones!
|
||||
@@ -0,0 +1,21 @@
|
||||
# Browser Use Cloud API Configuration
|
||||
# Copy this file to .env and fill in your values
|
||||
|
||||
# Required: Your Browser Use Cloud API key
|
||||
# Get it from: https://cloud.browser-use.com/new-api-key
|
||||
BROWSER_USE_API_KEY=your_api_key_here
|
||||
|
||||
# Optional: Custom API base URL (for enterprise installations)
|
||||
# BROWSER_USE_BASE_URL=https://api.browser-use.com/api/v1
|
||||
|
||||
# Optional: Default model preference
|
||||
# BROWSER_USE_DEFAULT_MODEL=gemini-2.5-flash
|
||||
|
||||
# Optional: Cost limits
|
||||
# BROWSER_USE_MAX_COST_PER_TASK=5.0
|
||||
|
||||
# Optional: Request timeout (seconds)
|
||||
# BROWSER_USE_TIMEOUT=30
|
||||
|
||||
# Optional: Logging configuration
|
||||
# LOG_LEVEL=INFO
|
||||
Reference in New Issue
Block a user