Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use a real browser renderer when the JPEG must look like the HTML in a modern browser. Playwright’s Python API loads your markup, applies CSS and JavaScript, waits for the page to become ready, and writes a JPEG directly. You control viewport size, JPEG quality, full-page capture, and element-level capture. The trade-off is that Playwright requires both the Python package and browser binaries.

Convert an HTML string to a JPEG

Install Playwright and its browser binaries first:

python -m pip install --upgrade pip
python -m pip install playwright
playwright install

The last command downloads the browser engines used by Playwright. A synchronous script is the shortest complete example:

from playwright.sync_api import sync_playwright

html = """

  
    
    
  
  
    

Rendered HTML

This card becomes a JPEG.

""" with sync_playwright() as p: browser = p.chromium.launch() page = browser.new_page(viewport={"width": 1280, "height": 900}) page.set_content(html, wait_until="load") page.screenshot( path="output.jpeg", type="jpeg", quality=90, full_page=True, ) browser.close()

type="jpeg" selects JPEG output, while quality accepts an integer from 0 to 100. The documented default JPEG quality is 80; setting 90 usually preserves more detail at the cost of a larger file. full_page=True captures the page’s complete scrollable height instead of only the viewport.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What each Playwright setting does

Viewport and device scale

The viewport determines the CSS layout that is rendered. A 375-pixel viewport can trigger a mobile breakpoint, while 1280 pixels may select a desktop layout. Set it when reproducible output matters:

page = browser.new_page(
    viewport={"width": 1440, "height": 1000},
    device_scale_factor=1,
)

Use a higher device scale factor when you need denser pixels, but expect greater memory use. Keep the value fixed in CI so output does not change between runs.

Full page versus the visible viewport

Omit full_page (or set it to False) for exactly the visible viewport. Use full_page=True for documents, landing pages, and long reports. Extremely tall pages can consume substantial memory; splitting a long document into sections may be more reliable.

Capture one element

A locator can save only a component, chart, or card:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
card = page.locator(".card")
card.screenshot(path="card.jpeg", type="jpeg", quality=92)

The element must exist and be visible. If it is created by JavaScript, wait for a selector or a meaningful state before taking the shot.

Return bytes instead of writing a file

Remove path to receive JPEG bytes. This is useful for an HTTP response, object storage upload, or an image-processing pipeline:

jpeg_bytes = page.screenshot(type="jpeg", quality=85, full_page=True)
with open("output.jpeg", "wb") as f:
    f.write(jpeg_bytes)

Convert a local HTML file

For a local file, use a file:// URL. Converting the path to an absolute URI avoids working-directory surprises:

from pathlib import Path
from playwright.sync_api import sync_playwright

html_file = Path("report.html").resolve()

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page(viewport={"width": 1280, "height": 900})
    page.goto(html_file.as_uri(), wait_until="load")
    page.screenshot(path="report.jpeg", type="jpeg", quality=90, full_page=True)
    browser.close()

Relative images, stylesheets, and fonts must be reachable from that file URL. If the document references resources that require a web server, serve the directory locally and navigate to its HTTP address instead.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Convert a live URL

Navigate to the page and choose a readiness condition that matches the site:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page(viewport={"width": 1280, "height": 900})
    page.goto("https://example.com", wait_until="networkidle")
    page.screenshot(path="site.jpeg", type="jpeg", quality=88, full_page=True)
    browser.close()

networkidle waits for network activity to settle, but it is not appropriate for every application: analytics, advertisements, or live feeds can keep making requests. In those cases, use wait_until="load" and then wait for the page state that proves the content is ready:

page.goto("https://example.com/dashboard", wait_until="domcontentloaded")
page.locator("main.dashboard").wait_for(state="visible")
page.screenshot(path="dashboard.jpeg", type="jpeg", quality=90, full_page=True)

For a known animation or delayed data request, a targeted wait is preferable to an arbitrary long sleep. If the page changes after capture, disable animations with a stylesheet or wait for the final selector state.

Control rendering before capture

Set page content and wait for fonts

set_content is convenient for generated HTML. External fonts and images may still be loading when the initial document event fires. Wait for a specific element, or for fonts when typography is important:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
page.set_content(html, wait_until="load")
page.evaluate("document.fonts ? document.fonts.ready : Promise.resolve()")
page.locator(".card").wait_for(state="visible")
page.screenshot(path="fonts.jpeg", type="jpeg", quality=90)

Make responsive output deterministic

  • Set an explicit viewport rather than relying on a machine’s default window size.
  • Choose one browser engine and pin your Playwright version in deployment.
  • Use fixed test data and wait for content to finish rendering.
  • Keep timezone, locale, and user-agent choices consistent when the page formats dates or currencies.

JPEG limitations

JPEG is lossy and does not preserve transparency. If your design has transparent areas, a solid page background is safer; otherwise choose a format that supports alpha for that stage of your pipeline. Text and line art can show compression artifacts at low quality, so compare quality 80, 90, and 100 for your output size and readability requirements.

Async Python version

The asynchronous API fits an async web service or a batch worker:

import asyncio
from playwright.async_api import async_playwright

async def render():
    html = "<html><body><h1>Async JPEG</h1></body></html>"
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        page = await browser.new_page(viewport={"width": 1280, "height": 900})
        await page.set_content(html, wait_until="load")
        await page.screenshot(
            path="async-output.jpeg",
            type="jpeg",
            quality=90,
            full_page=True,
        )
        await browser.close()

asyncio.run(render())

Reuse one browser process for multiple pages in a worker, but create a separate browser context when jobs need isolation. Always close pages, contexts, and the browser in error paths.

Alternatives to Playwright

Approach Rendering model JPEG path Important trade-off
Playwright Browser-faithful Chromium, Firefox, or WebKit rendering Direct screenshot with JPEG type and quality Requires the Python package and browser binaries
imgkit/wkhtmltoimage Wrapper around the external wkhtmltoimage utility imgkit.from_file('test.html', 'out.jpg') You must install and operate the separate utility; modern JavaScript/CSS fidelity can differ from a current browser
WeasyPrint Primarily an HTML/CSS-to-PDF renderer Render PDF first, then rasterize that PDF to JPEG Not a direct browser screenshot; untrusted HTML or CSS can create security problems

Choose Playwright when JavaScript, web fonts, responsive layout, or pixel similarity to a browser matters. Choose imgkit when an existing wkhtmltoimage deployment already meets your rendering needs. Choose WeasyPrint when PDF is the intended intermediate or final document and a separate rasterization step is acceptable.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Troubleshooting

“Executable doesn’t exist” or browser launch failure

The Python package and browser binaries are separate installations. Run playwright install in the same environment used by the script. In containers, install the required system dependencies as documented for your base image and avoid assuming a developer workstation’s browsers are available.

Blank or incomplete JPEG

Capture may occur before client-side rendering finishes. Replace a broad timeout with a readiness check such as locator.wait_for(), wait for fonts, and verify that the target selector is visible. For a URL, confirm that the process can reach every required resource.

Missing images or fonts

Check relative paths, CORS and authentication. A file:// page cannot magically access resources that require a server or credentials. Serve the files over HTTP when necessary, or provide the required request context and cookies.

Layout differs between machines

Fix the viewport, browser engine, device scale factor, fonts, locale, timezone, and input data. A page that depends on system fonts can reflow when those fonts are absent; package the fonts or use a controlled environment.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Full-page capture is too large

Reduce viewport width only if the intended layout allows it, capture selected elements, or divide the document into sections. Increase JPEG compression moderately rather than relying on an extreme quality reduction that damages text.

Network-idle never completes

Live dashboards, polling, analytics, and ads can keep requests open. Use domcontentloaded or load, then wait for a specific content selector. This makes the capture rule explicit and avoids an indefinite readiness wait.

Reliability, performance, and security

  • Startup cost: launching a browser for every image is slower than reusing a process. A controlled worker can keep the browser alive and create short-lived contexts for jobs.
  • Concurrency: each page consumes CPU and memory, especially for full-page screenshots. Limit parallel pages and measure on the largest documents you expect.
  • Retries: retry transient navigation failures, but do not blindly repeat a page with side effects. Capture after a deterministic readiness check and record the URL, viewport, browser version, and quality.
  • Untrusted input: HTML and CSS can trigger network access, expensive layouts, or unexpected file and browser behavior. Isolate renderer workers, restrict outbound access where appropriate, enforce time and memory limits, and review the browser sandbox configuration before processing user-supplied content. WeasyPrint’s documentation specifically warns about security problems from untrusted HTML or CSS; the same input-trust review is prudent for any renderer.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

ScreenshotNeo is a hosted screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF, so Python code does not need to install or maintain browser binaries. Before capture it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in X-Page-Verdict and X-Billed headers.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

For Python, request JPEG explicitly with the API’s image options; the basic call is:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo documentation for the JPEG parameter and the other controls: full-page and CSS-selector capture, dark mode, device presets and custom viewports, retina scale, PDF settings, custom CSS or JavaScript, clicks, waits, request blocking, headers, cookies, user agents, timezone and geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, usage data, and the OpenAPI specification. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

The Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is included on every plan, and yearly billing gives two months free. Create a free ScreenshotNeo account to start.

FAQ

Is this conversion the same as parsing HTML into an image?

No. A screenshot renders the document as a browser would, including layout, CSS, and JavaScript, then encodes the rendered pixels as JPEG.

Can Playwright capture just a chart instead of the entire page?

Yes. Use a locator for the chart or other element and call its screenshot method; the element must be present and visible.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why does a JPEG look different from the browser tab?

Viewport, fonts, device scale factor, page state, and JPEG compression all affect pixels. Fix those inputs and capture only after the final content is visible.

Frequently Asked Questions

Can I convert HTML to JPEG without opening a visible browser window?

Yes. Playwright launches Chromium headlessly by default, so the rendering happens without a desktop window.

Does Playwright support other image formats?

The screenshot API supports image output types including JPEG; select the required type in the screenshot options.

When should I prefer PDF over JPEG?

Use PDF when pagination, selectable text, or print-oriented layout is more important than a single raster image.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.