bormeparserv2 is a Python library for parsing BORME files
Project description
bormeparserv2
Librería Python para parsear el Boletín Oficial del Registro Mercantil (BORME)
Resumen
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ísticas principales
| 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 |
| SBOM | Generación CycloneDX 1.7, validación sbom-tools NTIA y gate de rating mínimo A en CI |
| 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 |
Instalación
Desde PyPI
pip install bormeparserv2
Desde código fuente
git 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 gcc
Docker
docker build -t bormeparserv2 .
docker run --rm bormeparserv2 borme_info.py /ruta/al/BORME-A-2015-27-10.pdf
Inicio rápido
import 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
CLI
| 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.
Almacenamiento e indexación
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-31
El 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:6333
El 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:latest
SBOM y rating
El repositorio incluye un generador de SBOM CycloneDX 1.7 basado en las
dependencias runtime de requirements.txt y la metadata instalada del entorno
Python. La CI instala sbom-tools 0.1.21, valida el SBOM contra NTIA con
--fail-on-warning y exige rating mínimo A en el perfil standard.
Uso local:
python -m pip install -r requirements.txt
python -m pip install -e .
cargo install sbom-tools --version 0.1.21 --locked
python scripts/generate_sbom.py -o build/sbom/bormeparserv2.cdx.json
~/.cargo/bin/sbom-tools validate build/sbom/bormeparserv2.cdx.json --standard ntia --fail-on-warning
~/.cargo/bin/sbom-tools quality build/sbom/bormeparserv2.cdx.json --profile standard -o json -O build/sbom/bormeparserv2.quality.json
python scripts/check_sbom_rating.py build/sbom/bormeparserv2.quality.json
El score numérico es el que calcula sbom-tools; el proyecto no añade CPEs ni
firmas inventadas para inflar la nota. El gate operativo es A o superior.
Releases por tag
El repositorio publica releases desde tags Git con formato vX.Y.Z o una
versión PEP 440 equivalente como vX.Y.Zrc1. Al empujar el tag, GitHub Actions
ejecuta .github/workflows/release.yml, valida la suite con 100% de cobertura,
construye los paquetes, crea o actualiza la GitHub Release y publica el wheel y
el sdist en PyPI.
La publicación a PyPI usa el environment pypi y espera un secret llamado
PYPI_API_TOKEN. Para la primera subida puede ser necesario un token de cuenta
de PyPI; después conviene reemplazarlo por un token limitado al proyecto.
git tag v1.0.0
git push origin v1.0.0
Assets adjuntos a la release:
bormeparserv2-X.Y.Z-py3-none-any.whlbormeparserv2-X.Y.Z.tar.gzbormeparserv2-X.Y.Z.cdx.jsonbormeparserv2-X.Y.Z.quality.jsonSHA256SUMS
Durante la release, el workflow inyecta BORMEPARSERV2_VERSION desde el tag
para que la wheel, el sdist y el SBOM usen la versión publicada, aunque la rama
principal siga declarando una versión de desarrollo.
Uso como librería
API básica
import 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"])
PROVINCIA.coerce — acepta todas las formas
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) # passthrough
Roundtrip JSON
from bormeparserv2 import Borme
borme.to_json("/tmp/out.json")
borme2 = Borme.from_json("/tmp/out.json")
assert borme2.cve == borme.cve
Requisitos
- 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.
Contribuir
- 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).
Apoya el proyecto
Si te resulta útil:
Licencia
Distribuido bajo licencia GPL-3.0-or-later. Ver LICENSE.txt.
El paquete completo se distribuye como GPL-3.0-or-later. El código heredado mantiene la autoría de Pablo Castellano; los cambios y ficheros nuevos del fork mantienen la autoría de Marc Rivero López. Las cabeceras SPDX de los ficheros Python reflejan esa atribución. No se relicencia a MIT ninguna parte integrada del fork para evitar ambigüedad sobre el alcance de la GPL.
Atribución
- Mantenedor del fork: Marc Rivero López | mriverolopez@gmail.com | @seifreed
- Repositorio: github.com/seifreed/bormeparserv2
- Proyecto original: github.com/PabloCastellano/bormeparser
Agradecimientos
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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bormeparserv2-1.0.0.tar.gz.
File metadata
- Download URL: bormeparserv2-1.0.0.tar.gz
- Upload date:
- Size: 656.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e8044730f0e7c404dae5afb5f4d5ea8caadcd41caa10d9335d427b6bcd5b10a6
|
|
| MD5 |
ccfa36ee7a90130a441f4a91d455a3ce
|
|
| BLAKE2b-256 |
9719fb0ec6d7ab6b1cf432bb606223bcfe32a2e61ca72b588786c99211df6281
|
File details
Details for the file bormeparserv2-1.0.0-py3-none-any.whl.
File metadata
- Download URL: bormeparserv2-1.0.0-py3-none-any.whl
- Upload date:
- Size: 664.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5cd8a5becbbbe312d43b98ef92b758e62f5382b4a32643ef86c3b5d2dea32c60
|
|
| MD5 |
b3c3ccb53d9e613a76405d2c0df4f13d
|
|
| BLAKE2b-256 |
1035ce425db9b40398b5866d7f7d1e946f5ee7018ebea91e03a678789e2875a4
|