Spaces:
Runtime error
Runtime error
Upload 4 files
Browse files- src/browser/__init__.py +6 -0
- src/browser/config.py +30 -0
- src/browser/custom_browser.py +126 -0
- src/browser/custom_context.py +96 -0
src/browser/__init__.py
CHANGED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
# @Time : 2025/1/1
|
| 3 |
+
# @Author : wenshao
|
| 4 |
+
# @Email : wenshaoguo1026@gmail.com
|
| 5 |
+
# @Project : browser-use-webui
|
| 6 |
+
# @FileName: __init__.py.py
|
src/browser/config.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
# @Time : 2025/1/6
|
| 3 |
+
# @Author : wenshao
|
| 4 |
+
# @ProjectName: browser-use-webui
|
| 5 |
+
# @FileName: config.py
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
from dataclasses import dataclass
|
| 9 |
+
from typing import Optional
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@dataclass
|
| 13 |
+
class BrowserPersistenceConfig:
|
| 14 |
+
"""Configuration for browser persistence"""
|
| 15 |
+
|
| 16 |
+
persistent_session: bool = False
|
| 17 |
+
user_data_dir: Optional[str] = None
|
| 18 |
+
debugging_port: Optional[int] = None
|
| 19 |
+
debugging_host: Optional[str] = None
|
| 20 |
+
|
| 21 |
+
@classmethod
|
| 22 |
+
def from_env(cls) -> "BrowserPersistenceConfig":
|
| 23 |
+
"""Create config from environment variables"""
|
| 24 |
+
return cls(
|
| 25 |
+
persistent_session=os.getenv("CHROME_PERSISTENT_SESSION", "").lower()
|
| 26 |
+
== "true",
|
| 27 |
+
user_data_dir=os.getenv("CHROME_USER_DATA"),
|
| 28 |
+
debugging_port=int(os.getenv("CHROME_DEBUGGING_PORT", "9222")),
|
| 29 |
+
debugging_host=os.getenv("CHROME_DEBUGGING_HOST", "localhost"),
|
| 30 |
+
)
|
src/browser/custom_browser.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
# @Time : 2025/1/2
|
| 3 |
+
# @Author : wenshao
|
| 4 |
+
# @ProjectName: browser-use-webui
|
| 5 |
+
# @FileName: browser.py
|
| 6 |
+
|
| 7 |
+
import asyncio
|
| 8 |
+
|
| 9 |
+
from playwright.async_api import Browser as PlaywrightBrowser
|
| 10 |
+
from playwright.async_api import (
|
| 11 |
+
BrowserContext as PlaywrightBrowserContext,
|
| 12 |
+
)
|
| 13 |
+
from playwright.async_api import (
|
| 14 |
+
Playwright,
|
| 15 |
+
async_playwright,
|
| 16 |
+
)
|
| 17 |
+
from browser_use.browser.browser import Browser
|
| 18 |
+
from browser_use.browser.context import BrowserContext, BrowserContextConfig
|
| 19 |
+
from playwright.async_api import BrowserContext as PlaywrightBrowserContext
|
| 20 |
+
import logging
|
| 21 |
+
|
| 22 |
+
from .config import BrowserPersistenceConfig
|
| 23 |
+
from .custom_context import CustomBrowserContext
|
| 24 |
+
|
| 25 |
+
logger = logging.getLogger(__name__)
|
| 26 |
+
|
| 27 |
+
class CustomBrowser(Browser):
|
| 28 |
+
|
| 29 |
+
async def new_context(
|
| 30 |
+
self,
|
| 31 |
+
config: BrowserContextConfig = BrowserContextConfig()
|
| 32 |
+
) -> CustomBrowserContext:
|
| 33 |
+
return CustomBrowserContext(config=config, browser=self)
|
| 34 |
+
|
| 35 |
+
async def _setup_browser(self, playwright: Playwright) -> PlaywrightBrowser:
|
| 36 |
+
"""Sets up and returns a Playwright Browser instance with anti-detection measures."""
|
| 37 |
+
if self.config.wss_url:
|
| 38 |
+
browser = await playwright.chromium.connect(self.config.wss_url)
|
| 39 |
+
return browser
|
| 40 |
+
elif self.config.chrome_instance_path:
|
| 41 |
+
import subprocess
|
| 42 |
+
|
| 43 |
+
import requests
|
| 44 |
+
|
| 45 |
+
try:
|
| 46 |
+
# Check if browser is already running
|
| 47 |
+
response = requests.get('http://localhost:9222/json/version', timeout=2)
|
| 48 |
+
if response.status_code == 200:
|
| 49 |
+
logger.info('Reusing existing Chrome instance')
|
| 50 |
+
browser = await playwright.chromium.connect_over_cdp(
|
| 51 |
+
endpoint_url='http://localhost:9222',
|
| 52 |
+
timeout=20000, # 20 second timeout for connection
|
| 53 |
+
)
|
| 54 |
+
return browser
|
| 55 |
+
except requests.ConnectionError:
|
| 56 |
+
logger.debug('No existing Chrome instance found, starting a new one')
|
| 57 |
+
|
| 58 |
+
# Start a new Chrome instance
|
| 59 |
+
subprocess.Popen(
|
| 60 |
+
[
|
| 61 |
+
self.config.chrome_instance_path,
|
| 62 |
+
'--remote-debugging-port=9222',
|
| 63 |
+
],
|
| 64 |
+
stdout=subprocess.DEVNULL,
|
| 65 |
+
stderr=subprocess.DEVNULL,
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
# Attempt to connect again after starting a new instance
|
| 69 |
+
for _ in range(10):
|
| 70 |
+
try:
|
| 71 |
+
response = requests.get('http://localhost:9222/json/version', timeout=2)
|
| 72 |
+
if response.status_code == 200:
|
| 73 |
+
break
|
| 74 |
+
except requests.ConnectionError:
|
| 75 |
+
pass
|
| 76 |
+
await asyncio.sleep(1)
|
| 77 |
+
|
| 78 |
+
try:
|
| 79 |
+
browser = await playwright.chromium.connect_over_cdp(
|
| 80 |
+
endpoint_url='http://localhost:9222',
|
| 81 |
+
timeout=20000, # 20 second timeout for connection
|
| 82 |
+
)
|
| 83 |
+
return browser
|
| 84 |
+
except Exception as e:
|
| 85 |
+
logger.error(f'Failed to start a new Chrome instance.: {str(e)}')
|
| 86 |
+
raise RuntimeError(
|
| 87 |
+
' To start chrome in Debug mode, you need to close all existing Chrome instances and try again otherwise we can not connect to the instance.'
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
else:
|
| 91 |
+
try:
|
| 92 |
+
disable_security_args = []
|
| 93 |
+
if self.config.disable_security:
|
| 94 |
+
disable_security_args = [
|
| 95 |
+
'--disable-web-security',
|
| 96 |
+
'--disable-site-isolation-trials',
|
| 97 |
+
'--disable-features=IsolateOrigins,site-per-process',
|
| 98 |
+
]
|
| 99 |
+
|
| 100 |
+
browser = await playwright.chromium.launch(
|
| 101 |
+
headless=self.config.headless,
|
| 102 |
+
args=[
|
| 103 |
+
'--no-sandbox',
|
| 104 |
+
'--disable-blink-features=AutomationControlled',
|
| 105 |
+
'--disable-infobars',
|
| 106 |
+
'--disable-background-timer-throttling',
|
| 107 |
+
'--disable-popup-blocking',
|
| 108 |
+
'--disable-backgrounding-occluded-windows',
|
| 109 |
+
'--disable-renderer-backgrounding',
|
| 110 |
+
'--disable-window-activation',
|
| 111 |
+
'--disable-focus-on-load',
|
| 112 |
+
'--no-first-run',
|
| 113 |
+
'--no-default-browser-check',
|
| 114 |
+
'--no-startup-window',
|
| 115 |
+
'--window-position=0,0',
|
| 116 |
+
# '--window-size=1280,1000',
|
| 117 |
+
]
|
| 118 |
+
+ disable_security_args
|
| 119 |
+
+ self.config.extra_chromium_args,
|
| 120 |
+
proxy=self.config.proxy,
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
return browser
|
| 124 |
+
except Exception as e:
|
| 125 |
+
logger.error(f'Failed to initialize Playwright browser: {str(e)}')
|
| 126 |
+
raise
|
src/browser/custom_context.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
# @Time : 2025/1/1
|
| 3 |
+
# @Author : wenshao
|
| 4 |
+
# @Email : wenshaoguo1026@gmail.com
|
| 5 |
+
# @Project : browser-use-webui
|
| 6 |
+
# @FileName: context.py
|
| 7 |
+
|
| 8 |
+
import json
|
| 9 |
+
import logging
|
| 10 |
+
import os
|
| 11 |
+
|
| 12 |
+
from browser_use.browser.browser import Browser
|
| 13 |
+
from browser_use.browser.context import BrowserContext, BrowserContextConfig
|
| 14 |
+
from playwright.async_api import Browser as PlaywrightBrowser
|
| 15 |
+
from playwright.async_api import BrowserContext as PlaywrightBrowserContext
|
| 16 |
+
|
| 17 |
+
from .config import BrowserPersistenceConfig
|
| 18 |
+
logger = logging.getLogger(__name__)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class CustomBrowserContext(BrowserContext):
|
| 22 |
+
def __init__(
|
| 23 |
+
self,
|
| 24 |
+
browser: "Browser",
|
| 25 |
+
config: BrowserContextConfig = BrowserContextConfig()
|
| 26 |
+
):
|
| 27 |
+
super(CustomBrowserContext, self).__init__(browser=browser, config=config)
|
| 28 |
+
|
| 29 |
+
async def _create_context(self, browser: PlaywrightBrowser) -> PlaywrightBrowserContext:
|
| 30 |
+
"""Creates a new browser context with anti-detection measures and loads cookies if available."""
|
| 31 |
+
# If we have a context, return it directly
|
| 32 |
+
|
| 33 |
+
# Check if we should use existing context for persistence
|
| 34 |
+
if self.browser.config.chrome_instance_path and len(browser.contexts) > 0:
|
| 35 |
+
# Connect to existing Chrome instance instead of creating new one
|
| 36 |
+
context = browser.contexts[0]
|
| 37 |
+
else:
|
| 38 |
+
# Original code for creating new context
|
| 39 |
+
context = await browser.new_context(
|
| 40 |
+
viewport=self.config.browser_window_size,
|
| 41 |
+
no_viewport=False,
|
| 42 |
+
user_agent=(
|
| 43 |
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
| 44 |
+
"(KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36"
|
| 45 |
+
),
|
| 46 |
+
java_script_enabled=True,
|
| 47 |
+
bypass_csp=self.config.disable_security,
|
| 48 |
+
ignore_https_errors=self.config.disable_security,
|
| 49 |
+
record_video_dir=self.config.save_recording_path,
|
| 50 |
+
record_video_size=self.config.browser_window_size,
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
if self.config.trace_path:
|
| 54 |
+
await context.tracing.start(screenshots=True, snapshots=True, sources=True)
|
| 55 |
+
|
| 56 |
+
# Load cookies if they exist
|
| 57 |
+
if self.config.cookies_file and os.path.exists(self.config.cookies_file):
|
| 58 |
+
with open(self.config.cookies_file, "r") as f:
|
| 59 |
+
cookies = json.load(f)
|
| 60 |
+
logger.info(
|
| 61 |
+
f"Loaded {len(cookies)} cookies from {self.config.cookies_file}"
|
| 62 |
+
)
|
| 63 |
+
await context.add_cookies(cookies)
|
| 64 |
+
|
| 65 |
+
# Expose anti-detection scripts
|
| 66 |
+
await context.add_init_script(
|
| 67 |
+
"""
|
| 68 |
+
// Webdriver property
|
| 69 |
+
Object.defineProperty(navigator, 'webdriver', {
|
| 70 |
+
get: () => undefined
|
| 71 |
+
});
|
| 72 |
+
|
| 73 |
+
// Languages
|
| 74 |
+
Object.defineProperty(navigator, 'languages', {
|
| 75 |
+
get: () => ['en-US', 'en']
|
| 76 |
+
});
|
| 77 |
+
|
| 78 |
+
// Plugins
|
| 79 |
+
Object.defineProperty(navigator, 'plugins', {
|
| 80 |
+
get: () => [1, 2, 3, 4, 5]
|
| 81 |
+
});
|
| 82 |
+
|
| 83 |
+
// Chrome runtime
|
| 84 |
+
window.chrome = { runtime: {} };
|
| 85 |
+
|
| 86 |
+
// Permissions
|
| 87 |
+
const originalQuery = window.navigator.permissions.query;
|
| 88 |
+
window.navigator.permissions.query = (parameters) => (
|
| 89 |
+
parameters.name === 'notifications' ?
|
| 90 |
+
Promise.resolve({ state: Notification.permission }) :
|
| 91 |
+
originalQuery(parameters)
|
| 92 |
+
);
|
| 93 |
+
"""
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
return context
|