notebookvb
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
# ZPMVČR (211) — Stahování seznamu registrovaných pojištěnců
|
||||
|
||||
## Co skript dělá
|
||||
|
||||
`StahniSeznamPojistencuZPMVCR.py` (čistý Python, requests + bs4):
|
||||
|
||||
1. **Přihlásí se** PIN + heslem (POST formulář, bez certifikátu / NMSigneru)
|
||||
2. **Projde stránkovaný přehled** všech registrací pro IČP 09305001
|
||||
3. **Uloží CSV** do `…\Zúčtovací zprávy\SeznamyPojištěnců\`
|
||||
|
||||
## Platforma — ODLIŠNÁ od ostatních
|
||||
|
||||
ZPMVČR běží na **eforms.zpmvcr.cz**, NE na portalzp.cz. Žádné certifikáty, žádné schránky,
|
||||
žádné datové rozhraní .001. Login je PIN + heslo.
|
||||
|
||||
## Zásadní rozdíl: NENÍ datový soubor
|
||||
|
||||
Ostatní pojišťovny dávají datový soubor (.001 / F-soubor). ZPMVČR **nemá** ekvivalent:
|
||||
- EP2 sekce (`dokumenty_ke_stazeni/ep2`) je prázdná — *"nebylo stahování dokumentů nastaveno"*.
|
||||
- Jediný zdroj seznamu je **HTML přehled** na stránce `registrovani_pojistenci`,
|
||||
který se musí naparsovat → proto výstupem je **CSV**, ne datový soubor.
|
||||
|
||||
## Přihlášení
|
||||
|
||||
POST `https://eforms.zpmvcr.cz/eforms/ekomunikace`
|
||||
Pole: `pin` (9023895287), `pin2` (prázdné), `pwd` (heslo).
|
||||
|
||||
## Stažení seznamu
|
||||
|
||||
POST `https://eforms.zpmvcr.cz/eforms/smluvni_zdravotnicke_zarizeni/registrovani_pojistenci`
|
||||
|
||||
| Pole | Hodnota | Význam |
|
||||
|------|---------|--------|
|
||||
| `icp` | `09305001` | IČP (nebo "Vše") |
|
||||
| `arztart` | `` (prázdné = Vše) | odbornost D/G/P/S |
|
||||
| `mesic` / `rok` | aktuální měsíc/rok | období |
|
||||
| `registrace` | `3` | 1=platné, 2=neplatné, **3=všechny** |
|
||||
| `tridit` | `1` | 1=příjmení, 2=číslo pojištěnce |
|
||||
| `vyhledat` | `Vyhledat` | submit |
|
||||
|
||||
Výsledek je **stránkovaný** (~20 řádků/strana). Další strany: POST + pole `page=N`.
|
||||
Řádky v HTML: `<tr class="c1|c2">`, hodnoty za `<span class="responsiveColumn">Label:</span>`.
|
||||
Hláška "Přehled ... (celkem N)" udává očekávaný počet (kontrola úplnosti).
|
||||
|
||||
## CSV výstup
|
||||
|
||||
Soubor `YYYY-MM-DD 211 ZPMVČR vsechny registrace.csv`, kódování utf-8-sig (Excel),
|
||||
oddělovač `;`. Sloupce: Číslo pojištěnce; Titul; Příjmení; Jméno; Registrace od; Registrace do.
|
||||
|
||||
## Soubory
|
||||
|
||||
| Soubor | Popis |
|
||||
|--------|-------|
|
||||
| `StahniSeznamPojistencuZPMVCR.py` | Hlavní skript — login + scrape přehledu → CSV |
|
||||
|
||||
## Parametry
|
||||
|
||||
- **IČP**: 09305001 (MUDr. Michaela Buzalková)
|
||||
- **Login**: PIN 9023895287 + heslo (v kódu, stejně jako StahováníZpráv/211)
|
||||
|
||||
## Stav
|
||||
|
||||
Hotovo a otestováno (17.06.2026): login ✓, staženo 172 registrací (9 stran, sedí s "celkem 172"),
|
||||
CSV uloženo. Volba uživatele: VŠECHNY registrace (registrace=3).
|
||||
@@ -0,0 +1,174 @@
|
||||
"""
|
||||
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()
|
||||
Reference in New Issue
Block a user