#!/usr/bin/env python3 """ KRS PDF Parser - wyciąga dane zarządu i wspólników z odpisu KRS Parsuje odpisy pełne pobrane z ekrs.ms.gov.pl i wyciąga: - Członków zarządu (funkcja, imię, nazwisko, PESEL) - Wspólników (imię, nazwisko, PESEL, udziały) - Prokurentów Usage: python scripts/parse_krs_pdf.py --file /path/to/odpis.pdf python scripts/parse_krs_pdf.py --dir /path/to/pdfs/ """ import re import json import argparse from pathlib import Path from dataclasses import dataclass, asdict from typing import List, Optional, Dict, Any try: import pdfplumber except ImportError: print("Wymagana biblioteka pdfplumber. Zainstaluj: pip install pdfplumber") exit(1) @dataclass class Person: """Osoba powiązana z firmą""" nazwisko: str imiona: str pesel: Optional[str] = None rola: str = "" # PREZES ZARZĄDU, CZŁONEK ZARZĄDU, WSPÓLNIK, PROKURENT udzialy: Optional[str] = None # dla wspólników def full_name(self) -> str: return f"{self.imiona} {self.nazwisko}" @dataclass class KRSData: """Dane wyciągnięte z odpisu KRS""" krs: str nazwa: str nip: Optional[str] = None regon: Optional[str] = None zarzad: List[Person] = None wspolnicy: List[Person] = None prokurenci: List[Person] = None zrodlo: str = "ekrs.ms.gov.pl" def __post_init__(self): if self.zarzad is None: self.zarzad = [] if self.wspolnicy is None: self.wspolnicy = [] if self.prokurenci is None: self.prokurenci = [] def to_dict(self) -> Dict[str, Any]: return { 'krs': self.krs, 'nazwa': self.nazwa, 'nip': self.nip, 'regon': self.regon, 'zarzad': [asdict(p) for p in self.zarzad], 'wspolnicy': [asdict(p) for p in self.wspolnicy], 'prokurenci': [asdict(p) for p in self.prokurenci], 'zrodlo': self.zrodlo } def extract_text_from_pdf(pdf_path: str) -> str: """Wyciąga tekst z PDF""" with pdfplumber.open(pdf_path) as pdf: text = "" for page in pdf.pages: page_text = page.extract_text() if page_text: text += page_text + "\n" return text def parse_person_block(lines: List[str], start_idx: int) -> Optional[Person]: """ Parsuje blok danych osoby z linii PDF Format w PDF: 1.Nazwisko / Nazwa lub firma 1 - NAZWISKO 2.Imiona 1 - IMIĘ DRUGIE_IMIĘ 3.Numer PESEL/REGON lub data 1 - 12345678901, ------ """ person = Person(nazwisko="", imiona="") found_nazwisko = False found_imiona = False found_pesel = False for i in range(start_idx, min(start_idx + 10, len(lines))): line = lines[i].strip() # Wykryj początek następnej osoby - przestań parsować if i > start_idx and ('1.Nazwisko' in line or 'Nazwisko / Nazwa' in line): # Początek nowej osoby - koniec bloku break # Nazwisko (tylko pierwsze znalezione) if not found_nazwisko and 'Nazwisko' in line and ' - ' in line: match = re.search(r' - ([A-ZĄĆĘŁŃÓŚŹŻ\-]+)$', line) if match: person.nazwisko = match.group(1) found_nazwisko = True # Imiona (tylko pierwsze znalezione) if not found_imiona and 'Imiona' in line and ' - ' in line: match = re.search(r' - ([A-ZĄĆĘŁŃÓŚŹŻ ]+)$', line) if match: person.imiona = match.group(1).strip() found_imiona = True # PESEL (tylko pierwsze znalezione) if not found_pesel and 'PESEL' in line and ' - ' in line: match = re.search(r' - (\d{11})', line) if match: person.pesel = match.group(1) found_pesel = True # Funkcja (dla zarządu) if 'Funkcja' in line and ' - ' in line: match = re.search(r' - ([A-ZĄĆĘŁŃÓŚŹŻ ]+)$', line) if match: person.rola = match.group(1).strip() if person.nazwisko and person.imiona: return person return None def parse_krs_pdf(pdf_path: str) -> KRSData: """ Parsuje odpis KRS i wyciąga dane """ text = extract_text_from_pdf(pdf_path) lines = text.split('\n') # Extract basic info krs_match = re.search(r'Numer KRS:\s*(\d{10})', text) krs = krs_match.group(1) if krs_match else "" # Find company name - format: "3.Firma, pod którą spółka działa 1 - NAZWA FIRMY" nazwa = "" nazwa_match = re.search(r'3\.Firma,?\s+pod którą spółka działa\s+\d+\s+-\s+([^\n]+)', text) if nazwa_match: nazwa = nazwa_match.group(1).strip() # NIP and REGON - format: "REGON: 369796786, NIP: 5862329746" nip_match = re.search(r'NIP:\s*(\d{10})', text) nip = nip_match.group(1) if nip_match else None regon_match = re.search(r'REGON:\s*(\d{9,14})', text) regon = regon_match.group(1) if regon_match else None data = KRSData(krs=krs, nazwa=nazwa, nip=nip, regon=regon) # Parse sections in_zarzad = False in_wspolnicy = False in_prokurenci = False for i, line in enumerate(lines): line_stripped = line.strip() # Detect sections if 'ZARZĄD' in line_stripped.upper() and 'Nazwa organu' in line_stripped: in_zarzad = True in_wspolnicy = False in_prokurenci = False continue # Wspólnicy - szukaj "Dane wspólników" żeby nie łapać "Wspólnik może mieć:" if 'Dane wspólników' in line_stripped or 'WSPÓLNICY' in line_stripped.upper(): in_wspolnicy = True in_zarzad = False in_prokurenci = False if 'PROKURENCI' in line_stripped.upper() or 'Prokurent' in line_stripped: in_prokurenci = True in_zarzad = False in_wspolnicy = False # Parse person data when we find "Nazwisko" if '1.Nazwisko' in line_stripped or 'Nazwisko / Nazwa' in line_stripped: person = parse_person_block(lines, i) if person: if in_zarzad: # Look for function in nearby lines for j in range(i, min(i + 8, len(lines))): if 'Funkcja' in lines[j] and ' - ' in lines[j]: func_match = re.search(r' - ([A-ZĄĆĘŁŃÓŚŹŻ ]+)$', lines[j]) if func_match: person.rola = func_match.group(1).strip() break if not person.rola: person.rola = "CZŁONEK ZARZĄDU" data.zarzad.append(person) elif in_wspolnicy: person.rola = "WSPÓLNIK" # Look for share info for j in range(i, min(i + 10, len(lines))): if 'udziałów' in lines[j].lower() or 'udział' in lines[j].lower(): data.wspolnicy.append(person) break else: data.wspolnicy.append(person) elif in_prokurenci: person.rola = "PROKURENT" data.prokurenci.append(person) return data def main(): parser = argparse.ArgumentParser(description="Parse KRS PDF files") parser.add_argument("--file", type=str, help="Single PDF file to parse") parser.add_argument("--dir", type=str, help="Directory with PDF files") parser.add_argument("--output", type=str, help="Output JSON file") args = parser.parse_args() results = [] if args.file: print(f"Parsing: {args.file}") data = parse_krs_pdf(args.file) results.append(data.to_dict()) # Print summary print(f"\n=== {data.nazwa} (KRS: {data.krs}) ===") print(f"NIP: {data.nip}, REGON: {data.regon}") print(f"\nZarząd ({len(data.zarzad)} osób):") for p in data.zarzad: print(f" - {p.full_name()} - {p.rola}") print(f"\nWspólnicy ({len(data.wspolnicy)} osób):") for p in data.wspolnicy: print(f" - {p.full_name()}") if data.prokurenci: print(f"\nProkurenci ({len(data.prokurenci)} osób):") for p in data.prokurenci: print(f" - {p.full_name()}") elif args.dir: pdf_dir = Path(args.dir) pdf_files = list(pdf_dir.glob("*.pdf")) print(f"Found {len(pdf_files)} PDF files") for pdf_file in pdf_files: print(f"Parsing: {pdf_file.name}...") try: data = parse_krs_pdf(str(pdf_file)) results.append(data.to_dict()) print(f" OK: {data.nazwa}") except Exception as e: print(f" ERROR: {e}") # Save results if args.output and results: with open(args.output, 'w', encoding='utf-8') as f: json.dump(results, f, ensure_ascii=False, indent=2) print(f"\nResults saved to: {args.output}") elif results: print("\n=== JSON OUTPUT ===") print(json.dumps(results, ensure_ascii=False, indent=2)) if __name__ == "__main__": main()