diff --git a/Insurance/StahováníZpráv/209 ZPŠ/zps_cookies.json b/Insurance/StahováníZpráv/209 ZPŠ/zps_cookies.json index 38ecfac..77ac302 100644 --- a/Insurance/StahováníZpráv/209 ZPŠ/zps_cookies.json +++ b/Insurance/StahováníZpráv/209 ZPŠ/zps_cookies.json @@ -1,7 +1,7 @@ [ { "name": "SID", - "value": "df433a68180840ba413c8f9c68eae034", + "value": "9ed44d48a017f8915cdfb566d2e2e952", "domain": ".portal.zpskoda.cz", "path": "/", "expires": -1, @@ -14,7 +14,7 @@ "value": "CERT", "domain": ".portal.zpskoda.cz", "path": "/", - "expires": 1808227911, + "expires": 1808246438, "secure": true, "httpOnly": false, "sameSite": "Lax" diff --git a/Insurance/StahováníZpráv/211 ZPMVČR/01_prihlaseni.py b/Insurance/StahováníZpráv/211 ZPMVČR/01_prihlaseni.py new file mode 100644 index 0000000..2d3d12c --- /dev/null +++ b/Insurance/StahováníZpráv/211 ZPMVČR/01_prihlaseni.py @@ -0,0 +1,69 @@ +""" +Přihlášení na portál ZPMVČR (eforms.zpmvcr.cz) jménem a heslem. +Odlišná platforma od portalzp.cz — přihlášení přes POST formulář s polem PIN a heslo. + +Přihlašovací pole: + pin — přihlašovací jméno (zobrazeno jako "PIN" na stránce) + pin2 — druhá část PINu (obvykle prázdné) + pwd — heslo + +POUŽITÍ: + pip install requests + python 01_prihlaseni.py +""" + +import json +import os +import sys + +import requests + +# ── Přihlašovací údaje ──────────────────────────────────────────────────────── +PIN = "9023895287" +PIN2 = "" # druhé pole PINu — obvykle prázdné +HESLO = "Ax162q8+" +# ───────────────────────────────────────────────────────────────────────────── + +LOGIN_URL = "https://eforms.zpmvcr.cz/eforms/ekomunikace" +COOKIES_FILE = os.path.abspath(os.path.join(os.path.dirname(__file__), "zpmvcr_cookies.json")) + + +def main() -> None: + session = requests.Session() + session.headers["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" + + # 1. GET → session cookie + r = session.get(LOGIN_URL, timeout=15) + r.raise_for_status() + + # 2. POST přihlášení + r = session.post(LOGIN_URL, data={"pin": PIN, "pin2": PIN2, "pwd": HESLO}, timeout=15) + r.raise_for_status() + + # Úspěch = server přesměroval pryč z login stránky na redir nebo smluvni + if "pin" in r.text and "Přihlásit" in r.text: + print(f"Prihlaseni selhalo — zkontroluj PIN a HESLO. URL: {r.url}") + sys.exit(1) + + print(f"Prihlaseni uspesne — {r.url}") + + cookies = [ + { + "name": c.name, + "value": c.value, + "domain": c.domain if c.domain.startswith(".") else "." + c.domain, + "path": c.path or "/", + "expires": int(c.expires) if c.expires else -1, + "secure": bool(c.secure), + "httpOnly": False, + "sameSite": "Lax", + } + for c in session.cookies + ] + with open(COOKIES_FILE, "w", encoding="utf-8") as f: + json.dump(cookies, f, indent=2, ensure_ascii=False) + print(f"Ulozeno {len(cookies)} cookies -> {COOKIES_FILE}") + + +if __name__ == "__main__": + main() diff --git a/Insurance/StahováníZpráv/211 ZPMVČR/02_stahuj_vse.py b/Insurance/StahováníZpráv/211 ZPMVČR/02_stahuj_vse.py new file mode 100644 index 0000000..d971c5e --- /dev/null +++ b/Insurance/StahováníZpráv/211 ZPMVČR/02_stahuj_vse.py @@ -0,0 +1,244 @@ +""" +Stahování dokumentů ze ZPMVČR portálu (eforms.zpmvcr.cz). + +Stahuje Zúčtovací zprávy a Avíza za celou dostupnou historii. + +Použij po 01_prihlaseni.py (ten uloží zpmvcr_cookies.json). + +JAK TO FUNGUJE: + Portál používá POST formuláře. Stažení souboru: + POST /eforms/.../zuctovaci_zprava + data: bin=1, dl=1, historie=1, doc_id= + +POUŽITÍ: + pip install playwright + playwright install chrome + python 02_stahuj_vse.py +""" + +import json +import os +import re +import sys +import time +from datetime import datetime + +BASE_URL = "https://eforms.zpmvcr.cz" +ZPRAVY_URL = f"{BASE_URL}/eforms/smluvni_zdravotnicke_zarizeni/dokumenty_ke_stazeni/zuctovaci_zprava" +AVIZA_URL = f"{BASE_URL}/eforms/smluvni_zdravotnicke_zarizeni/dokumenty_ke_stazeni/aviza" + +COOKIES_FILE = os.path.abspath(os.path.join(os.path.dirname(__file__), "zpmvcr_cookies.json")) +DOWNLOAD_DIR = os.path.join(os.path.dirname(__file__), "Staženo") + +# Konfigurace sekcí — sloupce se liší podle stránky: +# Zúčtovací zprávy: IČ | VarSymb | IntČ | Typ | Velikost | Období | Pobočka | Staženo +# Avíza: IČ | Dokument | Typ | Velikost | Období | Pobočka | Staženo +SEKCE = [ + { + "name": "Zúčtovací zprávy", + "url": ZPRAVY_URL, + "need_history": True, # je třeba kliknout "Zobrazit historii" + "dl_historie": "1", # hodnota pole 'historie' při stahování souboru + "ident_col": 1, # Variabilní symbol + "typ_col": 3, # Typ (pdf) + "obdobi_col": 5, # Období (01.02.2026) + "ident_prefix": "VS-", + }, + { + "name": "Avíza", + "url": AVIZA_URL, + "need_history": False, # Avíza zobrazuje vše rovnou + "dl_historie": "0", # Avíza nevyužívá history mode — musí být 0 + "ident_col": 1, # Název dokumentu + "typ_col": 2, # Typ (pdf) + "obdobi_col": 4, # Období (jen rok: 2024) + "ident_prefix": "", + }, +] + + +def parse_period(raw: str) -> str: + """01.02.2026 → 2026-02 | 2024 → 2024""" + try: + return datetime.strptime(raw.strip()[:10], "%d.%m.%Y").strftime("%Y-%m") + except Exception: + cleaned = raw.strip()[:7].replace(".", "-") + return cleaned if cleaned else "0000" + + +def safe_filename(name: str) -> str: + return re.sub(r'[\\/:*?"<>|]', "_", name).strip() + + +def make_js_extract(ident_col: int, typ_col: int, obdobi_col: int) -> str: + return f"""() => {{ + const KNOWN_EXTS = new Set(['pdf', 'xml', 'txt', 'zip', 'doc', 'xls', 'csv']); + const docs = []; + for (const inp of document.querySelectorAll('input[name="doc_id"]')) {{ + const docId = inp.value; + if (!docId) continue; + const td = inp.closest('td'); + const tr = td ? td.closest('tr') : null; + let ident = '', typ = 'pdf', obdobi = ''; + if (tr) {{ + const tds = Array.from(tr.querySelectorAll('td')).map(t => t.innerText.trim()); + ident = tds[{ident_col}] || ''; + const t = (tds[{typ_col}] || '').toLowerCase().trim(); + typ = KNOWN_EXTS.has(t) ? t : 'pdf'; + obdobi = tds[{obdobi_col}] || ''; + }} + docs.push({{ docId, ident, typ, obdobi }}); + }} + return docs; + }}""" + + +def click_next_page(page, page_num: int) -> bool: + """Klikne na stránkovací input[name='page'][value='N+1']. Vrátí True pokud existoval.""" + # Stránkovací prvky jsou input[type=submit] s name="page" a value=číslo stránky + # Příklad: + next_val = str(page_num + 1) + btn = page.locator(f"input[name='page'][value='{next_val}']") + if btn.count() == 0: + return False + btn.first.click() + page.wait_for_load_state("networkidle", timeout=20_000) + return True + + +def process_sekce(page, context, cfg: dict, already: set) -> tuple: + name = cfg["name"] + url = cfg["url"] + ident_pfx = cfg["ident_prefix"] + js_extract = make_js_extract(cfg["ident_col"], cfg["typ_col"], cfg["obdobi_col"]) + + downloaded = 0 + skipped = 0 + + print(f"\n=== {name} ===") + page.goto(url, wait_until="domcontentloaded", timeout=30_000) + page.wait_for_load_state("networkidle", timeout=15_000) + + if cfg["need_history"]: + hist = page.get_by_text("Zobrazit historii dokumentů za poslední 3 roky") + if hist.count() > 0: + hist.first.click() + page.wait_for_load_state("networkidle", timeout=20_000) + + page_num = 1 + seen_ids: set = set() + + while True: + print(f" Strana {page_num}...") + docs = page.evaluate(js_extract) + docs = [d for d in docs if d["docId"] not in seen_ids] + + if not docs: + print(f" Strana {page_num} — žádné nové dokumenty, konec sekce.") + break + + seen_ids.update(d["docId"] for d in docs) + print(f" Nalezeno {len(docs)} dokumentů") + + for doc in docs: + period = parse_period(doc["obdobi"]) + ext = doc["typ"] or "pdf" + ident = safe_filename(doc["ident"]) + label = safe_filename(name) + prefix = ident_pfx + ident if ident else f"id-{doc['docId']}" + filename = f"{period} {label} {prefix}.{ext}" + target = os.path.join(DOWNLOAD_DIR, filename) + + if filename in already or os.path.exists(target): + skipped += 1 + continue + + r = context.request.post(url, form={ + "bin": "1", "dl": "1", "historie": cfg["dl_historie"], + "doc_id": doc["docId"], "save": "Stáhnout", + }, timeout=60_000) + + if not r.ok: + print(f" HTTP {r.status} doc_id={doc['docId']}") + time.sleep(1.0) + continue + + # Content-Disposition pro originální název + cd = r.headers.get("content-disposition", "") + m = re.search(r'filename=["\']?([^"\';\r\n]+)', cd) + if m: + orig = m.group(1).strip() + stem, suf = os.path.splitext(orig) + ext2 = suf.lstrip(".").lower() or ext + filename = f"{period} {label} {prefix} ({safe_filename(stem)}).{ext2}" + target = os.path.join(DOWNLOAD_DIR, filename) + + with open(target, "wb") as f: + f.write(r.body()) + print(f" OK: {filename}") + already.add(filename) + downloaded += 1 + time.sleep(1.0) + + # Přejdi na další stránku + if not click_next_page(page, page_num): + break + page_num += 1 + + return downloaded, skipped + + +def main() -> None: + try: + from playwright.sync_api import sync_playwright + except ImportError: + print("Chybi playwright: pip install playwright && playwright install chrome") + sys.exit(1) + + os.makedirs(DOWNLOAD_DIR, exist_ok=True) + + if not os.path.exists(COOKIES_FILE): + print(f"Soubor {COOKIES_FILE} nenalezen — spust 01_prihlaseni.py") + sys.exit(1) + + with open(COOKIES_FILE, encoding="utf-8") as f: + cookies = json.load(f) + + with sync_playwright() as p: + context = p.chromium.launch_persistent_context( + user_data_dir=os.path.join(os.path.dirname(__file__), "chrome_profile"), + channel="chrome", + headless=False, + slow_mo=100, + ignore_https_errors=True, + ) + try: + context.add_cookies(cookies) + page = context.new_page() + + # Ověř přihlášení + page.goto(ZPRAVY_URL, wait_until="domcontentloaded", timeout=30_000) + page.wait_for_load_state("networkidle", timeout=15_000) + if page.locator("input[name='pin']").count() > 0: + print("Cookies expirovala — spust 01_prihlaseni.py") + return + print("Prihlaseni OK\n") + + already = set(os.listdir(DOWNLOAD_DIR)) + print(f"V archivu: {len(already)} souboru.") + + total_dl = total_skip = 0 + for cfg in SEKCE: + dl, sk = process_sekce(page, context, cfg, already) + print(f" {cfg['name']}: stazeno {dl}, preskoceno {sk}") + total_dl += dl + total_skip += sk + + print(f"\nHotovo. Celkem stazeno: {total_dl}, preskoceno: {total_skip}") + + finally: + context.close() + + +if __name__ == "__main__": + main() diff --git a/Insurance/StahováníZpráv/211 ZPMVČR/zpmvcr_cookies.json b/Insurance/StahováníZpráv/211 ZPMVČR/zpmvcr_cookies.json new file mode 100644 index 0000000..26c9ade --- /dev/null +++ b/Insurance/StahováníZpráv/211 ZPMVČR/zpmvcr_cookies.json @@ -0,0 +1,12 @@ +[ + { + "name": "JSESSIONID", + "value": "36137BBDC47B5AA6EBACEA28257BB821", + "domain": ".eforms.zpmvcr.cz", + "path": "/eforms", + "expires": -1, + "secure": false, + "httpOnly": false, + "sameSite": "Lax" + } +] \ No newline at end of file