Site-Shot

Tutorial ·Apr 6, 2026 ·7 min read

How to Take a Website Screenshot with Python

Capturing a website screenshot programmatically is a common requirement for monitoring dashboards, link preview generators, SEO tools, and testing pipelines. In this guide, you'll learn how to take a screenshot of any website using Python and the Site-Shot API.

There are two ways to do it, and both are here. Site-Shot ships an official Python SDK — pip install site-shot — a typed client with zero dependencies. If you'd rather not add a dependency at all, the API is one HTTP GET, so requests is enough.

Prerequisites

  • Python 3.9+ for the SDK (pip install site-shot), or any Python 3 with requests for the plain-HTTP route
  • A Site-Shot API key — API plans start at $5/mo for 2,000 screenshots (pricing). The free browser tool at site-shot.com needs no signup and no key, but it's a browser tool, not an API tier

The Official Python SDK

pip install site-shot
from site_shot import SiteShot

client = SiteShot("YOUR_API_KEY")  # or set SITESHOT_API_KEY in the environment

png = client.capture("https://example.com/", full_size=True)
with open("screenshot.png", "wb") as f:
    f.write(png)

The PyPI distribution is site-shot and the import package is site_shot — the two spellings are never swapped. The client is standard library only (zero dependencies), needs Python 3.9 or newer, ships type hints, and is synchronous. The target URL is positional; every capture option is a keyword argument. Source and issues live at github.com/site-shot/site-shot-python.

Four Return Modes

One capture concept, four methods — you pick the return type instead of passing a flag:

png = client.capture("https://example.com/")                          # bytes
client.capture_to_file("https://example.com/", "shot.png")            # writes the file, returns the path
b64 = client.capture_base64("https://example.com/")                   # str, data-URL prefix stripped
meta = client.capture_json("https://example.com/", source_code=True)  # dict: image + metadata

A fifth method, build_url(), returns the request URL without sending it — useful when debugging a capture. It embeds your API key as userkey and Site-Shot has no signed-URL scheme, so keep that URL server-side: never put it in an <img src> attribute.

No Dependency: One Plain GET

The API is a single GET request, so the dependency-free route is still first-class:

import requests

response = requests.get("https://api.site-shot.com/", params={
    "url": "https://example.com",
    "userkey": "YOUR_API_KEY",
    "width": 1280,
    "height": 1024,
    "format": "png",
}, timeout=70)

with open("screenshot.png", "wb") as f:
    f.write(response.content)

That's it. The API renders the page in a real Chromium browser and returns the image bytes directly. Every keyword the SDK accepts is one of these query parameters, spelled the same way, so the reference table below serves both routes.

Full Page Screenshot

To capture the entire scrollable page rather than just the viewport, ask for full_size and cap the result with max_height (up to 20,000 pixels):

png = client.capture("https://example.com/", full_size=True, max_height=15000)
# plain HTTP: add "full_size": 1 and "max_height": 15000 to params

The API scrolls through the whole document before capturing, which also triggers lazy-loaded images.

Mobile Device Screenshot

There is no device preset — you set the viewport and the user agent yourself:

png = client.capture(
    "https://example.com/",
    width=375,
    height=812,
    user_agent="Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) "
               "AppleWebKit/605.1.15 (KHTML, like Gecko) "
               "Version/16.0 Mobile/15E148 Safari/604.1",
)

Screenshot from a Specific Country

Pass a two-letter ISO 3166-1 country code and the API routes the render through an IP in that country, with a matching language, time zone, and geolocation:

from site_shot import SiteShot, CountryUnavailableError

client = SiteShot("YOUR_API_KEY")

try:
    png = client.capture("https://example.com/", country="DE", strict_country=True)
except CountryUnavailableError:
    ...  # no capacity in DE right now — retry later, or drop strict_country

Use ISO codes only — country="DE", not the country's full name, which is not a valid value and silently renders from the US instead. The same silent US fallback happens when the requested country has no capacity at that moment; strict_country turns that into an honest failure — country_unavailable in the API's JSON, CountryUnavailableError in the SDK. Country routing is included with any paid plan.

JSON, Metadata and Rendered HTML

When you need more than the image — the target's HTTP status, its response headers, or the HTML as rendered — ask for the JSON result:

meta = client.capture_json("https://example.com/", source_code=True)

print(meta["response"]["status_code"])   # 200
print(meta["response"]["headers"])       # [{"name": ..., "value": ...}, ...]
print(meta["source_code"][:200])         # rendered HTML

On the plain-HTTP route the same envelope comes back from response_type=json, and the image arrives as a data URL you strip and decode yourself:

import base64
import requests

response = requests.get("https://api.site-shot.com/", params={
    "url": "https://example.com",
    "userkey": "YOUR_API_KEY",
    "response_type": "json",
}, timeout=70)

payload = response.json()
if payload.get("error"):
    raise RuntimeError(payload["error"])

with open("screenshot.png", "wb") as f:
    f.write(base64.b64decode(payload["image"].split(",", 1)[-1]))

The envelope carries image (a base64 data URL), response (the target's status_code and headers), screenshot_parameters (every value the renderer actually used), and source_code when you ask for it. A failed capture arrives in-band as a JSON body with an error field — country_unavailable, for instance — which is why that check comes before the decode. The SDK runs the check for you and raises instead: AuthError, QuotaError, InvalidParamsError, CountryUnavailableError, SiteShotTimeoutError or APIError, all subclasses of SiteShotError.

Batch Screenshots

To capture multiple URLs, loop through them:

urls = ["https://example.com", "https://wikipedia.org", "https://github.com"]

for url in urls:
    filename = url.replace("https://", "").replace("/", "_") + ".png"
    client.capture_to_file(url, filename, width=1280, height=1024)
    print(f"Saved {filename}")

For higher throughput, move that call into a concurrent.futures.ThreadPoolExecutor. The client holds no pooled connections, so one instance can be shared across threads; the API supports concurrent requests based on your plan's dedicated worker count.

Where the honest limits are

  • Two languages have an SDK. Node.js (npm install site-shot-sdk) and Python (pip install site-shot). In Go, Ruby, Java, C# or PHP you call the same HTTP endpoint directly — the plain-GET section above is the whole integration.
  • The Python client is synchronous. There is no async client, so in asyncio code run a capture in a worker thread (asyncio.to_thread).
  • The API always needs a key. The no-signup path is the browser tool, not the API.
  • Images only. Site-Shot returns PNG or JPEG — no PDF and no video output.

Parameter Reference

The SDK takes booleans where the query string takes 1/0.

Parameter Description Example
url Target web page (positional in the SDK) https://example.com
userkey Your API key (SDK: constructor argument) abc123
width Viewport width (100–8000) 1280
height Viewport height (100–20000) 1024
full_size Capture full page True / 1
max_height Height cap for full-page captures 15000
format Output format png or jpeg
delay_time Wait before capture (ms) 2000
response_type Response format (SDK: pick the method) image or json
source_code Include the rendered HTML True / 1
country Proxy country (two-letter ISO code) DE
strict_country Fail instead of falling back to the US True / 1
no_ads / no_cookie_popup Strip ads / cookie banners True / 1

Check the full API documentation for all available parameters. For scheduled captures without code, see automatic daily screenshots; for the viewport-vs-full-page decision, see full page vs viewport screenshots; for country routing in depth, see screenshots from another country.

FAQ

Is there an official Python SDK for Site-Shot?

Yes. pip install site-shot installs the official client (the import package is site_shot): zero dependencies, Python 3.9 and newer, type hints included, synchronous. It wraps the same HTTP API — client.capture(url, full_size=True) returns PNG bytes, and capture_to_file(), capture_base64() and capture_json() cover the other return modes. Node.js has an official SDK too (npm install site-shot-sdk); in every other language you call the HTTP API directly.

Do I need an API key to take website screenshots with Python?

For the Site-Shot API, yes — every request carries your userkey (the SDK takes it in the constructor and sends it for you), and keys come with any paid plan (from $5/mo for 2,000 screenshots). For a quick one-off capture without code, the free in-browser tool at site-shot.com needs no signup and no key at all.

How do I capture a full-page screenshot in Python?

Pass full_size=True to the SDK, or add full_size=1 to a plain HTTP request, and cap the height with max_height (up to 20,000 pixels). The API scrolls the whole document — triggering lazy-loaded content — and returns one tall image.

Can Python capture a screenshot as seen from another country?

Yes. Pass country with a two-letter ISO code (for example country=DE) and the API routes the render through a real IP in that country, with matching language, time zone, and geolocation. Country routing is included with any paid plan.

Capture your first screenshot free in your browser — no signup — at site-shot.com, or compare API plans on the pricing page.

← All articles