ScreenshotNeo

BlogHow-to

How to Bulk Capture Screenshots of Pages with Geolocation Settings

Use Playwright contexts to capture batches of pages at specific locations. Get runnable code, safe batching patterns, and fixes for common issues.

By the ScreenshotNeo team4 October 20269 min read

To bulk capture screenshots of pages with geolocation settings, use Playwright and set latitude, longitude, and the geolocation permission on a browser context before navigating. Capture all URLs assigned to that location in that context. For another location, use a separate context or finish the first batch and then change the context setting; a context’s geolocation applies to all pages in it.

This controls the browser’s geolocation signal. A website may also use IP address, account preferences, saved state, or its own permission logic, so browser geolocation alone cannot guarantee that every site will show a particular regional experience.

1. Install Playwright and prepare the input

The example below uses Playwright’s JavaScript API. It groups URLs by named location, creates an isolated context per location, visits each URL, and saves a full-page PNG. Use a stable location label and page index in filenames so captures remain identifiable.

npm init -y
npm install playwright
npx playwright install chromium

Save the following as capture.mjs. Edit the URL list and coordinates. Coordinates are latitude and longitude in decimal degrees; use the values you intend to emulate.

import { chromium } from 'playwright';
import { mkdir } from 'node:fs/promises';

const batches = [
  {
    location: 'new-york',
    latitude: 40.7128,
    longitude: -74.0060,
    urls: ['https://example.com/', 'https://www.wikipedia.org/'],
  },
  {
    location: 'london',
    latitude: 51.5072,
    longitude: -0.1276,
    urls: ['https://example.com/'],
  },
];

const outputDir = './screenshots';
const viewport = { width: 1440, height: 900 };
await mkdir(outputDir, { recursive: true });
const browser = await chromium.launch({ headless: true });

try {
  for (const batch of batches) {
    const context = await browser.newContext({
      viewport,
      geolocation: {
        latitude: batch.latitude,
        longitude: batch.longitude,
      },
      permissions: ['geolocation'],
    });

    try {
      const page = await context.newPage();
      for (const [index, url] of batch.urls.entries()) {
        const filename = `${outputDir}/${batch.location}-${String(index + 1).padStart(3, '0')}.png`;
        try {
          await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 45000 });
          // Replace this with a site-specific locator when regional content has a known signal.
          await page.waitForTimeout(1000);
          await page.screenshot({ path: filename, fullPage: true, type: 'png' });
          console.log(JSON.stringify({ url, location: batch.location, filename, status: 'captured' }));
        } catch (error) {
          console.error(JSON.stringify({ url, location: batch.location, filename, status: 'failed', error: String(error) }));
        }
      }
    } finally {
      await context.close();
    }
  }
} finally {
  await browser.close();
}

Run it with node capture.mjs. The script continues to the next URL if one navigation or screenshot fails and reports the failure in the console. For repeatable jobs, write those records to a JSONL or CSV manifest as well.

2. Understand the geolocation and screenshot settings

Geolocation belongs to a context

Playwright configures geolocation on a browser context and requires the page to have permission to access it. The setting can be changed later, but it affects every page in that context. Group concurrent work by location and give each location its own context. This avoids one batch changing the location signal for another. See the official Playwright emulation documentation.

Choice Use it when Trade-off
One context per location Location groups may run concurrently or need separate browser state More contexts and memory are used while they are open
Reuse one context sequentially You process one location group at a time Close or finish the prior pages before changing location; the setting is context-wide
One page per URL in a shared context URLs share the same location and browser settings Pages share context-level state such as cookies and permissions

To change location between sequential batches in a reused context, call await context.setGeolocation({ latitude, longitude }) before creating or navigating pages for the next batch. Do not expect that call to target only one page. If pages at different locations need to run at the same time, use separate contexts.

Choose the capture extent and output

  • fullPage: true captures the full scrollable document, including content below the viewport. It can create very tall images and increase memory and file size.
  • Omit fullPage or set it to false for only the visible viewport.
  • path saves the screenshot to a file. The screenshot API also supports returning image bytes when you want to upload or process the result instead of writing it directly.
  • PNG is lossless and useful for visual comparisons. The Page API also documents JPEG and screenshot scale options. Check the installed Playwright version’s Page API reference for supported options.
  • Viewport dimensions are CSS pixels. Keep them fixed across a batch if you are comparing regional pages.

For a single element rather than a whole page, locate it and call locator.screenshot({ path: filename }). Ensure the element exists and is visible first. The Playwright screenshot command guide also describes viewport, element, and full-page capture modes.

3. Make batches reliable and traceable

  1. Validate inputs. Reject missing URLs, duplicate output names, and coordinates outside latitude −90 to 90 or longitude −180 to 180 before launching a browser.
  2. Choose a wait condition per site. domcontentloaded is a starting point, not a guarantee that a location-dependent component has finished rendering. Prefer waiting for a known result, such as await page.getByText('Local delivery').waitFor(), when the page offers a stable signal. A short fixed delay can be a fallback but is less reliable.
  3. Use a bounded concurrency limit. For larger lists, process a few URLs at once within each location context rather than opening every page simultaneously. This controls memory and avoids overwhelming the target sites. Start sequentially, then increase concurrency while monitoring failures and resource use.
  4. Keep names safe. Use a sanitized location label and numeric index or stable URL hash, rather than the raw URL, in filenames. Avoid collisions when the same URL is captured at multiple locations.
  5. Record capture metadata. Save the URL, location label, coordinates, timestamp, viewport, capture mode, output path, and success or error in a manifest. This makes a batch auditable and lets you retry only failed items.
  6. Close resources. Close each context when its location batch finishes and close the browser in a finally block. This releases pages and browser resources after errors too.

For very large jobs, use a retry policy for transient navigation failures: retry a small, bounded number of times with a delay, record each attempt, and keep permanent failures visible. Do not retry indefinitely or silently treat a failed navigation as a valid screenshot.

4. Python and cURL alternatives

The Playwright Python API follows the same context-level model. Install the Python package and browser binary:

python -m pip install playwright
python -m playwright install chromium
import asyncio
from pathlib import Path
from playwright.async_api import async_playwright

batches = [
    {
        "location": "new-york",
        "latitude": 40.7128,
        "longitude": -74.0060,
        "urls": ["https://example.com/", "https://www.wikipedia.org/"],
    },
    {
        "location": "london",
        "latitude": 51.5072,
        "longitude": -0.1276,
        "urls": ["https://example.com/"],
    },
]

async def main():
    output_dir = Path("screenshots")
    output_dir.mkdir(parents=True, exist_ok=True)
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        try:
            for batch in batches:
                context = await browser.new_context(
                    viewport={"width": 1440, "height": 900},
                    geolocation={
                        "latitude": batch["latitude"],
                        "longitude": batch["longitude"],
                    },
                    permissions=["geolocation"],
                )
                try:
                    page = await context.new_page()
                    for index, url in enumerate(batch["urls"], start=1):
                        filename = output_dir / f"{batch['location']}-{index:03}.png"
                        try:
                            await page.goto(url, wait_until="domcontentloaded", timeout=45000)
                            await page.wait_for_timeout(1000)
                            await page.screenshot(path=str(filename), full_page=True, type="png")
                            print({"url": url, "location": batch["location"], "file": str(filename), "status": "captured"})
                        except Exception as exc:
                            print({"url": url, "location": batch["location"], "file": str(filename), "status": "failed", "error": str(exc)})
                finally:
                    await context.close()
        finally:
            await browser.close()

asyncio.run(main())

There is no direct cURL equivalent for browser geolocation emulation: cURL makes HTTP requests and does not run a browser context with the Geolocation API and page rendering. For raw HTTP checks, use a service or application endpoint designed to accept location data; that will not produce a rendered browser screenshot. For a screenshot API call, see the ScreenshotNeo option below.

5. Troubleshooting

Symptom Likely cause Fix
The page shows the wrong region The site uses IP geolocation, account settings, a saved preference, or a server-side default in addition to browser geolocation Check the site’s own location controls and test what its page reads from browser geolocation. Browser emulation does not change the machine’s public IP address.
Location permission is denied The context did not grant geolocation permission, or the page handles permission denial Create the context with permissions: ['geolocation'] and provide valid coordinates before navigation.
Changing location affects another page Geolocation is configured for all pages in the context Use one context per concurrent location, or process groups sequentially and change the setting between them.
The screenshot shows a loading state Navigation completion did not mean the location-specific content was ready Wait for a site-specific locator or response. Use a delay only as a fallback and tune it for that site.
Navigation times out Slow site, network issue, or a page that keeps connections open Set a reasonable timeout, consider domcontentloaded instead of waiting for every network connection to finish, and retry a limited number of times.
Output is missing or overwritten The directory does not exist or filenames collide Create the directory first and include both location and a unique page identifier in each filename.
Full-page capture is huge or fails The page is exceptionally tall or memory constrained Capture the viewport, target an element, or split the task into smaller captures. Reduce concurrent pages.
Browser executable is missing Playwright package is installed but its browser binary is not Run npx playwright install chromium for Node.js or python -m playwright install chromium for Python.

6. Performance, reliability, and cost

Local Playwright has no per-screenshot API charge, but you provide the machine, browser runtime, network access, and maintenance. Total batch time depends on the sites, navigation waits, concurrency, and full-page image sizes; the dossier provides no benchmark, so measure your own workload rather than relying on a universal throughput estimate.

Sequential capture is easiest to diagnose and places the least demand on memory. Bounded concurrency can shorten wall-clock time when pages are independent, but too many open pages increase memory use and can trigger rate limits or anti-bot behavior at target sites. Reuse a browser process across batches and close contexts promptly. Keep failed captures in the manifest and distinguish them from successful images.

Geolocation emulation is useful for testing browser-facing location features, but it does not reproduce a user’s complete network identity or guarantee a page’s regional response. For controlled comparisons, keep the viewport, browser version, cookies, locale, timezone, and wait condition consistent while changing only the location variable under study.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. A single request captures a URL; use bulk capture for up to 100 URLs per call. Its documented options include timezone and geolocation, though browser geolocation and IP-based location are distinct signals, so verify the target site’s behavior. See the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Each response identifies the page verdict and billing status in headers. Sign up for 1,000 free screenshots a month, with no card.

FAQ

Does Playwright geolocation change the browser’s IP address?

No. It emulates the browser Geolocation API. A site can use IP-derived location separately.

Can I capture the same URL for several locations?

Yes. Add that URL to each location batch and use filenames that include the location label.

Should I use a new context for every URL?

Usually not when URLs share a location and browser state. Use a context per location group; isolate individual URLs only when their state must be separate.

How do I know the site applied the location?

Check a visible location-specific result or the site’s own location indicator. A successful screenshot only confirms that an image was saved, not that the page used the intended signal.