How to Take Full-Page SERP Screenshots with Selenium in Headless Chrome
Capture an entire search results page with Python, Selenium, headless Chrome, and CDP. Handle dynamic content, version differences, and oversized pages.
To capture a full-page search engine results page (SERP) with Selenium in headless Chrome, use Selenium’s Chrome DevTools Protocol bridge: wait for the page content you need, read the document dimensions with Page.getLayoutMetrics, then call Page.captureScreenshot with a document-sized clip and captureBeyondViewport: true. Decode the returned base64 data and save it as a PNG. A regular WebDriver window screenshot captures the current window; it does not automatically capture the entire document.
This guide uses Python because Selenium is the browser automation layer in the title. CDP command support and metric field names vary with Chrome versions, so check your installed browser’s behavior if the example needs adjustment. The CDP reference marks captureBeyondViewport experimental and documents its default as false. Chrome DevTools Protocol: Page domain · Selenium Chromium WebDriver API · CDP overview and version caveat
1. Install and prepare headless Chrome
Install Selenium in the Python environment where the script will run. Make sure Chrome or Chromium is installed and that the Selenium version can manage a compatible driver for your setup. Headless mode controls how Chrome runs; the CDP screenshot command is what requests content beyond the viewport.
python -m pip install selenium
Set a reproducible viewport before navigating. SERPs can vary with viewport, browser version, locale, session, personalization, and timing. For comparisons, keep those inputs and your wait condition consistent. Use a target URL you are permitted to automate and follow the search engine’s terms and applicable access rules.
2. Capture the full document with CDP
The following complete script opens a SERP URL supplied on the command line, waits for the document body, measures the page, captures the full document, and writes serp.png. The readiness check is intentionally modest: for production use, replace it with a bounded condition appropriate to the content you actually need, such as the presence of a results container. Search pages may insert modules or load images after the initial document is ready.
import argparse
import base64
from pathlib import Path
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
def main():
parser = argparse.ArgumentParser(
description="Capture a full-page SERP screenshot using Selenium and Chrome CDP."
)
parser.add_argument("url", help="SERP URL to capture")
parser.add_argument("--output", default="serp.png", help="Output PNG path")
parser.add_argument("--timeout", type=float, default=30, help="Readiness timeout in seconds")
args = parser.parse_args()
options = Options()
options.add_argument("--headless")
options.add_argument("--window-size=1440,1000")
driver = webdriver.Chrome(options=options)
try:
driver.set_page_load_timeout(args.timeout)
driver.get(args.url)
# Replace or extend this with a page-specific readiness condition.
WebDriverWait(driver, args.timeout).until(
lambda d: d.execute_script(
"return document.readyState === 'interactive' || "
"document.readyState === 'complete';"
)
)
WebDriverWait(driver, args.timeout).until(
lambda d: d.execute_script("return !!document.body;")
)
# Newer protocol versions expose cssContentSize; older ones may expose contentSize.
metrics = driver.execute_cdp_cmd("Page.getLayoutMetrics", {})
size = metrics.get("cssContentSize", metrics.get("contentSize"))
if not size or not size.get("width") or not size.get("height"):
raise RuntimeError(
"Could not find document dimensions in Page.getLayoutMetrics: "
f"available fields: {', '.join(metrics.keys())}"
)
result = driver.execute_cdp_cmd(
"Page.captureScreenshot",
{
"format": "png",
"captureBeyondViewport": True,
"clip": {
"x": 0,
"y": 0,
"width": size["width"],
"height": size["height"],
"scale": 1,
},
},
)
image_bytes = base64.b64decode(result["data"])
Path(args.output).write_bytes(image_bytes)
print(f"Saved {args.output} ({size['width']} × {size['height']} CSS pixels)")
finally:
driver.quit()
if __name__ == "__main__":
main()
python capture_serp.py "https://www.google.com/search?q=example" --output results.png
What the important parts do
driver.execute_cdp_cmd(command, arguments)sends a Chrome DevTools Protocol command through Selenium’s Chromium WebDriver.Page.getLayoutMetricsprovides document layout dimensions. The example checks bothcssContentSizeandcontentSize, since field shapes depend on the protocol version exposed by the browser.- The clip starts at document coordinate
(0, 0)and uses the measured width and height. captureBeyondViewport: trueasks CDP to include content outside the visible viewport. CDP documents this setting as experimental and defaults it to false.- The result’s
datafield is base64-encoded image data. The example requests PNG and decodes those bytes before writing the file.
3. Wait for the SERP content you need
Page load completion does not mean every search result module, image, or deferred section is ready. Pick a bounded wait condition tied to the page and the evidence you want to capture. For example, wait for a known results container, a specific result count, or a page-specific marker. Avoid relying on an unbounded network-idle assumption: search pages may keep analytics or other requests open.
Some pages defer images or modules until they approach the viewport. Controlled scrolling can trigger that loading, but it also changes scroll position and may affect sticky elements. If you scroll to encourage lazy loading, return to the intended capture state, wait for the page to settle, and then query layout metrics again immediately before taking the screenshot. There is no guarantee that a SERP’s content is stable across visits.
4. Formats, dimensions, and capture choices
CDP’s Page.captureScreenshot supports PNG, JPEG, and WebP. PNG is a practical default for text-heavy results because it is lossless. JPEG can reduce file size with some quality loss; WebP is another supported option. Change the format value and output extension together, and verify support for the installed Chrome/CDP version. The CDP command returns base64 data in each case.
| Choice | Guidance |
|---|---|
| Clip width and height | Use the measured document dimensions for a full-document capture. Confirm the metric field and units exposed by your browser protocol version. |
| Clip scale | The example uses 1. A larger scale can increase pixel dimensions and memory use; use it only when higher raster resolution is required and the page remains capturable. |
| Viewport | Set a consistent window size before navigation because responsive SERP layouts change with viewport dimensions. |
| Image format | PNG, JPEG, or WebP are documented CDP formats. Select the format based on fidelity and file size needs. |
| Full document versus viewport | Standard WebDriver screenshot methods save the current window. CDP’s beyond-viewport capture plus a document-sized clip is the full-page approach described here. |
5. Very long pages and stitching fallback
A single image of a very long document can become large in both pixel dimensions and memory use. If Chrome fails to allocate or encode it, a practical fallback is to capture overlapping viewport-sized sections and stitch them into one image. That approach adds complexity: sticky headers can appear repeatedly, lazy-loaded content can shift layout, and timing differences can create seams or duplicate elements. Use a consistent viewport, overlap sections slightly, hide or account for sticky UI if appropriate, and validate the assembled result. Stitching is an engineering fallback, not an equivalent single CDP capture.
6. Troubleshooting
| Symptom | Likely cause | What to try |
|---|---|---|
execute_cdp_cmd is unavailable |
The driver is not a Chromium WebDriver implementation, or Selenium/driver setup differs from the example. | Use Selenium’s Chrome or Chromium driver and consult the installed Selenium Chromium WebDriver API. |
Page.getLayoutMetrics has no cssContentSize |
Metric field names vary across CDP versions. | Inspect the returned keys and check for contentSize; validate the installed Chrome protocol’s metric shape before relying on it. |
Page.captureScreenshot rejects a parameter |
The Chrome/CDP version may not support the command option or may expose a different protocol shape. | Check the browser’s protocol version and command documentation. The tip-of-tree reference changes and is not a version-independent guarantee. |
| Screenshot only shows the viewport | The call may be using a normal window screenshot, omit captureBeyondViewport, or use a clip that is only viewport-sized. |
Use the CDP command with captureBeyondViewport: true and dimensions measured for the document. |
| Bottom of page is blank or incomplete | Deferred content had not loaded when dimensions or pixels were captured. | Wait for the needed page-specific content, optionally scroll to trigger lazy loading, then settle and remeasure before capture. |
| Capture fails on a huge page | The resulting bitmap may exceed available memory or browser encoding limits. | Capture smaller sections and stitch them, or reduce scale/viewport-dependent content where acceptable. |
| Image looks different between runs | SERP content may be dynamic, localized, personalized, or delayed. | Control viewport, browser version, locale/session, URL parameters, wait condition, and capture timing as far as the use case permits. |
7. Performance, reliability, and cost
The main performance costs are starting Chrome, loading the SERP, waiting for deferred content, and encoding a potentially large image. Keep a single browser session open when capturing multiple pages if isolation requirements allow, set bounded navigation and readiness timeouts, and avoid unnecessary scrolling or repeated full-page captures. Large dimensions and high scale increase memory and output size.
For reliable comparisons, record the URL, viewport, browser version, locale/session conditions, wait rule, and timestamp alongside the image. Treat screenshots as observations of one page state, not canonical snapshots of a search engine’s results. This Selenium workflow has infrastructure costs based on where and how you run Chrome; the research sources do not establish a universal cost figure.
8. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. Make one GET request with the SERP URL, then save the returned image. See the ScreenshotNeo API documentation for request options and response details.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://www.google.com/search?q=example -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://www.google.com/search?q=example"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://www.google.com/search?q=example'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));
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. Create a free ScreenshotNeo account.
9. FAQ
Can I use driver.save_screenshot() for a full SERP?
That WebDriver method saves a screenshot of the current window. For content beyond it, use the CDP capture command and a document-sized clip.
Why measure dimensions after waiting?
Dynamic modules and lazy-loaded content can change document size. Measure after the content you intend to include has settled so the clip reflects the page state being captured.
Is the CDP example guaranteed to work with every Chrome version?
No. CDP’s tip-of-tree documentation changes, and the capture option is marked experimental. Check command support and metric fields for the Chrome version installed in your environment.
Does headless mode itself make a screenshot full-page?
No. Headless mode runs Chrome without a visible browser window. The CDP command and its clip define the beyond-viewport capture.


