Geo-Targeted Price Monitoring: Multi-Region Browser Agents with Anchor VPN

Hands On
Jun 21
by Idan Raman

Your competitors price their SaaS plans differently in Germany than in Australia. Your enterprise tier costs 40% less in Southeast Asia than in the US. Without geo-targeted automation, you would never catch it — and you certainly could not verify it at scale.

Anchor VPN was built precisely for this: purpose-built network infrastructure that gives your browser agents genuine residential IP addresses in any country, without the fingerprint drift that kills generic proxy setups. In this hands-on guide, we will build a multi-region price monitoring agent that scrapes pricing pages from five different countries simultaneously using Python, Playwright, and Pydantic.

What We're Building

A Python agent that:

  • Spins up 5 parallel Anchor sessions, each routing through a different country
  • Navigates to a target pricing page from each region
  • Extracts structured pricing data with Pydantic
  • Returns a normalized comparison you can act on

Setup

pip install anchorbrowser playwright pydantic anthropic
playwright install chromium
import os
import asyncio
from anchorbrowser import Anchorbrowser
from pydantic import BaseModel
from playwright.async_api import async_playwright

anchor = Anchorbrowser(api_key=os.environ["ANCHOR_API_KEY"])

Defining the Data Model

Using Pydantic keeps the output consistent regardless of which region's page you scraped:

class RegionalSnapshot(BaseModel):
    country_code: str
    page_title: str
    body_excerpt: str
    error: str | None = None

Creating Geo-Targeted Sessions

Each call to anchor.sessions.create() accepts a proxy object. Set country_code to any ISO 3166-1 alpha-2 code and Anchor routes the session through a residential IP in that country:

REGIONS = ["us", "de", "au", "gb", "sg"]

async def fetch_from_region(country_code: str, url: str) -> RegionalSnapshot:
    session = anchor.sessions.create(
        proxy={"active": True, "country_code": country_code},
        timeout={"max_duration": 60, "idle_timeout": 30},
    )
    try:
        async with async_playwright() as pw:
            browser = await pw.chromium.connect_over_cdp(session.ws_endpoint)
            page = browser.contexts[0].pages[0]

            await page.goto(url, wait_until="domcontentloaded")
            title   = await page.title()
            excerpt = (await page.locator("body").inner_text())[:2000]

            await browser.close()

        return RegionalSnapshot(
            country_code=country_code,
            page_title=title,
            body_excerpt=excerpt,
        )
    except Exception as exc:
        return RegionalSnapshot(
            country_code=country_code,
            page_title="",
            body_excerpt="",
            error=str(exc),
        )
    finally:
        anchor.sessions.stop(session.id)

Running All Regions in Parallel

Python's asyncio.gather fires all five sessions at the same time. Each session is fully isolated — separate browser context, separate IP, separate fingerprint:

async def monitor_pricing(competitor_url: str) -> list[RegionalSnapshot]:
    tasks = [fetch_from_region(code, competitor_url) for code in REGIONS]
    return list(await asyncio.gather(*tasks))

if __name__ == "__main__":
    url = "https://example-competitor.com/pricing"
    snapshots = asyncio.run(monitor_pricing(url))

    for snap in snapshots:
        if snap.error:
            print(f"[{snap.country_code.upper()}] ERROR: {snap.error}")
        else:
            print(f"[{snap.country_code.upper()}] {snap.page_title}")
            print(snap.body_excerpt[:200])
            print()

Parsing Prices with an LLM

Raw page text is not structured. Wire the snapshots into a quick LLM call to extract plan names and prices — and you get a normalized dataset without writing a custom scraper for every site:

from anthropic import Anthropic
import json

client = Anthropic()

def extract_prices(snapshot: RegionalSnapshot) -> dict:
    if snapshot.error:
        return {"country": snapshot.country_code, "plans": [], "error": snapshot.error}

    msg = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=512,
        messages=[{
            "role": "user",
            "content": (
                "Extract all pricing plans and their monthly costs from this text. "
                'Return JSON: {"plans": [{"name": str, "monthly_usd": float|null}]}.

'
                + snapshot.body_excerpt
            ),
        }],
    )
    try:
        return {"country": snapshot.country_code, **json.loads(msg.content[0].text)}
    except Exception:
        return {"country": snapshot.country_code, "plans": [], "raw": msg.content[0].text}

# Usage
snapshots = asyncio.run(monitor_pricing("https://example-competitor.com/pricing"))
price_data = [extract_prices(s) for s in snapshots]

for region in price_data:
    print(f"
=== {region['country'].upper()} ===")
    for plan in region.get("plans", []):
        print(f"  {plan['name']}: ${plan['monthly_usd']}/mo")

Production Tips

  • Fresh sessions every run. Do not reuse sessions across runs. Stale cookies and cached geoIP signals can drift your results. Create a new session per job.
  • Use idle timeouts. The idle_timeout parameter (seconds) auto-kills sessions that stall on slow third-party scripts, preventing orphaned sessions from running up costs.
  • Handle region blocks gracefully. Some sites block entire countries. Wrap each call in a try/except and log errors to RegionalSnapshot.error rather than crashing the batch.
  • Scale to hundreds of regions. Anchor supports up to 5,000 concurrent sessions created asynchronously. Extend REGIONS to cover every market you care about without changing the architecture.
  • Combine with OmniConnect. If the pricing page requires a login, OmniConnect handles authentication per region so your agent lands on the correct account-specific pricing view.

What's Next

This pattern — parallel geo-targeted sessions feeding into a structured extraction step — works for any competitive intelligence workflow: feature pages, changelog entries, job listings, availability status. Swap the URL and the Pydantic model and you have a new monitoring agent in minutes.

For the next layer, connect the output to a scheduled job and store results in a database. Our Trigger.dev + Anchor guide covers exactly that.

Get Started

Sign up at anchorbrowser.io, grab your API key, and run your first geo-targeted browser agent. The full proxy configuration reference is at docs.anchorbrowser.io.

Building a geo-intelligence pipeline on Anchor? We would love to hear about it.

Stay ahead in browser automation

We respect your inbox. Privacy policy

Welcome aboard! Thanks for signing up
Oops! Something went wrong while submitting the form.