ScreenshotNeo

BlogHow-to

How to Convert HTML to an Image in Python Without a Browser

Render HTML without Chrome: use WeasyPrint for a browserless PDF workflow, understand PNG limits, and choose a hosted screenshot API when needed.

By the ScreenshotNeo team1 October 20267 min read

Short answer: for a strict browserless workflow, render the HTML to PDF with WeasyPrint. WeasyPrint’s documented Python API accepts an HTML string, file, file object, or URL and writes PDF output. It does not establish direct PNG output, so producing a PNG requires a separate PDF rasterization step whose package and installation must be selected and verified for your environment.

If you need a browser screenshot rather than a document render, a browser-backed tool is a different solution. The html2image package uses headless Chrome or Chromium, and hosted HTML-to-image APIs may render in real Chrome. A Python SDK or HTTP client does not make that rendering browserless.

1. Decide what “without a browser” means

There are three different jobs that are often called “HTML to image”:

Approach Input and output Browser engine Use it when
WeasyPrint HTML to PDF through Python No browser is involved in its documented document-rendering workflow You need a local, repeatable document render and PDF is acceptable as the first output
html2image HTML, CSS, files, or URLs to screenshots Requires headless Chrome or Chromium Browser rendering is acceptable and you need a screenshot
CairoSVG SVG to PNG, PDF, PS, or SVG Not an HTML renderer Your source can be authored or generated as SVG
Hosted HTML-to-image API HTML or a public URL to PNG; PDF may also be available The reviewed hosted service renders in real Chrome You want hosted capture and accept external browser-backed rendering

Do not choose WeasyPrint solely because your code is Python. Choose it when a browserless HTML/CSS document engine is appropriate and a PDF-first pipeline fits your output requirements.

2. Install WeasyPrint

Install the Python package in an isolated environment:

python -m venv .venv
source .venv/bin/activate
python -m pip install weasyprint

On some operating systems, WeasyPrint also needs native libraries. Follow the current installation instructions in the official getting-started guide for your platform instead of assuming that a wheel alone is sufficient.

3. Render an HTML string to PDF in Python

This is the complete, documented browserless stage. It writes out.pdf, not out.png.

from weasyprint import HTML

html = """\n


  
  Invoice
  


  

Invoice

Rendered from an HTML string with WeasyPrint.

Total: $125.00

""" HTML(string=html).write_pdf("out.pdf") print("Wrote out.pdf")

The same API can read a file, file object, or URL:

from weasyprint import HTML

HTML(filename="report.html").write_pdf("report.pdf")
HTML(url="https://example.com/report").write_pdf("remote-report.pdf")

When you pass a relative image, stylesheet, or font URL, give WeasyPrint a meaningful base URL:

from pathlib import Path
from weasyprint import HTML

base = Path("report.html").resolve().parent.as_uri()
HTML(string=html, base_url=base).write_pdf("report.pdf")

4. Why this does not directly produce a PNG

WeasyPrint’s documented output method is HTML.write_pdf(). The official API reference does not establish a direct PNG writer. A PDF-first pipeline therefore looks like this:

  1. Build or load HTML.
  2. Render HTML to PDF with WeasyPrint.
  3. Rasterize the PDF with a separately selected and verified PDF-to-image tool.

The second step is environment-dependent. Do not label the WeasyPrint example as HTML-to-PNG code, and do not add a PDF rasterizer to production until you have checked its current official documentation, native dependencies, page handling, and licensing for your deployment.

5. HTML and CSS limitations to check

  • HTML that depends on interactive browser behavior may not render the same way in a document engine.
  • JavaScript-driven content, animations, client-side hydration, and click-triggered state require explicit verification against the selected engine.
  • Remote images, stylesheets, and fonts must be reachable from the deployment environment and resolved from the correct base URL.
  • WeasyPrint’s default fetcher can access file and HTTP URLs but does not support advanced cookies or authentication. A custom URL fetcher is the documented workaround.
  • For deterministic output, bundle important fonts and assets where practical and avoid time-dependent content.

6. If your source is SVG, use CairoSVG instead

CairoSVG is documented as an SVG 1.1 converter and supports PNG output. It is a good fit when you control an SVG source; it is not a general HTML/CSS renderer.

import cairosvg

cairosvg.svg2png(url="diagram.svg", write_to="diagram.png")

Converting HTML to SVG first is a separate design and templating project. Do not assume that arbitrary HTML can be passed to CairoSVG.

7. Why html2image is not browserless

html2image wraps headless Chrome or Chromium. It can capture HTML, CSS, files, and URLs, but it does not satisfy a strict requirement to avoid browsers. Its project documentation also says it cannot request a full-page screenshot, which matters for long documents.

8. Troubleshooting

Symptom Likely cause Fix
ModuleNotFoundError: weasyprint The package is not installed in the active environment. Activate the intended virtual environment and run python -m pip install weasyprint.
Import or shared-library error during installation A platform dependency required by WeasyPrint is missing. Use the native-dependency instructions in the official WeasyPrint guide for your operating system.
Images or fonts are missing Relative URLs have no useful base URL, or the worker cannot reach the asset. Pass base_url, use absolute URLs where appropriate, and verify access from the production host.
Authenticated assets return 401/403 The default URL fetcher does not carry your application’s advanced cookies or authentication. Implement a custom URL fetcher, or make the required assets available through a controlled, accessible path.
JavaScript content is absent The document engine is not behaving like an interactive browser for that page. Pre-render the data into HTML, choose a browser-backed renderer, or verify whether your selected engine supports the required behavior.
Expectation of a PNG from write_pdf() WeasyPrint produced the documented PDF output. Add and verify a separate PDF rasterization stage; do not rename the PDF file to .png.
Remote URL hangs or fails DNS, TLS, firewall, timeout, or an inaccessible private URL. Test the URL from the same runtime, set appropriate timeouts in your surrounding fetch code, and provide local or authenticated assets through a supported fetcher.

9. Performance, reliability, and cost planning

  • Performance: rendering time depends on document size, CSS complexity, fonts, images, and network assets. Measure your actual templates rather than borrowing browser-screenshot benchmarks.
  • Reliability: local rendering removes a hosted API dependency but leaves you responsible for native libraries, fonts, asset availability, process isolation, and upgrades.
  • Repeatability: pin Python and system dependencies, keep templates deterministic, and store the generated PDF when it is an audit artifact.
  • Memory: large images and long documents can increase process memory. Bound input size and process jobs in a worker when rendering untrusted or user-supplied HTML.
  • Cost: local software has infrastructure and maintenance costs rather than per-shot API billing. A hosted service adds request pricing and external-data considerations; compare those costs with the engineering time saved.

10. Or skip the browser setup

If you need a clean screenshot of a URL instead of maintaining a local rendering stack, ScreenshotNeo provides a website screenshot API and MCP server. Its API renders the page for you, so it is not a browserless renderer; it is the practical choice when you want a ready-made capture endpoint.

See the ScreenshotNeo API documentation for request options and response details.

cURL

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 data = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', data);

ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and the response identifies the page verdict and billing status with X-Page-Verdict and X-Billed headers. Its MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf. You get 1,000 screenshots per month free with no card; paid plans start at $5 for 3,000 shots.

Create a free ScreenshotNeo account to start with 1,000 screenshots a month and no card.

11. FAQ

Can WeasyPrint save HTML as PNG?

The reviewed documentation establishes HTML-to-PDF with write_pdf(), not direct PNG output. Use a separately verified PDF rasterizer after the PDF stage.

Is a Python HTML-to-image package automatically browserless?

No. Check the rendering engine. html2image uses headless Chrome, and a hosted API can render in real Chrome even when you call it from Python.

When should I choose CairoSVG?

Choose it when the source is SVG and you need SVG-to-PNG or another documented SVG output. It is not a replacement for an HTML renderer.

Can WeasyPrint fetch a private page?

The default fetcher does not support advanced cookies or authentication. Use a custom URL fetcher or provide accessible, controlled assets.

What is the simplest route to a screenshot URL?

Use a hosted screenshot API such as ScreenshotNeo when browser-backed rendering is acceptable and you do not want to install and operate the browser or document-rendering stack yourself.