Files
browser-use__browser-use/browser_use/tools/views.py
Magnus Müller 6c53f20d11 feat: add extract_data action and save_to_file for efficient data extraction
- Add new `extract_data` action that uses CSS selectors (querySelectorAll) to
  extract structured data directly from the page without LLM cost. Supports
  extracting text, href, src, or any HTML attribute from matching elements.
  Returns structured JSON and optionally saves to CSV/JSONL/JSON files.

- Add `save_to_file` parameter to existing `extract` action so LLM-extracted
  content can be written directly to a file instead of consuming context window
  tokens. Enables accumulating data across multiple pages.

- Update both system prompts with a new `<data_extraction>` section that guides
  the agent on when to use extract_data vs extract vs evaluate, and documents
  the multi-page extraction pattern (extract → click Next → extract → repeat
  with save_to_file appending to same file).

These changes address the core data extraction inefficiency where the agent was
forced to output all extracted tokens through the LLM, making bulk extraction
(100+ items, multi-page scraping) slow and expensive.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 00:06:18 -08:00

128 lines
4.3 KiB
Python

from typing import Generic, TypeVar
from pydantic import BaseModel, ConfigDict, Field
# Action Input Models
class ExtractAction(BaseModel):
query: str
extract_links: bool = Field(
default=False, description='Set True to true if the query requires links, else false to safe tokens'
)
start_from_char: int = Field(
default=0, description='Use this for long markdowns to start from a specific character (not index in browser_state)'
)
save_to_file: str | None = Field(
default=None,
description='If set, append extracted content to this file (e.g. "results.csv") instead of returning all tokens. Use for multi-page extraction to accumulate data across pages.',
)
class ExtractDataAction(BaseModel):
selector: str = Field(description='CSS selector to match elements, e.g. "table tr", ".product-card", "ul.results > li"')
attributes: list[str] = Field(
default=['text'],
description='List of attributes to extract per element. Use "text" for innerText, "href" for links, or any HTML attribute name like "src", "data-id", etc.',
)
save_to_file: str | None = Field(
default=None,
description='If set, append extracted rows to this file as JSON lines (e.g. "data.jsonl") or CSV (e.g. "data.csv"). Keeps data out of context window.',
)
max_results: int = Field(
default=10000,
description='Maximum number of elements to extract. Prevents accidental extraction of huge DOMs.',
)
class SearchAction(BaseModel):
query: str
engine: str = Field(
default='duckduckgo', description='duckduckgo, google, bing (use duckduckgo by default because less captchas)'
)
# Backward compatibility alias
SearchAction = SearchAction
class NavigateAction(BaseModel):
url: str
new_tab: bool = Field(default=False)
# Backward compatibility alias
GoToUrlAction = NavigateAction
class ClickElementAction(BaseModel):
index: int | None = Field(default=None, ge=1, description='Element index from browser_state')
coordinate_x: int | None = Field(default=None, description='Horizontal coordinate relative to viewport left edge')
coordinate_y: int | None = Field(default=None, description='Vertical coordinate relative to viewport top edge')
# expect_download: bool = Field(default=False, description='set True if expecting a download, False otherwise') # moved to downloads_watchdog.py
# click_count: int = 1 # TODO
class ClickElementActionIndexOnly(BaseModel):
model_config = ConfigDict(title='ClickElementAction')
index: int = Field(ge=1, description='Element index from browser_state')
class InputTextAction(BaseModel):
index: int = Field(ge=0, description='from browser_state')
text: str
clear: bool = Field(default=True, description='1=clear, 0=append')
class DoneAction(BaseModel):
text: str = Field(description='Final user message in the format the user requested')
success: bool = Field(default=True, description='True if user_request completed successfully')
files_to_display: list[str] | None = Field(default=[])
T = TypeVar('T', bound=BaseModel)
class StructuredOutputAction(BaseModel, Generic[T]):
success: bool = Field(default=True, description='True if user_request completed successfully')
data: T = Field(description='The actual output data matching the requested schema')
class SwitchTabAction(BaseModel):
tab_id: str = Field(min_length=4, max_length=4, description='4-char id')
class CloseTabAction(BaseModel):
tab_id: str = Field(min_length=4, max_length=4, description='4-char id')
class ScrollAction(BaseModel):
down: bool = Field(default=True, description='down=True=scroll down, down=False scroll up')
pages: float = Field(default=1.0, description='0.5=half page, 1=full page, 10=to bottom/top')
index: int | None = Field(default=None, description='Optional element index to scroll within specific element')
class SendKeysAction(BaseModel):
keys: str = Field(description='keys (Escape, Enter, PageDown) or shortcuts (Control+o)')
class UploadFileAction(BaseModel):
index: int
path: str
class NoParamsAction(BaseModel):
model_config = ConfigDict(extra='ignore')
# Optional field required by Gemini API which errors on empty objects in response_schema
description: str | None = Field(None, description='Optional description for the action')
class GetDropdownOptionsAction(BaseModel):
index: int
class SelectDropdownOptionAction(BaseModel):
index: int
text: str = Field(description='exact text/value')