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

Use aiohttp to download the HTML, then hand the result to a renderer. For static, print-oriented markup, WeasyPrint is the smallest solution. For pages that depend on JavaScript, browser layout, or print behavior, use Playwright instead. aiohttp performs the asynchronous fetch; it does not itself convert HTML into a PDF.

What the pipeline does

The conversion has two separate stages:

  1. Fetch: an aiohttp.ClientSession requests the source, checks the status, and decodes the response.
  2. Render: WeasyPrint or a browser engine turns HTML, CSS, images, and fonts into a PDF.

Keeping those stages separate makes failures easier to diagnose. A 404 is a fetch problem; missing JavaScript content is a renderer choice; an absent logo may be a relative-URL or authentication problem.

Static HTML: aiohttp plus WeasyPrint

Install the Python packages and the native libraries required by WeasyPrint for your operating system. Then run this complete example:

import asyncio
from pathlib import Path

import aiohttp
from weasyprint import HTML


async def html_to_pdf(url: str, output_path: str) -> None:
    timeout = aiohttp.ClientTimeout(total=30)
    async with aiohttp.ClientSession(timeout=timeout) as session:
        async with session.get(url, allow_redirects=True) as response:
            response.raise_for_status()
            content_type = response.headers.get("Content-Type", "")
            if "text/html" not in content_type and "application/xhtml+xml" not in content_type:
                raise ValueError(f"Expected HTML, got {content_type!r}")
            html = await response.text()
            final_url = str(response.url)

    Path(output_path).parent.mkdir(parents=True, exist_ok=True)
    HTML(string=html, base_url=final_url).write_pdf(output_path)


if __name__ == "__main__":
    asyncio.run(html_to_pdf("https://example.com", "out.pdf"))

response.raise_for_status() stops a failed HTTP request before rendering. base_url is essential: it lets relative links such as /styles/site.css and images/logo.png resolve against the page’s final URL after redirects.

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.
#1 Best Overall
Elebase USB to USB C Adapter for iPhone 18 Pro Max,USBC Car Charger Adapter
  • Read Before You Buy — No Video Output: These adapters support charging and USB 2.0 data transfer, but cannot transmit video signals. Except for standard USB webcams (which use USB data only), they are not compatible with HDMI/DisplayPort cables, video-capable USB-C hubs, or docking stations with video output.
  • Convert USB-A Ports to USB-C: Designed to connect USB-C earphones, cables, flash drives, card readers, and other USB-C accessories to standard USB-A ports. Plug-and-play with no drivers or software required.
  • Aluminum Alloy Housing: Built with a sturdy aluminum alloy shell that aids in heat dissipation and protects against daily wear and scratches. Designed to maintain a stable and secure connection.
  • Compact & Travel-Friendly: The ultra-compact design allows the adapter to stay plugged into your device without blocking adjacent ports or adding bulk, reducing wear and tear on your original USB ports.
  • 12-Month Warranty: Backed by a 12-month manufacturer warranty for peace of mind. Designed to meet strict quality control standards for reliable everyday performance.

Why this works well for static pages

WeasyPrint accepts an HTML string and exposes HTML.write_pdf(). It handles HTML and print-oriented CSS without starting a full browser, so the program is straightforward and often has a lower startup cost. It does not execute page JavaScript. A script that inserts the article after load will therefore produce an incomplete document.

Large responses without loading everything

response.text(), response.read(), and response.json() load the complete response into memory. For a known small document that is convenient. For a large or user-selected URL, stream chunks and enforce a limit:

import aiohttp


async def download_html(session: aiohttp.ClientSession, url: str,
                        max_bytes: int = 10_000_000) -> tuple[str, str]:
    async with session.get(url, allow_redirects=False) as response:
        response.raise_for_status()
        content_type = response.headers.get("Content-Type", "")
        if "text/html" not in content_type and "application/xhtml+xml" not in content_type:
            raise ValueError("The response is not HTML")

        chunks: list[bytes] = []
        total = 0
        async for chunk in response.content.iter_chunked(64 * 1024):
            total += len(chunk)
            if total > max_bytes:
                raise ValueError("HTML response exceeds the configured limit")
            chunks.append(chunk)

        raw = b"".join(chunks)
        encoding = response.charset or "utf-8"
        return raw.decode(encoding, errors="strict"), str(response.url)

This still assembles the final string because WeasyPrint needs the document, but it prevents an unbounded download. For very large jobs, put a queue in front of the renderer and cap concurrent conversions.

When JavaScript requires Playwright

Choose Playwright when the page needs JavaScript execution, browser layout, client-side data fetching, or browser print behavior. The following example fetches and renders in one browser context:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #2
Anker USB-C Hub, 5-in-1 USB Hub for Laptops, 4K HDMI Multiport Adapter
  • 5-in-1 USB-C Hub: Experience comprehensive connectivity featuring a Power Delivery input, two USB-A 2.0 ports, a USB-A 3.0 port, and an HDMI port. (Note: The USB-C power delivery input port is only for connecting an external wall charger to power your laptop and cannot power peripheral devices.)
  • 90W Pass-Through Charging: Achieve optimal charging with 90W pass-through power to your laptop, supported by a total input of 100W, with the hub reserving 10W for operational efficiency. (Note: Wall charger not included.)
  • Quick Data Transfers: Accelerate your productivity with rapid data transfers using a high-speed 5Gbps USB 3.0 port and two 480Mbps USB 2.0 ports.
  • 4K HDMI Display: Enhance your visual experience with a hub capable of delivering 4K resolution at 30Hz in both mirror and extend modes. Please note that this hub is compatible with MacBook (macOS 12 and newer), Windows 10 and 11, ChromeOS, and laptops equipped with DP Alt Mode and Power Delivery. Note: This device is not compatible with Linux.
  • What You Get: Anker USB-C Hub (5-in-1, 4K HDMI), welcome guide, 18-month warranty, and our friendly customer service.
import asyncio
from playwright.async_api import async_playwright


async def page_to_pdf(url: str, output_path: str) -> None:
    async with async_playwright() as pw:
        browser = await pw.chromium.launch()
        context = await browser.new_context()
        page = await context.new_page()
        try:
            response = await page.goto(url, wait_until="networkidle", timeout=30_000)
            if response is None or not response.ok:
                status = response.status if response else "no response"
                raise RuntimeError(f"Navigation failed: {status}")
            await page.pdf(path=output_path, format="A4", print_background=True)
        finally:
            await context.close()
            await browser.close()


asyncio.run(page_to_pdf("https://example.com", "out.pdf"))

Playwright’s page.pdf() uses print CSS media by default. If the design is written for screens, call await page.emulate_media(media="screen") before generating the PDF. Replace networkidle with a specific selector wait when an application keeps long-lived connections open:

await page.goto(url, wait_until="domcontentloaded", timeout=30_000)
await page.wait_for_selector("main.article", state="visible", timeout=15_000)
await page.pdf(path="out.pdf", format="A4")

WeasyPrint or Playwright?

Requirement WeasyPrint Playwright
Already-rendered HTML and print CSS Usually the simpler choice Works, but starts a browser
JavaScript-generated content Not executed Supported
Exact browser layout and print behavior Different rendering engine Strongest match to Chromium
Startup and resource footprint Generally lighter Browser processes require more memory
Authenticated subresources Requires a custom URL fetcher Use browser context cookies, headers, or authentication

These are capability-based trade-offs, not a performance benchmark. Pick the renderer that matches the page rather than converting every URL with a browser by default.

Authentication, assets, and URL handling

WeasyPrint resources

WeasyPrint’s default fetcher can retrieve HTTP and file resources. Cookies, authorization headers, and other advanced controls require a custom URL fetcher. If the source is private, downloading HTML with aiohttp does not automatically authenticate every CSS, image, or font request made later by WeasyPrint.

Playwright resources

Create a context with the required cookies, extra headers, user agent, timezone, or geolocation before navigation. Wait for the actual content selector rather than assuming that a network event means the page is visually complete.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #3
Sale
Anker USB C Hub, 7in1 Multi-Port USB Adapter, 4K@60Hz USBC to HDMI Splitter
  • Sleek 7-in-1 USB-C Hub: Features an HDMI port, two USB-A 3.0 ports, and a USB-C data port, each providing 5Gbps transfer speeds. It also includes a USB-C PD input port for charging up to 100W and dual SD and TF card slots, all in a compact design.
  • Flawless 4K@60Hz Video with HDMI: Delivers exceptional clarity and smoothness with its 4K@60Hz HDMI port, making it ideal for high-definition presentations and entertainment. (Note: Only the HDMI port supports video projection; the USB-C port is for data transfer only.)
  • Double Up on Efficiency: The two USB-A 3.0 ports and a USB-C port support a fast 5Gbps data rate, significantly boosting your transfer speeds and improving productivity.
  • Fast and Reliable 85W Charging: Offers high-capacity, speedy charging for laptops up to 85W, so you spend less time tethered to an outlet and more time being productive.
  • What You Get: Anker USB-C Hub (7-in-1), welcome guide, 18-month warranty, and our friendly customer service.

Encoding and redirects

Use the response’s declared charset when decoding. If server metadata is wrong, supply an explicit encoding appropriate to your input. Record the final URL after redirects and use it as base_url. For user-controlled URLs, consider disabling redirects or validating every redirect destination.

Security and reliability checklist

  • Set both connect and total timeouts; a remote server can otherwise hold a worker indefinitely.
  • Validate status and content type before invoking a renderer.
  • Limit response bytes and PDF job duration.
  • Restrict outbound requests, redirects, private-network access, and local-file access when URLs are supplied by users.
  • Treat HTML, CSS, images, fonts, cookies, and redirects as untrusted. WeasyPrint warns that untrusted HTML or CSS can create security problems.
  • Run rendering in an isolated worker with a process or container limit, especially for public conversion endpoints.
  • Reuse one ClientSession for batches instead of creating a session per URL.
  • Log the source URL, final URL, HTTP status, renderer, elapsed time, and failure class without logging secrets.

Common failures and fixes

“ClientResponseError” or an HTTP 4xx/5xx

The server rejected the request or returned an error page. Inspect the status, response headers, and (for diagnostics) a bounded portion of the body. Supply required authentication or a realistic user agent only when you are authorized to access the page.

The PDF is blank or missing the article

The content is probably inserted by JavaScript, hidden until interaction, or behind a consent screen. Switch to Playwright, wait for the content selector, and perform the required interaction before page.pdf().

Images, CSS, or fonts are missing

Check the document’s relative URLs and the renderer’s base URL. Confirm that resources are publicly reachable or pass their credentials. A successful HTML download does not prove that every subresource can be fetched.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #4
Sale
UGREEN USB to USB C Adapter Combo 4-Pack, 10Gbps USB C Converter Space Gray
  • Dual Converters, Infinite Potential:Includes 2× USB C male to USB A female adapters and 2× USB A male to USB C female adapters. Perfect for a wide range of uses—tablets with Bluetooth keyboards, expand USB ports on macbook, and more. Two different converters for all your daily needs
  • Next-Level 10Gbps & 3A Charging: No more slow 480Mbps, this usb to usb c adapter has a transfer speed of up to 10Gbps, allowing you to do more transferring in less time. This usb adapter fits both USB A and USB C charger, supporting up to 3A fast charging
  • Upgraded Exquisite Craftsmanship: With an aluminum alloy housing and metal connector, the usbc to usb adapter is extremely durable and sturdy. Rigorously tested to withstand more than 10,000 times of plugging and unplugging, ensuring long-lasting performance
  • Broad Compatible: The usb c to usb adapter widely supports all USB C/ USB A devices like laptops, tablets, cellphones, car chargers, and phone chargers. Such as compatible with MacBook Pro/Air 2023/2022, Thunderbolt 4/3 Devices,Apple MagSafe Watch 9/8/7/SE/Ultra, iPad Pro 2022/2021, Samsung Galaxy S23/S20/S10, and iPhone 17/16/15 Pro. Plug and play
  • Please Note: To reach 10Gbps speed, keep the cable under 3.3 ft. For USB A Male to USB C adapters, try flipping the USB C connector. USB C Male to USB A adapters support bidirectional 10Gbps transfer within 3.3 ft

Playwright times out at networkidle

Analytics, websockets, and other persistent connections can prevent network idle. Use domcontentloaded followed by wait_for_selector or a carefully bounded delay.

WeasyPrint cannot handle a private asset

Implement a custom URL fetcher that adds the required cookies or authorization and restricts which hosts it may contact. Do not expose unrestricted file or network access to untrusted input.

Memory usage grows during batch jobs

Stream and cap downloads, limit concurrent renderers, close browser contexts, and recycle workers after a bounded number of jobs. Avoid calling response.text() on unbounded input.

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 can return a PDF from one API request, so you do not have to install Chromium or maintain rendering workers. Its capture pipeline accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the shot; each step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result.

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

For a PDF, use the API documented at ScreenshotNeo’s documentation. The same endpoint supports waits, custom headers and cookies, user-agent, authorization, timezone, geolocation, CSS or JavaScript, blocking rules, page ranges, paper size, margins, and landscape output.

Best Value
Anker USB C Hub, 5-in-1 USBC to HDMI Splitter with 4K Display
  • 5-in-1 Connectivity: Equipped with a 4K HDMI port, a 5 Gbps USB-C data port, two 5 Gbps USB-A ports, and a USB C 100W PD-IN port. Note: The USB C 100W PD-IN port supports only charging and does not support data transfer devices such as headphones or speakers.
  • Powerful Pass-Through Charging: Supports up to 85W pass-through charging so you can power up your laptop while you use the hub. Note: Pass-through charging requires a charger (not included). Note: To achieve full power for iPad, we recommend using a 45W wall charger.
  • Transfer Files in Seconds: Move files to and from your laptop at speeds of up to 5 Gbps via the USB-C and USB-A data ports. Note: The USB C 5Gbps Data port does not support video output.
  • HD Display: Connect to the HDMI port to stream or mirror content to an external monitor in resolutions of up to 4K@30Hz. Note: The USB-C ports do not support video output.
  • What You Get: Anker 332 USB-C Hub (5-in-1), welcome guide, our worry-free 18-month warranty, and friendly customer service.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.pdf

In 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.pdf", "wb").write(r.content)

In 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.pdf', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots each month without a card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Operational and cost decisions

  • Single conversion: WeasyPrint is convenient for static HTML; Playwright is appropriate when the page is dynamic.
  • Many conversions: reuse aiohttp sessions, queue rendering, cap concurrency, and cache stable inputs.
  • Untrusted URLs: isolate the renderer and enforce network, size, redirect, and time limits.
  • Hosted capture: ScreenshotNeo removes browser installation and offers asynchronous jobs, signed webhooks, bulk capture of up to 100 URLs per call, caching with a chosen TTL, and a usage API.

FAQ

Can aiohttp convert HTML to PDF by itself?

No. aiohttp is the asynchronous HTTP client/server layer; a PDF engine such as WeasyPrint or Playwright performs rendering.

Should I save the fetched HTML to disk first?

Not necessarily. Passing the string directly to WeasyPrint avoids a temporary file. Save it when you need reproducible debugging or an audit trail.

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

Why does a PDF look different from the browser?

WeasyPrint and Chromium implement different layout engines. Even with Playwright, PDF generation uses print media unless you explicitly emulate screen media.

Frequently Asked Questions

Can aiohttp convert HTML to PDF by itself?

No. aiohttp fetches the document; WeasyPrint or Playwright renders it.

Should I save fetched HTML before rendering?

No. You can pass the string directly, although saving a bounded copy can help debugging.

Why does the PDF differ from the browser view?

Different rendering engines and print CSS rules produce different layouts; Playwright can emulate screen media when needed.

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.