Ako stiahnuť databázu - #1/3: Jednoducho

03.11.2007 10:53 | blackhole_matej

Majme databázu rozmerov rádovo 10^3 až 10^7 záznamov, verejne prístupnú cez web formulár (naraz po jednom alebo niekoľkých záznamoch). Táto databáza je pre nás niečím zaujímavá, a chceme mať jej off-line kópiu.

V skratke je potrebné:

  1. Zmyslieť si, že danú databázu chceme stiahnuť.
  2. Stiahnuť.
  3. Tešiť sa.
  4. Dúfať, že ak sa to prevalí, tak novinári nebudú prekrucovať.
Zatiaľ čo body 1 a 3 sú triviálne a bod 4 beznádejný, samotné ťahanie môže byť menej či viac komplikované...

Disclaimer ----
Budeme popisovať rôzne úskalia pri ťahaní verejne prístupnej databázy. Nejedná sa o prienik do systému a surové "ukradnutie" databázy - to je síce spôsob efektnejší (nie nutne efektívnejší), nie vždy však jednoduchý, o zákonnosti ani nehovoriac.

Nežiadajte o detailnú implementáciu pre váš cieľ, ani o poskytnutie nejakej "stiahnutej" databázy, spravte si sami -- regexp powa.

Príklady tu uvedené sú v bashi, samozrejme dá sa to rovnako alebo aj lepšie spraviť v perli alebo inom skriptovacom jazyku. Bolo by takisto možné napísať to celé v C alebo vyšších kompilovaných jazykoch, či už volaním systémových API alebo pomocou nejakého frameworku, ale zrejme by to trvalo dosť dlho a
najmä by šlo z veľkej časti o "znovuvynaliezanie kolesa" -- v *nix shelli už totiž máme hotové všetky potrebné nástroje na sťahovanie a parsovanie výstupu, stačí ich pozliepať v skripte (rýchlosť kompilovaných programov v tomto prípade nepotrebujeme, viď. ďalej časť "Obmedzenia").

Používatelia Windows môžu siahnuť po Cygwine.

Ilustračná DB ----
Ako príklad poslúži jednoduchá jednotabuľková databáza. Budeme ťahať tabuľku zvieratiek a ich rodných čísel s L=5,396,168 riadkami (k 30.6.2007). Empiricky zistíme, že budeme pracovať s údajmi: id (v databáze), meno, mesto,
cislo.
ZvieratkoDB <http://zvieratko.db/> má 2 dynamické web stránky (podobne, ako veľa reálnych DB):

search.cgi
vyhľadávacia stránka s formulárom --- zadáme meno alebo časť mena
(?query=), vypľuje tabuľku s odkazmi na zobrazenie detailov
show.cgi
zobrazovacia stránka pre jedno zvieratko --- podľa odkazu z vyhľadávania
(?id=) zobrazí detaily o zvieratku, teda okrem mena a mesta, ktoré boli aj
vo výsledkoch vyhľadávania, aj rodné číslo
Je nutné pripomenúť, že ilustračná DB je vymyslená, a akákoľvek podobnosť s reálnym svetom je čisto náhodná.

1. Jednoduchý prípad ----
Nech ZvieratkoDB nemá žiadne obmedzenia a id v databáze sú za sebou nasledujúce od 1 do L. Ručne stiahneme niekoľko exemplárov show.cgi?id=..., aby sme určili presný formát výstupu a spôsob, ako z toho HTML vyparsovať čisté dáta (pomocou grep, sed, prípadne awk, cut apod.). Celú databázu potom
stiahneme:

#!/bin/bash
# priklad c.1, tahanie zvieratiek so za sebou nasledujucimi id
i=0; L=5389180
while [[ $i -lt $L ]]; do
    i=$((i+1))
    wget -O - http://zvieratko.db/show.cgi?id=$i \
        | grep ... | sed ... >> LokalneZvieratkoDB.txt
done

1.1 Ide to pomaly... ----
Dajme tomu, že jeden takýto wget zbehne za sekundu, čas parsovania a ukladania na disk zanedbáme. Potom stiahnutie bude trvať približne 5389180/3600/24 = 62.4 dní.

Preto skript rozdelíme na viaceré (rôzne počiatočné a koncové $i, a tiež rôzne .txt [aby sme nemuseli riešiť race conditions]) a pustíme na jednom alebo viacerých strojoch. Kým to cieľová mašina stíha (aj naša mašina a [bez]drôt), môžeme dosiahnuť až N-násobné zrýchlenie (N je počet navzájom
rýchlostne neovplyvnených "tahačov").

1.2 Ošetrovanie chýb ----
Wget nemusíme smerovať priamo do paličky, jeho výstup môžeme napríklad ukladať do /tmp/bla$$ a následne zisťovať, či sa podarilo daný záznam stiahnuť - pri chybe opakovať, alebo zapísať hlášku do logu, apod. Konkrétna implementácia závisí od detailov danej DB a prístupu k nej, toto necháme na
rozcvičku pre čitateľa.

-----------
V ďalšej časti (ak bude o ňu záujem) si povieme, ako na zložitejšie prípady, t.j. ako vypiecť s niektorými obmedzeniami.

----------
#1: Úvod, Disclaimer, Ilustračná DB, 1. Jednoduchý prípad, 1.1 Ide to pomaly..., 1.2 Ošetrovanie chýb
#2: 2. Obmedzenia, 2.1 Náhodné id, 2.2 Limit na IP, Ako získať veľa IP
#3: 2.3 Captcha, 2.4 Ďalšie obmedzenia, 3. Iné vychytávky, 4. Záver

    • Re: Ako stiahnuť databázu - #1/3: Jednoducho 03.11.2007 | 18:56
      Avatar blackhole   Návštevník

      Hmm skor by som povedal ze problem s rychlostou bude opacny... teda pokial samotne parsovanie cez X programikov (vratane obrovskeho perlu) nebude trvat 3x tolko co stahovanie. Na rychlej linke je to jasne, na pomalej linke az tak velmi viacnasobne spustenie nepomoze.
      Server ktoremu trva sekundu kym vygeneruje stranku je dost pomaly/pretazeny. Realne by som ratal s hodnotami tak 0.1 az 0.5 sekundy a aj to su uz vyssie cisla. Ja skor do takychto programov (ktore sa viackrat za sebou pripajaju na webserver) davam maly delay (100 ms), aby som dany server nepretazoval.
      Co sa tyka parsovania tak sa osvedcil jeden postup. Vacsina takychto dat je vypisovana html tabulkou, akurat ze kazdy ten html kod inac formatuje. Idealne na parsovanie je mat kazdy riadok tabulky na jednom riadku zdrojoveho suboru. Takze najdeme si napr "<tr>" a vlozime pred neho "\n". Dalej si najdeme "\n<td>" (pokial bola kazda bunka tabulky v zdrojaku na osobitnom riadku) a nahradime samotnym "<td>". Mensou obdobou sa to da pouzit na naformatovanie kazdeho html zdrojaku (hlavne tych co maju 30kb dat na jednom riadku a neda sa to citat). Ked mame kazdy riadok tabulky na osobitnom riadku v zdrojaku, je hracka napisat regexp, ktory z toho spravi napr. tab-delimited textovy subor, ktory sa da naimportovat do db.

      • Re: Ako stiahnuť databázu - #1/3: Jednoducho 03.11.2007 | 19:42
        Avatar blackhole_matej   Používateľ

        Ta sekunda bola samozrejme nadsadena hodnota, iba ilustracna. Parsovat nemusis cez Perl, staci grep a sed, pripadne awk. Ucelom clanku nie je naucit sa parsovat vystup html :)
        V dalsom dieli trilogie sa vyjasni, preco nam na case tahania+parsovania jednej stranky az tak velmi nezalezi, ked budeme obmedzovani ovela vyssimi casmi.

    • Re: Ako stiahnuť databázu - #1/3: Jednoducho 03.11.2007 | 22:52
      Avatar Maff   Používateľ

      Takze zoberme si teoreticky databazu ktora obsahuje 500tisic udajov
      Urobime si nejaky retardovany program ktory ju stiahne... Naco?
      existuje tolko offline browserov pri ktorych si urcim co chcem stiahnet ze
      pisat si soft je zbytocnost lenze niektore stranky ktore nechcu prist o udaje
      roznimi onimy co cucaju obsah z webov (napr.: google) maju proti tomu zabespecenie
      cize mozem si z jednej ip pozriet 1stranu za 1m. (cca) cize to mame vela minut
      sak co napiseme si soft cez proxyni ne? no ani velmi ne kym najdem aspon 5%
      proxin ktore pouzijem nato bude neskoro ale zoberme si ze tie proxyni mam
      tak to pojde pomali cize ak ma web zabespecenie (trosku vyssie ako je NBU)
      tak sme proste f <>. Cize jedine co mi ostava je pisat nadalej maily adminovi nech
      mi odblokuje ip lenze admin je chuj a nedokaze mi napisat ani mail ze
      ziadost zamietnuta ..... Cize vysledok ak web nechce prist o databazu a robi nieco proti tomu
      tak su len dve moznosti najebat im server alebo pekne prosit a dufat ze admin nieje
      az taky ...... ako v skutocnosti je .... Nic tym nechcem povedat ale som najebany a chcem pit
      a mam nervy a tak ....... a ak sa nekomu nepaci ze neviem gramatiku nech ide do ....

      • Re: Ako stiahnuť databázu - #1/3: Jednoducho 03.11.2007 | 23:12
        Avatar blackhole_matej   Používateľ

        Ja som to asi nemal rozdelovat na viac dielov :) ...len mi to ako 1 pripadalo moc dlhe. Offline browsery ti samozrejme na jednoduchy pripad pomozu, ale naco skladovat vela 10kB stranok, ked z kazdej potrebujes len 200 B. O "zabezpeceniach" bude pisane v dalsom dieli (dalsich 2), tento ber ako taky dlhsi uvod.

        > V ďalšej časti (ak bude o ňu záujem) si povieme, ako na zložitejšie prípady,
        > t.j. ako vypiecť s niektorými obmedzeniami.

        • Re: Ako stiahnuť databázu - #1/3: Jednoducho 04.11.2007 | 09:09
          Avatar blackhole   Návštevník

          Offline browsery ti samozrejme na jednoduchy pripad pomozu, ale naco skladovat vela 10kB stranok, ked z kazdej potrebujes len 200 B...

          No ale ide tu o to že tak či tak aj tvoja metóda stiahne celú stránku, čiže sa tu skôr jedná o zaťaženie linky, stroja, servera ako nejaký problém s kapacitou.

    • Re: Ako stiahnuť databázu - #1/3: Jednoducho 04.11.2007 | 09:04
      Avatar blackhole   Návštevník

      Zdravim,

      Myslíš, že každý select databázy je vedený cez URL? Čo ak "id" bude poslané napr. v hlavičke?
      Ďalej, myslíš že človek bude mať doma N strojov a N navzájom nezávislými linkami?(o tie stroje ani tak nejde ako o tie linky)

      • Re: Ako stiahnuť databázu - #1/3: Jednoducho 04.11.2007 | 11:36
        Avatar blackhole_matej   Používateľ

        Pod pojmom "id" je myslene cokolvek, co show.cgi zozerie. Vacsinou je to ciselna hodnota, a vacsinou je to priamo id zhodne s tym, co je v databaze (je to totiz najjednoduchsi sposob, ako webovy frontend implementovat). Ci je posielane cez GET, POST alebo ako je jedno, vzdy existuje nejaky sposob, ako sa na konkretny zaznam zo search.cgi na show.cgi dostat.

        Pre zaujemcov odporucam pozriet slovensky web, vytipovat si nejaku peknu db hodnu stiahnutia, a pozriet si, akym sposobom su odkazy zo "search.cgi" na "show.cgi" (nazvy samozrejme nemusia sediet, ide o princip) riesene.

        Na stiahnutie ZvieratkoDB, ak kazda show.cgi ma 10kB a nie su dalsie komplikacie (o ktorych az v dalsom dieli), budeme musiet stiahnut cca 52 GiB. Bezny smrtelnik to samozrejme nezvladne, bezny smrtelnik ani nevie co je to parsovat. Ani ho nenapadne, ze by to slo stiahnut. No a Ty to nemusis tahat doma :)

        • Re: Ako stiahnuť databázu - #1/3: Jednoducho 04.11.2007 | 17:37
          Avatar blackhole   Návštevník

          Zdravim,
          Tu ide o to, že keby išiel select DB cez HTTP hlavičku, tak je tvoj skript uplne nahovno, ak ma chapeš chcem tým povedať len toľko, že tvoj príklad funguje iba za špecifických podmienok, čiže veľmi obmedzene.

          No a už vidím ako by niekto ťahal v práci 52 Giga a to ani nehovorim v škole, kvôli jednej DB. Proste metóda, ktorú si zvolil je neefektívna lebo z celkového objemu čo musíš stiahnuť je drviva väčšina balast, ktorý aj tak zahadzuješ, je to podla mna svinstvo takto zaťažovať linku.

          • Re: Ako stiahnuť databázu - #1/3: Jednoducho 04.11.2007 | 17:44
            Avatar blackhole_matej   Používateľ

            > select DB cez HTTP hlavičku
            Co konkretne mas tym na mysli? Alebo daj priklad stranky, lebo neviem kde konkretne chces schovat data tak, aby o nich web prehliadac vedel, ale Ty sam nie.

            > už vidím ako by niekto ťahal
            No podla toho ako cenna ta db pre Teba je. Nehovorim ze to kazdy bude robit, to by bol iny bordel :) A ano, je to svinstvo, ale co ma clovek robit, ked chce vyhladavat zvieratka podla rodneho cisla a to sa cez webovy frontend ZvieratkoDB neda?

            • Re: Ako stiahnuť databázu - #1/3: Jednoducho 08.11.2007 | 19:18
              Avatar blackhole   Návštevník

              Co konkretne mas tym na mysli? Alebo daj priklad stranky, lebo neviem kde konkretne chces schovat data tak, aby o nich web prehliadac vedel, ale Ty sam nie.

              Zdravím,
              Mám na mysli to, že tvoj skript sa spolieha nato že "id" bude predávané metódou GET, čiže cez URL, keby bolo však predávané metódou POST, čiže v hlavičke a nie v URL tvoj skript je nefunkčný...

              • Re: Ako stiahnuť databázu - #1/3: Jednoducho 08.11.2007 | 19:38
                Avatar blackhole_matej   Používateľ

                Skript je, samozrejme, uvedený ako ilustračný. Zjavne si si to nevšimol. Nabudúce budem zrejme musieť dať do disclaimera komentár ku všetkému. Totiž ani

                | grep ... | sed ... |
                nebude fungovať.

                Keby som chcel uviesť konkrétne a kompletné skripty, uvediem ich na konkrétnu cieľovú databázu. Keďže som sa chcel vyhnúť prípadnej zodpovednosti za taký čin, písal som všeobecne.

                Pre posielanie metódou POST je možné jednoducho použiť prepínač --post-data, viď. wget(1). Napríklad:

                wget  --post-data="id=$i" -O - <a href="http://zvieratko.db/show.cgi" title="http://zvieratko.db/show.cgi">http://zvieratko.db/show.cgi</a>
                Obdobným spôsobom je možné pozmeniť ilustračné skripty uvedené v druhom článku.

                • Re: Ako stiahnuť databázu - #1/3: Jednoducho 08.11.2007 | 19:57
                  Avatar blackhole   Návštevník

                  A práve že ten skript nielen že je ťažko použitelný, ale je aj príliš zjednodušený. Sorry, ale to si rovno mohol napísať aby sme použili v cykle wget, kde tu istu premennu pouzijeme na pocitadlo cyklu a aj ako premennu do "id" a nemusel si pisať celý článok, chce to proste viac ozrejmit problematiku, mal si vytvoriť alebo stacilo aj vymysliet fiktívnu db a konkrétne na príklade ukázať celý skript ako by fungoval, poprípade ešte pár alternatív, hned by to bolo prehladnejšie ako skript s tromi ...

                  • Re: Ako stiahnuť databázu - #1/3: Jednoducho 08.11.2007 | 20:13
                    Avatar blackhole_matej   Používateľ

                    ale to si rovno mohol napísať aby sme použili v cykle wget, kde tu istu premennu pouzijeme na pocitadlo cyklu a aj ako premennu do "id" a nemusel si pisať celý článok
                    Práveže som chcel naznačiť, ako také ťahanie môže prebiehať - príklad je oveľa názornejší, ako litánie o cykloch a premenných. A nechcel som zachádzať až príliš do detailov - to by potom nikto pre rozsah nečítal. Napríklad môj posledný all-in-one (čo sa funkcionality týka):
                    $ wc get.sh
                    369 1198 8328 get.sh
                    (aj s poznámkami).
                    Fiktívnu db by som musel vyrobiť a zavesiť niekam na web. A nosiť drevo do lesa sa mi nechcelo, veď web je plný všakovakých databáz.

                    Je mi ľúto, ak Ti táto forma článkov nevyhovuje. V budúcnosti sa budem snažiť byť veľmi konkrétny. Dovtedy odporúčam man wget(1), grep(1), sed(1), gawk(1).