microsoft--ai-agents-for-beginners
2139 行
106 KiB
Plaintext
2139 行
106 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "dfd434e5",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Finding the Cheapest Airbnb with AI-Powered Web Automation\n",
|
|
"\n",
|
|
"This notebook demonstrates how to build an intelligent web automation agent that searches Airbnb, extracts prices, and finds the cheapest listing in Stockholm. You'll learn how to integrate **Playwright** with **Browser-Use** for powerful AI-driven automation.\n",
|
|
"\n",
|
|
"## What You'll Learn:\n",
|
|
"1. **Playwright + Browser-Use Integration**: Combining browser management with AI automation\n",
|
|
"2. **Vision-Based Price Extraction**: Let AI \"see\" and read prices from web pages\n",
|
|
"3. **Structured Data Extraction**: Extract listing data with type-safe Pydantic models\n",
|
|
"4. **Price Comparison Logic**: Find the cheapest option from multiple listings\n",
|
|
"5. **Real-world Application**: Practical price comparison automation\n",
|
|
"\n",
|
|
"## Prerequisites:\n",
|
|
"- Azure OpenAI deployment configured\n",
|
|
"- Playwright installed (`pip install playwright`)\n",
|
|
"- Understanding of async Python\n",
|
|
"- Basic web automation concepts"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "6340fa32",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Understanding the Playwright + Browser-Use Architecture\n",
|
|
"\n",
|
|
"This notebook uses the **official Playwright integration** pattern from Browser-Use documentation.\n",
|
|
"\n",
|
|
"### Architecture Flow:\n",
|
|
"```\n",
|
|
"┌──────────────────┐\n",
|
|
"│ Playwright │ ◄─── Manages browser lifecycle\n",
|
|
"│ Browser Manager │ Handles CDP connection\n",
|
|
"└────────┬─────────┘ Provides browser instance\n",
|
|
" │\n",
|
|
" │ playwright_browser parameter\n",
|
|
" ▼\n",
|
|
"┌──────────────────┐\n",
|
|
"│ Browser-Use │ ◄─── AI-powered automation\n",
|
|
"│ Browser Object │ Wraps Playwright browser\n",
|
|
"└────────┬─────────┘ Provides Agent interface\n",
|
|
" │\n",
|
|
" │ uses\n",
|
|
" ▼\n",
|
|
"┌──────────────────┐\n",
|
|
"│ Agent │ ◄─── Vision + Decision Making\n",
|
|
"│ (with LLM) │ Structured output extraction\n",
|
|
"└──────────────────┘ Natural language tasks\n",
|
|
" │\n",
|
|
" │ powered by\n",
|
|
" ▼\n",
|
|
"┌──────────────────┐\n",
|
|
"│ Azure OpenAI │ ◄─── GPT-4 Vision\n",
|
|
"│ (LLM + Vision) │ Analyzes screenshots\n",
|
|
"└──────────────────┘ Extracts structured data\n",
|
|
"```\n",
|
|
"\n",
|
|
"### Why This Approach?\n",
|
|
"\n",
|
|
"**Playwright provides:**\n",
|
|
"- ✅ Robust browser lifecycle management\n",
|
|
"- ✅ Full Chrome DevTools Protocol control\n",
|
|
"- ✅ Stable page and context handling\n",
|
|
"- ✅ Built-in waiting and synchronization\n",
|
|
"\n",
|
|
"**Browser-Use adds:**\n",
|
|
"- ✅ AI-powered element finding (no CSS selectors needed!)\n",
|
|
"- ✅ Vision-based page understanding\n",
|
|
"- ✅ Structured output extraction with Pydantic\n",
|
|
"- ✅ Natural language task execution\n",
|
|
"\n",
|
|
"**Together they enable:**\n",
|
|
"- 🎯 \"Search for Stockholm Airbnb\" → Agent navigates\n",
|
|
"- 👁️ Vision reads all prices on the page\n",
|
|
"- 📊 Structured extraction → clean Python objects\n",
|
|
"- 💰 Price comparison logic → find cheapest\n",
|
|
"\n",
|
|
"### Our Task Flow:\n",
|
|
"1. **Playwright** launches Chrome browser\n",
|
|
"2. **Browser-Use Agent** navigates to Airbnb.com\n",
|
|
"3. **Agent searches** for \"Stockholm, Sweden\"\n",
|
|
"4. **Vision model** reads and extracts all listing prices\n",
|
|
"5. **Structured output** returns typed data (Pydantic models)\n",
|
|
"6. **Python code** compares prices and finds cheapest\n",
|
|
"7. **Display results** with rich formatting"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "e1449801",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"pip install browser_use langchain-openai playwright "
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"id": "290efc2a",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"!playwright install chromium"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 12,
|
|
"id": "ed8648b4",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"✅ All packages imported successfully\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"import asyncio\n",
|
|
"import os\n",
|
|
"import re\n",
|
|
"from typing import Optional, List\n",
|
|
"from IPython.display import display, HTML, Markdown\n",
|
|
"from dotenv import load_dotenv\n",
|
|
"\n",
|
|
"# Playwright imports\n",
|
|
"from playwright.async_api import async_playwright\n",
|
|
"\n",
|
|
"# Browser-Use imports - USE BROWSER-USE'S AZURE OPENAI!\n",
|
|
"# Changed from langchain_openai\n",
|
|
"from browser_use import Agent, Browser, ChatAzureOpenAI\n",
|
|
"from pydantic import BaseModel, Field\n",
|
|
"\n",
|
|
"print(\"✅ All packages imported successfully\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"id": "26f7e392",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"✅ Azure OpenAI Configuration:\n",
|
|
" Endpoint: https://foundry-aiteam2510.cognitiveservices.azure.com/\n",
|
|
" Deployment: gpt-4.1-mini\n",
|
|
" API Version: 2024-12-01-preview\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"# Load environment variables\n",
|
|
"load_dotenv()\n",
|
|
"\n",
|
|
"# Azure OpenAI Configuration\n",
|
|
"azure_openai_deployment = os.getenv(\"AZURE_OPENAI_CHAT_DEPLOYMENT_NAME\")\n",
|
|
"azure_openai_endpoint = os.getenv(\"AZURE_OPENAI_ENDPOINT\")\n",
|
|
"azure_openai_api_key = os.getenv(\"AZURE_OPENAI_API_KEY\")\n",
|
|
"api_version = os.getenv(\"AZURE_OPENAI_API_VERSION\")\n",
|
|
"\n",
|
|
"# Verify configuration\n",
|
|
"print(\"✅ Azure OpenAI Configuration:\")\n",
|
|
"print(f\" Endpoint: {azure_openai_endpoint}\")\n",
|
|
"print(f\" Deployment: {azure_openai_deployment}\")\n",
|
|
"print(f\" API Version: {api_version}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "70b80842",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Initialize Azure OpenAI LLM\n",
|
|
"\n",
|
|
"The LLM powers the Agent's decision-making and vision capabilities. We use:\n",
|
|
"- **Temperature: 0.3** for consistent, predictable automation\n",
|
|
"- **Vision capabilities** to \"see\" and understand page content\n",
|
|
"- **Structured output** to extract data into Pydantic models"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 13,
|
|
"id": "8ce29377",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"✅ LLM initialized successfully\n",
|
|
" Model: gpt-4.1-mini\n",
|
|
" Endpoint: https://foundry-aiteam2510.cognitiveservices.azure.com/\n",
|
|
" Integration: Browser-Use ChatAzureOpenAI\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"# Initialize Azure OpenAI with Browser-Use's ChatAzureOpenAI\n",
|
|
"llm = ChatAzureOpenAI(\n",
|
|
" # Your deployment name (e.g., 'gpt-4o', 'gpt-4.1-mini')\n",
|
|
" model=azure_openai_deployment,\n",
|
|
" # Browser-Use reads these from environment variables automatically:\n",
|
|
" # AZURE_OPENAI_ENDPOINT\n",
|
|
" # AZURE_OPENAI_API_KEY\n",
|
|
" # AZURE_OPENAI_API_VERSION (optional, defaults to latest)\n",
|
|
")\n",
|
|
"\n",
|
|
"print(\"✅ LLM initialized successfully\")\n",
|
|
"print(f\" Model: {azure_openai_deployment}\")\n",
|
|
"print(f\" Endpoint: {azure_openai_endpoint}\")\n",
|
|
"print(f\" Integration: Browser-Use ChatAzureOpenAI\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "846bf74e",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Define Structured Output Models\n",
|
|
"\n",
|
|
"We use Pydantic models to extract structured data from Airbnb search results. The Agent will use GPT-4 Vision to read the page and extract data into these models automatically.\n",
|
|
"\n",
|
|
"This ensures type safety and validation of all extracted data."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 26,
|
|
"id": "5ff0e177",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"✅ Structured output models defined\n",
|
|
" AirbnbListing: Individual listing data with clickable URLs\n",
|
|
" SearchResult: Complete search results with price analysis\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"# UPDATE THIS CELL - Add URL field to AirbnbListing\n",
|
|
"class AirbnbListing(BaseModel):\n",
|
|
" \"\"\"Single Airbnb listing with price information\"\"\"\n",
|
|
" title: str = Field(description=\"Name/title of the listing\")\n",
|
|
" price_per_night: float = Field(\n",
|
|
" description=\"Price per night as a number (extract just the numeric value, ignore currency symbols)\")\n",
|
|
" currency: str = Field(\n",
|
|
" default=\"SEK\", description=\"Currency code (SEK for Swedish Krona)\")\n",
|
|
" rating: Optional[float] = Field(\n",
|
|
" default=None, description=\"Rating score if visible\")\n",
|
|
" url: Optional[str] = Field(\n",
|
|
" default=None, description=\"Full URL link to the listing page\") # ✅ NEW!\n",
|
|
"\n",
|
|
"\n",
|
|
"class SearchResult(BaseModel):\n",
|
|
" \"\"\"Complete search results from Airbnb\"\"\"\n",
|
|
" location: str = Field(description=\"Search location (Stockholm, Sweden)\")\n",
|
|
" total_listings_found: int = Field(\n",
|
|
" description=\"Number of listings found on the page\")\n",
|
|
" listings: List[AirbnbListing] = Field(\n",
|
|
" description=\"List of all listings with prices extracted from the page\")\n",
|
|
" cheapest_listing: AirbnbListing = Field(\n",
|
|
" description=\"The listing with the lowest price per night\")\n",
|
|
" average_price: float = Field(\n",
|
|
" description=\"Average price per night across all listings\")\n",
|
|
" price_range: str = Field(description=\"Price range as 'min - max SEK'\")\n",
|
|
"\n",
|
|
"\n",
|
|
"print(\"✅ Structured output models defined\")\n",
|
|
"print(\" AirbnbListing: Individual listing data with clickable URLs\")\n",
|
|
"print(\" SearchResult: Complete search results with price analysis\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "c50e9241",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Helper Functions for Display\n",
|
|
"\n",
|
|
"These functions provide rich, educational output in the notebook with formatted HTML."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "61e58afe",
|
|
"metadata": {},
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 27,
|
|
"id": "86977630",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"✅ Structured output models defined\n",
|
|
" ListingInfo: Extract listing details\n",
|
|
" BookingDates: Capture selected dates\n",
|
|
" BookingResult: Final booking status\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"class ListingInfo(BaseModel):\n",
|
|
" \"\"\"Information about the Airbnb listing\"\"\"\n",
|
|
" title: str = Field(description=\"The name/title of the listing\")\n",
|
|
" location: str = Field(description=\"City and country of the listing\")\n",
|
|
" price_per_night: Optional[str] = Field(\n",
|
|
" description=\"Price per night if visible\")\n",
|
|
" rating: Optional[str] = Field(description=\"Rating score if visible\")\n",
|
|
"\n",
|
|
"\n",
|
|
"class BookingDates(BaseModel):\n",
|
|
" \"\"\"Selected booking dates\"\"\"\n",
|
|
" check_in: str = Field(\n",
|
|
" description=\"Check-in date in format: Month DD, YYYY\")\n",
|
|
" check_out: str = Field(\n",
|
|
" description=\"Check-out date in format: Month DD, YYYY\")\n",
|
|
" nights: int = Field(description=\"Number of nights\")\n",
|
|
"\n",
|
|
"\n",
|
|
"class BookingResult(BaseModel):\n",
|
|
" \"\"\"Complete booking result information\"\"\"\n",
|
|
" success: bool = Field(description=\"Whether the booking flow was completed\")\n",
|
|
" listing_info: Optional[ListingInfo] = Field(\n",
|
|
" description=\"Details about the listing\")\n",
|
|
" booking_dates: Optional[BookingDates] = Field(description=\"Selected dates\")\n",
|
|
" total_price: Optional[str] = Field(description=\"Total price if shown\")\n",
|
|
" message: str = Field(description=\"Status message or error description\")\n",
|
|
"\n",
|
|
"\n",
|
|
"print(\"✅ Structured output models defined\")\n",
|
|
"print(\" ListingInfo: Extract listing details\")\n",
|
|
"print(\" BookingDates: Capture selected dates\")\n",
|
|
"print(\" BookingResult: Final booking status\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "cd44b2e8",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Helper Functions for Display\n",
|
|
"\n",
|
|
"These functions provide rich, educational output in the notebook."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 28,
|
|
"id": "cc83f640",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"✅ Helper functions loaded\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"def display_step(step_number: int, title: str, description: str, color: str = \"#2E8B57\"):\n",
|
|
" \"\"\"Display a workflow step with formatting\"\"\"\n",
|
|
" html = f\"\"\"\n",
|
|
" <div style='\n",
|
|
" margin: 20px 0; \n",
|
|
" padding: 20px; \n",
|
|
" border-left: 5px solid {color}; \n",
|
|
" background: linear-gradient(to right, rgba(46, 139, 87, 0.05), transparent); \n",
|
|
" border-radius: 8px;\n",
|
|
" '>\n",
|
|
" <h3 style='color: {color}; margin: 0 0 10px 0;'>\n",
|
|
" Step {step_number}: {title}\n",
|
|
" </h3>\n",
|
|
" <p style='margin: 0; line-height: 1.6; color: #333;'>{description}</p>\n",
|
|
" </div>\n",
|
|
" \"\"\"\n",
|
|
" display(HTML(html))\n",
|
|
"\n",
|
|
"\n",
|
|
"def display_action(action_type: str, details: str, emoji: str = \"⚙️\"):\n",
|
|
" \"\"\"Display an action being performed\"\"\"\n",
|
|
" html = f\"\"\"\n",
|
|
" <div style='\n",
|
|
" margin: 10px 20px;\n",
|
|
" padding: 12px 16px;\n",
|
|
" background: rgba(0, 123, 255, 0.05);\n",
|
|
" border: 1px solid #007BFF;\n",
|
|
" border-radius: 6px;\n",
|
|
" font-family: monospace;\n",
|
|
" font-size: 14px;\n",
|
|
" '>\n",
|
|
" <strong style='color: #007BFF;'>{emoji} {action_type}:</strong>\n",
|
|
" <span style='color: #555; margin-left: 10px;'>{details}</span>\n",
|
|
" </div>\n",
|
|
" \"\"\"\n",
|
|
" display(HTML(html))\n",
|
|
"\n",
|
|
"\n",
|
|
"def display_result(success: bool, message: str):\n",
|
|
" \"\"\"Display a result with success/failure indication\"\"\"\n",
|
|
" color = \"#28a745\" if success else \"#dc3545\"\n",
|
|
" emoji = \"✅\" if success else \"❌\"\n",
|
|
" html = f\"\"\"\n",
|
|
" <div style='\n",
|
|
" margin: 20px 0;\n",
|
|
" padding: 15px 20px;\n",
|
|
" border-left: 5px solid {color};\n",
|
|
" background: rgba({\"40, 167, 69\" if success else \"220, 53, 69\"}, 0.1);\n",
|
|
" border-radius: 8px;\n",
|
|
" '>\n",
|
|
" <strong style='color: {color}; font-size: 16px;'>{emoji} {message}</strong>\n",
|
|
" </div>\n",
|
|
" \"\"\"\n",
|
|
" display(HTML(html))\n",
|
|
"\n",
|
|
"\n",
|
|
"def display_screenshot(screenshot_base64: str, caption: str = \"\"):\n",
|
|
" \"\"\"Display a screenshot in the notebook\"\"\"\n",
|
|
" html = f\"\"\"\n",
|
|
" <div style='margin: 20px 0; text-align: center;'>\n",
|
|
" <img src='data:image/png;base64,{screenshot_base64}' \n",
|
|
" style='max-width: 100%; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);'/>\n",
|
|
" {f\"<p style='margin-top: 10px; color: #666; font-style: italic;'>{caption}</p>\" if caption else \"\"}\n",
|
|
" </div>\n",
|
|
" \"\"\"\n",
|
|
" display(HTML(html))\n",
|
|
"\n",
|
|
"\n",
|
|
"print(\"✅ Helper functions loaded\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "2267dd74",
|
|
"metadata": {},
|
|
"source": [
|
|
"## The Airbnb Booking Agent Class\n",
|
|
"\n",
|
|
"This class orchestrates the entire booking workflow, combining Agent and Actor approaches strategically."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 29,
|
|
"id": "9eb624c3",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"✅ AirbnbSearchAgent class defined\n",
|
|
" Integration: Playwright browser + Browser-Use Agent\n",
|
|
" Capabilities: Vision-based price extraction with structured output\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"class AirbnbSearchAgent:\n",
|
|
" \"\"\"\n",
|
|
" Intelligent Airbnb search agent using Playwright + Browser-Use integration.\n",
|
|
" \n",
|
|
" This agent:\n",
|
|
" 1. Navigates to Airbnb.com\n",
|
|
" 2. Searches for listings in Stockholm\n",
|
|
" 3. Extracts all visible prices using AI vision\n",
|
|
" 4. Compares prices and finds the cheapest listing\n",
|
|
" \"\"\"\n",
|
|
" \n",
|
|
" def __init__(self, llm, playwright_browser):\n",
|
|
" self.llm = llm\n",
|
|
" # Browser-Use wraps the Playwright browser\n",
|
|
" # Reference: https://docs.browser-use.com/examples/templates/playwright-integration\n",
|
|
" self.browser = Browser(playwright_browser=playwright_browser)\n",
|
|
" \n",
|
|
" async def take_screenshot(self, caption: str = \"\"):\n",
|
|
" \"\"\"Take and display a screenshot of current page\"\"\"\n",
|
|
" try:\n",
|
|
" page = await self.browser.get_current_page()\n",
|
|
" screenshot_base64 = await page.screenshot(format='png')\n",
|
|
" display_screenshot(screenshot_base64, caption)\n",
|
|
" except Exception as e:\n",
|
|
" print(f\"⚠️ Could not capture screenshot: {str(e)}\")\n",
|
|
" \n",
|
|
" async def search_stockholm(self) -> SearchResult:\n",
|
|
" \"\"\"\n",
|
|
" Main workflow: Search Airbnb for Stockholm and find cheapest listing.\n",
|
|
" \n",
|
|
" Returns:\n",
|
|
" SearchResult: Structured data with all listings and price analysis\n",
|
|
" \"\"\"\n",
|
|
" \n",
|
|
" # Step 1: Navigate and search\n",
|
|
" display_step(\n",
|
|
" 1, \n",
|
|
" \"Navigate & Search (AI Agent)\", \n",
|
|
" \"Using AI agent with vision to navigate Airbnb and search for Stockholm listings. \"\n",
|
|
" \"The agent will handle pop-ups, cookie banners, and search automatically.\"\n",
|
|
" )\n",
|
|
" \n",
|
|
" try:\n",
|
|
" # Agent navigates to Airbnb and searches\n",
|
|
" search_agent = Agent(\n",
|
|
" task=(\n",
|
|
" \"Navigate to https://www.airbnb.com. \"\n",
|
|
" \"Close any pop-ups, cookie banners, or login prompts if they appear. \"\n",
|
|
" \"Search for 'Stockholm, Sweden' in the search box. \"\n",
|
|
" \"Wait for the search results page to fully load with listing cards visible.\"\n",
|
|
" ),\n",
|
|
" llm=self.llm,\n",
|
|
" browser=self.browser,\n",
|
|
" use_vision=True # Critical: enables screenshot analysis\n",
|
|
" )\n",
|
|
" \n",
|
|
" display_action(\"Agent\", \"Navigating to Airbnb and searching for Stockholm...\")\n",
|
|
" await search_agent.run()\n",
|
|
" \n",
|
|
" # Wait for results to load\n",
|
|
" await asyncio.sleep(3)\n",
|
|
" await self.take_screenshot(\"Search results page loaded\")\n",
|
|
" \n",
|
|
" display_result(True, \"Successfully loaded Stockholm search results\")\n",
|
|
" \n",
|
|
" except Exception as e:\n",
|
|
" display_result(False, f\"Search failed: {str(e)}\")\n",
|
|
" raise\n",
|
|
" \n",
|
|
" # Step 2: Extract prices with vision\n",
|
|
" display_step(\n",
|
|
" 2,\n",
|
|
" \"Extract Prices (Vision + LLM)\",\n",
|
|
" \"Using GPT-4 Vision to read all listing prices from the page and extract \"\n",
|
|
" \"structured data into Pydantic models. The AI 'sees' the page like a human.\"\n",
|
|
" )\n",
|
|
" \n",
|
|
" try:\n",
|
|
" page = await self.browser.get_current_page()\n",
|
|
" \n",
|
|
" display_action(\"Vision\", \"AI is analyzing the page and reading prices...\")\n",
|
|
" \n",
|
|
" # Use page.extract_content with structured output\n",
|
|
" # Reference: https://docs.browser-use.com/customize/actor/all-parameters\n",
|
|
" # This uses LLM to parse the visible page content\n",
|
|
" extraction_prompt = \"\"\"\n",
|
|
" Extract ALL Airbnb listings visible on this page.\n",
|
|
" \n",
|
|
" For each listing, extract:\n",
|
|
" - Title/name of the property\n",
|
|
" - Price per night (numeric value only, without currency symbols)\n",
|
|
" - Currency (SEK for Swedish Krona)\n",
|
|
" - Rating if visible\n",
|
|
" \n",
|
|
" After extracting all listings:\n",
|
|
" - Identify which listing has the LOWEST price\n",
|
|
" - Calculate the average price across all listings\n",
|
|
" - Determine the price range (min to max)\n",
|
|
" \n",
|
|
" Focus on the listing cards on the search results page.\n",
|
|
" Only include listings where you can clearly see the price.\n",
|
|
" \"\"\"\n",
|
|
" \n",
|
|
" # Extract structured data using LLM vision\n",
|
|
" search_results = await page.extract_content(\n",
|
|
" prompt=extraction_prompt,\n",
|
|
" structured_output=SearchResult,\n",
|
|
" llm=self.llm\n",
|
|
" )\n",
|
|
" \n",
|
|
" display_action(\n",
|
|
" \"Extracted\", \n",
|
|
" f\"Found {search_results.total_listings_found} listings with prices\"\n",
|
|
" )\n",
|
|
" \n",
|
|
" # Display some sample prices\n",
|
|
" if len(search_results.listings) > 0:\n",
|
|
" sample_prices = [f\"{l.price_per_night:.0f} SEK\" for l in search_results.listings[:5]]\n",
|
|
" display_action(\n",
|
|
" \"Sample Prices\", \n",
|
|
" f\"{', '.join(sample_prices)}{'...' if len(search_results.listings) > 5 else ''}\"\n",
|
|
" )\n",
|
|
" \n",
|
|
" display_result(True, \"Price extraction completed successfully\")\n",
|
|
" \n",
|
|
" return search_results\n",
|
|
" \n",
|
|
" except Exception as e:\n",
|
|
" display_result(False, f\"Price extraction failed: {str(e)}\")\n",
|
|
" raise\n",
|
|
"\n",
|
|
"print(\"✅ AirbnbSearchAgent class defined\")\n",
|
|
"print(\" Integration: Playwright browser + Browser-Use Agent\")\n",
|
|
"print(\" Capabilities: Vision-based price extraction with structured output\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "f097a3de",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Execute the Search\n",
|
|
"\n",
|
|
"Now let's run the complete workflow and find the cheapest Airbnb in Stockholm!\n",
|
|
"\n",
|
|
"This will:\n",
|
|
"1. Launch a real Chrome browser (visible)\n",
|
|
"2. Use AI to navigate and search\n",
|
|
"3. Extract all prices using vision\n",
|
|
"4. Display the cheapest option"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 34,
|
|
"id": "75fcdd98",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"✅ AirbnbSearchAgent class defined\n",
|
|
" Integration: CDP-based connection for Playwright + Browser-Use\n",
|
|
" Capabilities: Vision-based price extraction with structured output\n",
|
|
" Browser Mode: keep_alive=True (prevents auto-close)\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"# UPDATED AirbnbSearchAgent class - Add keep_alive parameter\n",
|
|
"class AirbnbSearchAgent:\n",
|
|
" \"\"\"\n",
|
|
" Intelligent Airbnb search agent using Browser-Use integration via CDP.\n",
|
|
" \n",
|
|
" This agent:\n",
|
|
" 1. Connects to Chrome via CDP (Chrome DevTools Protocol)\n",
|
|
" 2. Both Playwright and Browser-Use share the same browser instance\n",
|
|
" 3. Searches for listings in Stockholm\n",
|
|
" 4. Extracts prices using AI vision\n",
|
|
" 5. Finds the cheapest listing\n",
|
|
" \"\"\"\n",
|
|
"\n",
|
|
" def __init__(self, llm, cdp_url: str):\n",
|
|
" \"\"\"\n",
|
|
" Initialize agent with LLM and CDP connection.\n",
|
|
" \n",
|
|
" Args:\n",
|
|
" llm: Language model for AI decisions\n",
|
|
" cdp_url: Chrome DevTools Protocol URL (e.g., 'http://localhost:9222')\n",
|
|
" \"\"\"\n",
|
|
" self.llm = llm\n",
|
|
" # Browser-Use connects to Chrome via CDP\n",
|
|
" # IMPORTANT: keep_alive=True prevents browser from closing after Agent completes\n",
|
|
" self.browser = Browser(\n",
|
|
" cdp_url=cdp_url,\n",
|
|
" keep_alive=True # ✅ This keeps the browser open!\n",
|
|
" )\n",
|
|
"\n",
|
|
" async def take_screenshot(self, caption: str = \"\"):\n",
|
|
" \"\"\"Take and display a screenshot of current page\"\"\"\n",
|
|
" try:\n",
|
|
" # Get pages and use the active one\n",
|
|
" pages = await self.browser.get_pages()\n",
|
|
" if not pages:\n",
|
|
" print(\"⚠️ No pages available for screenshot\")\n",
|
|
" return\n",
|
|
"\n",
|
|
" page = pages[0] # Use first page (active page)\n",
|
|
" screenshot_bytes = await page.screenshot()\n",
|
|
" import base64\n",
|
|
" screenshot_base64 = base64.b64encode(screenshot_bytes).decode()\n",
|
|
" display_screenshot(screenshot_base64, caption)\n",
|
|
" except Exception as e:\n",
|
|
" print(f\"⚠️ Could not capture screenshot: {str(e)}\")\n",
|
|
"\n",
|
|
" async def search_stockholm(self) -> SearchResult:\n",
|
|
" \"\"\"\n",
|
|
" Main workflow: Search Airbnb for Stockholm and find cheapest listing.\n",
|
|
" \n",
|
|
" Returns:\n",
|
|
" SearchResult: Structured data with all listings and price analysis\n",
|
|
" \"\"\"\n",
|
|
"\n",
|
|
" # Step 1: Navigate and search\n",
|
|
" display_step(\n",
|
|
" 1,\n",
|
|
" \"Navigate & Search (AI Agent)\",\n",
|
|
" \"Using AI agent with vision to navigate Airbnb and search for Stockholm listings. \"\n",
|
|
" \"The agent will handle pop-ups, cookie banners, and search automatically.\"\n",
|
|
" )\n",
|
|
"\n",
|
|
" try:\n",
|
|
" # Agent navigates to Airbnb and searches\n",
|
|
" search_agent = Agent(\n",
|
|
" task=(\n",
|
|
" \"Navigate to https://www.airbnb.com. \"\n",
|
|
" \"Close any pop-ups, cookie banners, or login prompts if they appear. \"\n",
|
|
" \"Search for 'Stockholm, Sweden' in the search box. \"\n",
|
|
" \"Wait for the search results page to fully load with listing cards visible.\"\n",
|
|
" ),\n",
|
|
" llm=self.llm,\n",
|
|
" browser=self.browser,\n",
|
|
" use_vision=True # Critical: enables screenshot analysis\n",
|
|
" )\n",
|
|
"\n",
|
|
" display_action(\n",
|
|
" \"Agent\", \"Navigating to Airbnb and searching for Stockholm...\")\n",
|
|
" await search_agent.run()\n",
|
|
"\n",
|
|
" # Wait for results to load\n",
|
|
" await asyncio.sleep(3)\n",
|
|
" await self.take_screenshot(\"Search results page loaded\")\n",
|
|
"\n",
|
|
" display_result(\n",
|
|
" True, \"Successfully loaded Stockholm search results\")\n",
|
|
"\n",
|
|
" except Exception as e:\n",
|
|
" display_result(False, f\"Search failed: {str(e)}\")\n",
|
|
" raise\n",
|
|
"\n",
|
|
" # Step 2: Extract prices with vision\n",
|
|
" display_step(\n",
|
|
" 2,\n",
|
|
" \"Extract Prices (Vision + LLM)\",\n",
|
|
" \"Using GPT-4 Vision to read all listing prices from the page and extract \"\n",
|
|
" \"structured data into Pydantic models. The AI 'sees' the page like a human.\"\n",
|
|
" )\n",
|
|
"\n",
|
|
" try:\n",
|
|
" # Get the pages created by the Agent\n",
|
|
" pages = await self.browser.get_pages()\n",
|
|
"\n",
|
|
" if not pages:\n",
|
|
" raise RuntimeError(\n",
|
|
" \"No pages available after Agent run. Browser might have closed.\")\n",
|
|
"\n",
|
|
" # Use the first (active) page\n",
|
|
" page = pages[0]\n",
|
|
"\n",
|
|
" display_action(\n",
|
|
" \"Vision\", \"AI is analyzing the page and reading prices...\")\n",
|
|
"\n",
|
|
" # Extract structured data using LLM vision\n",
|
|
" extraction_prompt = \"\"\"\n",
|
|
"Extract ALL Airbnb Home listings visible on this page. DO NOT include \"Experiences\" or other non-home listings.\n",
|
|
"\n",
|
|
"For each listing, extract:\n",
|
|
"- Title/name of the property\n",
|
|
"- Price per night (numeric value only, without currency symbols)\n",
|
|
"- Currency (SEK for Swedish Krona)\n",
|
|
"- Rating if visible\n",
|
|
"- URL: The full link to the listing detail page (should start with https://www.airbnb.com/rooms/)\n",
|
|
"\n",
|
|
"After extracting all listings:\n",
|
|
"- Identify which listing has the LOWEST price\n",
|
|
"- Calculate the average price across all listings\n",
|
|
"- Determine the price range (min to max)\n",
|
|
"\n",
|
|
"Focus on the listing cards on the search results page.\n",
|
|
"Only include listings where you can clearly see the price.\n",
|
|
"IMPORTANT: Extract the actual URL/link for each listing so users can click on it.\n",
|
|
"\"\"\"\n",
|
|
"\n",
|
|
" search_results = await page.extract_content(\n",
|
|
" prompt=extraction_prompt,\n",
|
|
" structured_output=SearchResult,\n",
|
|
" llm=self.llm\n",
|
|
" )\n",
|
|
"\n",
|
|
" display_action(\n",
|
|
" \"Extracted\",\n",
|
|
" f\"Found {search_results.total_listings_found} listings with prices\"\n",
|
|
" )\n",
|
|
"\n",
|
|
" # Display some sample prices\n",
|
|
" if len(search_results.listings) > 0:\n",
|
|
" sample_prices = [\n",
|
|
" f\"{l.price_per_night:.0f} SEK\" for l in search_results.listings[:5]]\n",
|
|
" display_action(\n",
|
|
" \"Sample Prices\",\n",
|
|
" f\"{', '.join(sample_prices)}{'...' if len(search_results.listings) > 5 else ''}\"\n",
|
|
" )\n",
|
|
"\n",
|
|
" display_result(True, \"Price extraction completed successfully\")\n",
|
|
"\n",
|
|
" return search_results\n",
|
|
"\n",
|
|
" except Exception as e:\n",
|
|
" display_result(False, f\"Price extraction failed: {str(e)}\")\n",
|
|
" raise\n",
|
|
"\n",
|
|
"\n",
|
|
"print(\"✅ AirbnbSearchAgent class defined\")\n",
|
|
"print(\" Integration: CDP-based connection for Playwright + Browser-Use\")\n",
|
|
"print(\" Capabilities: Vision-based price extraction with structured output\")\n",
|
|
"print(\" Browser Mode: keep_alive=True (prevents auto-close)\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 35,
|
|
"id": "2ea963e7",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"\n",
|
|
" <div style='\n",
|
|
" padding: 30px;\n",
|
|
" background: linear-gradient(135deg, #FF5A5F 0%, #FF385C 100%);\n",
|
|
" color: white;\n",
|
|
" border-radius: 12px;\n",
|
|
" margin: 30px 0;\n",
|
|
" box-shadow: 0 10px 30px rgba(0,0,0,0.2);\n",
|
|
" '>\n",
|
|
" <h2 style='margin: 0 0 15px 0;'>🏠 Find the Cheapest Airbnb in Stockholm</h2>\n",
|
|
" <p style='margin: 0; font-size: 16px; line-height: 1.8;'>\n",
|
|
" This demo uses <strong>CDP (Chrome DevTools Protocol)</strong> integration:<br>\n",
|
|
" • <strong>Chrome</strong> runs with remote debugging enabled<br>\n",
|
|
" • <strong>Playwright</strong> connects to Chrome via CDP<br>\n",
|
|
" • <strong>Browser-Use</strong> connects to same Chrome via CDP<br>\n",
|
|
" • <strong>GPT-4 Vision</strong> reads and extracts prices<br>\n",
|
|
" • <strong>Structured Output</strong> returns type-safe data\n",
|
|
" </p>\n",
|
|
" <p style='margin: 15px 0 0 0; font-size: 14px; opacity: 0.9;'>\n",
|
|
" 📊 Watch the browser as the AI agent searches and analyzes prices!\n",
|
|
" </p>\n",
|
|
" </div>\n",
|
|
" "
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"\n",
|
|
" <div style='\n",
|
|
" margin: 10px 20px;\n",
|
|
" padding: 12px 16px;\n",
|
|
" background: rgba(0, 123, 255, 0.05);\n",
|
|
" border: 1px solid #007BFF;\n",
|
|
" border-radius: 6px;\n",
|
|
" font-family: monospace;\n",
|
|
" font-size: 14px;\n",
|
|
" '>\n",
|
|
" <strong style='color: #007BFF;'>⚙️ Chrome:</strong>\n",
|
|
" <span style='color: #555; margin-left: 10px;'>Starting Chrome with CDP (remote debugging)...</span>\n",
|
|
" </div>\n",
|
|
" "
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"✅ Chrome started with CDP on port 9222\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"\n",
|
|
" <div style='\n",
|
|
" margin: 10px 20px;\n",
|
|
" padding: 12px 16px;\n",
|
|
" background: rgba(0, 123, 255, 0.05);\n",
|
|
" border: 1px solid #007BFF;\n",
|
|
" border-radius: 6px;\n",
|
|
" font-family: monospace;\n",
|
|
" font-size: 14px;\n",
|
|
" '>\n",
|
|
" <strong style='color: #007BFF;'>⚙️ Playwright:</strong>\n",
|
|
" <span style='color: #555; margin-left: 10px;'>Connecting Playwright to Chrome via CDP...</span>\n",
|
|
" </div>\n",
|
|
" "
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"\n",
|
|
" <div style='\n",
|
|
" margin: 20px 0;\n",
|
|
" padding: 15px 20px;\n",
|
|
" border-left: 5px solid #28a745;\n",
|
|
" background: rgba(40, 167, 69, 0.1);\n",
|
|
" border-radius: 8px;\n",
|
|
" '>\n",
|
|
" <strong style='color: #28a745; font-size: 16px;'>✅ Playwright connected successfully</strong>\n",
|
|
" </div>\n",
|
|
" "
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"\n",
|
|
" <div style='\n",
|
|
" margin: 10px 20px;\n",
|
|
" padding: 12px 16px;\n",
|
|
" background: rgba(0, 123, 255, 0.05);\n",
|
|
" border: 1px solid #007BFF;\n",
|
|
" border-radius: 6px;\n",
|
|
" font-family: monospace;\n",
|
|
" font-size: 14px;\n",
|
|
" '>\n",
|
|
" <strong style='color: #007BFF;'>⚙️ Browser-Use:</strong>\n",
|
|
" <span style='color: #555; margin-left: 10px;'>Creating Browser-Use agent with CDP connection...</span>\n",
|
|
" </div>\n",
|
|
" "
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"\n",
|
|
" <div style='\n",
|
|
" margin: 20px 0;\n",
|
|
" padding: 15px 20px;\n",
|
|
" border-left: 5px solid #28a745;\n",
|
|
" background: rgba(40, 167, 69, 0.1);\n",
|
|
" border-radius: 8px;\n",
|
|
" '>\n",
|
|
" <strong style='color: #28a745; font-size: 16px;'>✅ Agent initialized with CDP integration</strong>\n",
|
|
" </div>\n",
|
|
" "
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"\n",
|
|
" <div style='\n",
|
|
" margin: 20px 0; \n",
|
|
" padding: 20px; \n",
|
|
" border-left: 5px solid #2E8B57; \n",
|
|
" background: linear-gradient(to right, rgba(46, 139, 87, 0.05), transparent); \n",
|
|
" border-radius: 8px;\n",
|
|
" '>\n",
|
|
" <h3 style='color: #2E8B57; margin: 0 0 10px 0;'>\n",
|
|
" Step 1: Navigate & Search (AI Agent)\n",
|
|
" </h3>\n",
|
|
" <p style='margin: 0; line-height: 1.6; color: #333;'>Using AI agent with vision to navigate Airbnb and search for Stockholm listings. The agent will handle pop-ups, cookie banners, and search automatically.</p>\n",
|
|
" </div>\n",
|
|
" "
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"INFO [Agent] 🔗 Found URL in task: https://www.airbnb.com, adding as initial action...\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"\n",
|
|
" <div style='\n",
|
|
" margin: 10px 20px;\n",
|
|
" padding: 12px 16px;\n",
|
|
" background: rgba(0, 123, 255, 0.05);\n",
|
|
" border: 1px solid #007BFF;\n",
|
|
" border-radius: 6px;\n",
|
|
" font-family: monospace;\n",
|
|
" font-size: 14px;\n",
|
|
" '>\n",
|
|
" <strong style='color: #007BFF;'>⚙️ Agent:</strong>\n",
|
|
" <span style='color: #555; margin-left: 10px;'>Navigating to Airbnb and searching for Stockholm...</span>\n",
|
|
" </div>\n",
|
|
" "
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"INFO [Agent] \u001b[34m🚀 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.\u001b[0m\n",
|
|
"INFO [service] ────────────────────────────────────────\n",
|
|
"INFO [service] 🔐 To view this run in Browser Use Cloud, authenticate with:\n",
|
|
"INFO [service] 👉 browser-use auth\n",
|
|
"INFO [service] or: python -m browser_use.cli auth\n",
|
|
"INFO [service] ────────────────────────────────────────\n",
|
|
"\n",
|
|
"INFO [Agent] 🦾 \u001b[34m[ACTION 1/1]\u001b[0m go_to_url: url: https://www.airbnb.com, new_tab: False\n",
|
|
"INFO [tools] 🔗 Navigated to https://www.airbnb.com\n",
|
|
"INFO [Agent] \n",
|
|
"\n",
|
|
"INFO [Agent] 📍 Step 1:\n",
|
|
"ERROR [Agent] ❌ Result failed 1/4 times:\n",
|
|
" LLM call timed out after 60 seconds. Keep your thinking and output short.\n",
|
|
"INFO [Agent] \n",
|
|
"\n",
|
|
"INFO [Agent] 📍 Step 2:\n",
|
|
"INFO [Agent] \u001b[32m👍 Eval: Successfully loaded Airbnb homepage and identified the search input box for destination.\u001b[0m\n",
|
|
"INFO [Agent] \u001b[34m🎯 Next goal: Input 'Stockholm, Sweden' into the destination search box and click the search button to load results.\u001b[0m\n",
|
|
"INFO [Agent] 🦾 \u001b[34m[ACTION 1/2]\u001b[0m input_text: index: 27, text: Stockholm, Sweden, clear_existing: True\n",
|
|
"WARNING [BrowserSession] ⚠️ Some framework events may have failed to trigger\n",
|
|
"INFO [BrowserSession] ⌨️ Typed \"Stockholm, Sweden\" into element with index 27\n",
|
|
"INFO [service] Page changed after action: actions click_element_by_index are not yet executed\n",
|
|
"INFO [Agent] \n",
|
|
"\n",
|
|
"INFO [Agent] 📍 Step 3:\n",
|
|
"INFO [Agent] \u001b[32m👍 Eval: Successfully input 'Stockholm, Sweden' into the destination search box but did not yet click the search button to load results.\u001b[0m\n",
|
|
"INFO [Agent] \u001b[34m🎯 Next goal: Click the search button to perform the search and wait for the listings page to load with visible listing cards.\u001b[0m\n",
|
|
"INFO [Agent] 🦾 \u001b[34m[ACTION 1/1]\u001b[0m click_element_by_index: index: 34, while_holding_ctrl: False\n",
|
|
"INFO [tools] 🖱️ Clicked element\n",
|
|
"INFO [Agent] \n",
|
|
"\n",
|
|
"INFO [Agent] 📍 Step 4:\n",
|
|
"INFO [Agent] \u001b[32m👍 Eval: Successfully clicked the search button and loaded the Airbnb search results page with visible listing cards for Stockholm.\u001b[0m\n",
|
|
"INFO [Agent] \u001b[34m🎯 Next goal: Confirm no pop-ups or cookie banners are present and finalize task as completed since listings are visible.\u001b[0m\n",
|
|
"INFO [Agent] 🦾 \u001b[34m[ACTION 1/1]\u001b[0m done: text: I have navigated to Airbnb.com, searched for Stockholm, Sweden, and confirmed that the search results page is fully loa\n",
|
|
"INFO [Agent] \n",
|
|
"📄 \u001b[32m Final Result:\u001b[0m \n",
|
|
"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.\n",
|
|
"\n",
|
|
"\n",
|
|
"INFO [Agent] ✅ Task completed successfully\n",
|
|
"⚠️ Could not capture screenshot: a bytes-like object is required, not 'str'\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"\n",
|
|
" <div style='\n",
|
|
" margin: 20px 0;\n",
|
|
" padding: 15px 20px;\n",
|
|
" border-left: 5px solid #28a745;\n",
|
|
" background: rgba(40, 167, 69, 0.1);\n",
|
|
" border-radius: 8px;\n",
|
|
" '>\n",
|
|
" <strong style='color: #28a745; font-size: 16px;'>✅ Successfully loaded Stockholm search results</strong>\n",
|
|
" </div>\n",
|
|
" "
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"\n",
|
|
" <div style='\n",
|
|
" margin: 20px 0; \n",
|
|
" padding: 20px; \n",
|
|
" border-left: 5px solid #2E8B57; \n",
|
|
" background: linear-gradient(to right, rgba(46, 139, 87, 0.05), transparent); \n",
|
|
" border-radius: 8px;\n",
|
|
" '>\n",
|
|
" <h3 style='color: #2E8B57; margin: 0 0 10px 0;'>\n",
|
|
" Step 2: Extract Prices (Vision + LLM)\n",
|
|
" </h3>\n",
|
|
" <p style='margin: 0; line-height: 1.6; color: #333;'>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.</p>\n",
|
|
" </div>\n",
|
|
" "
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"\n",
|
|
" <div style='\n",
|
|
" margin: 10px 20px;\n",
|
|
" padding: 12px 16px;\n",
|
|
" background: rgba(0, 123, 255, 0.05);\n",
|
|
" border: 1px solid #007BFF;\n",
|
|
" border-radius: 6px;\n",
|
|
" font-family: monospace;\n",
|
|
" font-size: 14px;\n",
|
|
" '>\n",
|
|
" <strong style='color: #007BFF;'>⚙️ Vision:</strong>\n",
|
|
" <span style='color: #555; margin-left: 10px;'>AI is analyzing the page and reading prices...</span>\n",
|
|
" </div>\n",
|
|
" "
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"\n",
|
|
" <div style='\n",
|
|
" margin: 10px 20px;\n",
|
|
" padding: 12px 16px;\n",
|
|
" background: rgba(0, 123, 255, 0.05);\n",
|
|
" border: 1px solid #007BFF;\n",
|
|
" border-radius: 6px;\n",
|
|
" font-family: monospace;\n",
|
|
" font-size: 14px;\n",
|
|
" '>\n",
|
|
" <strong style='color: #007BFF;'>⚙️ Extracted:</strong>\n",
|
|
" <span style='color: #555; margin-left: 10px;'>Found 18 listings with prices</span>\n",
|
|
" </div>\n",
|
|
" "
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"\n",
|
|
" <div style='\n",
|
|
" margin: 10px 20px;\n",
|
|
" padding: 12px 16px;\n",
|
|
" background: rgba(0, 123, 255, 0.05);\n",
|
|
" border: 1px solid #007BFF;\n",
|
|
" border-radius: 6px;\n",
|
|
" font-family: monospace;\n",
|
|
" font-size: 14px;\n",
|
|
" '>\n",
|
|
" <strong style='color: #007BFF;'>⚙️ Sample Prices:</strong>\n",
|
|
" <span style='color: #555; margin-left: 10px;'>1066 SEK, 294 SEK, 294 SEK, 1008 SEK, 368 SEK...</span>\n",
|
|
" </div>\n",
|
|
" "
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"\n",
|
|
" <div style='\n",
|
|
" margin: 20px 0;\n",
|
|
" padding: 15px 20px;\n",
|
|
" border-left: 5px solid #28a745;\n",
|
|
" background: rgba(40, 167, 69, 0.1);\n",
|
|
" border-radius: 8px;\n",
|
|
" '>\n",
|
|
" <strong style='color: #28a745; font-size: 16px;'>✅ Price extraction completed successfully</strong>\n",
|
|
" </div>\n",
|
|
" "
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"<hr style='margin: 40px 0; border: none; border-top: 2px solid #ddd;'>"
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"\n",
|
|
" <div style='padding: 20px; background: #f8f9fa; border-radius: 8px; margin: 20px 0;'>\n",
|
|
" <h3 style='margin: 0 0 15px 0; color: #333;'>📊 Search Results</h3>\n",
|
|
" </div>\n",
|
|
" "
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"\n",
|
|
" <div style='margin: 20px; padding: 20px; background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);'>\n",
|
|
" <h4 style='margin: 0 0 15px 0; color: #FF5A5F;'>📈 Price Analysis</h4>\n",
|
|
" <table style='width: 100%; border-collapse: collapse;'>\n",
|
|
" <tr>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; font-weight: bold;'>Location:</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>Stockholm, Sweden</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; font-weight: bold;'>Total Listings Found:</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>18</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; font-weight: bold;'>Average Price:</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>572.72 SEK/night</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; font-weight: bold;'>Price Range:</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>257 - 1066 SEK</td>\n",
|
|
" </tr>\n",
|
|
" </table>\n",
|
|
" </div>\n",
|
|
" "
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"\n",
|
|
" <div style='\n",
|
|
" margin: 20px; \n",
|
|
" padding: 25px; \n",
|
|
" background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);\n",
|
|
" border-radius: 12px; \n",
|
|
" box-shadow: 0 4px 12px rgba(255,165,0,0.3);\n",
|
|
" border: 3px solid #FFD700;\n",
|
|
" '>\n",
|
|
" <h3 style='margin: 0 0 15px 0; color: #333; font-size: 24px;'>\n",
|
|
" 🏆 CHEAPEST AIRBNB IN STOCKHOLM\n",
|
|
" </h3>\n",
|
|
" <div style='background: white; padding: 20px; border-radius: 8px; margin-top: 15px;'>\n",
|
|
" <h4 style='margin: 0 0 10px 0; color: #FF5A5F;'>Room in Nacka</h4>\n",
|
|
" <p style='margin: 10px 0; font-size: 28px; font-weight: bold; color: #28a745;'>\n",
|
|
" 257.00 SEK/night\n",
|
|
" </p>\n",
|
|
" <p style='margin: 10px 0; color: #666;'>⭐ Rating: 4.84/5.0</p>\n",
|
|
" <p style='margin: 15px 0 0 0; color: #666; font-size: 14px;'>\n",
|
|
" 💰 Saves you <strong>315.72 SEK</strong> compared to average price!\n",
|
|
" </p>\n",
|
|
" \n",
|
|
" <a href=\"https://www.airbnb.com/rooms/528377\" target=\"_blank\" style=\"\n",
|
|
" display: inline-block;\n",
|
|
" margin-top: 15px;\n",
|
|
" padding: 12px 24px;\n",
|
|
" background: #FF5A5F;\n",
|
|
" color: white;\n",
|
|
" text-decoration: none;\n",
|
|
" border-radius: 8px;\n",
|
|
" font-weight: bold;\n",
|
|
" transition: background 0.3s;\n",
|
|
" \" onmouseover=\"this.style.background='#E00007'\" onmouseout=\"this.style.background='#FF5A5F'\">\n",
|
|
" 🔗 View Listing on Airbnb\n",
|
|
" </a>\n",
|
|
" \n",
|
|
" </div>\n",
|
|
" </div>\n",
|
|
" "
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"\n",
|
|
" <div style='margin: 20px; padding: 20px; background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);'>\n",
|
|
" <h4 style='margin: 0 0 15px 0; color: #FF5A5F;'>📋 All Listings Found (Click to View)</h4>\n",
|
|
" <table style='width: 100%; border-collapse: collapse;'>\n",
|
|
" <thead>\n",
|
|
" <tr style='background: #f8f9fa;'>\n",
|
|
" <th style='padding: 12px; text-align: left; border-bottom: 2px solid #dee2e6;'>Rank</th>\n",
|
|
" <th style='padding: 12px; text-align: left; border-bottom: 2px solid #dee2e6;'>Listing</th>\n",
|
|
" <th style='padding: 12px; text-align: right; border-bottom: 2px solid #dee2e6;'>Price/Night</th>\n",
|
|
" <th style='padding: 12px; text-align: center; border-bottom: 2px solid #dee2e6;'>Rating</th>\n",
|
|
" <th style='padding: 12px; text-align: center; border-bottom: 2px solid #dee2e6;'>Link</th>\n",
|
|
" </tr>\n",
|
|
" </thead>\n",
|
|
" <tbody>\n",
|
|
" \n",
|
|
" <tr style='background: #fff3cd;'>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>🏆 1</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'><a href=\"https://www.airbnb.com/rooms/528377\" target=\"_blank\" style=\"color: #333; text-decoration: none; font-weight: 500;\" onmouseover=\"this.style.color='#FF5A5F'\" onmouseout=\"this.style.color='#333'\">Room in Nacka...</a></td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: right; font-weight: bold;'>\n",
|
|
" 257.00 SEK\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" ⭐ 4.84\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" <a href=\"https://www.airbnb.com/rooms/528377\" 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>\n",
|
|
" </td>\n",
|
|
" </tr>\n",
|
|
" \n",
|
|
" <tr style=''>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>2</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'><a href=\"https://www.airbnb.com/rooms/1325170162426570770\" target=\"_blank\" style=\"color: #333; text-decoration: none; font-weight: 500;\" onmouseover=\"this.style.color='#FF5A5F'\" onmouseout=\"this.style.color='#333'\">Shared hotel room in Stockholms kommun...</a></td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: right; font-weight: bold;'>\n",
|
|
" 258.00 SEK\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" ⭐ 4.42\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" <a href=\"https://www.airbnb.com/rooms/1325170162426570770\" 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>\n",
|
|
" </td>\n",
|
|
" </tr>\n",
|
|
" \n",
|
|
" <tr style=''>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>3</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'><a href=\"https://www.airbnb.com/rooms/1076079717724966711\" target=\"_blank\" style=\"color: #333; text-decoration: none; font-weight: 500;\" onmouseover=\"this.style.color='#FF5A5F'\" onmouseout=\"this.style.color='#333'\">Shared room in Stockholms kommun...</a></td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: right; font-weight: bold;'>\n",
|
|
" 274.00 SEK\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" ⭐ 4.68\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" <a href=\"https://www.airbnb.com/rooms/1076079717724966711\" 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>\n",
|
|
" </td>\n",
|
|
" </tr>\n",
|
|
" \n",
|
|
" <tr style=''>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>4</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'><a href=\"https://www.airbnb.com/rooms/1325174269976835966\" target=\"_blank\" style=\"color: #333; text-decoration: none; font-weight: 500;\" onmouseover=\"this.style.color='#FF5A5F'\" onmouseout=\"this.style.color='#333'\">Shared hotel room in Stockholms kommun...</a></td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: right; font-weight: bold;'>\n",
|
|
" 282.00 SEK\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" ⭐ 4.6\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" <a href=\"https://www.airbnb.com/rooms/1325174269976835966\" 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>\n",
|
|
" </td>\n",
|
|
" </tr>\n",
|
|
" \n",
|
|
" <tr style=''>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>5</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'><a href=\"https://www.airbnb.com/rooms/1273965753087063640\" target=\"_blank\" style=\"color: #333; text-decoration: none; font-weight: 500;\" onmouseover=\"this.style.color='#FF5A5F'\" onmouseout=\"this.style.color='#333'\">Shared room in Stockholms kommun...</a></td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: right; font-weight: bold;'>\n",
|
|
" 294.00 SEK\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" ⭐ 4.76\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" <a href=\"https://www.airbnb.com/rooms/1273965753087063640\" 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>\n",
|
|
" </td>\n",
|
|
" </tr>\n",
|
|
" \n",
|
|
" <tr style=''>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>6</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'><a href=\"https://www.airbnb.com/rooms/1433962995093313189\" target=\"_blank\" style=\"color: #333; text-decoration: none; font-weight: 500;\" onmouseover=\"this.style.color='#FF5A5F'\" onmouseout=\"this.style.color='#333'\">Shared hotel room in Stockholms kommun...</a></td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: right; font-weight: bold;'>\n",
|
|
" 294.00 SEK\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" ⭐ 4.62\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" <a href=\"https://www.airbnb.com/rooms/1433962995093313189\" 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>\n",
|
|
" </td>\n",
|
|
" </tr>\n",
|
|
" \n",
|
|
" <tr style=''>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>7</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'><a href=\"https://www.airbnb.com/rooms/664399747581611456\" target=\"_blank\" style=\"color: #333; text-decoration: none; font-weight: 500;\" onmouseover=\"this.style.color='#FF5A5F'\" onmouseout=\"this.style.color='#333'\">Room in Stockholms kommun...</a></td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: right; font-weight: bold;'>\n",
|
|
" 317.00 SEK\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" ⭐ 4.93\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" <a href=\"https://www.airbnb.com/rooms/664399747581611456\" 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>\n",
|
|
" </td>\n",
|
|
" </tr>\n",
|
|
" \n",
|
|
" <tr style=''>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>8</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'><a href=\"https://www.airbnb.com/rooms/1497178259494952182\" target=\"_blank\" style=\"color: #333; text-decoration: none; font-weight: 500;\" onmouseover=\"this.style.color='#FF5A5F'\" onmouseout=\"this.style.color='#333'\">Shared hotel room in Stockholms kommun...</a></td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: right; font-weight: bold;'>\n",
|
|
" 352.00 SEK\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" N/A\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" <a href=\"https://www.airbnb.com/rooms/1497178259494952182\" 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>\n",
|
|
" </td>\n",
|
|
" </tr>\n",
|
|
" \n",
|
|
" <tr style=''>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>9</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'><a href=\"https://www.airbnb.com/rooms/1458105060876881238\" target=\"_blank\" style=\"color: #333; text-decoration: none; font-weight: 500;\" onmouseover=\"this.style.color='#FF5A5F'\" onmouseout=\"this.style.color='#333'\">Room in Stockholms kommun...</a></td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: right; font-weight: bold;'>\n",
|
|
" 353.00 SEK\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" ⭐ 5.0\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" <a href=\"https://www.airbnb.com/rooms/1458105060876881238\" 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>\n",
|
|
" </td>\n",
|
|
" </tr>\n",
|
|
" \n",
|
|
" <tr style=''>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>10</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'><a href=\"https://www.airbnb.com/rooms/1295414809162007319\" target=\"_blank\" style=\"color: #333; text-decoration: none; font-weight: 500;\" onmouseover=\"this.style.color='#FF5A5F'\" onmouseout=\"this.style.color='#333'\">Rooms in Enskede - Årsta - Vantör...</a></td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: right; font-weight: bold;'>\n",
|
|
" 365.00 SEK\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" ⭐ 4.76\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" <a href=\"https://www.airbnb.com/rooms/1295414809162007319\" 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>\n",
|
|
" </td>\n",
|
|
" </tr>\n",
|
|
" \n",
|
|
" <tr style=''>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>11</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'><a href=\"https://www.airbnb.com/rooms/36783661\" target=\"_blank\" style=\"color: #333; text-decoration: none; font-weight: 500;\" onmouseover=\"this.style.color='#FF5A5F'\" onmouseout=\"this.style.color='#333'\">Hostel in Norrmalm...</a></td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: right; font-weight: bold;'>\n",
|
|
" 368.00 SEK\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" ⭐ 4.46\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" <a href=\"https://www.airbnb.com/rooms/36783661\" 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>\n",
|
|
" </td>\n",
|
|
" </tr>\n",
|
|
" \n",
|
|
" <tr style=''>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>12</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'><a href=\"https://www.airbnb.com/rooms/939866565076342944\" target=\"_blank\" style=\"color: #333; text-decoration: none; font-weight: 500;\" onmouseover=\"this.style.color='#FF5A5F'\" onmouseout=\"this.style.color='#333'\">Room in Helenelund...</a></td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: right; font-weight: bold;'>\n",
|
|
" 383.00 SEK\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" ⭐ 4.87\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" <a href=\"https://www.airbnb.com/rooms/939866565076342944\" 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>\n",
|
|
" </td>\n",
|
|
" </tr>\n",
|
|
" \n",
|
|
" <tr style=''>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>13</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'><a href=\"https://www.airbnb.com/rooms/48073646\" target=\"_blank\" style=\"color: #333; text-decoration: none; font-weight: 500;\" onmouseover=\"this.style.color='#FF5A5F'\" onmouseout=\"this.style.color='#333'\">Apartment in Sjöberg...</a></td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: right; font-weight: bold;'>\n",
|
|
" 851.00 SEK\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" ⭐ 4.29\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" <a href=\"https://www.airbnb.com/rooms/48073646\" 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>\n",
|
|
" </td>\n",
|
|
" </tr>\n",
|
|
" \n",
|
|
" <tr style=''>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>14</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'><a href=\"https://www.airbnb.com/rooms/1025922981385700870\" target=\"_blank\" style=\"color: #333; text-decoration: none; font-weight: 500;\" onmouseover=\"this.style.color='#FF5A5F'\" onmouseout=\"this.style.color='#333'\">Apartment in Solna kommun...</a></td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: right; font-weight: bold;'>\n",
|
|
" 929.00 SEK\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" ⭐ 4.89\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" <a href=\"https://www.airbnb.com/rooms/1025922981385700870\" 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>\n",
|
|
" </td>\n",
|
|
" </tr>\n",
|
|
" \n",
|
|
" <tr style=''>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>15</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'><a href=\"https://www.airbnb.com/rooms/1515443925860201423\" target=\"_blank\" style=\"color: #333; text-decoration: none; font-weight: 500;\" onmouseover=\"this.style.color='#FF5A5F'\" onmouseout=\"this.style.color='#333'\">Apartment in Stockholms kommun...</a></td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: right; font-weight: bold;'>\n",
|
|
" 939.00 SEK\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" N/A\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" <a href=\"https://www.airbnb.com/rooms/1515443925860201423\" 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>\n",
|
|
" </td>\n",
|
|
" </tr>\n",
|
|
" \n",
|
|
" <tr style=''>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>16</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'><a href=\"https://www.airbnb.com/rooms/1460063798046056638\" target=\"_blank\" style=\"color: #333; text-decoration: none; font-weight: 500;\" onmouseover=\"this.style.color='#FF5A5F'\" onmouseout=\"this.style.color='#333'\">Apartment in Stockholms kommun...</a></td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: right; font-weight: bold;'>\n",
|
|
" 966.00 SEK\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" ⭐ 4.37\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" <a href=\"https://www.airbnb.com/rooms/1460063798046056638\" 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>\n",
|
|
" </td>\n",
|
|
" </tr>\n",
|
|
" \n",
|
|
" <tr style=''>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>17</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'><a href=\"https://www.airbnb.com/rooms/1485494362111814492\" target=\"_blank\" style=\"color: #333; text-decoration: none; font-weight: 500;\" onmouseover=\"this.style.color='#FF5A5F'\" onmouseout=\"this.style.color='#333'\">Apartment in Stockholms kommun...</a></td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: right; font-weight: bold;'>\n",
|
|
" 1008.00 SEK\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" ⭐ 4.79\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" <a href=\"https://www.airbnb.com/rooms/1485494362111814492\" 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>\n",
|
|
" </td>\n",
|
|
" </tr>\n",
|
|
" \n",
|
|
" <tr style=''>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>18</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'><a href=\"https://www.airbnb.com/rooms/1429518738216103790\" target=\"_blank\" style=\"color: #333; text-decoration: none; font-weight: 500;\" onmouseover=\"this.style.color='#FF5A5F'\" onmouseout=\"this.style.color='#333'\">Apartment in Stockholms kommun...</a></td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: right; font-weight: bold;'>\n",
|
|
" 1066.00 SEK\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" ⭐ 4.77\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" <a href=\"https://www.airbnb.com/rooms/1429518738216103790\" 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>\n",
|
|
" </td>\n",
|
|
" </tr>\n",
|
|
" \n",
|
|
" </tbody>\n",
|
|
" </table>\n",
|
|
" <p style='margin-top: 15px; color: #666; font-size: 13px; font-style: italic;'>\n",
|
|
" 💡 Tip: Click on any listing title or the \"View\" button to open it in a new tab\n",
|
|
" </p>\n",
|
|
" </div>\n",
|
|
" "
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"\n",
|
|
" <div style='\n",
|
|
" margin: 20px 0;\n",
|
|
" padding: 15px 20px;\n",
|
|
" border-left: 5px solid #28a745;\n",
|
|
" background: rgba(40, 167, 69, 0.1);\n",
|
|
" border-radius: 8px;\n",
|
|
" '>\n",
|
|
" <strong style='color: #28a745; font-size: 16px;'>✅ Price comparison completed successfully!</strong>\n",
|
|
" </div>\n",
|
|
" "
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"\n",
|
|
" <div style='\n",
|
|
" margin: 10px 20px;\n",
|
|
" padding: 12px 16px;\n",
|
|
" background: rgba(0, 123, 255, 0.05);\n",
|
|
" border: 1px solid #007BFF;\n",
|
|
" border-radius: 6px;\n",
|
|
" font-family: monospace;\n",
|
|
" font-size: 14px;\n",
|
|
" '>\n",
|
|
" <strong style='color: #007BFF;'>⚙️ Cleanup:</strong>\n",
|
|
" <span style='color: #555; margin-left: 10px;'>Closing browser and cleaning up...</span>\n",
|
|
" </div>\n",
|
|
" "
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"WARNING [cdp_use.client] WebSocket connection closed: no close frame received or sent\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"\n",
|
|
" <div style='\n",
|
|
" margin: 20px 0;\n",
|
|
" padding: 15px 20px;\n",
|
|
" border-left: 5px solid #28a745;\n",
|
|
" background: rgba(40, 167, 69, 0.1);\n",
|
|
" border-radius: 8px;\n",
|
|
" '>\n",
|
|
" <strong style='color: #28a745; font-size: 16px;'>✅ Cleanup complete</strong>\n",
|
|
" </div>\n",
|
|
" "
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"\n",
|
|
" <div style='\n",
|
|
" margin: 40px 0 20px 0;\n",
|
|
" padding: 25px;\n",
|
|
" background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n",
|
|
" color: white;\n",
|
|
" border-radius: 12px;\n",
|
|
" '>\n",
|
|
" <h3 style='margin: 0 0 15px 0;'>🎓 What You Just Learned</h3>\n",
|
|
" <ul style='margin: 0; padding-left: 20px; line-height: 2;'>\n",
|
|
" <li><strong>CDP Integration:</strong> Chrome DevTools Protocol connects Playwright + Browser-Use</li>\n",
|
|
" <li><strong>Vision-Based Extraction:</strong> GPT-4 Vision reads prices like a human</li>\n",
|
|
" <li><strong>Structured Output:</strong> Type-safe data extraction with Pydantic models</li>\n",
|
|
" <li><strong>Price Comparison:</strong> Automated analysis to find best deals</li>\n",
|
|
" <li><strong>Clickable URLs:</strong> Direct links to Airbnb listings for easy viewing</li>\n",
|
|
" <li><strong>Real-World Application:</strong> Practical web scraping for price monitoring</li>\n",
|
|
" </ul>\n",
|
|
" </div>\n",
|
|
" "
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
}
|
|
],
|
|
"source": [
|
|
"import subprocess\n",
|
|
"import tempfile\n",
|
|
"\n",
|
|
"\n",
|
|
"async def start_chrome_with_cdp(port: int = 9222):\n",
|
|
" \"\"\"\n",
|
|
" Start Chrome with CDP (Chrome DevTools Protocol) enabled.\n",
|
|
" Returns the Chrome process.\n",
|
|
" \"\"\"\n",
|
|
" # Create temporary directory for Chrome user data\n",
|
|
" user_data_dir = tempfile.mkdtemp(prefix='chrome_cdp_')\n",
|
|
"\n",
|
|
" # Chrome paths for different platforms\n",
|
|
" chrome_paths = [\n",
|
|
" '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', # macOS\n",
|
|
" '/usr/bin/google-chrome', # Linux\n",
|
|
" '/usr/bin/chromium-browser', # Linux Chromium\n",
|
|
" 'chrome', # Windows/PATH\n",
|
|
" 'chromium', # Generic\n",
|
|
" ]\n",
|
|
"\n",
|
|
" chrome_exe = None\n",
|
|
" for path in chrome_paths:\n",
|
|
" if os.path.exists(path) or path in ['chrome', 'chromium']:\n",
|
|
" try:\n",
|
|
" test_proc = await asyncio.create_subprocess_exec(\n",
|
|
" path, '--version',\n",
|
|
" stdout=subprocess.DEVNULL,\n",
|
|
" stderr=subprocess.DEVNULL\n",
|
|
" )\n",
|
|
" await test_proc.wait()\n",
|
|
" chrome_exe = path\n",
|
|
" break\n",
|
|
" except Exception:\n",
|
|
" continue\n",
|
|
"\n",
|
|
" if not chrome_exe:\n",
|
|
" raise RuntimeError(\n",
|
|
" '❌ Chrome not found. Please install Chrome or Chromium.')\n",
|
|
"\n",
|
|
" # Chrome command arguments\n",
|
|
" cmd = [\n",
|
|
" chrome_exe,\n",
|
|
" f'--remote-debugging-port={port}',\n",
|
|
" f'--user-data-dir={user_data_dir}',\n",
|
|
" '--no-first-run',\n",
|
|
" '--no-default-browser-check',\n",
|
|
" 'about:blank',\n",
|
|
" ]\n",
|
|
"\n",
|
|
" # Start Chrome process\n",
|
|
" process = await asyncio.create_subprocess_exec(\n",
|
|
" *cmd,\n",
|
|
" stdout=subprocess.DEVNULL,\n",
|
|
" stderr=subprocess.DEVNULL\n",
|
|
" )\n",
|
|
"\n",
|
|
" # Wait for Chrome to start and CDP to be ready\n",
|
|
" import aiohttp\n",
|
|
" cdp_ready = False\n",
|
|
" for _ in range(20): # 20 second timeout\n",
|
|
" try:\n",
|
|
" async with aiohttp.ClientSession() as session:\n",
|
|
" async with session.get(\n",
|
|
" f'http://localhost:{port}/json/version',\n",
|
|
" timeout=aiohttp.ClientTimeout(total=1)\n",
|
|
" ) as response:\n",
|
|
" if response.status == 200:\n",
|
|
" cdp_ready = True\n",
|
|
" break\n",
|
|
" except Exception:\n",
|
|
" pass\n",
|
|
" await asyncio.sleep(1)\n",
|
|
"\n",
|
|
" if not cdp_ready:\n",
|
|
" process.terminate()\n",
|
|
" raise RuntimeError('❌ Chrome failed to start with CDP')\n",
|
|
"\n",
|
|
" print(f\"✅ Chrome started with CDP on port {port}\")\n",
|
|
" return process\n",
|
|
"\n",
|
|
"\n",
|
|
"async def main():\n",
|
|
" \"\"\"\n",
|
|
" Main execution function for Airbnb price comparison.\n",
|
|
" \n",
|
|
" Uses CDP to connect both Playwright and Browser-Use to the same Chrome instance.\n",
|
|
" \"\"\"\n",
|
|
"\n",
|
|
" display(HTML(\"\"\"\n",
|
|
" <div style='\n",
|
|
" padding: 30px;\n",
|
|
" background: linear-gradient(135deg, #FF5A5F 0%, #FF385C 100%);\n",
|
|
" color: white;\n",
|
|
" border-radius: 12px;\n",
|
|
" margin: 30px 0;\n",
|
|
" box-shadow: 0 10px 30px rgba(0,0,0,0.2);\n",
|
|
" '>\n",
|
|
" <h2 style='margin: 0 0 15px 0;'>🏠 Find the Cheapest Airbnb in Stockholm</h2>\n",
|
|
" <p style='margin: 0; font-size: 16px; line-height: 1.8;'>\n",
|
|
" This demo uses <strong>CDP (Chrome DevTools Protocol)</strong> integration:<br>\n",
|
|
" • <strong>Chrome</strong> runs with remote debugging enabled<br>\n",
|
|
" • <strong>Playwright</strong> connects to Chrome via CDP<br>\n",
|
|
" • <strong>Browser-Use</strong> connects to same Chrome via CDP<br>\n",
|
|
" • <strong>GPT-4 Vision</strong> reads and extracts prices<br>\n",
|
|
" • <strong>Structured Output</strong> returns type-safe data\n",
|
|
" </p>\n",
|
|
" <p style='margin: 15px 0 0 0; font-size: 14px; opacity: 0.9;'>\n",
|
|
" 📊 Watch the browser as the AI agent searches and analyzes prices!\n",
|
|
" </p>\n",
|
|
" </div>\n",
|
|
" \"\"\"))\n",
|
|
"\n",
|
|
" chrome_process = None\n",
|
|
" playwright_browser = None\n",
|
|
"\n",
|
|
" try:\n",
|
|
" # Step 1: Start Chrome with CDP\n",
|
|
" display_action(\n",
|
|
" \"Chrome\", \"Starting Chrome with CDP (remote debugging)...\")\n",
|
|
" chrome_process = await start_chrome_with_cdp(port=9222)\n",
|
|
" cdp_url = 'http://localhost:9222'\n",
|
|
"\n",
|
|
" # Step 2: Connect Playwright to CDP (optional - for custom Playwright actions)\n",
|
|
" display_action(\n",
|
|
" \"Playwright\", \"Connecting Playwright to Chrome via CDP...\")\n",
|
|
" playwright = await async_playwright().start()\n",
|
|
" playwright_browser = await playwright.chromium.connect_over_cdp(cdp_url)\n",
|
|
" display_result(True, \"Playwright connected successfully\")\n",
|
|
"\n",
|
|
" # Step 3: Create Browser-Use agent with CDP connection\n",
|
|
" display_action(\n",
|
|
" \"Browser-Use\", \"Creating Browser-Use agent with CDP connection...\")\n",
|
|
" agent = AirbnbSearchAgent(llm=llm, cdp_url=cdp_url)\n",
|
|
" display_result(True, \"Agent initialized with CDP integration\")\n",
|
|
"\n",
|
|
" # Step 4: Search and extract prices\n",
|
|
" result = await agent.search_stockholm()\n",
|
|
"\n",
|
|
" # Step 5: Display results\n",
|
|
" display(\n",
|
|
" HTML(\"<hr style='margin: 40px 0; border: none; border-top: 2px solid #ddd;'>\"))\n",
|
|
"\n",
|
|
" display(HTML(\"\"\"\n",
|
|
" <div style='padding: 20px; background: #f8f9fa; border-radius: 8px; margin: 20px 0;'>\n",
|
|
" <h3 style='margin: 0 0 15px 0; color: #333;'>📊 Search Results</h3>\n",
|
|
" </div>\n",
|
|
" \"\"\"))\n",
|
|
"\n",
|
|
" # Display summary stats\n",
|
|
" display(HTML(f\"\"\"\n",
|
|
" <div style='margin: 20px; padding: 20px; background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);'>\n",
|
|
" <h4 style='margin: 0 0 15px 0; color: #FF5A5F;'>📈 Price Analysis</h4>\n",
|
|
" <table style='width: 100%; border-collapse: collapse;'>\n",
|
|
" <tr>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; font-weight: bold;'>Location:</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>{result.location}</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; font-weight: bold;'>Total Listings Found:</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>{result.total_listings_found}</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; font-weight: bold;'>Average Price:</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>{result.average_price:.2f} SEK/night</td>\n",
|
|
" </tr>\n",
|
|
" <tr>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; font-weight: bold;'>Price Range:</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>{result.price_range}</td>\n",
|
|
" </tr>\n",
|
|
" </table>\n",
|
|
" </div>\n",
|
|
" \"\"\"))\n",
|
|
"\n",
|
|
" # Display the CHEAPEST listing with clickable link\n",
|
|
" cheapest = result.cheapest_listing\n",
|
|
"\n",
|
|
" # Create View Listing button if URL exists\n",
|
|
" view_button = \"\"\n",
|
|
" if cheapest.url:\n",
|
|
" view_button = f\"\"\"\n",
|
|
" <a href=\"{cheapest.url}\" target=\"_blank\" style=\"\n",
|
|
" display: inline-block;\n",
|
|
" margin-top: 15px;\n",
|
|
" padding: 12px 24px;\n",
|
|
" background: #FF5A5F;\n",
|
|
" color: white;\n",
|
|
" text-decoration: none;\n",
|
|
" border-radius: 8px;\n",
|
|
" font-weight: bold;\n",
|
|
" transition: background 0.3s;\n",
|
|
" \" onmouseover=\"this.style.background='#E00007'\" onmouseout=\"this.style.background='#FF5A5F'\">\n",
|
|
" 🔗 View Listing on Airbnb\n",
|
|
" </a>\n",
|
|
" \"\"\"\n",
|
|
"\n",
|
|
" display(HTML(f\"\"\"\n",
|
|
" <div style='\n",
|
|
" margin: 20px; \n",
|
|
" padding: 25px; \n",
|
|
" background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);\n",
|
|
" border-radius: 12px; \n",
|
|
" box-shadow: 0 4px 12px rgba(255,165,0,0.3);\n",
|
|
" border: 3px solid #FFD700;\n",
|
|
" '>\n",
|
|
" <h3 style='margin: 0 0 15px 0; color: #333; font-size: 24px;'>\n",
|
|
" 🏆 CHEAPEST AIRBNB IN STOCKHOLM\n",
|
|
" </h3>\n",
|
|
" <div style='background: white; padding: 20px; border-radius: 8px; margin-top: 15px;'>\n",
|
|
" <h4 style='margin: 0 0 10px 0; color: #FF5A5F;'>{cheapest.title}</h4>\n",
|
|
" <p style='margin: 10px 0; font-size: 28px; font-weight: bold; color: #28a745;'>\n",
|
|
" {cheapest.price_per_night:.2f} {cheapest.currency}/night\n",
|
|
" </p>\n",
|
|
" {f\"<p style='margin: 10px 0; color: #666;'>⭐ Rating: {cheapest.rating}/5.0</p>\" if cheapest.rating else \"\"}\n",
|
|
" <p style='margin: 15px 0 0 0; color: #666; font-size: 14px;'>\n",
|
|
" 💰 Saves you <strong>{(result.average_price - cheapest.price_per_night):.2f} SEK</strong> compared to average price!\n",
|
|
" </p>\n",
|
|
" {view_button}\n",
|
|
" </div>\n",
|
|
" </div>\n",
|
|
" \"\"\"))\n",
|
|
"\n",
|
|
" # Display all listings in a clickable table\n",
|
|
" if len(result.listings) > 1:\n",
|
|
" listings_html = \"\"\"\n",
|
|
" <div style='margin: 20px; padding: 20px; background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);'>\n",
|
|
" <h4 style='margin: 0 0 15px 0; color: #FF5A5F;'>📋 All Listings Found (Click to View)</h4>\n",
|
|
" <table style='width: 100%; border-collapse: collapse;'>\n",
|
|
" <thead>\n",
|
|
" <tr style='background: #f8f9fa;'>\n",
|
|
" <th style='padding: 12px; text-align: left; border-bottom: 2px solid #dee2e6;'>Rank</th>\n",
|
|
" <th style='padding: 12px; text-align: left; border-bottom: 2px solid #dee2e6;'>Listing</th>\n",
|
|
" <th style='padding: 12px; text-align: right; border-bottom: 2px solid #dee2e6;'>Price/Night</th>\n",
|
|
" <th style='padding: 12px; text-align: center; border-bottom: 2px solid #dee2e6;'>Rating</th>\n",
|
|
" <th style='padding: 12px; text-align: center; border-bottom: 2px solid #dee2e6;'>Link</th>\n",
|
|
" </tr>\n",
|
|
" </thead>\n",
|
|
" <tbody>\n",
|
|
" \"\"\"\n",
|
|
"\n",
|
|
" sorted_listings = sorted(\n",
|
|
" result.listings, key=lambda x: x.price_per_night)\n",
|
|
"\n",
|
|
" for idx, listing in enumerate(sorted_listings, 1):\n",
|
|
" is_cheapest = listing.price_per_night == cheapest.price_per_night\n",
|
|
" row_style = \"background: #fff3cd;\" if is_cheapest else \"\"\n",
|
|
" badge = \"🏆 \" if is_cheapest else \"\"\n",
|
|
"\n",
|
|
" # Create clickable link if URL exists\n",
|
|
" if listing.url:\n",
|
|
" 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>'\n",
|
|
" 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>'\n",
|
|
" else:\n",
|
|
" link_html = '<span style=\"color: #999;\">N/A</span>'\n",
|
|
" title_html = f'{listing.title[:50]}...'\n",
|
|
"\n",
|
|
" listings_html += f\"\"\"\n",
|
|
" <tr style='{row_style}'>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>{badge}{idx}</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee;'>{title_html}</td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: right; font-weight: bold;'>\n",
|
|
" {listing.price_per_night:.2f} {listing.currency}\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" {f\"⭐ {listing.rating}\" if listing.rating else \"N/A\"}\n",
|
|
" </td>\n",
|
|
" <td style='padding: 10px; border-bottom: 1px solid #eee; text-align: center;'>\n",
|
|
" {link_html}\n",
|
|
" </td>\n",
|
|
" </tr>\n",
|
|
" \"\"\"\n",
|
|
"\n",
|
|
" listings_html += \"\"\"\n",
|
|
" </tbody>\n",
|
|
" </table>\n",
|
|
" <p style='margin-top: 15px; color: #666; font-size: 13px; font-style: italic;'>\n",
|
|
" 💡 Tip: Click on any listing title or the \"View\" button to open it in a new tab\n",
|
|
" </p>\n",
|
|
" </div>\n",
|
|
" \"\"\"\n",
|
|
"\n",
|
|
" display(HTML(listings_html))\n",
|
|
"\n",
|
|
" display_result(True, \"Price comparison completed successfully!\")\n",
|
|
"\n",
|
|
" except Exception as e:\n",
|
|
" display_result(False, f\"Error during search: {str(e)}\")\n",
|
|
" import traceback\n",
|
|
" print(traceback.format_exc())\n",
|
|
"\n",
|
|
" finally:\n",
|
|
" # Cleanup\n",
|
|
" display_action(\"Cleanup\", \"Closing browser and cleaning up...\")\n",
|
|
"\n",
|
|
" if playwright_browser:\n",
|
|
" await playwright_browser.close()\n",
|
|
"\n",
|
|
" if chrome_process:\n",
|
|
" chrome_process.terminate()\n",
|
|
" try:\n",
|
|
" await asyncio.wait_for(chrome_process.wait(), 5)\n",
|
|
" except TimeoutError:\n",
|
|
" chrome_process.kill()\n",
|
|
"\n",
|
|
" display_result(True, \"Cleanup complete\")\n",
|
|
"\n",
|
|
" # Display educational summary\n",
|
|
" display(HTML(\"\"\"\n",
|
|
" <div style='\n",
|
|
" margin: 40px 0 20px 0;\n",
|
|
" padding: 25px;\n",
|
|
" background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n",
|
|
" color: white;\n",
|
|
" border-radius: 12px;\n",
|
|
" '>\n",
|
|
" <h3 style='margin: 0 0 15px 0;'>🎓 What You Just Learned</h3>\n",
|
|
" <ul style='margin: 0; padding-left: 20px; line-height: 2;'>\n",
|
|
" <li><strong>CDP Integration:</strong> Chrome DevTools Protocol connects Playwright + Browser-Use</li>\n",
|
|
" <li><strong>Vision-Based Extraction:</strong> GPT-4 Vision reads prices like a human</li>\n",
|
|
" <li><strong>Structured Output:</strong> Type-safe data extraction with Pydantic models</li>\n",
|
|
" <li><strong>Price Comparison:</strong> Automated analysis to find best deals</li>\n",
|
|
" <li><strong>Clickable URLs:</strong> Direct links to Airbnb listings for easy viewing</li>\n",
|
|
" <li><strong>Real-World Application:</strong> Practical web scraping for price monitoring</li>\n",
|
|
" </ul>\n",
|
|
" </div>\n",
|
|
" \"\"\"))\n",
|
|
"\n",
|
|
"# Run the demo\n",
|
|
"await main()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "644c431e",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "1ef2945b",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Key Takeaways and Best Practices\n",
|
|
"\n",
|
|
"### When to Use Agent vs Actor\n",
|
|
"\n",
|
|
"| Scenario | Use Agent | Use Actor |\n",
|
|
"|----------|-----------|-----------|\n",
|
|
"| **Dynamic layouts** | ✅ AI adapts to changes | ❌ CSS selectors break |\n",
|
|
"| **Known structure** | ❌ Slower than direct control | ✅ Fast and precise |\n",
|
|
"| **Finding elements** | ✅ Natural language queries | ❌ Need exact selectors |\n",
|
|
"| **Timing control** | ❌ Less predictable | ✅ Full timing control |\n",
|
|
"| **Complex workflows** | ✅ Handles unexpected UI | ❌ Requires explicit code |\n",
|
|
"\n",
|
|
"### Browser-Use Best Practices\n",
|
|
"\n",
|
|
"1. **Start with Agent for exploration**: Let AI navigate complex sites first\n",
|
|
"2. **Switch to Actor for precision**: Use CSS selectors for predictable elements\n",
|
|
"3. **Always handle errors**: Websites change—build fallback strategies\n",
|
|
"4. **Use structured output**: Pydantic models ensure type-safe data\n",
|
|
"5. **Add delays strategically**: Use `asyncio.sleep()` after actions that trigger changes\n",
|
|
"6. **Take screenshots**: Visual debugging is invaluable\n",
|
|
"7. **Combine approaches**: Hybrid workflows leverage strengths of both paradigms\n",
|
|
"\n",
|
|
"### Real-World Applications\n",
|
|
"\n",
|
|
"- **Travel Booking**: Monitor prices, auto-book deals, compare options\n",
|
|
"- **E-commerce**: Track inventory, compare prices, automated purchasing\n",
|
|
"- **Data Collection**: Scrape dynamic sites, extract structured data\n",
|
|
"- **Testing**: Automated UI testing with vision-based verification\n",
|
|
"- **Monitoring**: Check website changes, alert on specific conditions\n",
|
|
"- **Form Automation**: Fill complex multi-step forms intelligently"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": ".venv (3.12.11)",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.12.11"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
} |