XPath - co, proč a hlavně jak?
Potom, co jsem na nedávné Poslední Sobotě mluvil o Matcheru, se zájem o tento nástroj znatelně zvýšil. Lidé začali posílat pull-requesty, bugreporty a maily s otázkami jak se dá udělat to či ono. Na všechny jsem rád odpověděl, protože dotazy byly buď triviální a tudíž jednoduché na vytřešení nebo zajímavé oříšky, které musely být vyřešeny. Jedno měly však společné: V naprosté většině řešení nemělo nic společného s Matcherem, ale ve správném použití XPath, na němž je nástroj založený. Právě z toho důvodu jsem napsal následující letmý úvod do XPath, který ukáže vlastnosti nejčastěji používané při extrakci dat z HTML.
XPath plní stejný účel jako CSS selektory: Z HTML stromu vybere uzly, které mě zajímají a se kterými bych chtěl dál pracovat - ostylovat v případě CSS nebo z nich extrahovat data v případě Matcheru. Ve srovnání se současnou verzí CSS selektorů je však XPath expresivnější a místy má odlišnou sémantiku.
Všechno bude nejlépe patrné z ukázek.
/html
- Vybere element html
, který je kořenovým elementem stromu.
/html/body - Odpovídá všem elementům
body, které jsou přímými potomky kořenového elementu
html`.
//h2
- Všechny elementy h2
, které se v dokumentu vyskytují, včetně vlastních podstromů. Pokud jeden h2
kdekoli v sobě obsahuje jiný element h2, výsledkem budou všechny tyto elementy.
//div/span//a
- Selektory je možno libovolně řetězit a tento najde všechny a
jako potomky span
, které jsou přímými potomky libovolného div
u.
./div
nebo div
- Vybere všechny elementy div
které jsou přímými potomky právě aktivního elementu. Jde o relativní polohu a ne o polohu fixovanou ke kořenu dokumentu.
.//div
- To samé jako předchozí výraz, ale připouští i nepřímé potomky.
div[@class="asdf"]
- div
, jehož atribut class je "asdf". XPath nepodporuje CSS třídy a k obsahu atributů přistupuje jako ke stringům a proto, tento výraz není stejný jako css selektor div.asdf
. XPath podmínce @class="asdf"
vyhoví jen ty elementy jejichž atribut class je jen a pouze string "asdf". Na toto je si třeba dávat pozor, protože jde o největší odchylku od chování, které by čekal člověk odkojený CSS selektory.
div[@class != "asdf"]
nebo div[not(@class = "asdf")]
- Odpovídá div
ům jejichž třída není "asdf".
div[contains(@class, "asdf")]
- divy jejichž atribut class obsahuje string "asdf". Například: Třída "xasdfy" také vyhovuje tomuto predikátu.
div[starts-with(@class, "asdf")]
- divy jejichž atribut class začíná na "asdf".
div[contains(concat(" ", normalize-space(@class), " "), " asdf ")]
- Tento výraz má stejný význam jako CSS selektor div.asdf
(tedy kromě toho, že CSS nepřihlíží v velikosti písmen a XPath ano). Naštěstí takové konstrukce nejsou skoro nikdy potřeba, protože většinou stačí jednoduché @class="asdf"
nebo funkce contains()
.
div[@class]
- divy, které mají nějaký atribut class
div[span]
- divy, které mají jako přímého potomka span
div[.//span]
- divy, které obsahují span
div[span[@class]]
- divy, které mají přímého potomka span, který má nastavenou libovolnou třídu
div[@class and span]
- divy, které mají nastaven atribut class a zároveň obsahují span jako potomka. Stejně jako and
se dá použít i logické or
nebo funkce not()
.
div[1]
- První div v pořadí v jakém se vyskytuje v dokumentu.
div[last()]
- Poslední div.
div[position() mod 2 = 0]
- Každý sudý div.
Na pořadí predikátů v hranatých závorkách záleží:
div[@class="asdf"][1]
- Vybere všechny divy s třídou "asdf" a z nich pak vybere ten první. Pokud existují nějaké divy s touto třídou, výsledkem bude vždycky první z nich.
div[1][@class="asdf"]
- Vybere první div a ten ve výsledné množině ponechá jen pokud má třídu "asdf". Když existují divy s touto třídou, ale první ji nemá, výsledkem je prázdná množina.
div/text()[1]
- Z divu vybere první kus textu, který není obsažen v žádném jiném elementu.
preceding-sibling::h2
- Vybere elementy h2
, které předcházejí aktivnímu elementu a zároveň jsou jeho sourozenci (tj. jsou přímými potomky stejného elementu jako aktivní element).
a[@class="active"]/following-sibling::a[1]
- Vybere první odkaz, který následuje (a je sourozenec) po odkazu s třídou "active".
Dále k tématu: