Convertir un PDF a markdown para un LLM consiste en transformar las páginas en texto que conserve la estructura que necesita un modelo: encabezados como encabezados, tablas como tablas y columnas en su orden de lectura. Markdown es el destino habitual porque los modelos lo leen bien, suele ser más ligero que el HTML y un pipeline de RAG puede dividirlo por encabezados en lugar de por un número arbitrario de caracteres.
Esto se hace por una de tres razones: indexar documentos para recuperarlos, pegar un documento en un prompt sin perder sus tablas o darle a un agente un archivo que pueda leer. La extracción de texto plano no basta para ninguna de las tres, porque una tabla aplanada en una celda por línea es peor que no tener tabla.
Esta página ejecuta tres conversores de código abierto sobre los mismos PDFs, muestra el código e informa de lo que salió. También dice lo que no probamos, que incluye anyformat.
Qué necesitas
- Python 3.10 o superior y unos 4 GB de disco libre si instalas las tres, porque Docling y Marker descargan modelos la primera vez que se usan.
- Un PDF tuyo, a ser posible un documento de ejemplo o ficticio y no un archivo real de un cliente. Usa el más feo que tengas: una tabla, dos columnas o un escaneo. Los conversores se parecen todos con texto limpio en una sola columna.
- Una terminal. Las tres herramientas de código abierto no necesitan claves de API.
- Un entorno virtual por herramienta. Instalamos cada una por separado para que sus dependencias no choquen:
pip install "markitdown[pdf]" # entorno 1
pip install docling # entorno 2
pip install marker-pdf # entorno 3Cómo lo probamos
Generamos tres PDFs ficticios: un informe de dos páginas (encabezados, una lista con viñetas, una tabla de 5 por 4 con fila de cabecera y después una página en dos columnas), un PDF tipo escaneo sin capa de texto (una imagen ligeramente girada con tres frases y una tabla pequeña) y un documento de 40 páginas. Ejecutamos el fragmento mínimo del README de cada proyecto en un Mac con Apple Silicon y sin GPU, el 30-09-2026. Las versiones fueron MarkItDown 0.1.8, Docling 2.131.0 y Marker 2.0.0.
Es una sola máquina y archivos sintéticos pequeños, cada uno medido una vez. Tómalo como una forma de ver los modos de fallo, no como un benchmark.
Paso 1. Ejecutar los tres conversores
Cada uno son unas pocas líneas. MarkItDown, de Microsoft:
from markitdown import MarkItDown
text = MarkItDown().convert("report.pdf").text_contentDocling, de IBM Research:
from docling.document_converter import DocumentConverter
text = DocumentConverter().convert("report.pdf").document.export_to_markdown()Marker, de Datalab. Ponlo bajo una guarda main, porque con PDFs de varias páginas lanza procesos de trabajo y, sin la guarda, vuelve a ejecutar el principio de tu script:
from marker.converters.pdf import PdfConverter
from marker.models import create_model_dict
from marker.output import text_from_rendered
if __name__ == "__main__":
converter = PdfConverter(artifact_dict=create_model_dict())
text, _, images = text_from_rendered(converter("report.pdf"))Qué vimos
| MarkItDown | Docling | Marker | |
|---|---|---|---|
| Tabla | Rota: una celda por línea, columnas mezcladas | Correcta | Correcta |
| Dos columnas | Orden correcto, líneas cortadas a mano | Orden correcto, párrafos unidos | Orden correcto, pierde un encabezado |
| Niveles de encabezado | No conserva ninguno | Todos salieron como ## |
## o ###, niveles aplanados |
| Página escaneada | Salida vacía | La lee, tabla correcta | Falló sin una instalación extra (más abajo) |
| 40 páginas, segunda ejecución | 0,5 s | unos 3 s | unos 3,5 s |
| Primera ejecución | ninguna | 41 s, más 28 s de inicialización | 36 s, después unos 7 s |
| Instalación y modelos | 150 MB | 1,1 GB más unos 570 MB de modelos | unos 1 GB más 418 MB de modelos |
Los tiempos son de un Mac con Apple Silicon y sin GPU, ejecuciones únicas, el 30-09-2026.
MarkItDown es rápido y solo sirve con texto limpio
Convirtió dos páginas en 0,04 segundos y conservó el orden de lectura a dos columnas. No conservó ningún encabezado, convirtió los símbolos de viñeta en líneas (cid:127) y desordenó la tabla: cada celda pasó a ser un párrafo y el orden de las columnas se mezcló.
Region Q1 Q2 Q3
North
100
120
140
South
Con el PDF escaneado devolvió una cadena vacía. El README de MarkItDown describe un plugin aparte, markitdown-ocr, que envía las imágenes a un LLM con visión; no lo probamos. Para texto limpio en una sola columna es la opción más barata con diferencia. Para tablas o escaneos no basta por sí sola.
Docling dio el mejor resultado aquí, a un precio
Docling devolvió la tabla como una tabla markdown correcta, la lista como lista y las dos columnas en orden con los párrafos unidos. También aplicó OCR al escaneo sin configurar nada, y devolvió las tres frases y una tabla correcta. Lo comparamos a ojo con el original y no vimos errores; es una observación, no una medida de precisión.
| Region | Q1 | Q2 | Q3 |
|----------|------|------|------|
| North | 100 | 120 | 140 |
| South | 107 | 125 | 143 |
El precio son unos 1,1 GB de paquetes y unos 570 MB de modelos, unos 70 segundos la primera vez (descarga de modelos e inicialización) y después unos segundos por documento. También aplanó los niveles de encabezado: todos salieron como ##, así que un título y sus apartados se ven igual. Si tu fragmentador divide por profundidad de encabezado, eso importa.
Marker: comprueba tu instalación antes de fiarte
Marker leyó los PDFs de texto, con una tabla correcta y el orden correcto en dos columnas, salvo un encabezado que perdió en la página a dos columnas. En nuestra ejecución fallaron dos cosas, y las dos conviene conocerlas.
La primera: en un Mac sin GPU, Marker 2.0.0 no pudo convertir la página escaneada. Se detuvo con SpawnError: llama-server binary not found. Su README dice que el modo para CPU y Apple Silicon necesita el binario llama-server de llama.cpp, instalado aparte. No lo instalamos, así que no tenemos resultado de Marker con escaneos y no afirmamos ninguno.
La segunda: con un PDF de 40 páginas donde todas las secciones tenían el mismo texto, Marker devolvió 701 caracteres y descartó el cuerpo de las secciones 2 a 40. Sospechamos de su eliminación de cabeceras repetidas, pero no lo confirmamos, así que tómalo como algo que observamos, no como algo explicado. Con un PDF de 40 páginas con texto distinto en cada sección se conservaron las 40 secciones, pero solo salieron 42 de 81 encabezados, con los niveles aplanados a ### y ####.
Lee las licencias antes de publicar cualquiera de ellos. El README de Marker dice que el código es Apache 2.0 y que los pesos del modelo usan "a modified AI Pubs Open Rail-M license (free for research, personal use, and startups under $5M funding/revenue)", con una vía comercial más allá de ese umbral a través del proveedor. El código de Docling es MIT, y su README remite a la licencia de cada modelo. MarkItDown es MIT.
Lo que ninguno nos dio
Ningún conversor conservó los niveles reales de encabezado, y ninguno devuelve en qué parte de la página estaba un bloque ni con cuánta seguridad se leyó. Para un fragmentador de RAG suele bastar. Para cualquier caso en el que quien revisa deba ver el origen, o en el que decidas automáticamente de qué fiarte, necesitas esa información, que es lo que describe la sección de anyformat más abajo.
Comprueba con tus propios PDFs
No elijas a partir de nuestra tabla. Anota algunas cosas que sepas que están en tu documento y comprueba que aparecen. Lleva diez minutos y te dice más que cualquier benchmark. La función de abajo solo informa de qué comprobación falló, nunca del contenido del documento, así que su salida es segura para registrar.
def check_markdown(md: str, must_contain: list[str], table_row: list[str]) -> list[str]:
problems = []
for i, needle in enumerate(must_contain, start=1):
if needle not in md:
problems.append(f"expected text #{i} is missing")
# una fila de tabla sobrevive si una línea markdown que empieza por | tiene exactamente esas celdas
def is_table_row(line: str) -> bool:
cells = [c.strip() for c in line.strip().strip("|").split("|")]
return line.lstrip().startswith("|") and cells == table_row
if not any(is_table_row(line) for line in md.splitlines()):
problems.append("table row is not intact")
return problems
print(check_markdown(text, ["Quarterly Operations Report"], ["North", "100", "120", "140"]))Ejecútala con cada conversor sobre el mismo archivo y tendrás tu propia comparación.
Cuándo usar cuál
- Texto limpio en una sola columna, donde importa más la velocidad que la estructura: MarkItDown.
- Tablas, escaneos y diseños de varias columnas, si puedes asumir la instalación: Docling.
- Marker, si tienes una GPU o la dependencia de llama.cpp instalada y has leído la licencia del modelo. Pruébalo primero con documentos largos.
Otra forma de hacerlo: anyformat
anyformat es una plataforma de extracción de documentos, y el análisis es su primer paso, disponible por separado. Envías un PDF y la API te devuelve markdown, sin modelos que instalar ni GPU.
import os
import time
from anyformat.sdk import Client
from anyformat.sdk.errors import StillParsing
client = Client(api_key=os.environ["ANYFORMAT_API_KEY"])
run = client.parse("report.pdf")
while True:
try:
markdown = client.get_markdown(run.id)
break
except StillParsing:
time.sleep(3)Se instala con pip install anyformat. El markdown que devuelve es estructurado: contiene anclas que identifican cada bloque de la página. El resultado completo del análisis también lista esos bloques con su página, su caja delimitadora y una puntuación de confianza, y así conservas el vínculo entre un fragmento de texto y el lugar de la página donde está. Este ejemplo obtiene solo la cadena de markdown; el resultado por bloques se describe en la documentación de la API. La estructura de la respuesta está en la documentación de la API. El mismo análisis es accesible desde un agente a través del servidor MCP.
No ejecutamos anyformat en la prueba de arriba, así que esta página no afirma nada sobre cómo se compara su resultado con estos archivos. Ejecuta con el markdown que devuelve la comprobación de la sección anterior y compara tú mismo, con un documento ficticio, porque los archivos reales pueden contener datos de clientes. Si tus documentos están limpios y solo necesitas texto para un índice, un conversor local de código abierto es la opción más sencilla.
Preguntas frecuentes
¿Cuál es el mejor conversor de PDF a markdown para RAG?
Depende de tus documentos. Con nuestros archivos de prueba Docling dio la salida más completa, MarkItDown fue el más rápido y solo fiable con texto limpio, y Marker necesitó configuración extra para los escaneos. Prueba con tus propios PDFs con una comprobación como la de arriba antes de elegir.
¿MarkItDown hace OCR?
No en una conversión normal de PDF. Su README describe un plugin aparte, markitdown-ocr, que usa un LLM con visión para leer imágenes, y una opción que usa Azure Document Intelligence.
¿Marker es gratis para uso comercial?
Su código es Apache 2.0. Los pesos del modelo usan una licencia Open Rail-M de AI Pubs modificada que su README describe como gratuita para investigación, uso personal y startups con menos de 5 M$ de financiación o ingresos. Lee la licencia en su repositorio para tu caso.
¿Por qué se rompen las tablas al convertir un PDF a markdown?
Un PDF guarda caracteres en posiciones de una página, no filas y columnas. Un conversor que solo lee el texto en orden convierte cada celda en su propia línea. Uno que detecta la estructura de la tabla puede reconstruir las filas.
¿Necesito una GPU?
No para las tres herramientas que ejecutamos: las tres convirtieron PDFs de texto en un Mac sin GPU. El README de Marker describe un modo con GPU de mayor precisión, y su modo para páginas escaneadas en un Mac necesitó un binario extra, como se explica arriba.







