{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "dfd434e5",
   "metadata": {},
   "source": [
    "# 使用 AI 驱动的网页自动化寻找最便宜的 Airbnb\n",
    "\n",
    "本笔记本展示了如何构建一个智能网页自动化代理，搜索 Airbnb，提取价格，并找到斯德哥尔摩最便宜的房源。你将学习如何将 **Playwright** 与 **Browser-Use** 集成，实现强大的 AI 驱动自动化。\n",
    "\n",
    "## 你将学到：\n",
    "1. **Playwright + Browser-Use 集成**：结合浏览器管理与 AI 自动化\n",
    "2. <strong>基于视觉的价格提取</strong>：让 AI “看见” 并读取网页上的价格\n",
    "3. <strong>结构化数据提取</strong>：使用类型安全的 Pydantic 模型提取房源数据\n",
    "4. <strong>价格比较逻辑</strong>：从多个房源中找到最便宜的选项\n",
    "5. <strong>实际应用</strong>：实用的价格比较自动化\n",
    "\n",
    "## 先决条件：\n",
    "- 已配置 Azure OpenAI 部署\n",
    "- 安装 Playwright（`pip install playwright`）\n",
    "- 了解异步 Python\n",
    "- 基础网页自动化知识\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6340fa32",
   "metadata": {},
   "source": [
    "## 理解 Playwright + Browser-Use 架构\n",
    "\n",
    "此笔记本使用了 Browser-Use 文档中的 **官方 Playwright 集成** 模式。\n",
    "\n",
    "### 架构流程：\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",
    "### 为什么采用这种方法？\n",
    "\n",
    "**Playwright 提供：**\n",
    "- ✅ 强大的浏览器生命周期管理\n",
    "- ✅ 完整的 Chrome DevTools 协议控制\n",
    "- ✅ 稳定的页面和上下文处理\n",
    "- ✅ 内置的等待与同步\n",
    "\n",
    "**Browser-Use 提供：**\n",
    "- ✅ AI 驱动的元素查找（无需 CSS 选择器！）\n",
    "- ✅ 基于视觉的页面理解\n",
    "- ✅ 使用 Pydantic 的结构化输出提取\n",
    "- ✅ 自然语言任务执行\n",
    "\n",
    "**两者协同实现：**\n",
    "- 🎯 “搜索斯德哥尔摩 Airbnb” → Agent 导航\n",
    "- 👁️ 视觉读取页面上的所有价格\n",
    "- 📊 结构化提取 → 清晰的 Python 对象\n",
    "- 💰 价格比较逻辑 → 找出最便宜的\n",
    "\n",
    "### 我们的任务流程：\n",
    "1. **Playwright** 启动 Chrome 浏览器\n",
    "2. **Browser-Use Agent** 导航到 Airbnb.com\n",
    "3. **Agent 搜索** “斯德哥尔摩，瑞典”\n",
    "4. <strong>视觉模型</strong> 读取并提取所有房源价格\n",
    "5. <strong>结构化输出</strong> 返回类型化数据（Pydantic 模型）\n",
    "6. **Python 代码** 比较价格并找到最便宜的\n",
    "7. <strong>用丰富格式</strong> 显示结果\n"
   ]
  },
  {
   "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-5-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": [
    "## 初始化 Azure OpenAI LLM\n",
    "\n",
    "LLM 驱动代理的决策和视觉能力。我们使用：\n",
    "- **温度：0.3** 以实现一致且可预测的自动化\n",
    "- <strong>视觉能力</strong> 用于“观察”和理解页面内容\n",
    "- <strong>结构化输出</strong> 用于提取数据到 Pydantic 模型\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8ce29377",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ LLM initialized successfully\n",
      "   Model: gpt-5-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-5-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": [
    "## 定义结构化输出模型\n",
    "\n",
    "我们使用 Pydantic 模型从 Airbnb 搜索结果中提取结构化数据。代理将使用 GPT-4 Vision 自动读取页面并将数据提取到这些模型中。\n",
    "\n",
    "这确保了所有提取数据的类型安全和验证。\n"
   ]
  },
  {
   "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": [
    "## 显示辅助函数\n",
    "\n",
    "这些函数在笔记本中提供丰富的教育输出，带有格式化的 HTML。\n"
   ]
  },
  {
   "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": [
    "## 显示辅助函数\n",
    "\n",
    "这些函数在笔记本中提供丰富且具有教育意义的输出。\n"
   ]
  },
  {
   "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": [
    "## Airbnb 预订代理类\n",
    "\n",
    "该类协调整个预订工作流程，策略性地结合了代理和执行者方法。\n"
   ]
  },
  {
   "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": [
    "## 执行搜索\n",
    "\n",
    "现在让我们运行完整的工作流程，找到斯德哥尔摩最便宜的Airbnb！\n",
    "\n",
    "这将会：\n",
    "1. 启动一个真实的Chrome浏览器（可见）\n",
    "2. 使用AI进行导航和搜索\n",
    "3. 通过视觉识别提取所有价格\n",
    "4. 显示最便宜的选项\n"
   ]
  },
  {
   "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": [
    "## 主要收获与最佳实践\n",
    "\n",
    "### 何时使用 Agent 与 Actor\n",
    "\n",
    "| 场景 | 使用 Agent | 使用 Actor |\n",
    "|----------|-----------|-----------|\n",
    "| <strong>动态布局</strong> | ✅ AI 适应变化 | ❌ CSS 选择器失效 |\n",
    "| <strong>已知结构</strong> | ❌ 比直接控制慢 | ✅ 快速且精确 |\n",
    "| <strong>查找元素</strong> | ✅ 自然语言查询 | ❌ 需要精确选择器 |\n",
    "| <strong>时序控制</strong> | ❌ 可预测性较差 | ✅ 完全时序控制 |\n",
    "| <strong>复杂工作流</strong> | ✅ 处理意外 UI | ❌ 需要显式编码 |\n",
    "\n",
    "### 浏览器使用最佳实践\n",
    "\n",
    "1. **初期探索用 Agent**：让 AI 先浏览复杂网站\n",
    "2. **精确操作用 Actor**：针对可预测元素用 CSS 选择器\n",
    "3. <strong>始终处理错误</strong>：网站会变——构建降级策略\n",
    "4. <strong>使用结构化输出</strong>：Pydantic 模型确保类型安全数据\n",
    "5. <strong>策略性添加延迟</strong>：触发变更后用 `asyncio.sleep()`\n",
    "6. <strong>截图调试</strong>：视觉调试极为有用\n",
    "7. <strong>组合方法</strong>：混合工作流发挥两种范式优势\n",
    "\n",
    "### 现实应用场景\n",
    "\n",
    "- <strong>旅游预订</strong>：监控价格，自动订购，比较选项\n",
    "- <strong>电商</strong>：跟踪库存，价格比较，自动购买\n",
    "- <strong>数据采集</strong>：抓取动态站点，提取结构化数据\n",
    "- <strong>测试</strong>：基于视觉验证的自动化 UI 测试\n",
    "- <strong>监控</strong>：检查网站变化，对特定情况报警\n",
    "- <strong>表单自动化</strong>：智能填写复杂多步表单\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n\n<!-- CO-OP TRANSLATOR DISCLAIMER START -->\n**免责声明**：\n本文件由 AI 翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 翻译完成。尽管我们力求准确，但请注意，自动翻译可能包含错误或不准确之处。原始语言版文件应视为权威来源。对于重要信息，建议使用专业人工翻译。我们对因使用本翻译而产生的任何误解或误释不承担责任。\n<!-- CO-OP TRANSLATOR DISCLAIMER END -->\n"
   ]
  }
 ],
 "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
}