121 lines
5.6 KiB
Markdown
121 lines
5.6 KiB
Markdown
# Euni — stahování a tracking kurzů z euni.cz
|
||
|
||
Přihlásí se na euni.cz, projde kurzy, vytěží odkazy + metadata a stahuje obsah
|
||
(PDF/prezentace a videa Vimeo/YouTube). Vše se trackuje v **MongoDB EUNI**, takže
|
||
stahování je idempotentní — skript ví, co už má, a netahá dvakrát.
|
||
|
||
## Soubory
|
||
|
||
| Soubor | Popis |
|
||
|--------|-------|
|
||
| `euni_stahni.py` | hlavní pipeline: login → scrape → ingest do Mongo → stahování → záloha do SeaweedFS |
|
||
| `euni_db.py` | připojení a operace nad MongoDB EUNI (kolekce, indexy, upserty) |
|
||
| `euni_seaweed.py` | nahrávání/stahování souborů do SeaweedFS (filer HTTP API) |
|
||
| `euni_restore.py` | obnova všech souborů ze SeaweedFS na disk (na jakémkoli PC) |
|
||
| `euni_report.py` | dashboard: přehled stavu (kolik staženo/čeká/přeskočeno) |
|
||
| `.env` | `EUNI_USERNAME`, `EUNI_PASSWORD` (v .gitignore) |
|
||
| `euni_kurzy.json` | poslední inventura (záloha; primární zdroj je Mongo) |
|
||
| `stazeno/` | stažený obsah, `stazeno/<id>-<slug>/{dokumenty,videa}/` |
|
||
|
||
## Závislosti
|
||
|
||
```bat
|
||
python -m pip install -U requests beautifulsoup4 python-dotenv yt-dlp static-ffmpeg pymongo
|
||
```
|
||
|
||
Video stahuje sdílený modul `../Video/stahni_video.py` (yt-dlp + static-ffmpeg,
|
||
soukromá videa sám přeskočí).
|
||
|
||
## MongoDB EUNI
|
||
|
||
Server `mongodb://192.168.1.76:27017` (bez hesla), DB `EUNI`. Lze přepsat env
|
||
proměnnou `EUNI_MONGO_URI`.
|
||
|
||
### Kolekce `kurzy` (1 dokument na kurz)
|
||
`_id` = euni ID kurzu. Pole: `slug, nazev, url, profese[], autor,
|
||
autor_medailonek_url, datum_publikace, revidovano, akreditace, kredity,
|
||
pocet_videi, pocet_dokumentu, first_seen, updated_at`.
|
||
|
||
### Kolekce `materialy` (1 dokument na soubor)
|
||
Unikátní index `{kurz_id, klic}`. Pole: `kurz_id, kurz_nazev, druh
|
||
(video|dokument), platforma (vimeo|youtube), klic (vimeo:ID / youtube:ID /
|
||
doc:hash), zdroj_url, watch_url, popis, pripona, stav, duvod, soubor,
|
||
velikost_b, pokusy, posledni_chyba, first_seen, updated_at, stazeno_at`.
|
||
|
||
**Stavy:** `ceka` → `stazeno` / `preskoceno` (soukromé video) / `chyba`.
|
||
|
||
**SeaweedFS reference** (po nahrání kopie): `seaweed_path` (cesta ve filer =
|
||
identifikátor pro vyžádání, např. `euni/5618-.../dokumenty/x.pdf`),
|
||
`seaweed_fids` (fid chunků = čísla souborů v SeaweedFS), `seaweed_md5`,
|
||
`seaweed_size`, `seaweed_at`.
|
||
|
||
## SeaweedFS záloha + obnova
|
||
|
||
Každý stažený soubor se nahraje do **SeaweedFS** (filer na Unraidu,
|
||
default `http://192.168.1.50:8888`, přepíše env `EUNI_FILER`). Do Mongo se k
|
||
materiálu uloží `seaweed_path` + `seaweed_fids`, takže soubor lze kdykoli vyžádat.
|
||
|
||
- Strukturu na disku zrcadlí cesta: `euni/<id>-<slug>/<typ>/<soubor>`.
|
||
- Filer metadata (mapa cesta→chunky) jsou v Mongo DB `seaweedfs` na 192.168.1.76;
|
||
bloby na poli Unraidu. (Setup: `U:\\PythonProject\\Janssen\\SeaweedFS\\`.)
|
||
- Pozn.: přímý přístup přes raw fid/volume zvenčí nefunguje (volume se uvnitř
|
||
Dockeru jmenuje `seaweed-volume`); proto se čte/zapisuje přes filer.
|
||
|
||
**Obnova kdekoliv** (stačí síť na Mongo + filer):
|
||
```bat
|
||
python euni_restore.py # vše → ./obnoveno
|
||
python euni_restore.py --out D:\Euni # jiný cíl
|
||
python euni_restore.py --kurz 5618 # jen jeden kurz
|
||
python euni_restore.py --dry-run # jen výpis
|
||
```
|
||
|
||
**Backfill** (dohrát do SeaweedFS soubory stažené dřív):
|
||
```bat
|
||
python euni_stahni.py --seaweed-backfill --from-json
|
||
```
|
||
|
||
### Idempotence
|
||
- Scrape dělá *upsert*: nový materiál → `ceka`; existující si **drží stav**
|
||
(nepřepíše stažené). Lze tedy bez obav scrapovat opakovaně.
|
||
- Stahování bere jen `stav: ceka` (a volitelně `chyba` pro retry).
|
||
|
||
## Použití
|
||
|
||
Nejjednodušší: **`python euni_menu.py`** — interaktivní menu s volbami 1–9
|
||
(test / dokumenty / vše / 720p / dashboard / obnova / backfill / re-scrape).
|
||
Po doběhnutí akce se vrátí do menu, `Ctrl+C` přeruší jen aktuální akci.
|
||
|
||
Ručně přes CLI:
|
||
|
||
```bat
|
||
python euni_stahni.py --scrape-only # jen inventura → Mongo + JSON
|
||
python euni_stahni.py --no-videos # scrape + stáhne jen dokumenty
|
||
python euni_stahni.py # scrape + dokumenty + videa
|
||
python euni_stahni.py --from-json --no-videos # přeskočí scrape, stáhne z Mongo/JSON
|
||
python euni_stahni.py --professions all # všechny profese (2,4,5,6,7)
|
||
python euni_stahni.py --limit 3 # jen prvních 3 kurzy (test)
|
||
python euni_stahni.py --no-mongo # bez zápisu do Mongo
|
||
python euni_stahni.py --frags 20 # víc paralelních HLS fragmentů (rychlejší)
|
||
python euni_stahni.py --video-format "bestvideo[height<=720]+bestaudio/best" # 720p
|
||
python euni_report.py # přehled stavu
|
||
python euni_report.py --soukroma # seznam přeskočených videí
|
||
```
|
||
|
||
## Jak to funguje (ověřeno)
|
||
|
||
- **Login** `/sign/` — formulář se parsuje (kopírují se skrytá Nette pole `_do`).
|
||
- **Seznam kurzů** — signál `studyAreaList-nextPage` vrací JSON snippet, stránkuje
|
||
se dokud přibývají kurzy (profese: 2=Lékař, 4=Farmaceut, 5/6=studenti, 7=NLZP).
|
||
- **Detail kurzu** — server-rendered HTML; videa z `<iframe>` (u Vimea se zachová
|
||
`?h=` hash), dokumenty z přímých odkazů i `/redirect/<base64>`.
|
||
- Metadata z bloků `lecture-info-label` → `lecture-info-mark`.
|
||
|
||
## Úskalí
|
||
|
||
- **Vimeo** dává oddělené video/audio HLS → nutný ffmpeg (řeší static-ffmpeg).
|
||
Domain-restricted videa se stahují s referer `https://www.euni.cz/`.
|
||
- **Soukromá videa** (autor je zamkl) nejdou stáhnout — skript je označí
|
||
`preskoceno` s důvodem, nepadá.
|
||
- Anotace kurzu na stránce není (jen obecný text webu) → neukládá se.
|
||
- Diakritika v názvech: v konzoli cp1250 OK; výpis má pojistku proti pádu.
|