主题
15 · 15-browser-user
返回:Computer Use 与 Browser Use · 不可变原始文件
原课程完整 Notebook · 静态阅读与代码解析
代码按英文源文件顺序保留,中文说明以同版本译本为基础。原始安装单元格可能含无版本上限的 -U;请跳过它们,先按准备篇固定依赖。云端服务、模型权限、网站布局和部分 SDK 接口需在你自己的环境验证。本站没有执行云端请求;第 18 章的离线验证状态单独记录在检查报告。
运行准备
Python 3.12+;在独立虚拟环境安装源仓库依赖与本页中声明的额外依赖。原文件路径:upstream/15-browser-use/15-browser-user.ipynb。以原仓库根目录为工作目录,在 Jupyter 中按顺序执行;身份与环境变量见准备篇。
bash
cd upstream
python -m jupyterlab下载原始 Notebook。输出为上游文件保存的历史结果,不能用作本站实测证明。
使用 AI 驱动的网页自动化寻找最便宜的 Airbnb
本笔记本展示了如何构建一个智能网页自动化 Agent,搜索 Airbnb,提取价格,并找到斯德哥尔摩最便宜的房源。你将学习如何将 Playwright 与 Browser-Use 集成,实现强大的 AI 驱动自动化。
你将学到:
- Playwright + Browser-Use 集成 :结合浏览器管理与 AI 自动化
- 基于视觉的价格提取 :让 AI “看见” 并读取网页上的价格
- 结构化数据提取 :使用类型安全的 Pydantic 模型提取房源数据
- 价格比较逻辑 :从多个房源中找到最便宜的选项
- 实际应用 :实用的价格比较自动化
先决条件:
- 已配置 Azure OpenAI 部署
- 安装 Playwright(
pip install playwright) - 了解异步 Python
- 基础网页自动化知识
理解 Playwright + Browser-Use 架构
此笔记本使用了 Browser-Use 文档中的 官方 Playwright 集成 模式。
架构流程:
┌──────────────────┐
│ Playwright │ ◄─── Manages browser lifecycle
│ Browser Manager │ Handles CDP connection
└────────┬─────────┘ Provides browser instance
│
│ playwright_browser parameter
▼
┌──────────────────┐
│ Browser-Use │ ◄─── AI-powered automation
│ Browser Object │ Wraps Playwright browser
└────────┬─────────┘ Provides Agent interface
│
│ uses
▼
┌──────────────────┐
│ Agent │ ◄─── Vision + Decision Making
│ (with LLM) │ Structured output extraction
└──────────────────┘ Natural language tasks
│
│ powered by
▼
┌──────────────────┐
│ Azure OpenAI │ ◄─── GPT-4 Vision
│ (LLM + Vision) │ Analyzes screenshots
└──────────────────┘ Extracts structured data为什么采用这种方法?
Playwright 提供:
- ✅ 强大的浏览器生命周期管理
- ✅ 完整的 Chrome DevTools 协议控制
- ✅ 稳定的页面和上下文处理
- ✅ 内置的等待与同步
Browser-Use 提供:
- ✅ AI 驱动的元素查找(无需 CSS 选择器!)
- ✅ 基于视觉的页面理解
- ✅ 使用 Pydantic 的结构化输出提取
- ✅ 自然语言任务执行
两者协同实现:
- 🎯 “搜索斯德哥尔摩 Airbnb” → Agent 导航
- 👁️ 视觉读取页面上的所有价格
- 📊 结构化提取 → 清晰的 Python 对象
- 💰 价格比较逻辑 → 找出最便宜的
我们的任务流程:
- Playwright 启动 Chrome 浏览器
- Browser-Use Agent 导航到 Airbnb.com
- Agent 搜索 “斯德哥尔摩,瑞典”
- 视觉模型 读取并提取所有房源价格
- 结构化输出 返回类型化数据(Pydantic 模型)
- Python 代码 比较价格并找到最便宜的
- 用丰富格式 显示结果
代码单元格 3
阅读提示:跟踪本单元格读取的变量、修改的状态以及返回值。按原顺序执行,确认依赖的前序变量已经存在。
text
%pip install browser_use langchain-openai playwright代码单元格 4
阅读提示:跟踪本单元格读取的变量、修改的状态以及返回值。按原顺序执行,确认依赖的前序变量已经存在。
python
!playwright install chromium代码单元格 5
配置加载:从本地环境读取端点与部署名;缺少变量时先修复配置,不要把密钥写进代码。
数据结构:Pydantic 模型定义字段类型;只有传入实际的 response_format 并检查解析结果,才能约束本次输出。
输出观察:print 展示应用可观察结果;预存输出和现场结果可能不同,它不是模型内部思考记录。
python
import asyncio
import os
import re
from typing import Optional, List
from IPython.display import display, HTML, Markdown
from dotenv import load_dotenv
# Playwright imports
from playwright.async_api import async_playwright
# Browser-Use imports - USE BROWSER-USE'S AZURE OPENAI!
# Changed from langchain_openai
from browser_use import Agent, Browser, ChatAzureOpenAI
from pydantic import BaseModel, Field
print("✅ All packages imported successfully")原文件保存的输出(不是本项目实测)
text
✅ All packages imported successfully代码单元格 6
配置加载:从本地环境读取端点与部署名;缺少变量时先修复配置,不要把密钥写进代码。
输出观察:print 展示应用可观察结果;预存输出和现场结果可能不同,它不是模型内部思考记录。
python
# Load environment variables
load_dotenv()
# Azure OpenAI Configuration
azure_openai_deployment = os.getenv("AZURE_OPENAI_CHAT_DEPLOYMENT_NAME")
azure_openai_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT")
azure_openai_api_key = os.getenv("AZURE_OPENAI_API_KEY")
api_version = os.getenv("AZURE_OPENAI_API_VERSION")
# Verify configuration
print("✅ Azure OpenAI Configuration:")
print(f" Endpoint: {azure_openai_endpoint}")
print(f" Deployment: {azure_openai_deployment}")
print(f" API Version: {api_version}")原文件保存的输出(不是本项目实测)
text
✅ Azure OpenAI Configuration:
Endpoint: https://foundry-aiteam2510.cognitiveservices.azure.com/
Deployment: gpt-5-mini
API Version: 2024-12-01-preview初始化 Azure OpenAI LLM
LLM 驱动 Agent 的决策和视觉能力。我们使用:
- 温度:0.3 以实现一致且可预测的自动化
- 视觉能力 用于“观察”和理解页面内容
- 结构化输出 用于提取数据到 Pydantic 模型
代码单元格 8
输出观察:print 展示应用可观察结果;预存输出和现场结果可能不同,它不是模型内部思考记录。
python
# Initialize Azure OpenAI with Browser-Use's ChatAzureOpenAI
llm = ChatAzureOpenAI(
# Your deployment name (e.g., 'gpt-5-mini')
model=azure_openai_deployment,
# Browser-Use reads these from environment variables automatically:
# AZURE_OPENAI_ENDPOINT
# AZURE_OPENAI_API_KEY
# AZURE_OPENAI_API_VERSION (optional, defaults to latest)
)
print("✅ LLM initialized successfully")
print(f" Model: {azure_openai_deployment}")
print(f" Endpoint: {azure_openai_endpoint}")
print(f" Integration: Browser-Use ChatAzureOpenAI")原文件保存的输出(不是本项目实测)
text
✅ LLM initialized successfully
Model: gpt-5-mini
Endpoint: https://foundry-aiteam2510.cognitiveservices.azure.com/
Integration: Browser-Use ChatAzureOpenAI定义结构化输出模型
我们使用 Pydantic 模型从 Airbnb 搜索结果中提取结构化数据。Agent 将使用 GPT-4 Vision 自动读取页面并将数据提取到这些模型中。
这确保了所有提取数据的类型安全和验证。
代码单元格 10
数据结构:Pydantic 模型定义字段类型;只有传入实际的 response_format 并检查解析结果,才能约束本次输出。
检索过程:跟踪查询、候选结果和实际选入的证据;检索为空时应明确返回缺失,而不是补写答案。
输出观察:print 展示应用可观察结果;预存输出和现场结果可能不同,它不是模型内部思考记录。
python
# UPDATE THIS CELL - Add URL field to AirbnbListing
class AirbnbListing(BaseModel):
"""Single Airbnb listing with price information"""
title: str = Field(description="Name/title of the listing")
price_per_night: float = Field(
description="Price per night as a number (extract just the numeric value, ignore currency symbols)")
currency: str = Field(
default="SEK", description="Currency code (SEK for Swedish Krona)")
rating: Optional[float] = Field(
default=None, description="Rating score if visible")
url: Optional[str] = Field(
default=None, description="Full URL link to the listing page") # ✅ NEW!
class SearchResult(BaseModel):
"""Complete search results from Airbnb"""
location: str = Field(description="Search location (Stockholm, Sweden)")
total_listings_found: int = Field(
description="Number of listings found on the page")
listings: List[AirbnbListing] = Field(
description="List of all listings with prices extracted from the page")
cheapest_listing: AirbnbListing = Field(
description="The listing with the lowest price per night")
average_price: float = Field(
description="Average price per night across all listings")
price_range: str = Field(description="Price range as 'min - max SEK'")
print("✅ Structured output models defined")
print(" AirbnbListing: Individual listing data with clickable URLs")
print(" SearchResult: Complete search results with price analysis")原文件保存的输出(不是本项目实测)
text
✅ Structured output models defined
AirbnbListing: Individual listing data with clickable URLs
SearchResult: Complete search results with price analysis显示辅助函数
这些函数在笔记本中提供丰富的教育输出,带有格式化的 HTML。
代码单元格 13
数据结构:Pydantic 模型定义字段类型;只有传入实际的 response_format 并检查解析结果,才能约束本次输出。
输出观察:print 展示应用可观察结果;预存输出和现场结果可能不同,它不是模型内部思考记录。
python
class ListingInfo(BaseModel):
"""Information about the Airbnb listing"""
title: str = Field(description="The name/title of the listing")
location: str = Field(description="City and country of the listing")
price_per_night: Optional[str] = Field(
description="Price per night if visible")
rating: Optional[str] = Field(description="Rating score if visible")
class BookingDates(BaseModel):
"""Selected booking dates"""
check_in: str = Field(
description="Check-in date in format: Month DD, YYYY")
check_out: str = Field(
description="Check-out date in format: Month DD, YYYY")
nights: int = Field(description="Number of nights")
class BookingResult(BaseModel):
"""Complete booking result information"""
success: bool = Field(description="Whether the booking flow was completed")
listing_info: Optional[ListingInfo] = Field(
description="Details about the listing")
booking_dates: Optional[BookingDates] = Field(description="Selected dates")
total_price: Optional[str] = Field(description="Total price if shown")
message: str = Field(description="Status message or error description")
print("✅ Structured output models defined")
print(" ListingInfo: Extract listing details")
print(" BookingDates: Capture selected dates")
print(" BookingResult: Final booking status")原文件保存的输出(不是本项目实测)
text
✅ Structured output models defined
ListingInfo: Extract listing details
BookingDates: Capture selected dates
BookingResult: Final booking status显示辅助函数
这些函数在笔记本中提供丰富且具有教育意义的输出。
代码单元格 15
输出观察:print 展示应用可观察结果;预存输出和现场结果可能不同,它不是模型内部思考记录。
python
def display_step(step_number: int, title: str, description: str, color: str = "#2E8B57"):
"""Display a workflow step with formatting"""
html = f"""
<div style='
margin: 20px 0;
padding: 20px;
border-left: 5px solid {color};
background: linear-gradient(to right, rgba(46, 139, 87, 0.05), transparent);
border-radius: 8px;
'>
<h3 style='color: {color}; margin: 0 0 10px 0;'>
Step {step_number}: {title}
</h3>
<p style='margin: 0; line-height: 1.6; color: #333;'>{description}</p>
</div>
"""
display(HTML(html))
def display_action(action_type: str, details: str, emoji: str = "⚙️"):
"""Display an action being performed"""
html = f"""
<div style='
margin: 10px 20px;
padding: 12px 16px;
background: rgba(0, 123, 255, 0.05);
border: 1px solid #007BFF;
border-radius: 6px;
font-family: monospace;
font-size: 14px;
'>
<strong style='color: #007BFF;'>{emoji} {action_type}:</strong>
<span style='color: #555; margin-left: 10px;'>{details}</span>
</div>
"""
display(HTML(html))
def display_result(success: bool, message: str):
"""Display a result with success/failure indication"""
color = "#28a745" if success else "#dc3545"
emoji = "✅" if success else "❌"
html = f"""
<div style='
margin: 20px 0;
padding: 15px 20px;
border-left: 5px solid {color};
background: rgba({"40, 167, 69" if success else "220, 53, 69"}, 0.1);
border-radius: 8px;
'>
<strong style='color: {color}; font-size: 16px;'>{emoji} {message}</strong>
</div>
"""
display(HTML(html))
def display_screenshot(screenshot_base64: str, caption: str = ""):
"""Display a screenshot in the notebook"""
html = f"""
<div style='margin: 20px 0; text-align: center;'>
<img src='data:image/png;base64,{screenshot_base64}'
style='max-width: 100%; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);'/>
{f"<p style='margin-top: 10px; color: #666; font-style: italic;'>{caption}</p>" if caption else ""}
</div>
"""
display(HTML(html))
print("✅ Helper functions loaded")原文件保存的输出(不是本项目实测)
text
✅ Helper functions loadedAirbnb 预订 Agent 类
该类协调整个预订工作流程,策略性地结合了 Agent 和执行者方法。
代码单元格 17
异步执行:async def 定义协程,await 等待结果;普通 .py 脚本需要 asyncio.run() 入口,Notebook 支持顶层 await。
检索过程:跟踪查询、候选结果和实际选入的证据;检索为空时应明确返回缺失,而不是补写答案。
输出观察:print 展示应用可观察结果;预存输出和现场结果可能不同,它不是模型内部思考记录。
python
class AirbnbSearchAgent:
"""
Intelligent Airbnb search agent using Playwright + Browser-Use integration.
This agent:
1. Navigates to Airbnb.com
2. Searches for listings in Stockholm
3. Extracts all visible prices using AI vision
4. Compares prices and finds the cheapest listing
"""
def __init__(self, llm, playwright_browser):
self.llm = llm
# Browser-Use wraps the Playwright browser
# Reference: https://docs.browser-use.com/examples/templates/playwright-integration
self.browser = Browser(playwright_browser=playwright_browser)
async def take_screenshot(self, caption: str = ""):
"""Take and display a screenshot of current page"""
try:
page = await self.browser.get_current_page()
screenshot_base64 = await page.screenshot(format='png')
display_screenshot(screenshot_base64, caption)
except Exception as e:
print(f"⚠️ Could not capture screenshot: {str(e)}")
async def search_stockholm(self) -> SearchResult:
"""
Main workflow: Search Airbnb for Stockholm and find cheapest listing.
Returns:
SearchResult: Structured data with all listings and price analysis
"""
# Step 1: Navigate and search
display_step(
1,
"Navigate & Search (AI Agent)",
"Using AI agent with vision to navigate Airbnb and search for Stockholm listings. "
"The agent will handle pop-ups, cookie banners, and search automatically."
)
try:
# Agent navigates to Airbnb and searches
search_agent = Agent(
task=(
"Navigate to https://www.airbnb.com. "
"Close any pop-ups, cookie banners, or login prompts if they appear. "
"Search for 'Stockholm, Sweden' in the search box. "
"Wait for the search results page to fully load with listing cards visible."
),
llm=self.llm,
browser=self.browser,
use_vision=True # Critical: enables screenshot analysis
)
display_action("Agent", "Navigating to Airbnb and searching for Stockholm...")
await search_agent.run()
# Wait for results to load
await asyncio.sleep(3)
await self.take_screenshot("Search results page loaded")
display_result(True, "Successfully loaded Stockholm search results")
except Exception as e:
display_result(False, f"Search failed: {str(e)}")
raise
# Step 2: Extract prices with vision
display_step(
2,
"Extract Prices (Vision + LLM)",
"Using GPT-4 Vision to read all listing prices from the page and extract "
"structured data into Pydantic models. The AI 'sees' the page like a human."
)
try:
page = await self.browser.get_current_page()
display_action("Vision", "AI is analyzing the page and reading prices...")
# Use page.extract_content with structured output
# Reference: https://docs.browser-use.com/customize/actor/all-parameters
# This uses LLM to parse the visible page content
extraction_prompt = """
Extract ALL Airbnb listings visible on this page.
For each listing, extract:
- Title/name of the property
- Price per night (numeric value only, without currency symbols)
- Currency (SEK for Swedish Krona)
- Rating if visible
After extracting all listings:
- Identify which listing has the LOWEST price
- Calculate the average price across all listings
- Determine the price range (min to max)
Focus on the listing cards on the search results page.
Only include listings where you can clearly see the price.
"""
# Extract structured data using LLM vision
search_results = await page.extract_content(
prompt=extraction_prompt,
structured_output=SearchResult,
llm=self.llm
)
display_action(
"Extracted",
f"Found {search_results.total_listings_found} listings with prices"
)
# Display some sample prices
if len(search_results.listings) > 0:
sample_prices = [f"{l.price_per_night:.0f} SEK" for l in search_results.listings[:5]]
display_action(
"Sample Prices",
f"{', '.join(sample_prices)}{'...' if len(search_results.listings) > 5 else ''}"
)
display_result(True, "Price extraction completed successfully")
return search_results
except Exception as e:
display_result(False, f"Price extraction failed: {str(e)}")
raise
print("✅ AirbnbSearchAgent class defined")
print(" Integration: Playwright browser + Browser-Use Agent")
print(" Capabilities: Vision-based price extraction with structured output")原文件保存的输出(不是本项目实测)
text
✅ AirbnbSearchAgent class defined
Integration: Playwright browser + Browser-Use Agent
Capabilities: Vision-based price extraction with structured output执行搜索
现在让我们运行完整的工作流程,找到斯德哥尔摩最便宜的Airbnb!
这将会:
- 启动一个真实的Chrome浏览器(可见)
- 使用AI进行导航和搜索
- 通过视觉识别提取所有价格
- 显示最便宜的选项
代码单元格 19
异步执行:async def 定义协程,await 等待结果;普通 .py 脚本需要 asyncio.run() 入口,Notebook 支持顶层 await。
检索过程:跟踪查询、候选结果和实际选入的证据;检索为空时应明确返回缺失,而不是补写答案。
输出观察:print 展示应用可观察结果;预存输出和现场结果可能不同,它不是模型内部思考记录。
python
# UPDATED AirbnbSearchAgent class - Add keep_alive parameter
class AirbnbSearchAgent:
"""
Intelligent Airbnb search agent using Browser-Use integration via CDP.
This agent:
1. Connects to Chrome via CDP (Chrome DevTools Protocol)
2. Both Playwright and Browser-Use share the same browser instance
3. Searches for listings in Stockholm
4. Extracts prices using AI vision
5. Finds the cheapest listing
"""
def __init__(self, llm, cdp_url: str):
"""
Initialize agent with LLM and CDP connection.
Args:
llm: Language model for AI decisions
cdp_url: Chrome DevTools Protocol URL (e.g., 'http://localhost:9222')
"""
self.llm = llm
# Browser-Use connects to Chrome via CDP
# IMPORTANT: keep_alive=True prevents browser from closing after Agent completes
self.browser = Browser(
cdp_url=cdp_url,
keep_alive=True # ✅ This keeps the browser open!
)
async def take_screenshot(self, caption: str = ""):
"""Take and display a screenshot of current page"""
try:
# Get pages and use the active one
pages = await self.browser.get_pages()
if not pages:
print("⚠️ No pages available for screenshot")
return
page = pages[0] # Use first page (active page)
screenshot_bytes = await page.screenshot()
import base64
screenshot_base64 = base64.b64encode(screenshot_bytes).decode()
display_screenshot(screenshot_base64, caption)
except Exception as e:
print(f"⚠️ Could not capture screenshot: {str(e)}")
async def search_stockholm(self) -> SearchResult:
"""
Main workflow: Search Airbnb for Stockholm and find cheapest listing.
Returns:
SearchResult: Structured data with all listings and price analysis
"""
# Step 1: Navigate and search
display_step(
1,
"Navigate & Search (AI Agent)",
"Using AI agent with vision to navigate Airbnb and search for Stockholm listings. "
"The agent will handle pop-ups, cookie banners, and search automatically."
)
try:
# Agent navigates to Airbnb and searches
search_agent = Agent(
task=(
"Navigate to https://www.airbnb.com. "
"Close any pop-ups, cookie banners, or login prompts if they appear. "
"Search for 'Stockholm, Sweden' in the search box. "
"Wait for the search results page to fully load with listing cards visible."
),
llm=self.llm,
browser=self.browser,
use_vision=True # Critical: enables screenshot analysis
)
display_action(
"Agent", "Navigating to Airbnb and searching for Stockholm...")
await search_agent.run()
# Wait for results to load
await asyncio.sleep(3)
await self.take_screenshot("Search results page loaded")
display_result(
True, "Successfully loaded Stockholm search results")
except Exception as e:
display_result(False, f"Search failed: {str(e)}")
raise
# Step 2: Extract prices with vision
display_step(
2,
"Extract Prices (Vision + LLM)",
"Using GPT-4 Vision to read all listing prices from the page and extract "
"structured data into Pydantic models. The AI 'sees' the page like a human."
)
try:
# Get the pages created by the Agent
pages = await self.browser.get_pages()
if not pages:
raise RuntimeError(
"No pages available after Agent run. Browser might have closed.")
# Use the first (active) page
page = pages[0]
display_action(
"Vision", "AI is analyzing the page and reading prices...")
# Extract structured data using LLM vision
extraction_prompt = """
Extract ALL Airbnb Home listings visible on this page. DO NOT include "Experiences" or other non-home listings.
For each listing, extract:
- Title/name of the property
- Price per night (numeric value only, without currency symbols)
- Currency (SEK for Swedish Krona)
- Rating if visible
- URL: The full link to the listing detail page (should start with https://www.airbnb.com/rooms/)
After extracting all listings:
- Identify which listing has the LOWEST price
- Calculate the average price across all listings
- Determine the price range (min to max)
Focus on the listing cards on the search results page.
Only include listings where you can clearly see the price.
IMPORTANT: Extract the actual URL/link for each listing so users can click on it.
"""
search_results = await page.extract_content(
prompt=extraction_prompt,
structured_output=SearchResult,
llm=self.llm
)
display_action(
"Extracted",
f"Found {search_results.total_listings_found} listings with prices"
)
# Display some sample prices
if len(search_results.listings) > 0:
sample_prices = [
f"{l.price_per_night:.0f} SEK" for l in search_results.listings[:5]]
display_action(
"Sample Prices",
f"{', '.join(sample_prices)}{'...' if len(search_results.listings) > 5 else ''}"
)
display_result(True, "Price extraction completed successfully")
return search_results
except Exception as e:
display_result(False, f"Price extraction failed: {str(e)}")
raise
print("✅ AirbnbSearchAgent class defined")
print(" Integration: CDP-based connection for Playwright + Browser-Use")
print(" Capabilities: Vision-based price extraction with structured output")
print(" Browser Mode: keep_alive=True (prevents auto-close)")原文件保存的输出(不是本项目实测)
text
✅ AirbnbSearchAgent class defined
Integration: CDP-based connection for Playwright + Browser-Use
Capabilities: Vision-based price extraction with structured output
Browser Mode: keep_alive=True (prevents auto-close)代码单元格 20
异步执行:async def 定义协程,await 等待结果;普通 .py 脚本需要 asyncio.run() 入口,Notebook 支持顶层 await。
检索过程:跟踪查询、候选结果和实际选入的证据;检索为空时应明确返回缺失,而不是补写答案。
代码执行边界:这里演示生成代码的执行;不要对不可信模型输出直接 exec。实际应用应使用隔离环境、时间/资源限制和最小权限。
python
import subprocess
import tempfile
async def start_chrome_with_cdp(port: int = 9222):
"""
Start Chrome with CDP (Chrome DevTools Protocol) enabled.
Returns the Chrome process.
"""
# Create temporary directory for Chrome user data
user_data_dir = tempfile.mkdtemp(prefix='chrome_cdp_')
# Chrome paths for different platforms
chrome_paths = [
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', # macOS
'/usr/bin/google-chrome', # Linux
'/usr/bin/chromium-browser', # Linux Chromium
'chrome', # Windows/PATH
'chromium', # Generic
]
chrome_exe = None
for path in chrome_paths:
if os.path.exists(path) or path in ['chrome', 'chromium']:
try:
test_proc = await asyncio.create_subprocess_exec(
path, '--version',
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
await test_proc.wait()
chrome_exe = path
break
except Exception:
continue
if not chrome_exe:
raise RuntimeError(
'❌ Chrome not found. Please install Chrome or Chromium.')
# Chrome command arguments
cmd = [
chrome_exe,
f'--remote-debugging-port={port}',
f'--user-data-dir={user_data_dir}',
'--no-first-run',
'--no-default-browser-check',
'about:blank',
]
# Start Chrome process
process = await asyncio.create_subprocess_exec(
*cmd,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
# Wait for Chrome to start and CDP to be ready
import aiohttp
cdp_ready = False
for _ in range(20): # 20 second timeout
try:
async with aiohttp.ClientSession() as session:
async with session.get(
f'http://localhost:{port}/json/version',
timeout=aiohttp.ClientTimeout(total=1)
) as response:
if response.status == 200:
cdp_ready = True
break
except Exception:
pass
await asyncio.sleep(1)
if not cdp_ready:
process.terminate()
raise RuntimeError('❌ Chrome failed to start with CDP')
print(f"✅ Chrome started with CDP on port {port}")
return process
async def main():
"""
Main execution function for Airbnb price comparison.
Uses CDP to connect both Playwright and Browser-Use to the same Chrome instance.
"""
display(HTML("""
<div style='
padding: 30px;
background: linear-gradient(135deg, #FF5A5F 0%, #FF385C 100%);
color: white;
border-radius: 12px;
margin: 30px 0;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
'>
<h2 style='margin: 0 0 15px 0;'>🏠 Find the Cheapest Airbnb in Stockholm</h2>
<p style='margin: 0; font-size: 16px; line-height: 1.8;'>
This demo uses <strong>CDP (Chrome DevTools Protocol)</strong> integration:<br>
• <strong>Chrome</strong> runs with remote debugging enabled<br>
• <strong>Playwright</strong> connects to Chrome via CDP<br>
• <strong>Browser-Use</strong> connects to same Chrome via CDP<br>
• <strong>GPT-4 Vision</strong> reads and extracts prices<br>
• <strong>Structured Output</strong> returns type-safe data
</p>
<p style='margin: 15px 0 0 0; font-size: 14px; opacity: 0.9;'>
📊 Watch the browser as the AI agent searches and analyzes prices!
</p>
</div>
"""))
chrome_process = None
playwright_browser = None
try:
# Step 1: Start Chrome with CDP
display_action(
"Chrome", "Starting Chrome with CDP (remote debugging)...")
chrome_process = await start_chrome_with_cdp(port=9222)
cdp_url = 'http://localhost:9222'
# Step 2: Connect Playwright to CDP (optional - for custom Playwright actions)
display_action(
"Playwright", "Connecting Playwright to Chrome via CDP...")
playwright = await async_playwright().start()
playwright_browser = await playwright.chromium.connect_over_cdp(cdp_url)
display_result(True, "Playwright connected successfully")
# Step 3: Create Browser-Use agent with CDP connection
display_action(
"Browser-Use", "Creating Browser-Use agent with CDP connection...")
agent = AirbnbSearchAgent(llm=llm, cdp_url=cdp_url)
display_result(True, "Agent initialized with CDP integration")
# Step 4: Search and extract prices
result = await agent.search_stockholm()
# Step 5: Display results
display(
HTML("<hr style='margin: 40px 0; border: none; border-top: 2px solid #ddd;'>"))
display(HTML("""
<div style='padding: 20px; background: #f8f9fa; border-radius: 8px; margin: 20px 0;'>
<h3 style='margin: 0 0 15px 0; color: #333;'>📊 Search Results</h3>
</div>
"""))
# Display summary stats
display(HTML(f"""
<div style='margin: 20px; padding: 20px; background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);'>
<h4 style='margin: 0 0 15px 0; color: #FF5A5F;'>📈 Price Analysis</h4>
<table style='width: 100%; border-collapse: collapse;'>
<tr>
<td style='padding: 10px; border-bottom: 1px solid #eee; font-weight: bold;'>Location:</td>
<td style='padding: 10px; border-bottom: 1px solid #eee;'>{result.location}</td>
</tr>
<tr>
<td style='padding: 10px; border-bottom: 1px solid #eee; font-weight: bold;'>Total Listings Found:</td>
<td style='padding: 10px; border-bottom: 1px solid #eee;'>{result.total_listings_found}</td>
</tr>
<tr>
<td style='padding: 10px; border-bottom: 1px solid #eee; font-weight: bold;'>Average Price:</td>
<td style='padding: 10px; border-bottom: 1px solid #eee;'>{result.average_price:.2f} SEK/night</td>
</tr>
<tr>
<td style='padding: 10px; border-bottom: 1px solid #eee; font-weight: bold;'>Price Range:</td>
<td style='padding: 10px; border-bottom: 1px solid #eee;'>{result.price_range}</td>
</tr>
</table>
</div>
"""))
# Display the CHEAPEST listing with clickable link
cheapest = result.cheapest_listing
# Create View Listing button if URL exists
view_button = ""
if cheapest.url:
view_button = f"""
<a href="{cheapest.url}" target="_blank" style="
display: inline-block;
margin-top: 15px;
padding: 12px 24px;
background: #FF5A5F;
color: white;
text-decoration: none;
border-radius: 8px;
font-weight: bold;
transition: background 0.3s;
" onmouseover="this.style.background='#E00007'" onmouseout="this.style.background='#FF5A5F'">
🔗 View Listing on Airbnb
</a>
"""
display(HTML(f"""
<div style='
margin: 20px;
padding: 25px;
background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
border-radius: 12px;
box-shadow: 0 4px 12px rgba(255,165,0,0.3);
border: 3px solid #FFD700;
'>
<h3 style='margin: 0 0 15px 0; color: #333; font-size: 24px;'>
🏆 CHEAPEST AIRBNB IN STOCKHOLM
</h3>
<div style='background: white; padding: 20px; border-radius: 8px; margin-top: 15px;'>
<h4 style='margin: 0 0 10px 0; color: #FF5A5F;'>{cheapest.title}</h4>
<p style='margin: 10px 0; font-size: 28px; font-weight: bold; color: #28a745;'>
{cheapest.price_per_night:.2f} {cheapest.currency}/night
</p>
{f"<p style='margin: 10px 0; color: #666;'>⭐ Rating: {cheapest.rating}/5.0</p>" if cheapest.rating else ""}
<p style='margin: 15px 0 0 0; color: #666; font-size: 14px;'>
💰 Saves you <strong>{(result.average_price - cheapest.price_per_night):.2f} SEK</strong> compared to average price!
</p>
{view_button}
</div>
</div>
"""))
# Display all listings in a clickable table
if len(result.listings) > 1:
listings_html = """
<div style='margin: 20px; padding: 20px; background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);'>
<h4 style='margin: 0 0 15px 0; color: #FF5A5F;'>📋 All Listings Found (Click to View)</h4>
<table style='width: 100%; border-collapse: collapse;'>
<thead>
<tr style='background: #f8f9fa;'>
<th style='padding: 12px; text-align: left; border-bottom: 2px solid #dee2e6;'>Rank</th>
<th style='padding: 12px; text-align: left; border-bottom: 2px solid #dee2e6;'>Listing</th>
<th style='padding: 12px; text-align: right; border-bottom: 2px solid #dee2e6;'>Price/Night</th>
<th style='padding: 12px; text-align: center; border-bottom: 2px solid #dee2e6;'>Rating</th>
<th style='padding: 12px; text-align: center; border-bottom: 2px solid #dee2e6;'>Link</th>
</tr>
</thead>
<tbody>
"""
sorted_listings = sorted(
result.listings, key=lambda x: x.price_per_night)
for idx, listing in enumerate(sorted_listings, 1):
is_cheapest = listing.price_per_night == cheapest.price_per_night
row_style = "background: #fff3cd;" if is_cheapest else ""
badge = "🏆 " if is_cheapest else ""
# Create clickable link if URL exists
if listing.url:
link_html = f'<a href="{listing.url}" target="_blank" style="color: #FF5A5F; text-decoration: none; font-weight: bold; padding: 6px 12px; border: 1px solid #FF5A5F; border-radius: 4px; transition: all 0.3s;" onmouseover="this.style.background=\'#FF5A5F\'; this.style.color=\'white\'" onmouseout="this.style.background=\'transparent\'; this.style.color=\'#FF5A5F\'">🔗 View</a>'
title_html = f'<a href="{listing.url}" target="_blank" style="color: #333; text-decoration: none; font-weight: 500;" onmouseover="this.style.color=\'#FF5A5F\'" onmouseout="this.style.color=\'#333\'">{listing.title[:50]}...</a>'
else:
link_html = '<span style="color: #999;">N/A</span>'
title_html = f'{listing.title[:50]}...'
listings_html += f"""
<tr style='{row_style}'>
<td style='padding: 10px; border-bottom: 1px solid #eee;'>{badge}{idx}</td>
<td style='padding: 10px; border-bottom: 1px solid #eee;'>{title_html}</td>
<td style='padding: 10px; border-bottom: 1px solid #eee; text-align: right; font-weight: bold;'>
{listing.price_per_night:.2f} {listing.currency}
</td>
<td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>
{f"⭐ {listing.rating}" if listing.rating else "N/A"}
</td>
<td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>
{link_html}
</td>
</tr>
"""
listings_html += """
</tbody>
</table>
<p style='margin-top: 15px; color: #666; font-size: 13px; font-style: italic;'>
💡 Tip: Click on any listing title or the "View" button to open it in a new tab
</p>
</div>
"""
display(HTML(listings_html))
display_result(True, "Price comparison completed successfully!")
except Exception as e:
display_result(False, f"Error during search: {str(e)}")
import traceback
print(traceback.format_exc())
finally:
# Cleanup
display_action("Cleanup", "Closing browser and cleaning up...")
if playwright_browser:
await playwright_browser.close()
if chrome_process:
chrome_process.terminate()
try:
await asyncio.wait_for(chrome_process.wait(), 5)
except TimeoutError:
chrome_process.kill()
display_result(True, "Cleanup complete")
# Display educational summary
display(HTML("""
<div style='
margin: 40px 0 20px 0;
padding: 25px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 12px;
'>
<h3 style='margin: 0 0 15px 0;'>🎓 What You Just Learned</h3>
<ul style='margin: 0; padding-left: 20px; line-height: 2;'>
<li><strong>CDP Integration:</strong> Chrome DevTools Protocol connects Playwright + Browser-Use</li>
<li><strong>Vision-Based Extraction:</strong> GPT-4 Vision reads prices like a human</li>
<li><strong>Structured Output:</strong> Type-safe data extraction with Pydantic models</li>
<li><strong>Price Comparison:</strong> Automated analysis to find best deals</li>
<li><strong>Clickable URLs:</strong> Direct links to Airbnb listings for easy viewing</li>
<li><strong>Real-World Application:</strong> Practical web scraping for price monitoring</li>
</ul>
</div>
"""))
# Run the demo
await main()原文件保存的输出(不是本项目实测)
text
<IPython.core.display.HTML object>
<IPython.core.display.HTML object>
✅ Chrome started with CDP on port 9222
<IPython.core.display.HTML object>
<IPython.core.display.HTML object>
<IPython.core.display.HTML object>
<IPython.core.display.HTML object>
<IPython.core.display.HTML object>
INFO [Agent] 🔗 Found URL in task: https://www.airbnb.com, adding as initial action...
<IPython.core.display.HTML object>
INFO [Agent] 🚀 Task: Navigate to https://www.airbnb.com. Close any pop-ups, cookie banners, or login prompts if they appear. Search for 'Stockholm, Sweden' in the search box. Wait for the search results page to fully load with listing cards visible.
INFO [service] ────────────────────────────────────────
INFO [service] 🔐 To view this run in Browser Use Cloud, authenticate with:
INFO [service] 👉 browser-use auth
INFO [service] or: python -m browser_use.cli auth
INFO [service] ────────────────────────────────────────
INFO [Agent] 🦾 [ACTION 1/1] go_to_url: url: https://www.airbnb.com, new_tab: False
INFO [tools] 🔗 Navigated to https://www.airbnb.com
INFO [Agent]
INFO [Agent] 📍 Step 1:
ERROR [Agent] ❌ Result failed 1/4 times:
LLM call timed out after 60 seconds. Keep your thinking and output short.
INFO [Agent]
INFO [Agent] 📍 Step 2:
INFO [Agent] 👍 Eval: Successfully loaded Airbnb homepage and identified the search input box for destination.
INFO [Agent] 🎯 Next goal: Input 'Stockholm, Sweden' into the destination search box and click the search button to load results.
INFO [Agent] 🦾 [ACTION 1/2] input_text: index: 27, text: Stockholm, Sweden, clear_existing: True
WARNING [BrowserSession] ⚠️ Some framework events may have failed to trigger
INFO [BrowserSession] ⌨️ Typed "Stockholm, Sweden" into element with index 27
INFO [service] Page changed after action: actions click_element_by_index are not yet executed
INFO [Agent]
INFO [Agent] 📍 Step 3:
INFO [Agent] 👍 Eval: Successfully input 'Stockholm, Sweden' into the destination search box but did not yet click the search button to load results.
INFO [Agent] 🎯 Next goal: Click the search button to perform the search and wait for the listings page to load with visible listing cards.
INFO [Agent] 🦾 [ACTION 1/1] click_element_by_index: index: 34, while_holding_ctrl: False
INFO [tools] 🖱️ Clicked element
INFO [Agent]
INFO [Agent] 📍 Step 4:
INFO [Agent] 👍 Eval: Successfully clicked the search button and loaded the Airbnb search results page with visible listing cards for Stockholm.
INFO [Agent] 🎯 Next goal: Confirm no pop-ups or cookie banners are present and finalize task as completed since listings are visible.
INFO [Agent] 🦾 [ACTION 1/1] done: text: I have navigated to Airbnb.com, searched for Stockholm, Sweden, and confirmed that the search results page is fully loa
INFO [Agent]
📄 Final Result:
I have navigated to Airbnb.com, searched for 'Stockholm, Sweden', and confirmed that the search results page is fully loaded with listing cards visible. There were no pop-ups or cookie banners to close. Task completed successfully.
INFO [Agent] ✅ Task completed successfully
⚠️ Could not capture screenshot: a bytes-like object is required, not 'str'
<IPython.core.display.HTML object>
<IPython.core.display.HTML object>
<IPython.core.display.HTML object>
<IPython.core.display.HTML object>
<IPython.core.display.HTML object>
<IPython.core.display.HTML object>
<IPython.core.display.HTML object>
<IPython.core.display.HTML object>
<IPython.core.display.HTML object>
<IPython.core.display.HTML object>
<IPython.core.display.HTML object>
<IPython.core.display.HTML object>
<IPython.core.display.HTML object>
WARNING [cdp_use.client] WebSocket connection closed: no close frame received or sent
<IPython.core.display.HTML object>
<IPython.core.display.HTML object>主要收获与最佳实践
何时使用 Agent 与 Actor
| 场景 | 使用 Agent | 使用 Actor |
|---|---|---|
| 动态布局 | ✅ AI 适应变化 | ❌ CSS 选择器失效 |
| 已知结构 | ❌ 比直接控制慢 | ✅ 快速且精确 |
| 查找元素 | ✅ 自然语言查询 | ❌ 需要精确选择器 |
| 时序控制 | ❌ 可预测性较差 | ✅ 完全时序控制 |
| 复杂工作流 | ✅ 处理意外 UI | ❌ 需要显式编码 |
浏览器使用最佳实践
- 初期探索用 Agent :让 AI 先浏览复杂网站
- 精确操作用 Actor :针对可预测元素用 CSS 选择器
- 始终处理错误 :网站会变——构建降级策略
- 使用结构化输出 :Pydantic 模型确保类型安全数据
- 策略性添加延迟 :触发变更后用
asyncio.sleep() - 截图调试 :视觉调试极为有用
- 组合方法 :混合工作流发挥两种范式优势
现实应用场景
- 旅游预订 :监控价格,自动订购,比较选项
- 电商 :跟踪库存,价格比较,自动购买
- 数据采集 :抓取动态站点,提取结构化数据
- 测试 :基于视觉验证的自动化 UI 测试
- 监控 :检查网站变化,对特定情况报警
- 表单自动化 :智能填写复杂多步表单