Librería Python para parsear el Boletín Oficial del Registro Mercantil (BORME)
bormeparserv2 es una librería de Python para descargar, parsear, serializar e indexar el Boletín Oficial del Registro Mercantil (BORME) de España. Convierte los PDFs de la sección A/B (actos inscritos) y los XML/HTML de la sección C (convocatorias) en objetos Python tipados, JSON listo para consumir desde otra aplicación o índices consultables para búsquedas OSINT.
El flujo de datos soporta pdf/ como caché de documentos originales, json/ como datos estructurados, SQLite/MariaDB como índices relacionales y Qdrant como vector store para similitud o relación entre anuncios.
Es un fork modernizado de PabloCastellano/bormeparser, mantenido por Marc Rivero López. Ver la sección Agradecimientos para el contexto.
| Característica | Descripción |
|---|---|
| API tipada | Objetos Borme, BormeAnuncio, BormeActo, Empresa, enums PROVINCIA/SECCION/ACTO/CARGO |
| Sección A/B (PDF) | Backend pypdf que extrae actos inscritos por anuncio |
| Sección C (XML/HTML) | Backend lxml para convocatorias y avisos legales |
| API de descarga | Cliente HTTP contra boe.es/datosabiertos/api/borme/sumario, multi-thread, idempotente |
| Serialización JSON | Roundtrip Borme ↔ JSON con versionado de esquema |
| Caché local | Estructura pdf/AAAA/MM/DD/ para originales y json/AAAA/MM/DD/ para datos estructurados |
| Búsqueda relacional | Índices SQLite o MariaDB para buscar por empresa, acto, cargo, provincia y fecha |
| Vector stores | Exportación JSONL y upsert a Qdrant con payloads de empresa, acto, provincia, fecha y CVE |
| CLI | Scripts borme_to_json, borme_info, check_bormes, download_borme_pdfs, … |
| Validación en frontera | PROVINCIA.coerce(...) acepta atributo ASCII, nombre acentuado o forma bilingüe del sumario |
| Soporte oficial | Python 3.13 y 3.14 |
pip install bormeparserv2git clone https://github.com/seifreed/bormeparserv2.git
cd bormeparserv2
python3 -m venv venv
source venv/bin/activate
pip install -e .Dependencias del sistema (Debian/Ubuntu):
sudo apt-get install python3-dev libxslt1-dev libffi-dev zlib1g-dev gccdocker build -t bormeparserv2 .
docker run --rm bormeparserv2 borme_info.py /ruta/al/BORME-A-2015-27-10.pdfimport datetime
import bormeparserv2
# 1) Resolver la URL del PDF de una provincia y descargarlo
date = datetime.date(2015, 2, 10)
url = bormeparserv2.get_url_pdf(date, bormeparserv2.SECCION.A, "CACERES")
bormeparserv2.download_pdf(date, "/tmp/cc.pdf", bormeparserv2.SECCION.A, "CACERES")
# 2) Parsear el PDF a objetos Python
borme = bormeparserv2.parse("/tmp/cc.pdf", bormeparserv2.SECCION.A, sanitize=True)
print(borme.cve, borme.date, borme.provincia, len(borme.anuncios))
# 3) Serializar a JSON
borme.to_json("/tmp/cc.json")CLI equivalente:
download_borme_pdfs.py -d /tmp/bormes -f 2015-02-10 -t 2015-02-10 -p CACERES
borme_to_json.py -o /tmp /tmp/bormes/pdf/2015/02/10/BORME-A-2015-27-10.pdf
borme_info.py -n 57315 /tmp/bormes/pdf/2015/02/10/BORME-A-2015-27-10.pdf| Comando | Descripción |
|---|---|
download_borme_pdfs.py |
Descarga PDFs por rango de fechas, sección y/o provincia |
check_bormes.py |
Verifica que todos los PDFs esperados están en disco y con el tamaño correcto |
borme_to_json.py |
Convierte un PDF en JSON canónico |
borme_info.py |
Imprime los anuncios de un BORME (filtrable con -n <id>) |
borme_json_all.py |
Convierte recursivamente toda una jerarquía pdf/AAAA/MM/DD/ |
borme_json_date.py |
Convierte solo el rango de fechas indicado |
borme_index.py |
Indexa json/ en SQLite/MariaDB, busca por empresa/acto/cargo y exporta vectores |
debug_content_pdf.py |
Vuelca el content stream del PDF (debug del backend pypdf) |
borme_poller.py |
Daemon que espera a que el sumario del día esté publicado |
Cada script acepta --help para ver todas sus opciones.
El proyecto trabaja de forma natural con esta estructura local:
data/
pdf/ # caché de PDFs originales del BOE
json/ # datos estructurados producidos desde los PDFs
borme.sqlite
Los PDFs se conservan como fuente original, los JSON son la representación estructurada y los índices se reconstruyen desde json/ sin volver a parsear PDFs. El índice relacional guarda documentos, anuncios y actos, y permite filtrar por empresa, acto, cargo, persona nombrada, provincia y rango de fechas.
Flujo completo con SQLite:
download_borme_pdfs.py -d ./data -f 2024-01-01 -t 2024-12-31
borme_json_all.py -d ./data
borme_index.py index -d ./data --sqlite ./data/borme.sqlite
borme_index.py search -d ./data --sqlite ./data/borme.sqlite --empresa "TECNICAS"Más búsquedas:
borme_index.py search -d ./data --sqlite ./data/borme.sqlite --acto "Nombramientos"
borme_index.py search -d ./data --sqlite ./data/borme.sqlite --cargo "Adm. Unico"
borme_index.py search -d ./data --sqlite ./data/borme.sqlite --nombre "Marc Rivero Lopez"
borme_index.py search -d ./data --sqlite ./data/borme.sqlite --provincia Madrid -f 2024-01-01 -t 2024-03-31El filtro --nombre normaliza mayúsculas y acentos, y también acepta el orden habitual nombre apellidos aunque el BORME publique muchos cargos como apellidos nombre.
El mismo índice puede residir en MariaDB:
borme_index.py index -d ./data --backend mariadb \
--mariadb-url 'mariadb://user:pass@localhost:3306/borme'
borme_index.py search -d ./data --backend mariadb \
--mariadb-url 'mariadb://user:pass@localhost:3306/borme' \
--empresa "TECNICAS"Para similitud o relación entre anuncios, los registros pueden exportarse como JSONL o subirse a Qdrant:
borme_index.py vector-jsonl -d ./data -o ./data/vectors.jsonl
borme_index.py qdrant-upsert -d ./data --qdrant-url http://localhost:6333El upsert a Qdrant usa un embedding léxico local y determinista basado en
hashing. Es útil para agrupar anuncios parecidos sin servicios externos; si
necesitas embeddings semánticos de alta calidad, exporta vector-jsonl y
re-embebe esos textos con tu modelo preferido.
Ejemplo de servicios de apoyo con Docker:
docker run -d --name borme-mariadb \
-e MARIADB_ROOT_PASSWORD=rootpass \
-e MARIADB_DATABASE=borme \
-e MARIADB_USER=borme \
-e MARIADB_PASSWORD=bormepass \
mariadb:11.4
docker run -d --name borme-qdrant -p 6333:6333 qdrant/qdrant:latestimport bormeparserv2
from bormeparserv2 import parse, SECCION, PROVINCIA, BormeXML
# Parsear PDF (sección A/B)
borme = parse("BORME-A-2015-27-10.pdf", SECCION.A, sanitize=True)
# Parsear XML del sumario diario
bxml = BormeXML.from_file("BORME-S-20150924.xml")
provincias = bxml.get_provincias(SECCION.A)
url = bxml.get_url_pdfs(seccion=SECCION.A, provincia=PROVINCIA.MADRID)
# Parsear sección C (XML o HTML)
data = parse("BORME-C-2011-20488.xml", SECCION.C)
print(data["empresa"], data["cifs"])from bormeparserv2 import PROVINCIA
PROVINCIA.coerce("CACERES") # → PROVINCIA.CACERES
PROVINCIA.coerce("Cáceres") # → PROVINCIA.CACERES
PROVINCIA.coerce("CÁCERES") # → PROVINCIA.CACERES
PROVINCIA.coerce("VALENCIA/VALÈNCIA") # → PROVINCIA.VALENCIA (forma bilingüe del sumario)
PROVINCIA.coerce(PROVINCIA.MADRID) # passthroughfrom bormeparserv2 import Borme
borme.to_json("/tmp/out.json")
borme2 = Borme.from_json("/tmp/out.json")
assert borme2.cve == borme.cve- Python 3.13 o 3.14
lxml >= 5.3pypdf >= 5.0pdfminer.six >= 20250506requests >= 2.32PyMySQL >= 1.2para indexación MariaDB
Ver requirements.txt para dependencias runtime y tooling; setup.py usa sólo la sección runtime al empaquetar.
- Haz un fork del repositorio
- Crea una rama (
git checkout -b feature/nombre) - Asegúrate de que pasan todos los quality gates y los tests, incluidos los live (
BORMEPARSERV2_LIVE=1) - Añade un test de regresión por cada bug y al menos uno end-to-end por cada feature
- Abre un Pull Request describiendo el porqué del cambio
Ver CLAUDE.md para el detalle de las políticas (no suprimir avisos, no mocks, regresiones obligatorias, sin código legacy).
Si te resulta útil:
Distribuido bajo licencia GPL-3.0-or-later. Ver LICENSE.txt.
Atribución
- Mantenedor del fork: Marc Rivero López | [email protected] | @seifreed
- Repositorio: github.com/seifreed/bormeparserv2
bormeparserv2 es un fork directo de bormeparser de Pablo Castellano (@_pablog). Todo el diseño original de los backends, el modelo de dominio (Borme, BormeAnuncio, BormeActo, enums ACTO/CARGO/PROVINCIA/SECCION), los regex de parsing y los fixtures de ejemplo son obra suya y son la base sobre la que está construido este proyecto.
Este fork se limita a:
- Modernizar el código a Python 3.13/3.14 y eliminar las ramas de compatibilidad con versiones antiguas.
- Migrar a los endpoints actuales del BOE (
datosabiertos/api/borme/sumario/). - Reforzar los quality gates (ruff/black/mypy/bandit/pip-audit/hadolint/actionlint/sphinx
-W) y la suite de tests sin mocks. - Corregir regresiones latentes encontradas al ejercitar la API y los scripts contra datos reales.
Si bormeparserv2 te resulta útil, considera también ⭐ el proyecto original — sin él, nada de esto existiría.
Hecho para análisis de transparencia, OSINT y herramientas de inteligencia económica sobre el Registro Mercantil español