# web2pdf and the case for a scriptable page to PDF printer

> web2pdf is a Python command line tool that renders web pages to formatted PDFs with WeasyPrint, and can crawl a whole site to a configurable depth and turn it into a set of documents or one assembled file. Its value is automation and styling control, and its cost is a native dependency chain that has to be installed before anything else.

**dvcoolarun/web2pdf** — 🔄 CLI to convert Webpages to PDFs 🚀 

- Repository: https://github.com/dvcoolarun/web2pdf
- Website: https://dvcoolarun.com
- Stars: 1,294 · Forks: 59
- Language: Python
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/dvcoolarun-web2pdf

## A script at the root of the repository, not a package

web2pdf is a command line tool that converts webpages to formatted PDFs. The repository layout explains a lot about where it sits in its own life cycle: there is a main.py at the root, a requirements.txt referenced by the README, an environment.yml, a Pipfile and a Pipfile.lock, a tests/ directory and an assets/ directory with the project image. There is no setup.py, no pyproject.toml and no console script entry point.

So the tool is run, not installed. Every example in the documentation ends with python main.py followed by a URL, and the development instructions are a clone plus pip install -r requirements.txt. That is a deliberate difference from most tools in this space, and it has an immediate consequence for anyone who wants to use it in a pipeline: you invoke a script path, not a command name, and you have to clone the repository or vendor main.py somewhere.

There is a further wrinkle in the tree. The README tells developers to install from requirements.txt, but that file does not appear among the top level entries, where the Pipfile and its lock file are present instead. A developer following the development section literally will find that file missing and will need to fall back to the conda route, which uses environment.yml, or to pip install the dependencies individually.

The audience is a developer or a small team who wants a repeatable way to turn documentation sites, reports or archives into PDFs, rather than a person who wants to print a single article.

## WeasyPrint, and the native libraries it insists on

The rendering engine is WeasyPrint, and the documentation treats it as a first class installation problem rather than a pip install away. The README states that WeasyPrint requires system libraries that must be installed before the Python packages, and it provides the exact lists per platform.

On macOS with Homebrew:

```bash
brew install cairo pango gdk-pixbuf libffi pkg-config
```

On Ubuntu and Debian the list is longer, because it includes the compiler and the headers:

```bash
sudo apt-get update && sudo apt-get install -y \
    build-essential \
    python3-dev \
    libffi-dev \
    libssl-dev \
    libxml2-dev \
    libxslt1-dev \
    libjpeg-dev \
    libpango1.0-dev \
    libcairo2-dev \
    libgirepository1.0-dev \
    gobject-introspection
```

Fedora, RHEL and CentOS get the equivalent dnf list, with gcc, python3-devel, libffi-devel, openssl-devel, libxml2-devel, libxslt-devel, libjpeg-turbo-devel, pango-devel, cairo-devel and gobject-introspection-devel.

The substance behind those package names is that WeasyPrint is a rendering engine, not a document generator. It lays out HTML and CSS into a paginated document the way a browser would, which is why it needs the same rendering stack the browser uses: cairo for drawing, pango for text layout, and the GObject introspection layer to talk to them. That also explains the feature list later on, because a real CSS layout engine gives you columns, page breaks and running page numbers for free, and a fake one would not.

## The crawler is bounded by depth and by domain

Recursive crawling is the feature that turns the tool from a converter into a site archiver. The usage examples show it clearly:

```bash
python main.py https://example.com --recursive
python main.py https://example.com --recursive --depth 3
python main.py https://example.com -r -d 3
```

The default depth is 2, and the short forms are r for recursive and d for depth. The behaviour list is explicit about what a recursive run does: it starts from the provided URL, crawls all links on the same domain, goes up to the specified depth, converts each found page into a separate PDF file, and generates filenames based on the URL structure, with the example given being web2pdf.pdf for the path github.com/zaphodbeeblebrox3rd/web2pdf.

Two design choices are worth pulling out. The first is the same domain restriction, which is also listed as smart link filtering, the automatic filtering to same domain links to avoid external sites. The second is that each page becomes its own PDF rather than one long document, and the path-to-filename mapping keeps the site structure recoverable from the output directory.

A depth of 2 sounds small until you consider that a documentation site with a table of contents on every page can reach a few hundred URLs from a single entry point. The same domain limit is the safety rail that keeps a crawl from wandering off, and it is also the reason a recursive run of a documentation site with a link to another site will silently skip that target rather than fetch it.

## Styling controls that map onto CSS features

The formatting features are grouped as multi-column support for more complex PDF layouts, page numbers for easier navigation, a table of contents automatically generated based on the headings in the HTML, and control over page breaks to ensure the PDF is formatted as intended. There is also custom styling through customisable CSS, allowing adjustment of everything from fonts to background colours, and a separate additional CSS option for further customisation.

Every one of these comes from the same source, which is that the content is laid out by a CSS engine rather than assembled by a script. A table of contents derived from the headings means the heading levels in the source page determine the outline, which is a real advantage over a hand-built document but also a real constraint: if the page uses headings for visual emphasis rather than structure, the table of contents will be wrong. Page breaks work the same way, through CSS rules that the user has to supply.

The page number feature is the one worth singling out, because producing a PDF with correct page numbers is a chore in most pipelines. Here it is a rendering feature, so the numbering is accurate for the final pagination, which a post-processing approach that stamps numbers onto an existing PDF cannot always guarantee.

What the README does not document is how the tool handles pages that rely on JavaScript to render their content, which is a common pattern on modern sites and a common reason a headless HTML to PDF conversion produces an empty page. That question is worth testing against your own pages before you rely on it.

## Rate limiting, assembling and re-running a crawl

The operational flags are where the project shows how it expects to be used.

Rate limiting is adjustable with the --rate-limit option, and the default is 5 seconds between batches of 10 requests. That default is a deliberate politeness setting for a tool that fetches whole sites, and it also implies the crawl is batched rather than sequential, so a site of a few hundred pages takes a meaningful amount of wall clock time. The --assemble option creates a single combined PDF with all pages, which is what turns a directory of files into something you can send to someone. The --force option forces re-processing of existing PDFs, where the default is to skip files that already exist, which makes an interrupted crawl resumable in the most literal sense.

Output goes to a default directory of ~/web2pdf_output, and the --output option points it elsewhere, creating the directory if it does not exist.

The combination of skip existing files, a custom output directory and a rate limit is what makes the recursive mode usable for a scheduled job. The first run populates the directory, later runs pick up only what is new, and the polite default keeps you on the right side of whoever owns the site. The alternative, the --assemble flag, is the part to think about carefully, since combining a crawl of an entire site into one document is convenient right up to the point where the reader has to scroll past a hundred pages of navigation.

## Install the libraries first, then the environment

The recommended path is conda, and the README is unusually insistent about the order of operations. You need to install the system dependencies first, then create the environment, and the note explains that if you created the conda environment already, you should deactivate it, remove it, install the dependencies and create a new one.

```bash
# Install system dependencies first (see System Dependencies section above)
# Then create and activate conda environment
conda env create -f environment.yml
conda activate web2pdf

# Run the tool
python main.py
```

The reason the order matters is not documented, and the guess is that WeasyPrint resolves its native libraries at install time, so an environment created before cairo and pango are present can end up with a broken binding. The macOS troubleshooting section supports that reading, since the reported failure is a cannot load library 'libgobject-2.0-0' error even though the dependencies were installed afterwards.

Three fixes are offered for that error. The first is to point the dynamic linker at the Homebrew libraries:

```bash
export DYLD_LIBRARY_PATH="/opt/homebrew/lib:$DYLD_LIBRARY_PATH"
export PKG_CONFIG_PATH="/opt/homebrew/lib/pkgconfig:$PKG_CONFIG_PATH"
```

The second is to reinstall WeasyPrint inside the environment with pip uninstall weasyprint -y followed by pip install weasyprint. The third is to install it from conda-forge with conda install -c conda-forge weasyprint, which the README describes as the alternative that may handle dependencies better.

For development, the documented steps are a clone and pip install -r requirements.txt, and the tests run with python -m pytest tests/ -v after pip install pytest.

## A paid service for the same job, and no release history

The README opens with a production section rather than with the tool, and it points at DocuQueue, a managed document generation service offered on a starter plan at 9 dollars per month, with no infrastructure maintenance required. Pro and Enterprise extensions add formatted PDFs with built-in templates, management pipelines and batch processing.

That is a genuine alternative with a clear difference in approach, and it is worth pricing honestly. The open source tool is free and MIT licensed, and you own the pipeline, but you operate the rendering stack, the crawler and the storage. The hosted service costs a subscription and takes the operations away, and adds templates and a pipeline model that the CLI does not have. If your requirement is one page to PDF occasionally, the service is overkill and the CLI is too, and printing from a browser is the right answer. If your requirement is a nightly job over a hundred URLs, the comparison becomes a real one.

The maintenance record is thin in a way that matters. The repository is not archived, but the last push was on 2026-07-09 and there are no GitHub releases at all, so there is no tagged version to pin, and the contributing note describes the CLI as being in its early version and asks for help with code, testing and additional features.

The licence is MIT, with the text in the LICENSE file, so there is nothing in the way of commercial use or of embedding the script in a larger toolchain.

## Conclusion

Adopt web2pdf if you need to turn web pages into documents on a schedule, in bulk, with consistent styling, and if you are comfortable installing a native rendering toolchain on the host. Do not adopt it for a one-off page where printing from a browser is faster, and do not expect a packaged command, since the project is run as a script and the README does not document installation as a library. Verify three things first: that the system libraries are installed before the Python environment, because the README is explicit that the order matters, that your pages render acceptably under WeasyPrint, and that a recursive run will not pull more of a site than you intended, since the depth default is 2 and the crawler stays on one domain. The last push was on 2026-07-09, the repository has no GitHub releases, and the licence is MIT.

## FAQ

### How do I install web2pdf on macOS?

Install the system libraries first with brew install cairo pango gdk-pixbuf libffi pkg-config, then create the environment with conda env create -f environment.yml, activate it with conda activate web2pdf and run python main.py. The README stresses that the order matters.

### What system libraries does web2pdf need on Linux?

On Ubuntu and Debian the README lists build-essential, python3-dev, libffi-dev, libssl-dev, libxml2-dev, libxslt1-dev, libjpeg-dev, libpango1.0-dev, libcairo2-dev, libgirepository1.0-dev and gobject-introspection, with an equivalent dnf list for Fedora, RHEL and CentOS.

### How do I crawl an entire site with web2pdf?

Pass --recursive, or -r, and optionally --depth, or -d, where the default depth is 2. The crawler stays on the same domain, converts each page to its own PDF, names files from the URL structure and writes to ~/web2pdf_output unless --output says otherwise.

### Can web2pdf combine a crawled site into one PDF?

Yes, the --assemble option creates a single assembled PDF with all pages combined. Rate limiting is controlled by --rate-limit, which defaults to 5 seconds between batches of 10 requests, and --force reprocesses files that already exist.

### How do I run the web2pdf tests?

Install pytest and run python -m pytest tests/ -v. The development instructions are a clone of the repository followed by pip install -r requirements.txt, though the tests directory also contains the test code itself.

### What licence is web2pdf released under?

MIT, with the text in the LICENSE file. The repository has no GitHub releases and the last push was on 2026-07-09, and the author also offers DocuQueue, a paid managed service for the same document generation job.

## Sources

- [dvcoolarun/web2pdf on GitHub](https://github.com/dvcoolarun/web2pdf)
- [Issues](https://github.com/dvcoolarun/web2pdf/issues)
- [License: MIT](https://github.com/dvcoolarun/web2pdf/blob/main/LICENSE)
- [Project website](https://dvcoolarun.com)
- [README](https://github.com/dvcoolarun/web2pdf/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/dvcoolarun-web2pdf
