Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesBuild a FastAPI endpoint that accepts a URL, renders it with Playwright, and returns PNG, JPEG, or WebP bytes directly. The reliable pattern is to launch one browser during FastAPI’s lifespan, create an isolated context per request, bound navigation and input sizes, and close each context even when capture fails.
How the screenshot endpoint works
The client sends a JSON request to POST /screenshot. FastAPI validates the request, Playwright opens the page in an isolated browser context, captures image bytes, and the endpoint returns those bytes with an image media type. The example below uses asynchronous Playwright calls in an asynchronous FastAPI handler.
Returning bytes avoids a temporary image file. This is suitable for synchronous captures that fit your response and request-time limits. For long-running captures or large outputs, an asynchronous job and stored artifact may be a better design.
Build a minimal runnable API
Install the dependencies and browser
In a fresh Python environment, install FastAPI, Uvicorn, and Playwright, then install Chromium:
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →#1 Best Overall
python -m pip install fastapi uvicorn playwright
python -m playwright install chromium
Save the following as main.py. Its limits are example product choices—not universal safe limits—and should be adjusted to the workload and deployment environment.
from contextlib import asynccontextmanager
from typing import Literal
from urllib.parse import urlsplit
from fastapi import FastAPI, HTTPException
from fastapi.responses import Response
from pydantic import BaseModel, Field
from playwright.async_api import async_playwright
MEDIA_TYPES = {
"png": "image/png",
"jpeg": "image/jpeg",
"webp": "image/webp",
}
class ScreenshotRequest(BaseModel):
url: str = Field(min_length=1, max_length=2048)
width: int = Field(default=1280, ge=320, le=2560)
height: int = Field(default=720, ge=240, le=1600)
full_page: bool = False
image_type: Literal["png", "jpeg", "webp"] = "png"
def validate_public_url(url: str) -> None:
"""Basic syntax check only; not a complete SSRF defense."""
parsed = urlsplit(url)
if parsed.scheme not in {"http", "https"} or not parsed.hostname:
raise HTTPException(status_code=422, detail="url must be an absolute HTTP or HTTPS URL")
@asynccontextmanager
async def lifespan(app: FastAPI):
playwright = await async_playwright().start()
browser = await playwright.chromium.launch()
app.state.playwright = playwright
app.state.browser = browser
try:
yield
finally:
await browser.close()
await playwright.stop()
app = FastAPI(lifespan=lifespan)
@app.post("/screenshot")
async def screenshot(request: ScreenshotRequest):
validate_public_url(request.url)
browser = app.state.browser
context = await browser.new_context(
viewport={"width": request.width, "height": request.height}
)
try:
page = await context.new_page()
try:
await page.goto(request.url, wait_until="domcontentloaded", timeout=15_000)
image = await page.screenshot(
full_page=request.full_page,
type=request.image_type,
timeout=15_000,
)
except Exception as exc:
# Do not return internal exception details to the caller.
raise HTTPException(status_code=502, detail="Page navigation or screenshot capture failed") from exc
return Response(content=image, media_type=MEDIA_TYPES[request.image_type])
finally:
await context.close()
Run the development server with:
uvicorn main:app --reload
Send a request from another terminal:
curl -X POST http://127.0.0.1:8000/screenshot
-H 'Content-Type: application/json'
-d '{"url":"https://example.com","width":1280,"height":720,"full_page":false,"image_type":"png"}'
--output screenshot.png
The resulting file should be a PNG. For JPEG or WebP, change image_type and the output filename accordingly. The API returns image bytes rather than JSON; inspect the HTTP status and response headers when diagnosing failures.
Choose capture scope and page readiness
Viewport, full page, or one element
The default screenshot covers the current viewport. Set full_page to true to capture the full document; this can create a much taller image and use more memory, so retain sensible dimension and timeout limits. Playwright also supports capturing a specific locator when the API needs a component rather than the whole page. See Playwright’s Python screenshot guide.
Rank #2
- HTML CSS Design and Build Web Sites
- Comes with secure packaging
- It can be a gift option
Pick a navigation wait condition deliberately
The example waits for domcontentloaded, then captures. That is a practical starting point, not a guarantee that every page’s fonts, images, or client-rendered content are ready. For a page with a known element, wait for that locator before capture. A fixed delay can help with a known animation or delayed render, but adds latency and does not prove readiness. networkidle can be unsuitable for pages with ongoing network activity, so test the chosen condition against the pages you intend to support.
Return image bytes correctly
FastAPI passes a returned Response subclass directly to the client; it does not serialize or validate the body as a Pydantic response model. The endpoint therefore sets both the raw screenshot body and its matching media type itself. The example maps PNG to image/png, JPEG to image/jpeg, and WebP to image/webp. See FastAPI’s direct response documentation.
For callers that need metadata as well as an image, add explicit response headers or use a separate JSON job/status endpoint. Do not wrap image bytes in JSON unless the client specifically needs an encoded representation; doing so adds encoding overhead.
Rank #3
Manage browser and request lifecycles
FastAPI’s lifespan context runs setup before the application accepts requests and cleanup after request handling ends. It is an appropriate place for a process-wide Playwright browser. Each request in this example gets a new context, which separates browser state such as cookies and storage from other requests; the finally block closes it on both success and failure.
This design reuses the browser process while isolating per-request contexts. Launching a browser for every request is another, simpler isolation model, but has different startup and resource costs; the reviewed documentation does not establish comparative performance figures. For higher throughput, consider a bounded queue or browser-pool strategy only after measuring your workload, and make sure shutdown drains or cancels outstanding work safely. See FastAPI lifespan events.
Protect a public screenshot endpoint
An endpoint that navigates to caller-supplied URLs is a security boundary: it can otherwise be used to probe internal services or consume excessive browser resources. The sample’s URL parser checks only basic syntax. It is not an SSRF defense and must not be treated as one for a public service.
Rank #4
- Brand: Wiley
- Set of 2 Volumes
- A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers
- Restrict destinations according to your use case. A robust policy must account for loopback, private and link-local addresses, DNS resolution, redirects, and outbound network controls; checking the hostname string alone is insufficient.
- Keep finite bounds for viewport width and height, navigation and capture timeouts, request concurrency, and request frequency. Limit or disable full-page capture if very long documents could exhaust memory.
- Require authentication and apply rate limits if the endpoint is exposed beyond a trusted network. Avoid returning browser traces, internal hostnames, or stack details to callers.
- Decide whether to block downloads, popups, and unnecessary resources, and define how cookies or credentials supplied by a caller are handled. Do not expose arbitrary browser launch flags as request options.
Playwright’s Docker guidance treats untrusted websites as a special case and discusses running with a dedicated user and an appropriate seccomp profile for crawling and scraping. These measures complement—not replace—destination restrictions and resource limits.
Deploy with aligned Playwright and browser versions
For a custom container, install Python, the Playwright package, browser binaries, and their system dependencies. Pin the Playwright package and use a browser image/version that matches it: a mismatch can prevent Playwright from finding its browser executable. Validate the selected base image, fonts, and system packages in the actual target environment. See Playwright’s Docker documentation.
- Use an init process in a container to help handle child processes and avoid PID 1 zombie-process issues.
- For Chromium, Playwright recommends
--ipc=host; without sufficient shared memory, Chromium may run out of memory and crash. - For untrusted-site crawling or scraping, follow Playwright’s guidance on a dedicated non-root browser user and seccomp configuration. Do not treat disabling the browser sandbox as a general production shortcut.
Container flags and security settings depend on the hosting environment; verify which controls it supports rather than assuming a local Docker command transfers unchanged to every platform.
Best Value
Troubleshooting common failures
| Symptom | Likely cause | What to check or change |
|---|---|---|
| Browser executable not found | Browser binaries were not installed, or the Playwright package and browser image versions do not match. | Install the required browser and system dependencies in the runtime image; align and pin package and image versions. |
| Chromium crashes in a container | Insufficient shared memory or container process-management issues. | Check Playwright’s Docker guidance for --ipc=host with Chromium and an init process for containers. |
| Request returns 502 | Navigation or screenshot capture raised an exception, commonly due to an unreachable page or timeout. | Check that the target is reachable from the API host, review server-side logs, and adjust readiness or timeout policy to suit the page. Keep internal exception details out of client responses. |
| Screenshot is blank or misses dynamic content | The page may not have rendered the relevant content by the selected capture point. | Wait for a page-specific locator or another suitable readiness signal before capturing; verify behavior on the target site. |
| API process slows or runs out of memory | Large full-page images, unbounded concurrent work, or contexts not being closed can increase resource use. | Retain the per-request finally cleanup, bound concurrency and output dimensions, and consider a queued or asynchronous capture model for heavier work. |
| Private or internal page is reachable | Basic URL syntax validation does not prevent SSRF, DNS rebinding, or redirects to internal addresses. | Implement destination and outbound-network controls that cover resolved addresses and redirect hops; do not rely on hostname validation alone. |
Or skip the browser setup:
If you need the endpoint’s result without operating Playwright and browser infrastructure, ScreenshotNeo accepts one GET request with a URL and returns a screenshot or PDF. For example:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
See the ScreenshotNeo API documentation for setup and options. It removes cookie/consent banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, failed loads, timeouts, and cache hits are not billed. Its MCP server lets AI agents use screenshot tools, and the free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Sign up free for 1,000 screenshots a month with no card.
Frequently Asked Questions
Can I capture a single element instead of the whole page?
Yes. Playwright supports screenshots scoped to a locator; use that when the API should return a component rather than the page viewport.
Can this endpoint return a PDF?
The example returns PNG, JPEG, or WebP images. PDF generation is a separate Playwright page operation and would need its own response media type and request contract.
Free tools Windows power users keep installed
One-click scans. No signup required.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




