Skip to main content

bormeparserv2

bormeparserv2

Librería Python para parsear el Boletín Oficial del Registro Mercantil (BORME)

PyPI Versiones de Python Licencia Estado CI Cobertura Codecov

Stars Issues Buy Me a Coffee

🇬🇧 English version


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.whl
  • bormeparserv2-X.Y.Z.tar.gz
  • bormeparserv2-X.Y.Z.cdx.json
  • bormeparserv2-X.Y.Z.quality.json
  • SHA256SUMS

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.3
  • pypdf >= 5.0
  • pdfminer.six >= 20250506
  • requests >= 2.32
  • PyMySQL >= 1.2 para indexación MariaDB

Ver requirements.txt para dependencias runtime y tooling; setup.py usa sólo la sección runtime al empaquetar.


Contribuir

  1. Haz un fork del repositorio
  2. Crea una rama (git checkout -b feature/nombre)
  3. Asegúrate de que pasan todos los quality gates y los tests, incluidos los live (BORMEPARSERV2_LIVE=1)
  4. Añade un test de regresión por cada bug y al menos uno end-to-end por cada feature
  5. 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:

Buy Me A Coffee

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


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

Release files for bormeparserv2 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for bormeparserv2 1.0.0
File Size Uploaded
bormeparserv2-1.0.0.tar.gz 656.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for bormeparserv2 1.0.0
File Interpreter ABI Platform
bormeparserv2-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.3 MB

Release files / bormeparserv2-1.0.0.tar.gz

Download URL bormeparserv2-1.0.0.tar.gz
Size 656.2 kB
Tags Source
SHA-256 checksum
How to use checksums
e8044730f0e7c404dae5afb5f4d5ea8caadcd41caa10d9335d427b6bcd5b10a6
BLAKE2b-256 checksum
How to use checksums
9719fb0ec6d7ab6b1cf432bb606223bcfe32a2e61ca72b588786c99211df6281
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.12

Release files / bormeparserv2-1.0.0-py3-none-any.whl

Download URL bormeparserv2-1.0.0-py3-none-any.whl
Size 664.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5cd8a5becbbbe312d43b98ef92b758e62f5382b4a32643ef86c3b5d2dea32c60
BLAKE2b-256 checksum
How to use checksums
1035ce425db9b40398b5866d7f7d1e946f5ee7018ebea91e03a678789e2875a4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.12

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page