DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix Now×
Skip to content
HTML

How to Generate an Image from HTML in Python (Playwright and WeasyPrint)

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

Use Playwright when your HTML must look like a browser page: install the Python package and its browser binaries, load the markup, then call page.screenshot(). Use WeasyPrint when the output is a paginated, document-style rendering and your HTML/CSS fits its supported feature set. The runnable examples below cover files, bytes, full pages, individual elements, dynamic content, and common deployment failures.

Choose the renderer before writing code

The right library depends on what “image from HTML” means for your project.

Requirement Recommended path Important qualification
Browser CSS, JavaScript, responsive layout, or a page that should match Chrome Playwright for Python Requires the Playwright package and downloaded browser binaries.
A single component or region Playwright locator screenshot The locator must resolve to a visible, stable element. Covered content is not captured.
Image bytes for an in-memory pipeline Playwright screenshot without a path Pass the returned bytes to storage, an image processor, or an HTTP response.
Document-oriented layout and pagination WeasyPrint Confirm that the HTML and CSS you use are supported; it is not a JavaScript browser.

There is no documented, controlled speed or visual-fidelity benchmark proving one is universally faster or better. Validate both with your actual templates, fonts, assets, and workload.

Generate a browser-rendered PNG with Playwright

1. Install Python and the browser binaries

  1. Create or activate a virtual environment for the project.
  2. Install the library: pip install playwright.
  3. Install its browser binaries: playwright install. This separate download is required in addition to the Python package and affects container and deployment size. See the official setup documentation.

2. Render a complete HTML document

from playwright.sync_api import sync_playwright

html = """
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <style>
      body { font-family: Arial, sans-serif; margin: 40px; }
      .card { padding: 24px; border-radius: 12px; background: #eef4ff; }
    </style>
  </head>
  <body>
    <div class="card"><h1>Hello from HTML</h1><p>Rendered in Chromium.</p></div>
  </body>
</html>
"""

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page(viewport={"width": 1280, "height": 800})
    page.set_content(html)
    page.screenshot(path="output.png", full_page=True)
    browser.close()

set_content() loads the string directly. For a URL, use page.goto("https://example.com", wait_until="networkidle") (or another appropriate load condition) before taking the shot. full_page=True captures the complete scrollable page instead of only the viewport. The screenshot API documents PNG, JPEG, and WebP output; JPEG and WebP accept quality controls.

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

3. Control viewport, device pixels, and transparency

The viewport controls CSS layout. Set device_scale_factor when you need device-pixel output (for example, a retina-style asset):

with sync_playwright() as p:
    browser = p.chromium.launch()
    context = browser.new_context(
        viewport={"width": 1440, "height": 900},
        device_scale_factor=2,
    )
    page = context.new_page()
    page.set_content(html)
    page.screenshot(path="retina.webp", type="webp", quality=85)
    browser.close()

Playwright supports CSS-pixel or device-pixel scaling. Transparent backgrounds apply to supported image types; design the page background accordingly and verify the result in your target viewer.

Capture one element instead of the whole page

Playwright’s locator screenshot is the documented way to capture a single element:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.set_content('<div class="header" style="padding:40px">Header</div>')
    page.locator(".header").screenshot(path="header.png")
    browser.close()

Playwright scrolls the target into view, but the result still reflects what is actually visible. An overlay can cover part of it, and a scrollable container contributes only the content currently scrolled into view. Use a stable selector and wait for it to be visible before capture.

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

Wait for fonts, images, and JavaScript

Dynamic pages can be captured before their final state. Choose an explicit readiness condition instead of relying on an arbitrary sleep.

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto("https://example.com", wait_until="domcontentloaded")
    page.wait_for_selector(".report-ready", state="visible")
    page.screenshot(path="report.png", full_page=True)
    browser.close()

For a page you control, add a class or data attribute after your application has loaded data. You can also wait for a bounded delay when no selector exists, but a selector is less flaky. Ensure remote fonts and images are reachable; otherwise the screenshot may contain fallback fonts or empty boxes.

Return image bytes instead of writing a file

Omit path and Playwright returns bytes. This is useful for an API response or object-storage upload:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.set_content("<h1>In memory</h1>")
    png_bytes = page.screenshot(full_page=True)
    with open("output.png", "wb") as f:
        f.write(png_bytes)
    browser.close()

The library also provides asynchronous APIs. Use the async variant in an asyncio application rather than blocking the event loop with the synchronous API.

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

Use WeasyPrint for document-style HTML

WeasyPrint’s Python API accepts HTML from a string, URL, filename, or file object and lays it out with render(). It is appropriate when pagination and print-like document flow matter more than browser JavaScript.

from weasyprint import HTML

html = """
<!doctype html>
<html><body>
  <h1>Invoice</h1>
  <p>A document-oriented rendering.</p>
</body></html>
"""

document = HTML(string=html, base_url=".").render()
# WeasyPrint is primarily a PDF renderer; write a PDF directly:
HTML(string=html, base_url=".").write_pdf("invoice.pdf")

See the WeasyPrint API reference for accepted inputs and rendering methods. If your HTML contains relative images, stylesheets, or fonts, provide an appropriate base_url (or load from a filename/URL) so those resources can be resolved. The documentation notes that long documents or specially crafted HTML can take a long time to render, so measure your real inputs. If you specifically need a PNG or JPEG, verify the current WeasyPrint API and output support for your version; for a browser-style raster screenshot, Playwright is the more direct choice.

Make output repeatable

  • Pin the Playwright package and browser revision in deployment so layout changes are deliberate.
  • Install the same fonts in development, CI, and production. A missing font changes line wrapping and image dimensions.
  • Fix viewport size, device scale factor, timezone, locale, and data state when pixel comparison matters.
  • Wait for a known readiness selector and ensure images and web fonts have finished loading.
  • Close the browser and contexts in a try/finally block in long-running services.
  • Reuse a browser process carefully for throughput, but isolate pages or contexts so cookies and state do not leak between jobs.

Troubleshoot common failures

“Executable doesn’t exist” or browser launch failure

Run playwright install in the same environment where the code runs. In a container, include the browser binaries and all required system dependencies; a package-only deployment is incomplete.

The screenshot is blank or missing images

Check the page URL, network access, authentication, and relative asset paths. For HTML strings, use absolute URLs or a correct base_url where supported. Wait for a selector that indicates the content is ready.

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

Text wraps differently on the server

Install the intended fonts and use the same browser revision, viewport, scale, and locale. Do not assume screenshots from different machines are byte-for-byte identical without controlling those inputs.

An element screenshot is clipped

Confirm the locator matches one visible element. Remove overlays, scroll the element to the required position, and remember that a scrollable element screenshot captures its currently visible content rather than every hidden row.

JavaScript content never appears

Use page.goto() with a suitable wait condition, then wait for an application-specific selector. Inspect console and network errors in a diagnostic run instead of increasing a timeout indefinitely.

WeasyPrint cannot find a stylesheet or image

Pass a valid base_url, use a filename/URL input, or convert resource references to resolvable absolute URLs. Then confirm that the CSS feature is supported by your installed WeasyPrint version.

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

Performance, reliability, and cost decisions

Browser startup and binary size are real deployment costs. A persistent browser process can avoid launching Chromium for every image, while separate contexts preserve isolation. Limit concurrency to what the host can support, and set practical navigation and selector timeouts so a dead upstream page does not occupy workers forever.

For visual reliability, test representative pages: long lazy-loaded pages, web fonts, cross-origin images, authenticated routes, animations, and elements inside scrollable containers. The cited documentation supplies no comparative benchmark, so choose based on your measured workload rather than a claimed universal speed advantage.

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 website screenshot API and MCP server. One request returns PNG, JPEG, WebP, or PDF and handles browser setup for you. Its capture flow can accept cookie and consent banners, then remove more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status.

Using the ScreenshotNeo API documentation, this is a complete cURL call:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

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}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo also supports full-page and element capture, dark mode, 12 device presets or any viewport, retina scale, PDF paper settings and page ranges, HTML/CSS input, custom JavaScript and CSS, click and wait actions, blocked requests, custom headers/cookies/user agents, timezone and geolocation, resizing, chosen cache TTLs, signed image links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage data, and an OpenAPI specification. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients. Parameter names used by other screenshot APIs also work, which can simplify migration.

The Free plan includes 1,000 screenshots each month with no card. Paid plans start at $5 for 3,000 shots; every feature is on every plan. Create a free ScreenshotNeo account to try it without a card.

FAQ

Can I screenshot HTML without creating a temporary file?

Yes. Playwright’s page.set_content() accepts an HTML string, and omitting path returns image bytes directly.

Should I use PNG, JPEG, or WebP?

PNG is a safe default for text and transparency. JPEG and WebP can reduce file size and expose quality controls; choose based on the consuming application.

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

Does Playwright capture hidden page content?

full_page=True captures the page’s full scrollable document. A locator screenshot captures the selected element’s visible state, not content hidden inside a separately scrollable region.

Can WeasyPrint execute JavaScript?

WeasyPrint is a document layout engine, not a browser runtime. If the page depends on JavaScript to create its content, use Playwright or render the data into the HTML before passing it to WeasyPrint.

Frequently Asked Questions

Can I screenshot HTML without creating a temporary file?

Yes. Playwright’s page.set_content() accepts an HTML string, and omitting path returns image bytes directly.

Should I use PNG, JPEG, or WebP?

PNG is a safe default for text and transparency. JPEG and WebP can reduce file size and expose quality controls; choose based on the consuming application.

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.

Does Playwright capture hidden page content?

full_page=True captures the page’s full scrollable document. A locator screenshot captures the selected element’s visible state, not content hidden inside a separately scrollable region.

Can WeasyPrint execute JavaScript?

WeasyPrint is a document layout engine, not a browser runtime. If the page depends on JavaScript to create its content, use Playwright or render the data into the HTML before passing it to WeasyPrint.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

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

Read next

Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.