notebookvb
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"name": "SID",
|
"name": "SID",
|
||||||
"value": "df433a68180840ba413c8f9c68eae034",
|
"value": "9ed44d48a017f8915cdfb566d2e2e952",
|
||||||
"domain": ".portal.zpskoda.cz",
|
"domain": ".portal.zpskoda.cz",
|
||||||
"path": "/",
|
"path": "/",
|
||||||
"expires": -1,
|
"expires": -1,
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
"value": "CERT",
|
"value": "CERT",
|
||||||
"domain": ".portal.zpskoda.cz",
|
"domain": ".portal.zpskoda.cz",
|
||||||
"path": "/",
|
"path": "/",
|
||||||
"expires": 1808227911,
|
"expires": 1808246438,
|
||||||
"secure": true,
|
"secure": true,
|
||||||
"httpOnly": false,
|
"httpOnly": false,
|
||||||
"sameSite": "Lax"
|
"sameSite": "Lax"
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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=<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: <input name="page" type="submit" value="2" title="2. Strana">
|
||||||
|
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()
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "JSESSIONID",
|
||||||
|
"value": "36137BBDC47B5AA6EBACEA28257BB821",
|
||||||
|
"domain": ".eforms.zpmvcr.cz",
|
||||||
|
"path": "/eforms",
|
||||||
|
"expires": -1,
|
||||||
|
"secure": false,
|
||||||
|
"httpOnly": false,
|
||||||
|
"sameSite": "Lax"
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user