Skip to main content

bormeparserv2 is a Python library for parsing BORME files

Project description

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bormeparserv2-1.0.0.tar.gz (656.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bormeparserv2-1.0.0-py3-none-any.whl (664.3 kB view details)

Uploaded Python 3

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

Hashes for bormeparserv2-1.0.0.tar.gz
Algorithm Hash digest
SHA256 e8044730f0e7c404dae5afb5f4d5ea8caadcd41caa10d9335d427b6bcd5b10a6
MD5 ccfa36ee7a90130a441f4a91d455a3ce
BLAKE2b-256 9719fb0ec6d7ab6b1cf432bb606223bcfe32a2e61ca72b588786c99211df6281

See more details on using hashes here.

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

Hashes for bormeparserv2-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5cd8a5becbbbe312d43b98ef92b758e62f5382b4a32643ef86c3b5d2dea32c60
MD5 b3c3ccb53d9e613a76405d2c0df4f13d
BLAKE2b-256 1035ce425db9b40398b5866d7f7d1e946f5ee7018ebea91e03a678789e2875a4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page