浏览器域
浏览器域是 Pydoll 无WebDriver架构的核心。该组件通过 Chrome 开发者工具协议(CDP)直接与浏览器实例交互,无需依赖传统 WebDriver,从而在性能和可靠性方面实现显著提升。
graph LR
A[Pydoll API] --> B[Browser Domain]
B <--> C[Chrome DevTools Protocol]
C <--> D[Browser Process]
subgraph "Internal Components"
B --> E[Connection Handler]
B --> F[Process Manager]
B --> G[Options Manager]
B --> H[Proxy Manager]
B --> I[Temp Directory Manager]
end
技术架构
浏览器域的核心实现为一个抽象基类(Browser),该基类为所有浏览器实现定义了基础契约。具体浏览器类(如 Chrome 和 Edge)继承自该基类,在共享通用架构的同时,提供各自特有的浏览器行为。
# Abstract base class (simplified)
class Browser(ABC):
def __init__(self, options_manager: BrowserOptionsManager, connection_port: Optional[int] = None):
# Initialize components
# ...
@abstractmethod
def _get_default_binary_location(self) -> str:
"""Must be implemented by subclasses"""
pass
async def start(self, headless: bool = False) -> Tab:
# Start browser process
# Establish CDP connection
# Return initial tab for interaction
# ...
# Implementation for Chrome
class Chrome(Browser):
def _get_default_binary_location(self) -> str:
# Return path to Chrome binary
# ...
这种抽象设计使 Pydoll 能够通过统一接口支持多种浏览器,而各具体实现则负责处理浏览器特有的细节,例如可执行文件定位、命令行参数配置以及协议差异适配。
核心使用模式
浏览器域遵循一套统一的初始化、标签页管理及资源清理模式。需注意,start() 方法现已直接返回一个 Tab 实例:
import asyncio
from pydoll.browser.chromium import Chrome
async def simple_browser_example():
# Create and start a browser instance
browser = Chrome()
tab = await browser.start() # Returns Tab directly
try:
# Navigate and interact with the tab
await tab.go_to("https://example.com")
# Perform operations with the tab
title = await tab.execute_script("return document.title")
print(f"Page title: {title}")
finally:
# Always ensure the browser is properly closed
await browser.stop()
# Run the async example
asyncio.run(simple_browser_example())
Context Manager Usage
For cleaner resource management, use the context manager pattern:
浏览器实现层级结构
浏览器域采用清晰的继承体系,在确保代码复用的同时支持针对不同浏览器的定制化实现。
classDiagram
class Browser {
<<Abstract>>
+__init__(options_manager, connection_port)
+start(headless) Tab
+stop()
+new_tab(url, browser_context_id) Tab
+create_browser_context() str
#_get_default_binary_location()*
}
class Chrome {
+_get_default_binary_location()
}
class Edge {
+_get_default_binary_location()
}
Browser <|-- Chrome : extends
Browser <|-- Edge : extends
该架构使Pydoll能够通过统一接口支持多种浏览器类型。每个具体实现(如Chrome、Edge)只需提供浏览器特定的细节(如可执行文件定位),同时从基础Browser类继承强大的核心功能。
初始化参数
Browser域在初始化时接受两个主要参数,分别控制浏览器的不同行为特性:
选项管理器参数
options_manager
参数接收一个BrowserOptionsManager
实例,用于处理浏览器选项的初始化和配置:
from pydoll.browser.chromium import Chrome
from pydoll.browser.interfaces import BrowserOptionsManager
# The options manager is typically handled internally by browser implementations
browser = Chrome() # Uses default ChromiumOptionsManager internally
选项管理器负责: - 以合理的默认值初始化浏览器选项 - 添加所需的CDP参数 - 管理浏览器特定配置
内部实现说明
大多数用户无需直接与选项管理器交互,因为像Chrome
这样的浏览器实现在内部会提供其专用的管理器。不过高级用户可以为特殊配置创建自定义选项管理器。
连接端口参数
connection_port
参数定义用于CDP WebSocket连接的端口号:
该参数具有两种用途:
- 浏览器启动场景:指定浏览器应开启哪个端口用于CDP通信
- 连接已有浏览器场景:定义使用外部浏览器实例时需要连接的端口号
端口可用性提示
若未指定端口,Pydoll会在9223至9322范围内随机选择一个可用端口。若您的环境存在防火墙或网络限制,可能需要显式设置一个可访问的端口。
内部组件
Browser域通过协调多个专业组件来实现其功能:
连接处理器
ConnectionHandler通过Chrome DevTools协议建立并维持与浏览器的通信。它在WebSocket连接之上提供了抽象层,负责处理命令执行、响应解析和事件订阅。
该组件是Pydoll架构的核心部分,我们将在专门的"连接域"章节进行详细阐述。
浏览器进程管理器
BrowserProcessManager负责管理浏览器进程的生命周期:
class BrowserProcessManager:
def start_browser_process(self, binary, port, arguments):
# Launch browser executable with proper arguments
# Monitor process startup
# ...
def stop_process(self):
# Terminate browser process
# Cleanup resources
# ...
这种关注点分离的设计确保了浏览器进程管理与协议通信的解耦,使代码更易于维护和测试。
临时目录管理器
TempDirectoryManager负责为浏览器用户数据创建临时目录并进行清理管理:
class TempDirectoryManager:
def create_temp_dir(self):
# Create temporary directory for browser user data
# Return directory handle
# ...
def cleanup(self):
# Remove temporary directories
# Clean up resources
# ...
该组件能妥善管理浏览器临时数据并进行自动清理,有效防止长时间自动化测试过程中出现磁盘空间问题。
代理管理器
ProxyManager负责配置浏览器的代理设置:
class ProxyManager:
def __init__(self, options):
# Parse proxy settings from options
# ...
def get_proxy_credentials(self):
# Extract authentication details
# Format proxy configuration
# ...
该组件对于需要代理轮换或身份验证的自动化网页抓取/测试场景至关重要。
生命周期与上下文管理
Browser域实现了Python的异步上下文管理协议(__aenter__
和__aexit__
),可自动完成资源清理工作:
async def scrape_data():
async with Chrome() as browser:
tab = await browser.start()
await tab.go_to('https://example.com')
# Work with tab...
# Browser automatically closes when exiting the context
这种设计模式能确保即使自动化过程中出现异常,浏览器进程也能被正确终止,从而避免资源泄漏。
启动浏览器并获取初始标签页
browser = Chrome()
tab = await browser.start() # Returns Tab instance
await tab.go_to("https://example.com")
创建额外的标签页
# Create additional tabs
tab2 = await browser.new_tab("https://github.com")
tab3 = await browser.new_tab() # Empty tab
# Work with multiple tabs
await tab.go_to("https://example.com")
await tab2.go_to("https://github.com")
Multi-Tab Automation
You can work with multiple tabs simultaneously:
async def multi_tab_example():
browser = Chrome()
tab1 = await browser.start()
# Create and work with multiple tabs
await tab1.go_to("https://example.com")
tab2 = await browser.new_tab("https://github.com")
# Get information from both tabs
title1 = await tab1.execute_script("return document.title")
title2 = await tab2.execute_script("return document.title")
print(f"Tab 1: {title1}")
print(f"Tab 2: {title2}")
await browser.stop()
浏览器上下文管理
理解浏览器上下文
浏览器上下文是Pydoll最强大的功能之一,用于创建隔离的浏览环境。可以将浏览器上下文视为同一浏览器进程内完全独立的会话——类似于无痕浏览窗口,但具备编程控制能力。
每个浏览器上下文都维护自己独立的: - Cookies和会话存储:与其他上下文完全隔离 - 本地存储和IndexedDB:每个上下文拥有独立的数据存储 - 缓存系统:各上下文拥有独立的缓存机制 - 权限设置:支持上下文级别的权限授予 - 网络配置:包括独立的代理设置 - 认证状态:登录会话具有上下文特异性
graph TB
A[Browser Process] --> B[Default Context]
A --> C[Context 1]
A --> D[Context 2]
B --> B1[Tab 1]
B --> B2[Tab 2]
C --> C1[Tab 3]
D --> D1[Tab 4]
为什么要使用浏览器上下文?
浏览器上下文对以下自动化场景至关重要:
- 多账号测试:同时测试不同用户账号且互不干扰
- A/B测试:并行比较不同的用户体验
- 地理位置测试:为每个上下文配置不同的代理设置
- 会话隔离:防止测试场景间的交叉污染
- 并行抓取:使用不同配置同时抓取多个网站
创建与管理上下文
# Create isolated browser context
context_id = await browser.create_browser_context()
# Create tab in specific context
tab = await browser.new_tab("https://example.com", browser_context_id=context_id)
# Get all browser contexts
contexts = await browser.get_browser_contexts()
print(f"Active contexts: {contexts}")
# Delete context (closes all associated tabs)
await browser.delete_browser_context(context_id)
默认上下文与自定义上下文
每个浏览器启动时会包含一个默认上下文,该上下文由browser.start()
返回的初始标签页构成。您可以根据需要创建更多自定义上下文:
browser = Chrome()
default_tab = await browser.start() # Uses default context
# Create custom context
custom_context_id = await browser.create_browser_context()
custom_tab = await browser.new_tab("https://example.com", browser_context_id=custom_context_id)
# Both tabs are completely isolated from each other
await default_tab.go_to("https://site1.com")
await custom_tab.go_to("https://site2.com")
实战示例:多账号测试
以下是一个同时测试多个用户账号的实际应用案例:
async def test_multiple_accounts():
browser = Chrome()
await browser.start()
# Test data for different accounts
accounts = [
{"username": "user1@example.com", "password": "pass1"},
{"username": "user2@example.com", "password": "pass2"},
{"username": "admin@example.com", "password": "admin_pass"}
]
contexts_and_tabs = []
# Create isolated context for each account
for i, account in enumerate(accounts):
context_id = await browser.create_browser_context()
tab = await browser.new_tab("https://app.example.com/login", browser_context_id=context_id)
# Login with account credentials
await tab.find(tag_name="input", name="username").type_text(account["username"])
await tab.find(tag_name="input", name="password").type_text(account["password"])
await tab.find(tag_name="button", type="submit").click()
contexts_and_tabs.append((context_id, tab, account["username"]))
# Now test different scenarios with each account simultaneously
for context_id, tab, username in contexts_and_tabs:
# Each tab maintains its own login session
await tab.go_to("https://app.example.com/dashboard")
user_info = await tab.find(class_name="user-info").text
print(f"User {username} dashboard: {user_info}")
# Cleanup: delete all contexts
for context_id, _, _ in contexts_and_tabs:
await browser.delete_browser_context(context_id)
await browser.stop()
上下文专属代理配置
每个浏览器上下文均可设置独立的代理参数,这使其成为地理位置测试或IP轮换的理想选择:
# Create context with specific proxy
context_id = await browser.create_browser_context(
proxy_server="http://proxy.example.com:8080",
proxy_bypass_list="localhost,127.0.0.1"
)
# All tabs in this context will use the specified proxy
tab = await browser.new_tab("https://example.com", browser_context_id=context_id)
高级上下文管理
上下文生命周期管理
async def manage_context_lifecycle():
browser = Chrome()
await browser.start()
# Create multiple contexts for different purposes
contexts = {}
# Context for US region testing
us_context = await browser.create_browser_context(
proxy_server="http://us-proxy.example.com:8080"
)
contexts['us'] = us_context
# Context for EU region testing
eu_context = await browser.create_browser_context(
proxy_server="http://eu-proxy.example.com:8080"
)
contexts['eu'] = eu_context
# Context for admin testing (no proxy)
admin_context = await browser.create_browser_context()
contexts['admin'] = admin_context
try:
# Use contexts for parallel testing
us_tab = await browser.new_tab("https://api.example.com/geo", browser_context_id=contexts['us'])
eu_tab = await browser.new_tab("https://api.example.com/geo", browser_context_id=contexts['eu'])
admin_tab = await browser.new_tab("https://admin.example.com", browser_context_id=contexts['admin'])
# Each tab will have different IP/location
us_location = await us_tab.execute_script("return fetch('/api/location').then(r => r.json())")
eu_location = await eu_tab.execute_script("return fetch('/api/location').then(r => r.json())")
print(f"US Context Location: {us_location}")
print(f"EU Context Location: {eu_location}")
finally:
# Clean up all contexts
for region, context_id in contexts.items():
await browser.delete_browser_context(context_id)
print(f"Deleted {region} context")
await browser.stop()
上下文存储隔离
async def demonstrate_storage_isolation():
browser = Chrome()
await browser.start()
# Create two contexts
context1 = await browser.create_browser_context()
context2 = await browser.create_browser_context()
# Create tabs in each context
tab1 = await browser.new_tab("https://example.com", browser_context_id=context1)
tab2 = await browser.new_tab("https://example.com", browser_context_id=context2)
# Set different data in localStorage for each context
await tab1.execute_script("localStorage.setItem('user', 'Alice')")
await tab2.execute_script("localStorage.setItem('user', 'Bob')")
# Verify isolation - each context has its own storage
user1 = await tab1.execute_script("return localStorage.getItem('user')")
user2 = await tab2.execute_script("return localStorage.getItem('user')")
print(f"Context 1 user: {user1}") # Alice
print(f"Context 2 user: {user2}") # Bob
# Clean up
await browser.delete_browser_context(context1)
await browser.delete_browser_context(context2)
await browser.stop()
目标管理
获取浏览器中所有活动目标(标签页、服务工作线程等)的相关信息:
# Get all targets
targets = await browser.get_targets()
# Filter for page targets only
pages = [t for t in targets if t.get('type') == 'page']
for page in pages:
print(f"Target ID: {page['targetId']}")
print(f"URL: {page['url']}")
print(f"Title: {page.get('title', 'No title')}")
窗口管理
Browser域提供了控制浏览器窗口的方法:
# Get the current window ID
window_id = await browser.get_window_id()
# Set window bounds (position and size)
await browser.set_window_bounds({
'left': 100,
'top': 100,
'width': 1024,
'height': 768
})
# Maximize the window
await browser.set_window_maximized()
# Minimize the window
await browser.set_window_minimized()
窗口管理使用场景
窗口管理特别适用于以下情况: - 设置精确的窗口尺寸以获得一致的截图效果 - 在多显示器配置中定位窗口位置 - 开发期间创建可见的、用户友好的自动化流程
Cookie管理
Browser域提供了浏览器全局Cookie管理的方法:
# Set cookies at the browser level
cookies_to_set = [
{
"name": "session_id",
"value": "global_session_123",
"domain": "example.com",
"path": "/",
"secure": True,
"httpOnly": True
}
]
await browser.set_cookies(cookies_to_set)
# Get all cookies from the browser
all_cookies = await browser.get_cookies()
print(f"Number of cookies: {len(all_cookies)}")
# Delete all cookies from the browser
await browser.delete_all_cookies()
上下文专属Cookie管理
# Create browser context
context_id = await browser.create_browser_context()
# Set cookies for specific context
await browser.set_cookies(cookies_to_set, browser_context_id=context_id)
# Get cookies from specific context
context_cookies = await browser.get_cookies(browser_context_id=context_id)
# Delete cookies from specific context
await browser.delete_all_cookies(browser_context_id=context_id)
浏览器与标签页Cookie管理区别
- 浏览器级Cookie(使用上述方法)适用于浏览器中的所有标签页或特定上下文
- 标签页级Cookie(使用
tab.set_cookies()
)仅作用于特定标签页
请根据自动化需求选择合适的范围。
下载管理
配置浏览器或特定上下文的下载行为:
# Set a custom download path
download_path = "/path/to/downloads"
await browser.set_download_path(download_path)
# Advanced download configuration
await browser.set_download_behavior(
behavior=DownloadBehavior.ALLOW,
download_path=download_path,
events_enabled=True # Enable download progress events
)
# Context-specific download configuration
context_id = await browser.create_browser_context()
await browser.set_download_behavior(
behavior=DownloadBehavior.ALLOW,
download_path="/path/to/context/downloads",
browser_context_id=context_id
)
权限管理
为自动化测试授予或重置浏览器权限:
from pydoll.constants import PermissionType
# Grant permissions globally
await browser.grant_permissions([
PermissionType.GEOLOCATION,
PermissionType.NOTIFICATIONS,
PermissionType.CAMERA
])
# Grant permissions for specific origin
await browser.grant_permissions(
[PermissionType.GEOLOCATION],
origin="https://example.com"
)
# Grant permissions for specific context
context_id = await browser.create_browser_context()
await browser.grant_permissions(
[PermissionType.MICROPHONE],
browser_context_id=context_id
)
# Reset all permissions to defaults
await browser.reset_permissions()
事件系统概述
Browser域提供了启用和监控各类事件的方法,包括enable_fetch_events()
以及用于注册事件回调的on()
方法。
请求拦截
# Enable request interception
await browser.enable_fetch_events(handle_auth_requests=True)
# Register event handler for intercepted requests
async def handle_request(event):
request_id = event['params']['requestId']
url = event['params']['request']['url']
if 'analytics' in url:
# Block analytics requests
await browser.fail_request(request_id, NetworkErrorReason.BLOCKED_BY_CLIENT)
else:
# Continue other requests
await browser.continue_request(request_id)
await browser.on('Fetch.requestPaused', handle_request)
自定义响应处理
async def fulfill_custom_response(event):
request_id = event['params']['requestId']
# Fulfill with custom response
await browser.fulfill_request(
request_id=request_id,
response_code=200,
response_headers=[{'name': 'Content-Type', 'value': 'application/json'}],
response_body={'message': 'Custom response from Pydoll'}
)
await browser.on('Fetch.requestPaused', fulfill_custom_response)
浏览器与标签页事件作用域
在浏览器级别启用事件(例如browser.enable_fetch_events()
)时,这些事件会全局性应用于浏览器中的所有标签页。相比之下,在标签页级别启用事件(例如tab.enable_fetch_events()
)则仅影响特定标签页。
这一区别对性能和资源管理至关重要。当需要监控所有标签页的活动时,请在浏览器级别启用事件;若仅关注特定标签页的事件,则应在标签页级别启用。
事件系统详细文档
事件系统是Pydoll架构的核心组件,我们将在专门章节进行详细介绍。内容包括事件类型、处理模式以及高级事件驱动技术。
代理配置
Pydoll支持为浏览器连接配置代理。这对于网页抓取、测试地域特定内容或绕过基于IP的速率限制非常有用:
from pydoll.browser.chromium import Chrome
from pydoll.browser.options import ChromiumOptions
options = ChromiumOptions()
# Configure a proxy
options.add_argument('--proxy-server=http://proxy.example.com:8080')
# For proxies requiring authentication
browser = Chrome(options=options)
tab = await browser.start()
# Pydoll automatically handles proxy authentication challenges
await tab.go_to("https://example.com")
私有代理认证
Pydoll可自动处理私有代理认证:
- 检测到代理认证挑战时,Pydoll会进行拦截
- 从配置选项中自动应用代理凭证
- 透明完成认证流程
- 自动化操作不间断继续执行
相较于传统浏览器自动化方案,这使得使用认证代理变得更加简便。
总结
Browser域作为Pydoll架构的基础,通过Chrome DevTools协议为浏览器实例提供了强大的控制接口。理解其功能和设计模式后,您可以构建比传统WebDriver方案更可靠、高效的复杂浏览器自动化流程。
凭借清晰的抽象层、全面的事件系统、基于标签页的架构以及对浏览器进程的直接控制,Pydoll能在保持简单直观API的同时,支持高级自动化场景的实现。