Building Agentic Web Browsing Tools: Integrating LLMs with Headless Browser Automation for Real-Time Data Grounding
Tutorials

Building Agentic Web Browsing Tools: Integrating LLMs with Headless Browser Automation for Real-Time Data Grounding

Learn how to combine LLMs with headless browsers for grounded, agentic web scraping—complete with code examples, architecture patterns, and best practices for reliable data extraction.

5 min read
9 views

AlterLab handles this automaticallyscrape any URL with one API call. No infrastructure required.

Try it free

TL;DR

Agentic web browsing combines an LLM planner with a headless browser executor. The LLM decides what to click or type based on live DOM context, while the browser performs actions and returns grounded observations. This loop yields reliable, real-time data extraction without brittle selectors.

Why Agentic Browsing Beats Traditional Scraping

Classic scrapers rely on static selectors (CSS, XPath) that break when a site updates its layout. An agentic approach replaces fixed selectors with a reasoning loop: the LLM observes the current page, chooses an action, and the browser enacts it. Because each step is grounded in the actual DOM, the system adapts to changes, handles dynamic content, and can recover from unexpected modals or redirects.

The core loop looks like this:

  1. Observe – extract relevant text, attributes, or screenshots from the browser.
  2. Reason – feed the observation to an LLM with a prompt that asks for the next action (e.g., “click the button labeled ‘Load more’” or “extract the price”).
  3. Act – translate the LLM’s output into a Playwright/Puppeteer command.
  4. Loop – repeat until a termination condition (data collected, timeout, or max steps).

This pattern mirrors how humans browse: look, decide, act, look again.

Architecture Overview

Below is a simplified data flow for an agentic scraper built on top of AlterLab’s headless browser API.

AlterLab’s API handles the heavy lifting: automatic retries, proxy rotation, and anti-bot mitigation, so your agentic loop focuses on logic rather than infrastructure.

Grounding the LLM: What to Send as Observation

Sending the full HTML wastes tokens and confuses the model. Instead, extract a concise representation:

  • Visible textpage.evaluate(() => document.body.innerText) trimmed to first 2000 characters.
  • Key attributes – for interactive elements, pull id, aria-label, innerText, and type.
  • Optional screenshot – a base64‑encoded thumbnail (≤ 200 KB) can help the model discern layout when text alone is ambiguous.

Example observation JSON:

JSON
{
  "url": "https://example.com/products",
  "visible_text": "Showing 1‑24 of 89 results… Load more",
  "elements": [
    {"tag":"button","id":"load-more","aria-label":"Load more results","innerText":"Load more"},
    {"tag":"span","class":"price","innerText":"$29.99"}
  ]
}

Keep each observation under ~1500 tokens to stay within typical LLM context limits.

Prompt Design for Reliable Actions

A well‑crafted prompt dramatically reduces hallucinations. Use a strict output format (e.g., JSON) and few‑shot examples.

System prompt:

Code
You are a web‑browsing agent. Given the current page observation and the user goal, return the next action as a JSON object.
Allowed actions:
- { "type": "click", "selector": "<css selector>" }
- { "type": "type", "selector": "<css selector>", "text": "<string>" }
- { "type": "extract", "selector": "<css selector>", "attribute": "innerText|href|value" }
- { "type": "wait", "milliseconds": <number> }
- { "type": "done" }
If unsure, return { "type": "wait", "milliseconds": 1000 }.

Provide 2‑3 short examples in the prompt showing correct JSON for clicking a button and extracting text.

Code Example: Python SDK

Below is a complete, runnable example using AlterLab’s Python SDK and OpenAI’s GPT‑4o (or any compatible LLM). The script searches a generic listings page and extracts all item titles.

Python
import alterlab
import json
import openai
import os
import time

# Initialize clients
browser = alterlab.Client(os.getenv("ALTERLAB_API_KEY"))
openai.api_key = os.getenv("OPENAI_API_KEY")

GOAL = "Extract the titles of all items on the page"
MAX_STEPS = 20

def observe(page):
    """Return a compact JSON observation for the LLM."""
    obs = page.evaluate("""() => {
        const inner = document.body.innerText.slice(0, 1500);
        const buttons = Array.from(document.querySelectorAll('button, [role="button"]'))
            .map(b => ({tag:b.tagName.toLowerCase(), id:b.id||'', 'aria-label':b.getAttribute('aria-label')||'', innerText:b.innerText.slice(0,50)}));
        const inputs = Array.from(document.querySelectorAll('input, textarea, select'))
            .map(i => ({tag:i.tagName.toLowerCase(), id:i.id||'', 'aria-label':i.getAttribute('aria-label')||'', innerText:i.value||''}));
        return {url: location.href, visible_text: inner, elements: [...buttons, ...inputs]};
    }""")
    return obs

def llm_decide(obs, goal):
    prompt = f"""You are a web‑browsing agent.
Goal: {goal}
Observation: {json.dumps(obs)}
Return ONLY a JSON action as described."""
    response = openai.chat.completions.create(
        model="gpt-4o",
        messages=[{"role":"system","content":"You output JSON actions only."},
                  {"role":"user","content":prompt}],
        temperature=0,
        max_tokens=200,
    )
    content = response.choices[0].message.content.strip()
    # Expect pure JSON; fallback to wait if malformed
    try:
        return json.loads(content)
    except json.JSONDecodeError:
        return {"type":"wait","milliseconds":1000}

def run():
    # Start a headless session with AlterLab (smart rendering enabled)
    session = browser.sessions.create({
        "url": "https://example.com/listings",
        "render": True,
        "wait_for": "networkidle"
    })
    page = session.page

    for step in range(MAX_STEPS):
        obs = observe(page)
        action = llm_decide(obs, GOAL)
        print(f"Step {step}: {action}")

        if action.get("type") == "click":
            selector = action["selector"]
            page.click(selector)
            page.wait_for_load_state("networkidle")
        elif action.get("type") == "type":
            selector = action["selector"]
            text = action["text"]
            page.fill(selector, text)
            page.keyboard.press("Enter")
            page.wait_for_load_state("networkidle")
        elif action.get("type") == "extract":
            selector = action["selector"]
            attr = action.get("attribute","innerText")
            value = page.evaluate(f"""() => {{
                const el = document.querySelector("{selector}");
                return el ? el.{attr} : null;
            }}""")
            print(f"Extracted: {value}")
        elif action.get("type") == "wait":
            time.sleep(action.get("milliseconds",1000)/1000)
        elif action.get("type") == "done":
            break
        else:
            # unknown action → wait
            time.sleep(1)

    browser.sessions.delete(session.id)

if __name__ == "__main__":
    run()

Highlighted lines: client initialization, observation extraction, LLM call, and action dispatch.

Code Example: cURL Equivalent

You can drive the same loop from Bash by calling AlterLab’s REST endpoints and a local LLM API (e.g., Ollama). This shows the protocol‑level flow.

Bash
#!/usr/bin/env bash
API_KEY="${ALTERLAB_API_KEY}"
LLM_URL="http://localhost:11434/api/generate"  # Ollama endpoint
GOAL="Extract all product prices"
MAX_STEPS=15

# 1. Create session
SESSION_ID=$(curl -s -X POST "https://api.alterlab.io/v1/sessions" \
  -H "X-API-Key: $API_KEY" \
  -d '{"url":"https://example.com/
Share

Was this article helpful?

Frequently Asked Questions

Agentic web browsing uses LLMs to decide navigation actions while a headless browser executes them, grounding the model’s outputs in live page state for reliable data extraction.
By grounding LLM prompts with actual DOM snippets or text extracted from the current browser state, you ensure the model’s decisions are based on real page content.
AlterLab provides automatic anti-bot bypass, rotating proxies, and smart rendering, letting agentic scripts focus on logic rather than evasion tactics.