Si trabajas con datos de entrenamiento para IA, pipelines de documentos o sistemas RAG, casi seguro que ya has tenido que lidiar con archivos DOCX. Los documentos de Word están en todas partes — borradores de investigación, informes empresariales, contratos legales, manuscritos — pero procesarlos mediante código es engorroso.
Este artículo muestra cómo convertir DOCX a Markdown limpio con la API REST de AnyMD desde Python, incluyendo procesamiento asíncrono por lotes, gestión de errores y paginación para flujos de trabajo a escala de producción.
¿Por qué convertir DOCX a Markdown?
DOCX es un formato binario (un ZIP de archivos XML). Bibliotecas como python-docx pueden extraer texto, pero pierden la estructura: los encabezados se convierten en párrafos simples, las listas colapsan en texto sangrado y las tablas se vuelven ruido separado por tabulaciones. Markdown conserva todo eso en un formato que cualquier LLM, base de datos vectorial y generador de sitios estáticos puede consumir de forma nativa.
Conversión de una sola vez
El caso más sencillo — un único archivo DOCX:
import requests
url = "https://anymd.net/api/convert"
headers = {"Authorization": "Bearer your-api-key"}
with open("report.docx", "rb") as f:
resp = requests.post(url, headers=headers, files={"file": ("report.docx", f)})
if resp.ok:
markdown = resp.text
print(f"Got {len(markdown)} chars of Markdown")
else:
print(f"Error {resp.status_code}: {resp.text}")
AnyMD detecta automáticamente DOCX a partir de la extensión del archivo y devuelve Markdown con sabor a GitHub. Se conservan encabezados, listas numeradas, listas con viñetas, negrita/cursiva, tablas, imágenes e hipervínculos, exactamente como los verías al abrir el documento en Word.
Conversión asíncrona por lotes con reintentos
Cuando tienes 50 o 500 archivos DOCX, convertirlos secuencialmente es demasiado lento. Aquí tienes un procesador asíncrono por lotes con una gestión de errores adecuada:
import asyncio
import aiohttp
from pathlib import Path
API_URL = "https://anymd.net/api/convert"
HEADERS = {"Authorization": "Bearer your-api-key"}
async def convert_one(session: aiohttp.ClientSession, path: Path, out_dir: Path) -> str:
for attempt in range(3):
try:
data = aiohttp.FormData()
data.add_field("file", path.read_bytes(), filename=path.name)
async with session.post(API_URL, headers=HEADERS, data=data) as resp:
if resp.status == 429:
wait = int(resp.headers.get("Retry-After", 5))
await asyncio.sleep(wait)
continue
if resp.ok:
md = await resp.text()
out_path = out_dir / f"{path.stem}.md"
out_path.write_text(md)
return f"✓ {path.name}"
return f"✗ {path.name} ({resp.status})"
except (aiohttp.ClientError, asyncio.TimeoutError) as e:
if attempt == 2:
return f"✗ {path.name} — {e}"
await asyncio.sleep(1.5 ** attempt)
async def batch_convert(docx_dir: str, out_dir: str, concurrency: int = 10):
docx_files = list(Path(docx_dir).glob("*.docx"))
out_path = Path(out_dir)
out_path.mkdir(exist_ok=True)
connector = aiohttp.TCPConnector(limit=concurrency)
async with aiohttp.ClientSession(connector=connector) as session:
tasks = [convert_one(session, p, out_path) for p in docx_files]
results = await asyncio.gather(*tasks)
ok = sum(1 for r in results if r.startswith("✓"))
print(f"{ok}/{len(results)} converted — see {out_dir}/")
asyncio.run(batch_convert("docx_input", "markdown_output"))
El script usa un pool de sesiones con límite de conexiones (10 solicitudes simultáneas de forma predeterminada), reintenta ante límites de tasa 429 y errores transitorios con backoff exponencial, y guarda cada resultado como un .md archivo.
Paginación para bibliotecas de documentos grandes
Si obtienes documentos desde una API o un bucket de S3, quizá necesites procesarlos con paginación. Este es un patrón con generador:
import requests
from pathlib import Path
from typing import Generator
def document_batches(doc_dir: str, batch_size: int = 25) -> Generator[list[Path], None, None]:
files = sorted(Path(doc_dir).glob("*.docx"))
for i in range(0, len(files), batch_size):
yield files[i : i + batch_size]
def convert_batch(batch: list[Path], api_key: str) -> list[tuple[str, bool]]:
results = []
with requests.Session() as sess:
sess.headers.update({"Authorization": f"Bearer {api_key}"})
for path in batch:
with open(path, "rb") as f:
resp = sess.post("https://anymd.net/api/convert", files={"file": (path.name, f)})
if resp.ok:
out = path.with_suffix(".md")
out.write_text(resp.text)
results.append((path.name, True))
else:
results.append((path.name, False))
return results
# Process 10,000 documents in batches
total, ok = 0, 0
for batch in document_batches("archive", batch_size=25):
results = convert_batch(batch, "your-api-key")
for name, success in results:
total += 1
if success:
ok += 1
print(f"Progress: {ok}/{total}")
print(f"Done: {ok}/{total} documents converted")
Gestión de archivos DOCX grandes
AnyMD admite archivos DOCX de hasta 50 MB en el plan de pago.
Qué se conserva
| Característica de DOCX | Salida Markdown |
|---|---|
| Encabezados 1–6 | # hasta ###### |
| Negrita / Cursiva | **bold** / *italic* |
| Listas numeradas | 1. item — anidación correcta |
| Listas con viñetas | - item — anidación correcta |
| Tablas | Sintaxis de tablas GFM con alineación |
| Hipervínculos | [text](url) |
| Imágenes |  |
| Bloques de código | Bloques delimitados con ``` |
| Citas en bloque | > quote |
Comparación con python-docx
La biblioteca estándar de Python python-docx te da acceso de bajo nivel a los detalles internos de DOCX — párrafos, fragmentos y estilos. Eso es potente para generar documentos mediante código, pero para la extracción significa que tienes que volver a implementar cada regla estructural que aplica el renderizador de Word:
- Encabezados — hay que asignar manualmente los estilos de párrafo a encabezados Markdown
- Listas — requiere analizar
numPry controlar la profundidad de las listas - Tablas — extracción celda por celda y fila por fila, con formato GFM manual
- Imágenes — extracción de referencias
rIdy posterior descompresión de los archivos multimedia - Control de cambios — sin soporte integrado
AnyMD hace todo esto con una sola llamada a la API. Un formato que aprender, una integración que mantener y un servicio que supervisar.
# python-docx approach — ~150 lines to get passable Markdown
from docx import Document
import re
doc = Document("report.docx")
output = []
for para in doc.paragraphs:
if para.style.name.startswith("Heading"):
level = para.style.name[-1]
output.append(f"{'#' * int(level)} {para.text}")
else:
output.append(para.text)
# Compare: AnyMD — 6 lines, no edge cases
# curl https://anymd.net/api/convert -H "Authorization: Bearer ***" -F "file=@report.docx"
De DOCX a un pipeline RAG
Una vez que tus archivos DOCX están en Markdown, el siguiente paso es fragmentarlos y generar embeddings para RAG. La salida de AnyMD es la entrada ideal para estrategias de fragmentación semántica — se conservan los límites de encabezados, la estructura de las listas y los encabezados de tablas, de modo que tus fragmentos respetan la semántica del documento en lugar de cortar párrafos a mitad de frase.
Para una comparación más detallada de los enfoques de fragmentación sobre salida Markdown, consulta nuestra guía sobre fragmentación recursiva por caracteres.
Precios
| Plan | Páginas/mes | Tamaño máximo de archivo | Precio |
|---|---|---|---|
| Gratis | 100 | 10 MB | $0 |
| Inicial | 500 | 25 MB | $19 |
| Pro | 5,000 | 50 MB | $99 |
| Empresa | Personalizado | Personalizado | Personalizado |
Una página DOCX típica se convierte en menos de 0,3 segundos.
Obtén tu clave de API gratuita — 100 páginas/mes, sin tarjeta de crédito.