Die Dateien herunterladen
- html_to_pdf.py: das vollständige Skript, das unten gezeigt wird
- requirements.txt: seine einzige Abhängigkeit, requests
- invoice.html: die Beispielrechnung aus der Anleitung zu Rechnungs-PDFs, zum ersten Umwandeln
Installieren und den Schlüssel setzen
Das Skript braucht Python 3.9 oder neuer und das Paket requests. Erstellen Sie im Portal einen API-Schlüssel und legen Sie ihn in der Umgebungsvariablen IRONFANG_API_KEY ab statt in der Datei.
python -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
export IRONFANG_API_KEY="if_live_..."Das Skript
"""Convert an HTML file to PDF with the Ironfang Render API.
Usage:
export IRONFANG_API_KEY="if_live_..."
python html_to_pdf.py invoice.html invoice.pdf
The HTML is sent to the hosted API and the PDF comes back in the response.
Needs Python 3.9 or newer and the requests package.
"""
import os
import sys
from pathlib import Path
import requests
API_URL = "https://api.ironfang.uk/render/v1/pdf"
FOOTER = (
'<div style="font-size:9px;width:100%;text-align:center;'
'color:#666;font-family:sans-serif">'
'Page <span class="pageNumber"></span> of <span class="totalPages"></span>'
"</div>"
)
def main() -> None:
if len(sys.argv) != 3:
sys.exit("Usage: python html_to_pdf.py INPUT.html OUTPUT.pdf")
source, target = Path(sys.argv[1]), Path(sys.argv[2])
key = os.environ.get("IRONFANG_API_KEY")
if not key:
sys.exit("Set IRONFANG_API_KEY to your Ironfang API key.")
body = {
"html": source.read_text(encoding="utf-8"),
"paper_format": "a4",
"print_background": True,
# Inches, per side. The bottom margin leaves room for the footer.
"margin": {"top": 0.6, "right": 0.5, "bottom": 0.8, "left": 0.5},
# A footer on its own makes Chromium print its default date and
# title header, so an empty header goes with it.
"header_html": "<span></span>",
"footer_html": FOOTER,
}
try:
resp = requests.post(
API_URL,
headers={"Authorization": f"Bearer {key}"},
json=body,
timeout=60,
)
except requests.RequestException as exc:
sys.exit(f"The request did not complete: {exc.__class__.__name__}")
content_type = resp.headers.get("Content-Type", "")
if resp.status_code != 200 or not content_type.startswith("application/pdf"):
# Errors are JSON even though success is binary. Never save them
# as the PDF.
try:
err = resp.json()["error"]
detail = f"{err['code']}: {err['message']}"
except (ValueError, KeyError, TypeError):
detail = f"unexpected response ({content_type or 'no content type'})"
retry = resp.headers.get("Retry-After")
hint = f" Retry after {retry} seconds." if retry else ""
sys.exit(f"HTTP {resp.status_code} {detail}.{hint}")
target.write_bytes(resp.content)
credits = resp.headers.get("X-Renderwolf-Credits", "?")
print(f"Wrote {target}: {len(resp.content):,} bytes, {credits} credits used.")
if __name__ == "__main__":
main()
Ausführen
python html_to_pdf.py invoice.html invoice.pdfBei Erfolg gibt es die Größe und die verbrauchten Credits aus, und invoice.pdf ist eine einseitige A4-Rechnung mit einer Seitenzahl in der Fußzeile:
Wrote invoice.pdf: 69,936 bytes, 2 credits used.
Was die Einstellungen bewirken
- paper_format: a3, a4, a5, letter, legal oder tabloid. Ohne Angabe ist die Seite Letter.
- print_background: true behält Hintergrundfarben und -bilder, die beim Drucken standardmäßig wegfallen.
- margin: in Zoll, je Seite. Die Fußzeile wird innerhalb des unteren Rands gezeichnet, deshalb ist der untere Rand größer.
- header_html und footer_html: Vorlagen, die auf jede Seite gezeichnet werden. Chromium füllt die Klassen pageNumber und totalPages. Eine Fußzeile schaltet beide ein, deshalb gehört eine leere Kopfzeile dazu; sonst erscheinen oben ein Datum und der Seitentitel.
Wenn es fehlschlägt
Ein Erfolg ist ein 200 mit Content-Type application/pdf, und nur dann wird die Datei geschrieben. Alles andere ist JSON in der Form {"error": {"code", "message"}}; das Skript gibt es aus und beendet sich mit Status 1. Einige der Fehler, die Ihnen begegnen können:
HTTP 401 invalid_api_key: missing or unknown API key.
HTTP 400 bad_request: paper_format must be one of a3, a4, a5, legal, letter, tabloid, got "a6".- 429 rate_limited: zu viele Renderings pro Minute für das Konto. Warten Sie und senden Sie die Anfrage erneut; das Skript gibt Retry-After aus, wenn die Antwort diesen Header enthält.
- 429 quota_exhausted: Die Credits des Monats sind aufgebraucht. Das Rendern pausiert, statt Mehrverbrauch abzurechnen.
- 422 render_failed: Die Seite konnte nicht gedruckt werden, zum Beispiel weil sie nicht rechtzeitig geladen wurde. Die Credits dafür werden erstattet.
Das Skript wiederholt Anfragen nicht von selbst. Wenn Sie Wiederholungen einbauen, wiederholen Sie nur Antworten mit 429 und 5xx, warten Sie zwischen den Versuchen und lassen Sie 400 und 401 aus: Sie schlagen jedes Mal auf dieselbe Weise fehl. Fehlermeldungen enthalten nie den Schlüssel oder das Dokument.
HTML aus Ihrer Anwendung umwandeln
Das Skript liest eine Datei, aber body["html"] kann jeder String sein, den Ihr Code erzeugt, etwa ein gerendertes Jinja-Template. Maskieren Sie jeden Wert, den Sie in das HTML einsetzen, so wie es die Anleitung zu Rechnungs-PDFs mit html.escape zeigt.
Wann eine lokale Bibliothek besser passt
Python-Bibliotheken wie WeasyPrint rendern HTML auf Ihrem eigenen Rechner mit einer eigenen Layout-Engine zu PDF, und Playwright steuert ein lokales Chromium. Sie vermeiden einen Netzwerkaufruf und halten Dokumente in Ihrer Infrastruktur, um den Preis, dass Sie die Engine, ihre Schriften und Systemabhängigkeiten installieren und aktualisieren und ihr den Speicher und die Zeit geben müssen, die ein Browser braucht. Eine gehostete API passt zu einem Dienst, der selbst keinen Browser betreiben soll; eine lokale Engine passt zu Offline-Arbeit oder zu Dokumenten, die Ihr Netzwerk nie verlassen dürfen.
Weiterlesen
- HTML-zu-PDF-API: Funktionen, Grenzen und Preise
- Kostenloser HTML-zu-PDF-Konverter: probieren Sie zuerst ein Dokument im Browser aus, ohne Schlüssel
- Referenz der PDF-Anfrage: jedes Feld, sein Typ und seine Grenzen
- HTML-Rechnungsvorlage mit funktionierendem PDF-Beispiel
- Website-Screenshots in Python
- Vollständige API-Referenz

