Saltar al contenido

Convertir ZPL a PDF en Python

Para convertir ZPL a PDF desde Python, envíe el ZPL al endpoint de renderizado de OpenZPL con Accept: application/pdf y guarde los bytes de respuesta. Establezca el tamaño de la etiqueta y la densidad en la URL y verifique el estado HTTP antes de escribir el archivo.

Actualizado 2026-10-20 · Renderizados hechos con el motor de OpenZPL

Abrir en el visor

Lo que necesita

  • Python 3.8 o superior y el paquete requests (pip install requests). Una versión de la biblioteca estándar está más abajo.
  • Su ZPL como texto, desde un archivo, una base de datos o una API de mensajería.
  • El tamaño de la etiqueta en pulgadas y la densidad de su impresora. La mayoría de las impresoras de escritorio son 203 dpi, que es 8dpmm; consulte 203 vs 300 dpi.

La versión más corta

Seis líneas son suficientes para probar el endpoint. El PDF tiene una página por etiqueta en el ZPL.

import requests

zpl = open("label.zpl", encoding="utf-8").read()
res = requests.post("https://api.openzpl.com/v1/printers/8dpmm/labels/4x2/",
                    data=zpl.encode("utf-8"), headers={"Accept": "application/pdf"}, timeout=30)
res.raise_for_status()
open("label.pdf", "wb").write(res.content)

Esta es la etiqueta en ese archivo, renderizada por el mismo motor que usa la API:

Orden de etiqueta con el número de pedido, el cliente, un código de barras de Código 128 y paquete 1 de 3
etiqueta de 4 × 2 in a 203 dpi (8 dpmm) · 812 × 406 px · Abrir en el visor
^XA
^CI28
^FO30,30^A0N,34,34^FDOrder #10482^FS
^FO30,75^A0N,24,24^FDAlex Morgan, Austin, TX 78701^FS
^FO30,110^GB752,2,2^FS
^BY2,3,120
^FO30,135^BCN,120,Y,N,N^FD10482-0001^FS
^FO560,140^A0N,22,22^FDPACKAGE^FS
^FO560,170^A0N,60,60^FD1/3^FS
^XZ

Una función que puede poner en producción

La versión corta no tiene reintentos y mantiene todo el PDF en memoria. Esta establece un tiempo de espera para conexión y lectura, espera tanto como la API solicita cuando responde 429 o 503, transmite el PDF al disco y devuelve cuántas etiquetas contiene.

import time

import requests

API = "https://api.openzpl.com/v1/printers/{dpmm}dpmm/labels/{size}/"


def zpl_to_pdf(zpl, path, *, dpmm=8, size="4x6", api_key=None, retries=3):
    """Render every label in `zpl` into one PDF at `path`. Returns the label count."""
    headers = {"Accept": "application/pdf"}
    if api_key:
        headers["X-API-Key"] = api_key
    url = API.format(dpmm=dpmm, size=size)
    for attempt in range(retries + 1):
        res = requests.post(url, data=zpl.encode("utf-8"), headers=headers,
                            timeout=(5, 30), stream=True)
        if res.status_code in (429, 503) and attempt < retries:
            # The API says how long to wait; never retry faster than that.
            time.sleep(int(res.headers.get("Retry-After", "1")))
            continue
        if res.status_code != 200:
            raise RuntimeError(f"HTTP {res.status_code}: {res.text.strip()}")
        with open(path, "wb") as f:
            for chunk in res.iter_content(64 * 1024):
                f.write(chunk)
        return int(res.headers["X-Total-Count"])
    raise RuntimeError("still rate limited after retries")


if __name__ == "__main__":
    zpl = open("label.zpl", encoding="utf-8").read()
    count = zpl_to_pdf(zpl, "label.pdf")
    print(f"label.pdf: {count} label(s)")

Ejecutar contra un archivo con cuatro etiquetas, imprimió label.pdf: 4 label(s) y el PDF tenía cuatro páginas de 4 × 6. X-Total-Count es el número de etiquetas que produce el ZPL, y las copias de ^PQ están incluidas.

Codifique el ZPL como UTF-8 y coloque ^CI28 en la parte superior de la etiqueta cuando los datos tengan acentos u otros caracteres no ASCII. Consulte caracteres acentuados.

Sin solicitudes

Si no puedes añadir una dependencia, urllib de la biblioteca estándar hace la misma solicitud. Lanza HTTPError para cualquier estado distinto de 2xx; el cuerpo del error está en e.read().

import urllib.request

zpl = b"^XA^FO50,50^A0N,50,50^FDHello from Python^FS^XZ"
req = urllib.request.Request(
    "https://api.openzpl.com/v1/printers/8dpmm/labels/4x6/",
    data=zpl, headers={"Accept": "application/pdf"}, method="POST")
with urllib.request.urlopen(req, timeout=30) as res, open("hello.pdf", "wb") as f:
    f.write(res.read())

Variantes

  • Un PNG en lugar de un PDF: elimine el encabezado Accept y agregue el índice de la etiqueta a la URL (.../4x6/0/). El PNG es la primera etiqueta, o la que está en ese índice.
  • Salida de 1 bit: envíe X-Quality: Bitonal para blanco y negro sin bordes grises, tal como lo imprime la impresora. El valor predeterminado es Grayscale.
  • Rotación: X-Rotation: 90 gira la salida en el sentido de las agujas del reloj; la página PDF gira con ella.
  • Límites de su plan: envíe su clave en X-API-Key. Sin ella, se aplican los límites gratuitos por IP.
  • Más de 50 etiquetas: divida el ZPL en solicitudes de hasta 50 etiquetas, como se muestra en varias etiquetas en un PDF.

Errores y qué hacer

Los errores regresan como texto simple comenzando con ERROR:, así que res.text es un mensaje que puedes registrar tal cual. Un 413 por demasiadas etiquetas dice ERROR: The ZPL produces 60 labels; the maximum per request is 50.

EstadoLo que significaQué hacer
400Un parámetro en la URL es incorrecto, o los datos de un código de barras son inválidos.Lea el texto después de ERROR: y corrija la solicitud. No la reintente sin cambios.
404El ZPL no tiene etiqueta, o el índice de la etiqueta está fuera de rango. X-Total-Count indica cuántas etiquetas hay.Verifique que el ZPL tenga bloques completos de ^XA … ^XZ, o pida un índice por debajo de X-Total-Count.
406El encabezado Accept solicita un formato que la API no produce.Enviar image/png o application/pdf.
413El cuerpo supera 1 MB, o el ZPL produce más de 50 etiquetas.Divide el trabajo en varias solicitudes.
429Se alcanzó un límite de su plan: solicitudes por segundo o etiquetas por día UTC.Espere los segundos en Retry-After, luego reintente.
503La etiqueta tardó más de 10 segundos en renderizar, o el servicio está a capacidad.Intente de nuevo una vez después de Retry-After; si se repite, baje la densidad o divida la etiqueta.

Un 429 nombra el límite que alcanzó, por ejemplo ERROR: Rate limit: 3 requests per second on this plan; retry in 1 second. El límite diario envía un Retry-After de los segundos restantes hasta la medianoche UTC, por lo que la función anterior esperaría horas: en un trabajador, limita la espera y vuelve a poner la tarea en la cola.

Límites a tener en cuenta

  • Punto final: POST https://api.openzpl.com/v1/printers/8dpmm/labels/4x6/ con el ZPL como cuerpo de la solicitud. La densidad es 6dpmm, 8dpmm, 12dpmm o 24dpmm; el tamaño está en pulgadas, hasta 15 por lado.
  • Accept: application/pdf sin un índice de etiqueta devuelve cada etiqueta, una por página. Agregue un índice (.../4x6/0/) para obtener una sola etiqueta.
  • Plan gratuito: sin clave, 3 solicitudes por segundo y 5,000 etiquetas por día por dirección IP. Cada etiqueta cuenta, se incluyen copias ^PQ y páginas PDF. Consulte precios.
  • Por solicitud: hasta 50 etiquetas y 1 MB de ZPL.

Preguntas

¿Hay una biblioteca de Python para ZPL a PDF?

No necesita uno: la API toma el ZPL como cuerpo de la solicitud y devuelve el PDF, así que requests o urllib es suficiente. La función anterior es el cliente completo.

¿Puedo obtener un PDF por etiqueta?

Sí. Solicite cada etiqueta por índice con Accept: application/pdf (.../4x6/0/, .../4x6/1/), o envíe cada etiqueta en su propia solicitud.

¿La API guarda mi ZPL?

No. La etiqueta se renderiza y se devuelve; el ZPL no se almacena.

Fuentes