5.7 KiB
ČPZP — Automatické stahování zpráv
Co to dělá
Dva skripty které se přihlásí na portál ČPZP a stáhnou všechny zprávy ze schránek:
01_prihlaseni.py— přihlásí se certifikátem, uloží cookies docpzp_cookies.json02_stahuj_vse.py— použije cookies, projde schránky, stáhne soubory doStaženo/
Jak funguje přihlášení
Portál nepoužívá heslo — autentizuje certifikátem přes 3 kroky:
Krok 1 — Získej session a challenge
GET https://portal.cpzp.cz/app/login/
→ server nastaví cookie: PHPSESSID=...
→ v HTML stránce je vložen JS objekt s challengem a CSRF tokenem
Challenge je v HTML jako JS výraz (ne JSON):
CPZP = {
settings : {
certificateLoginKey : 'Prohlášení:'+ String.fromCharCode(13, 10) +
'Tímto se přihlašuji k Portálu ČPZP'+ String.fromCharCode(13, 10) +
''+ String.fromCharCode(13, 10) +
'Okamžik vygenerování tohoto prohlášení: 20.04.2026 14:52:47',
...
}
}
CSRF token je v HTML formuláři:
<form name="frmPrihlasCert" method="post" action="/app/">
<input name="csrfCert" type="hidden" value="42ead46f7d374805c5d9...">
<input id="sign" name="sign" type="hidden" value="">
</form>
Krok 2 — Podpis certifikátem
Challenge (sestavená z JS výrazu) se podepíše jako PKCS7 / CMS SignedData:
- Algoritmus: RSA + SHA-256
- Typ: DetachedSignature (obsah není vložen do podpisu)
- BEZ CA řetězu — pouze end-entity certifikát
- Výsledný formát: PEM s hlavičkami (
-----BEGIN PKCS7-----)
pem_podpis = (
pkcs7.PKCS7SignatureBuilder()
.set_data(challenge.encode("utf-8"))
.add_signer(cert, private_key, hashes.SHA256())
.sign(Encoding.PEM, [PKCS7Options.DetachedSignature])
)
⚠️ Na rozdíl od VoZP portál ČPZP očekává celý PEM string včetně hlaviček, ne jen base64 DER (přestože oboje přes NMSigner). Bez hlaviček vrátí 500.
Krok 3 — Přihlášení
POST https://portal.cpzp.cz/app/
Content-Type: application/x-www-form-urlencoded
csrfCert=<token_z_formulare>&sign=<pem_podpis>
Úspěch: odpověď neobsahuje "frmPrihlasCert" (login stránka)
Certifikát
| Položka | Hodnota |
|---|---|
| Soubor | U:\ordinaceprojekt\Insurance\Certificates\MBQualifiedCert.pfx |
| Vlastník | MUDr. Michaela Buzalková |
| Vydavatel | I.CA EU Qualified CA2/RSA 06/2022 |
| Platnost | do 2027-01-16 |
| Thumbprint | 056ED80A3CDDE31DD36EECE0181B4E78D61122A7 |
Stahování souborů
02_stahuj_vse.py používá requests (bez Playwright) — portál nevyžaduje JS pro navigaci.
Schránky které se prochází
| URL | Název |
|---|---|
/app/schranka/ |
Schránka klienta |
/app/schranka-pzs/ |
Schránka PZS |
Obě schránky obsahují stejné zprávy — skript deduplicuje podle ID zprávy.
Paginace
Portál zobrazuje 20 zpráv na stránku, stránkování přes ?offset=N:
GET /app/schranka/?offset=0 → zprávy 1–20
GET /app/schranka/?offset=20 → zprávy 21–40
...
Detekce konce: pokud stránka neobsahuje žádné nové ID, zastav.
Struktura zprávy v seznamu
<tr id="message-69cd7aa38ec68a837b5d8cbc" class="mail-read status-done">
<td>...</td>
<td>zpracováno</td>
<td>09305000 - MUDr. Michaela Buzalková</td>
<td>VYÚČTOVÁNÍ ZDRAVOTNÍ PÉČE Ref. č. 26274350</td>
<td>01.04.2026 22:05:42</td>
<td><a href="/app/schranka/detail/69cd7aa38ec68a837b5d8cbc/">zobrazit detail</a></td>
</tr>
ID zprávy je hex string (24 znaků), ne číslo jako u VoZP.
Detail zprávy a stažení
GET /app/schranka/detail/{hex_id}/
→ stránka obsahuje odkaz na soubor
<a href="/app/schranka/protokol/?path=c387260ba8f041609a663038be79281c">
ZU250168094V1.pdf ← název souboru (nebo "26274350 (stáhnout protokol)")
</a>
GET /app/schranka/protokol/?path={hash}
→ vrátí soubor (PDF nebo HTML podle druhu zprávy)
Typy souborů
| Druh zprávy | Typ souboru | Obsah |
|---|---|---|
| IČZ: ... Č.faktury: ... | Zúčtovací zpráva | |
| VYÚČTOVÁNÍ ZDRAVOTNÍ PÉČE | HTML | Protokol přijetí vyúčtování |
| KLIENTELA | HTML | Protokol přijetí |
| Konečné vyúčtování | Závěrečné vyúčtování |
Přípona se určuje podle textu linku (.pdf) nebo Content-Type odpovědi.
Pojmenování stažených souborů
YYYY-MM-DD Druh zprávy (Ref. XXXXXXXXX).přípona
Příklady:
2026-03-26 IČZ_ 09305000 Č.faktury_ 260027 (Ref. 26220064).pdf
2026-04-01 VYÚČTOVÁNÍ ZDRAVOTNÍ PÉČE (Ref. 26274350).html
2025-05-29 Konečné vyúčtování (Ref. 24774290).pdf
Znaky nepovolené ve Windows názvech (/ : * ? " < > |) se nahrazují podtržítkem.
Rozdíly oproti VoZP
| VoZP | ČPZP | |
|---|---|---|
| Challenge zdroj | JSON API (/json-api/prihlaseni/prihlasovaci-zprava) |
HTML stránka (JS výraz) |
| Challenge formát | JSON string s \r\n |
JS string concatenation + String.fromCharCode |
| Podpis formát | PEM s hlavičkami | PEM s hlavičkami |
| Odeslání | POST JSON na API endpoint | POST form-data na /app/ |
| CSRF | ne | ano (csrfCert) |
| ID zprávy | číselné | hex string (24 znaků) |
| Stahování | Playwright + context.request.get() |
čisté requests |
| Typy souborů | vždy HTML | HTML i PDF |
Závislosti
pip install requests cryptography beautifulsoup4
Spuštění
python 01_prihlaseni.py # přihlásí, uloží cookies (nutné při expiraci session)
python 02_stahuj_vse.py # stáhne vše, přeskočí již existující soubory
Session cookie (PHPSESSID) expiruje — pokud 02_stahuj_vse.py zahlásí
"Cookies expirovala", spusť nejdřív 01_prihlaseni.py.