How to Capture Screenshots of Competitor Landing Pages from SERP Rankings
Capture both the search result and the competitor landing page, then save the query, location, device, destination URL, and timestamp with each image.
To capture competitor landing pages from SERP rankings, record the search context, screenshot the results page, open the ranking result, note its final URL after redirects, and capture the landing page at a consistent viewport. Keep the SERP image and landing-page image together: one documents the observed ranking and result features, while the other documents the destination’s visitor experience.
1. Record the search context
Before searching, write down the exact query, search engine, date and time, target market or location, and device or viewport. Results can vary across those settings, so preserving them makes later comparisons easier to interpret.
- Query: copy the exact search phrase.
- Engine and market: record the search engine and the country, region, or location setting.
- Date and time: record when you viewed the results, including the timezone if timing matters.
- Device: note the device preset or viewport dimensions.
- Browser state: note whether you were logged in and which consent choice or browser state applied.
Use the same settings when you repeat the comparison. A screenshot without this context is difficult to compare reliably with a later capture.
2. Capture the SERP before opening a result
Save a screenshot of the search results page while the ranking and visible SERP features are still on screen. Include enough of the page to show the query context and the result you plan to inspect. Note the result’s visible title, displayed URL, rank, and result type where relevant. A landing-page screenshot alone does not show where or how the page appeared in the search results.
For a short list, a manual browser screenshot is usually sufficient. A SERP-focused extension may help archive results and metadata. The SERPshot Chrome Web Store listing describes full-page Google and Bing capture, result parsing, and position logging; treat those as features described by the listing, not as an independent evaluation. SERPshot listing
3. Follow the ranking result and record redirects
Open the result you observed rather than navigating to a guessed URL. Record both the URL displayed in the SERP and the final destination URL after redirects. This preserves the connection between the ranking result and the page you actually captured, including cases where the result points through a redirect or to a different canonical destination.
4. Capture the competitor landing page
Capture the initial visitor view at a fixed viewport. Add a full-page screenshot when the below-the-fold structure matters, such as when comparing page sections, calls to action, or content order. Viewport and full-page captures answer different questions: the first records what appears initially; the second includes content down the scrollable page. Playwright supports page, element, and full-page screenshots, with options for output path and scale. Playwright screenshot documentation · Playwright Page API
5. Automate repeatable captures with Playwright
Use Playwright when you have multiple URLs or need consistent capture settings and filenames. Install the Python package and Chromium once:
python -m pip install playwright
python -m playwright install chromium
Save the following as capture_pages.py. It captures a viewport image and an optional full-page image for each URL. Pass the landing-page URLs you recorded from the SERP; this script does not retrieve rankings or capture the SERP itself.
import asyncio
import re
import sys
from pathlib import Path
from urllib.parse import urlparse
from playwright.async_api import async_playwright
def safe_name(url: str) -> str:
host = urlparse(url).netloc or "page"
return re.sub(r"[^a-zA-Z0-9.-]+", "_", host)
async def main(urls: list[str]) -> None:
if not urls:
raise SystemExit("Usage: python capture_pages.py URL [URL ...]")
output = Path("captures")
output.mkdir(exist_ok=True)
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page(viewport={"width": 1440, "height": 900}, device_scale_factor=1)
for index, url in enumerate(urls, start=1):
name = f"{index:02d}-{safe_name(url)}"
response = await page.goto(url, wait_until="domcontentloaded", timeout=60000)
try:
await page.wait_for_load_state("networkidle", timeout=10000)
except Exception:
# Some pages keep connections open; capture the rendered page anyway.
pass
await page.screenshot(path=str(output / f"{name}-viewport.png"))
await page.screenshot(path=str(output / f"{name}-full.png"), full_page=True)
print({"requested_url": url, "final_url": page.url,
"status": response.status if response else None,
"viewport": f"{output / (name + '-viewport.png')}",
"full_page": f"{output / (name + '-full.png')}"})
await browser.close()
if __name__ == "__main__":
asyncio.run(main(sys.argv[1:]))
python capture_pages.py https://competitor.example/landing-page https://another.example/product
The script uses a fixed 1440 × 900 viewport and device scale factor 1 so captures share a baseline. Change these values to match the target device, and keep the chosen dimensions in your capture metadata. It waits for DOM content, then gives network idle up to ten seconds; if a page keeps connections open, it continues rather than waiting forever. For pages whose content appears after a known interaction or selector, use Playwright’s locator and wait APIs before taking the screenshot.
Capture one element instead of the full page
To isolate a hero, pricing section, or other component, locate it and screenshot the element:
hero = page.locator("main h1").first
await hero.screenshot(path="captures/hero.png")
Use a selector that identifies the intended element on each site. Selectors are site-specific and can fail when markup changes.
Use the JavaScript API
For a Node.js workflow, install Playwright and its browser:
npm install playwright
npx playwright install chromium
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
try {
const response = await page.goto('https://competitor.example/landing-page', {
waitUntil: 'domcontentloaded', timeout: 60000
});
try {
await page.waitForLoadState('networkidle', { timeout: 10000 });
} catch (_) {
// Continue when long-lived requests prevent network idle.
}
await page.screenshot({ path: 'competitor-viewport.png' });
await page.screenshot({ path: 'competitor-full.png', fullPage: true });
console.log({ requestedUrl: 'https://competitor.example/landing-page',
finalUrl: page.url(), status: response?.status() ?? null });
} finally {
await browser.close();
}
})();
Playwright’s Python documentation also covers full-page, element, and buffer capture. Playwright screenshots for Python
6. Name files and keep evidence together
Use a repeatable name that identifies the query or project, competitor or domain, engine, market, device or viewport, date, and capture mode. For example:
crm-software__acme-example__google-us__1440x900__2026-10-04__serp.png
crm-software__acme-example__google-us__1440x900__2026-10-04__landing-viewport.png
crm-software__acme-example__google-us__1440x900__2026-10-04__landing-full.png
Store the SERP screenshot, landing-page screenshot, displayed result URL, final destination URL, and metadata together. A small CSV or JSON sidecar can hold the query, engine, market, timestamp, viewport, rank, result title, URLs, and browser state.
7. Compare captures consistently
For a meaningful visual comparison, hold the viewport and capture mode constant across competitors and dates. Keep the query, engine, geography, and browser state explicit as well. Preserve separate SERP evidence so a landing-page design comparison is not mistaken for evidence of ranking.
| Capture | What it documents | When to use it |
|---|---|---|
| SERP screenshot | Observed result, rank, title, displayed URL, and visible result features | Every ranking observation |
| Landing viewport | Initial visitor view at a defined device size | First-impression and above-the-fold comparisons |
| Landing full page | Page content through the scrollable document | Page structure and below-the-fold comparisons |
| Element screenshot | A selected component, such as the hero or pricing section | Focused component comparisons |
8. Tool choice and limits
ScreenshotNeo is the first option to consider when you want a screenshot API: cookie and consent banners, newsletter popups, and chat widgets are removed before capture; only clean shots are billed, and the lowest paid plan is $5 for 3,000 screenshots. For browser-controlled capture, Playwright provides page, element, and full-page screenshot workflows. SERPshot is a Chrome extension whose listing describes SERP archiving and rank metadata. Google Search Console URL Inspection is scoped to inspecting a page in the property owner’s Search Console context; its live test can show a screenshot of the page fetched by Google, so it is not a general competitor inspection method. Google Search Console URL Inspection help
Or skip the browser setup
Use ScreenshotNeo to capture the destination page with one GET request. See the ScreenshotNeo API documentation for request options.
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; each step can be disabled.
- Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed. Response headers identify the page verdict and billing status.
- An MCP server lets AI agents use
take_screenshot,get_page_info, andcapture_pdf. - The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month with no card.
Troubleshooting
| Problem | Likely cause | Fix |
|---|---|---|
| Navigation times out | The site is slow or keeps network requests open. | Use a longer navigation timeout, wait for domcontentloaded, and treat network idle as a bounded best-effort wait. If the page needs more time, wait for a specific selector. |
| Screenshot is blank or incomplete | Capture happened before the relevant content rendered, or content loads after scrolling. | Wait for the page’s visible content or selector. For lazy-loaded content, scroll through the page before full-page capture and verify the result. |
| Element selector fails | The selector is absent, ambiguous, or changed. | Inspect the current DOM, use a more specific locator, and handle missing elements explicitly. |
| Page differs between runs | Query context, viewport, geography, browser state, consent, or time changed. | Record these inputs and keep them fixed for the comparison; websites and search results can also change over time. |
| Displayed and captured URLs do not match | The result redirected or resolved to another destination. | Record both the SERP URL and the final URL after navigation, as in the sample scripts. |
| Browser executable is missing | Playwright package is installed but its Chromium browser is not. | Run python -m playwright install chromium or npx playwright install chromium, matching the language setup. |
Performance, reliability, and cost
Local Playwright avoids a per-capture screenshot API charge, but you maintain the browser installation, execution environment, and capture workflow. Batch captures in one browser session to avoid repeated browser startup. Use a fixed viewport and bounded waits; avoid a global wait for every network request to finish because analytics or long-lived connections can delay captures. Full-page screenshots and large viewports produce larger images and may take longer than viewport captures.
For repeatability, retain the inputs and outputs, record final URLs and navigation status, and recapture under the same conditions at a later date. A successful screenshot is a visual record of one visit, not proof that every visitor sees the same page. Search results, redirects, page content, and consent state can change.
ScreenshotNeo offers 1,000 shots per month free, then plans of $5 for 3,000, $15 for 15,000, $39 for 60,000, $99 for 250,000, and $249 for 1,000,000; yearly billing gives two months free, and every feature is available on every plan. Its billing rules exclude bot checks, blank pages, timeouts, failed loads, and cache hits; use the response verdict and billing headers when reconciling capture results.
FAQ
Should I capture the SERP and landing page separately?
Yes. They document different evidence: the result’s observed rank and appearance versus the destination page’s design.
Is a full-page screenshot always better?
No. Use a viewport capture for the initial visitor view; add full-page capture when the content below the fold matters.
Can Search Console inspect a competitor URL?
URL Inspection is a property-owner workflow. Its live screenshot concerns the page retrieved in that inspection context, not a general competitor research feature.
How often should I recapture?
Choose an interval that matches the comparison you need, and preserve each date as a separate observation with the same recorded settings.


