175 lines
6.0 KiB
Python
175 lines
6.0 KiB
Python
"""
|
|
Stahování seznamu registrovaných pojištěnců ZPMVČR (211) — čistý Python (requests + bs4).
|
|
|
|
ZPMVČR běží na ODLIŠNÉ platformě (eforms.zpmvcr.cz) — ne portalzp.cz:
|
|
- login: PIN + heslo (POST formulář), bez certifikátu a bez NMSigneru
|
|
- seznam: NENÍ datový soubor jako u ostatních pojišťoven (EP2 sekce je prázdná).
|
|
Jediný zdroj je HTML "Přehled registrací" na stránce registrovani_pojistenci,
|
|
který se naparsuje a uloží jako CSV.
|
|
|
|
Co skript dělá:
|
|
1. Přihlásí se (PIN + heslo)
|
|
2. Projde stránkovaný přehled VŠECH registrací (platné i neplatné) pro IČP 09305001
|
|
3. Uloží výsledek jako CSV do složky SeznamyPojištěnců (sloupce níže)
|
|
|
|
CSV sloupce: Číslo pojištěnce; Titul; Příjmení; Jméno; Registrace od; Registrace do
|
|
"""
|
|
|
|
import csv
|
|
import os
|
|
import sys
|
|
from datetime import date
|
|
|
|
import requests
|
|
from bs4 import BeautifulSoup
|
|
|
|
try:
|
|
sys.stdout.reconfigure(encoding="utf-8")
|
|
sys.stderr.reconfigure(encoding="utf-8")
|
|
except Exception:
|
|
pass
|
|
|
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..")))
|
|
from Knihovny.najdi_dropbox import get_dropbox_root
|
|
|
|
# ── Přihlašovací údaje ────────────────────────────────────────────────────────
|
|
PIN = "9023895287"
|
|
PIN2 = ""
|
|
HESLO = "Ax162q8+"
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
BASE_URL = "https://eforms.zpmvcr.cz"
|
|
LOGIN_URL = f"{BASE_URL}/eforms/ekomunikace"
|
|
SEZNAM_URL = f"{BASE_URL}/eforms/smluvni_zdravotnicke_zarizeni/registrovani_pojistenci"
|
|
|
|
ICP = "09305001" # IČP MUDr. Michaela Buzalková
|
|
REGISTRACE = "3" # 1=platné, 2=neplatné, 3=všechny
|
|
TRIDIT = "1" # 1=příjmení, 2=číslo pojištěnce
|
|
|
|
CSV_HLAVICKA = ["Číslo pojištěnce", "Titul", "Příjmení", "Jméno", "Registrace od", "Registrace do"]
|
|
|
|
DEST_DIR = os.path.join(
|
|
get_dropbox_root(),
|
|
"Ordinace", "Dokumentace_ke_zpracování", "Zúčtovací zprávy", "SeznamyPojištěnců",
|
|
)
|
|
|
|
|
|
def prihlaseni() -> requests.Session:
|
|
"""Přihlásí se PIN + heslem, vrátí session."""
|
|
session = requests.Session()
|
|
session.headers["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
|
|
|
session.get(LOGIN_URL, timeout=15).raise_for_status()
|
|
r = session.post(LOGIN_URL, data={"pin": PIN, "pin2": PIN2, "pwd": HESLO}, timeout=15)
|
|
r.raise_for_status()
|
|
|
|
if 'name="pin"' in r.text and "Přihlásit" in r.text:
|
|
raise RuntimeError("Přihlášení selhalo — zkontroluj PIN a heslo")
|
|
|
|
print("Přihlášení úspěšné!")
|
|
return session
|
|
|
|
|
|
def parse_rows(html: str) -> list[list[str]]:
|
|
"""Naparsuje řádky přehledu. Vrátí seznam [číslo, titul, příjmení, jméno, reg_od, reg_do]."""
|
|
soup = BeautifulSoup(html, "html.parser")
|
|
rows = []
|
|
for tr in soup.select("tr.c1, tr.c2"):
|
|
vals = []
|
|
for td in tr.find_all("td"):
|
|
for sp in td.select("span.responsiveColumn"):
|
|
sp.extract()
|
|
vals.append(td.get_text(strip=True))
|
|
# platný datový řádek má vyplněné číslo pojištěnce v prvním sloupci
|
|
if len(vals) >= 6 and vals[0]:
|
|
rows.append(vals[:6])
|
|
return rows
|
|
|
|
|
|
def precti_celkem(html: str) -> int | None:
|
|
"""Z hlášky 'Přehled ... (celkem N)' získá očekávaný počet."""
|
|
import re
|
|
m = re.search(r"celkem\s+(\d+)", html)
|
|
return int(m.group(1)) if m else None
|
|
|
|
|
|
def stahni_seznam(session: requests.Session) -> list[list[str]]:
|
|
"""Projde stránkovaný přehled a vrátí všechny řádky."""
|
|
base_data = {
|
|
"icp": ICP, "arztart": "",
|
|
"mesic": str(date.today().month), "rok": str(date.today().year),
|
|
"registrace": REGISTRACE, "tridit": TRIDIT, "vyhledat": "Vyhledat",
|
|
}
|
|
|
|
vsechny: list[list[str]] = []
|
|
videno: set = set()
|
|
celkem_ocekavano = None
|
|
|
|
page = 1
|
|
while page <= 200:
|
|
data = dict(base_data)
|
|
if page > 1:
|
|
data["page"] = str(page)
|
|
r = session.post(SEZNAM_URL, data=data, timeout=30)
|
|
r.raise_for_status()
|
|
|
|
if celkem_ocekavano is None:
|
|
celkem_ocekavano = precti_celkem(r.text)
|
|
if celkem_ocekavano is not None:
|
|
print(f"Přehled hlásí celkem {celkem_ocekavano} registrací.")
|
|
|
|
rows = parse_rows(r.text)
|
|
nove = [row for row in rows if tuple(row) not in videno]
|
|
|
|
if not nove:
|
|
break
|
|
|
|
for row in nove:
|
|
videno.add(tuple(row))
|
|
vsechny.extend(nove)
|
|
print(f" Strana {page}: +{len(nove)} (celkem {len(vsechny)})")
|
|
|
|
# poslední strana — méně řádků než plná stránka
|
|
if len(rows) < 20:
|
|
break
|
|
page += 1
|
|
|
|
if celkem_ocekavano is not None and len(vsechny) != celkem_ocekavano:
|
|
print(f" POZOR: staženo {len(vsechny)}, ale přehled hlásil {celkem_ocekavano}.")
|
|
|
|
return vsechny
|
|
|
|
|
|
def uloz_csv(rows: list[list[str]]) -> str:
|
|
"""Uloží řádky jako CSV (Excel-friendly: utf-8-sig, oddělovač ;). Vrátí cestu."""
|
|
os.makedirs(DEST_DIR, exist_ok=True)
|
|
dnes = date.today().strftime("%Y-%m-%d")
|
|
filename = f"{dnes} 211 ZPMVČR vsechny registrace.csv"
|
|
path = os.path.join(DEST_DIR, filename)
|
|
|
|
with open(path, "w", encoding="utf-8-sig", newline="") as f:
|
|
w = csv.writer(f, delimiter=";")
|
|
w.writerow(CSV_HLAVICKA)
|
|
w.writerows(rows)
|
|
|
|
return path
|
|
|
|
|
|
def hlavni() -> None:
|
|
session = prihlaseni()
|
|
|
|
print("\n=== Stahování přehledu registrací ===")
|
|
rows = stahni_seznam(session)
|
|
print(f"Staženo: {len(rows)} registrací.")
|
|
|
|
if not rows:
|
|
print("Žádné registrace — CSV se neuloží.")
|
|
return
|
|
|
|
path = uloz_csv(rows)
|
|
print(f"\nHotovo — uloženo: {path}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
hlavni()
|