Obsah
1. Zpracování XML a HTML v Pythonu s využitím knihovny lxml
3. Použití objektů typu Element a ElementTree
4. Uložení vytvořeného XML v komprimované i čitelné podobě
5. Vložení obsahu (textu) do značek
6. Nepatrně složitější strom se třemi úrovněmi
7. Specifikace atributů jednotlivých uzlů ve stromu
8. Parsování XML s využitím knihovny lxml
9. Přístup k atributům a poduzlům naparsovaného dokumentu
10. Využití cesty (path) při přístupu k uzlům
12. Přístup k hodnotám uzlů s využitím XPath
13. Získání cesty k vybranému uzlu pro pozdější použití
14. Základy zpracování HTML stránek
15. Použití objektu typu HTMLParser
16. Přečtení obsahu všech odstavců, získání atributů prvků atd.
17. Úplný zdrojový kód příkladu pro parsing HTML stránky
18. Získávání informací z HTML stránek zajímavější cestou: knihovna Beautiful Soup
19. Repositář s demonstračními příklady
1. Zpracování XML a HTML v Pythonu s využitím knihovny lxml
Knihovna lxml, s jejímiž základy se dnes seznámíme, slouží k načítání (parsování) XML souborů, přístup k jednotlivým prvkům výsledného stromu, tvorbě a zapisování nových XML a v případě potřeby lze tuto knihovnu použít i pro zpracování HTML stránek. Zajímavé je, že se tato knihovna poměrně dobře hodí i pro práci s nevalidními XML, XML bez schématu atd. – tj. se soubory, které může být obtížné zpracovat v jiných nástrojích. Vývojářům jsou v případě potřeby k dispozici i další zajímavé technologie, zejména XPath (zjednodušeně: přístup k elementům a jejich atributům přes doménově specifický jazyk) a SAX, tj. možnost zpracovávat XML jako sekvenci elementů, což je přístup mnohem méně náročný na paměť. Navíc se většinou jedná o rychlejší způsob práce s XML.
Na knihovnu lxml se můžeme dívat jako na vhodný doplněk ke knihovnám libxml2 a libxslt, pro které samozřejmě existují příslušná rozhraní pro Python. Tyto knihovny jsou především rychlé a nabízí prakticky všechny užitečné operace pro práci s XML. Na druhou stranu se jedná o spíše nízkoúrovňové knihovny poměrně přesně kopírující céčkové rozhraní, což některým uživatelům Pythonu nemusí plně vyhovovat. Navíc – jelikož se skutečně jedná o relativně tenkou vrstvu mezi programovacím jazykem C a Pythonem – může poměrně snadno dojít k pádům celé aplikace (segfault), což je velmi nepříjemné, zejména při produkčním nasazení. Mj. i z těchto dvou důvodů vznikla knihovna lxml, která je více „pythonovská“ a tudíž snadněji použitelná. Za snadnost použití však někdy zaplatíme pomalejším zpracováním XML, takže záleží na tom, jak velké soubory a v jakém množství se mají zpracovávat.
2. Instalace knihovny lxml
Pokud v Pythonu vytváříte aplikace používající další moduly (knihovny), máte již s velkou pravděpodobností knihovnu lxml ve svém systému nainstalovanou. O tom, zda je knihovna skutečně nainstalovaná a dostupná (interpret ji nalezne), se můžete snadno přesvědčit, a to buď příkazem pip3 show lxml nebo pip3 list | grep lxml (což ovšem není tak přesné):
$ pip3 show lxml --- Name: lxml Version: 3.3.3 Location: /usr/lib/python3/dist-packages Requires:
Jen pro zajímavost (pip3 show je ovšem lepší řešení):
$ pip3 list | grep lxml lxml (3.3.3)
$ sudo pip3 install lxml -U Collecting lxml Downloading https://files.pythonhosted.org/packages/03/a4/9eea8035fc7c7670e5eab97f34ff2ef0ddd78a491bf96df5accedb0e63f5/lxml-4.2.5-cp36-cp36m-manylinux1_x86_64.whl (5.8MB) 100% |████████████████████████████████| 5.8MB 273kB/s Installing collected packages: lxml Found existing installation: lxml 3.3.3 Uninstalling lxml-3.3.3: Successfully uninstalled lxml-3.3.3 Successfully installed lxml-4.2.5
Nyní znovu zkontrolujeme verzi nainstalované knihovny:
$ pip3 show lxml Name: lxml Version: 4.2.5 Summary: Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API. Home-page: http://lxml.de/ Author: lxml dev team Author-email: lxml-dev@lxml.de License: BSD Location: /usr/lib64/python3.6/site-packages Requires:
Pokud z nějakého důvodu není knihovna lxml nainstalovaná, je její instalace většinou otázkou několika sekund. Na výpisu níže je ukázána instalace této knihovny určené pro Python 2 (používá se tedy příkaz pip a nikoli pip3):
$ pip install --user lxml Downloading https://files.pythonhosted.org/packages/e5/14/f4343239f955442da9da1919a99f7311bc5627522741bada61b2349c8def/lxml-4.2.5-cp27-cp27mu-manylinux1_x86_64.whl (5.8MB) 100% |████████████████████████████████| 5.8MB 89kB/s Installing collected packages: lxml Successfully installed lxml-4.2.5
3. Použití objektů typu Element a ElementTree
Nejprve se podívejme na způsob vytváření souborů XML. Vzhledem k tomu, že se ve skutečnosti jedná o zápis stromové struktury (s jediným kořenovým uzlem, každý uzel/element kromě kořenového má jediného předka, neexistují žádné cykly), pravděpodobně nás nepřekvapí, že se XML skutečně konstruuje s využitím objektů Element a ElementTree.
Objekt typu Element je možné považovat za datovou strukturu, jejíž základní vlastnosti jsou převzaté z klasických seznamů a současně i slovníků (viz další seznam). Instance třídy Element představuje jeden uzel stromu a může obsahovat celou řadu vlastností (properties), především pak:
- Značku (tag), což je řetězec se jménem elementu (uzlu).
- Atributy přiřazené k elementu. Interně se jedná o slovník s dvojicemi jméno atributu:hodnota atributu. Počet atributů je libovolný, jejich jméno jsou samozřejmě unikátní.
- Text (hodnota) ve značce. Může být prázdný. Viz navazující kapitoly.
- Všechny přímé potomky (children). Interně se jedná o sekvenci a opět platí, že koncový uzel (list) nemá žádné potomky.
Existuje několik způsobů, jak zavolat konstruktor třídy Element. V tom nejjednodušším případě se konstruktoru předá pouze značka (jméno) elementu ve formě řetězce:
import lxml.etree as ET root = ET.Element("root")
Potomky jakéhokoli elementu můžeme vytvořit s využitím SubElement. Jedná se o tovární funkci, které se předává reference na předka (dříve vytvořený uzel/element) a taktéž značka (jméno) nového elementu. K jedinému uzlu, který jsme vytvořili předchozím příkazem, tedy přidáme dva potomky následujícím způsobem:
left = ET.SubElement(root, "left") right = ET.SubElement(root, "right")
V dalším kroku již můžeme vytvořit instanci třídy ElementTree, která celou hierarchii uzlů zabaluje. Konstruktoru této třídy se předává kořenový uzel:
# konstrukce stromu tree = ET.ElementTree(root)
Tato třída nabízí uživatelům celou řadu užitečných metod. Například se jedná o metodu nazvanou find sloužící k nalezení určitého uzlu na základě zadané cesty; dále pak o metody pojmenované findall a findnext, popř. o metodu write, kterou je možné použít pro uložení stromu do souboru (v XML podobě).
4. Uložení vytvořeného XML v komprimované i čitelné podobě
K poslední metodě, o níž jsme se zmínili na konci předchozí kapitoly, se na chvíli vraťme. Tuto metodu totiž použijeme ve chvíli, kdy potřebujeme vytvořit výsledný XML soubor. Metodě se předává buď pouze název souboru, nebo i další nepovinné (pojmenované) parametry. Nejjednodušší způsob použití vypadá následovně:
tree.write("test1.xml")
Nepovinným parametrem pretty_print si můžeme (alespoň částečně) vynutit vygenerování XML souboru určeného pro čtení uživateli. Poduzly jsou v takovém případě odsazeny od levého okraje, ovšem pouze v případě, že neobsahují žádný text (což je v našem případě splněno):
tree.write("test1_pretty_print.xml", pretty_print=True)
Podívejme se nyní na první demonstrační příklad, v němž vytvoříme jednoduchý strom s kořenem a dvěma poduzly. Tento strom následně uložíme do dvou XML souborů – bez formátování a s formátováním:
import lxml.etree as ET root = ET.Element("root") left = ET.SubElement(root, "left") right = ET.SubElement(root, "right") # konstrukce stromu tree = ET.ElementTree(root) # zapis do souboru tree.write("test1.xml") tree.write("test1_pretty_print.xml", pretty_print=True)
První XML soubor je vygenerován takovým způsobem, aby byl co nejkratší:
<root><left/><right/></root>
Druhý XML soubor naopak poduzly odsazuje:
<root> <left/> <right/> </root>
5. Vložení obsahu (textu) do značek
Zkusme si nyní vytvořit nepatrně složitější XML soubor, v němž budou poduzly obsahovat nějaký text. Ten může být libovolný, tj. můžeme použít například klasické ASCII znaky (univerzálně podporovaná podmnožina Unicode):
root = ET.Element("root") root.text = "any text"
Ovšem nic nám nebrání použít i další znaky Unicode:
left = ET.SubElement(root, "left") left.text = "ěščř"
Dokonce můžeme použít i znaky, které mají v XML speciální význam:
middle = ET.SubElement(root, "middle") middle.text = "<&>"
A samozřejmě je možné použít i prázdný text:
right = ET.SubElement(root, "right") right.text = ""
XML soubory s takovými uzly vytvoříme v dnešním druhém demonstračním příkladu:
import lxml.etree as ET root = ET.Element("root") root.text = "any text" left = ET.SubElement(root, "left") left.text = "ěščř" middle = ET.SubElement(root, "middle") middle.text = "<&>" right = ET.SubElement(root, "right") right.text = "" # konstrukce stromu tree = ET.ElementTree(root) # zapis do souboru tree.write("test2.xml") tree.write("test2_pretty_print.xml", pretty_print=True)
Výsledek bude vypadat následovně:
<root>any text<left>ěščř</left><middle><&></middle><right></right></root>
Vzhledem k tomu, že uzly obsahují text, bude výsledek neformátovaný i v případě, že použijeme parametr pretty_print:
<root>any text<left>ěščř</left><middle><&></middle><right></right></root>
Nyní si zkusme ověřit, co se stane ve chvíli, kdy do textového uzlu přidáme text s konci řádků:
import lxml.etree as ET root = ET.Element("root") root.text = "text, který může\nobsahovat i konce řádků" left = ET.SubElement(root, "left") left.text = "ěščř" right = ET.SubElement(root, "right") right.text = "\n\n\n" # konstrukce stromu tree = ET.ElementTree(root) # zapis do souboru tree.write("test3.xml") tree.write("test3_pretty_print.xml", pretty_print=True)
Konce řádků se přesně objeví i ve výsledném XML:
<root>text, který může obsahovat i konce řádků<left>ěščř</left><right> </right></root>
Vzhledem k tomu, že uzly obsahují text, bude výsledek neformátovaný i v případě, že použijeme parametr pretty_print:
<root>text, který může obsahovat i konce řádků<left>ěščř</left><right> </right></root>
6. Nepatrně složitější strom se třemi úrovněmi
Další demonstrační příklad je uveden pouze pro úplnost a pro ukázku nepatrně složitějšího XML. Vytvoříme v něm stromovou strukturu se třemi úrovněmi. Na první úrovni se samozřejmě nachází kořenový uzel. Na úrovni druhé jsou tři uzly (levý, prostřední a pravý) a každý z těchto uzlů obsahuje tři potomky (listy):
import lxml.etree as ET root = ET.Element("root") left1stLevel = ET.SubElement(root, "left") middle1stLevel = ET.SubElement(root, "middle") right1stLevel = ET.SubElement(root, "right") left2ndLevelA = ET.SubElement(left1stLevel, "left") middle2ndLevelA = ET.SubElement(left1stLevel, "middle") right2ndLevelA = ET.SubElement(left1stLevel, "right") left2ndLevelB = ET.SubElement(middle1stLevel, "left") middle2ndLevelB = ET.SubElement(middle1stLevel, "middle") right2ndLevelB = ET.SubElement(middle1stLevel, "right") left2ndLevelC = ET.SubElement(right1stLevel, "left") middle2ndLevelC = ET.SubElement(right1stLevel, "middle") right2ndLevelC = ET.SubElement(right1stLevel, "right") # konstrukce stromu tree = ET.ElementTree(root) # zapis do souboru tree.write("test4.xml") tree.write("test4_pretty_print.xml", pretty_print=True)
Výsledné XML zapsané ve zhuštěné podobě na jediném řádku:
<root><left><left/><middle/><right/></left><middle><left/><middle/><right/></middle><right><left/><middle/><right/></right></root>
V případě použití parametru pretty_print je ze struktury a odsazení uzlů jasně patrná původní stromová struktura, kterou jsme v příkladu vytvořili:
<root> <left> <left/> <middle/> <right/> </left> <middle> <left/> <middle/> <right/> </middle> <right> <left/> <middle/> <right/> </right> </root>
7. Specifikace atributů jednotlivých uzlů ve stromu
Již z předchozího popisu víme, že uzel může kromě textu (umístěného uvnitř jeho značky) obsahovat i libovolný počet atributů, přičemž každý atribut má své jméno a hodnotu. Jména atributů by měla být unikátní a mělo by se jednat o platné identifikátory odpovídající této gramatice:
NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF] NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
Na hodnotu atributu se toto omezení nevztahuje, pouze je zapotřebí řešit kódování uvozovek, úhlových závorek atd. To za nás provede knihovna lxml automaticky.
V následujícím příkladu opět vytvoříme strom se třemi úrovněmi, nyní však budou mít jednotlivé uzly přiřazené atributy (všechny budou mít atribut „popis“):
import lxml.etree as ET root = ET.Element("root", atribut1="1", attribut2="2", popis="koren") left1stLevel = ET.SubElement(root, "left", popis="levy vnitrni poduzel") middle1stLevel = ET.SubElement(root, "middle", popis="prostredni vnitrni poduzel") right1stLevel = ET.SubElement(root, "right", popis="pravy vnitrni poduzel") left2ndLevelA = ET.SubElement(left1stLevel, "left", popis="list zcela nalevo") middle2ndLevelA = ET.SubElement(left1stLevel, "middle", popis="list") right2ndLevelA = ET.SubElement(left1stLevel, "right", popis="list") left2ndLevelB = ET.SubElement(middle1stLevel, "left", popis="list") middle2ndLevelB = ET.SubElement(middle1stLevel, "middle", popis="prostredni list") right2ndLevelB = ET.SubElement(middle1stLevel, "right", popis="list") left2ndLevelC = ET.SubElement(right1stLevel, "left", popis="list") middle2ndLevelC = ET.SubElement(right1stLevel, "middle", popis="list") right2ndLevelC = ET.SubElement(right1stLevel, "right", popis="list zcela napravo") # konstrukce stromu tree = ET.ElementTree(root) # zapis do souboru tree.write("test5.xml") tree.write("test5_pretty_print.xml", pretty_print=True)
Výsledné XML zapsané ve zhuštěné podobě na jediném řádku:
<root atribut1="1" attribut2="2" popis="koren"><left popis="levy vnitrni poduzel"><left popis="list zcela nalevo"/><middle popis="list"/><right popis="list"/></left><middle popis="prostredni vnitrni poduzel"><left popis="list"/><middle popis="prostredni list"/><right popis="list"/></middle><right popis="pravy vnitrni poduzel"><left popis="list"/><middle popis="list"/><right popis="list zcela napravo"/></right></root>
V případě použití parametru pretty_print je ze struktury a odsazení uzlů jasně patrná původní stromová struktura, kterou jsme v příkladu vytvořili:
<root atribut1="1" attribut2="2" popis="koren"> <left popis="levy vnitrni poduzel"> <left popis="list zcela nalevo"/> <middle popis="list"/> <right popis="list"/> </left> <middle popis="prostredni vnitrni poduzel"> <left popis="list"/> <middle popis="prostredni list"/> <right popis="list"/> </middle> <right popis="pravy vnitrni poduzel"> <left popis="list"/> <middle popis="list"/> <right popis="list zcela napravo"/> </right> </root>
8. Parsování XML s využitím knihovny lxml
Ve druhé části článku si ukážeme základy parsování XML, samozřejmě opět s využitím knihovny lxml. V následujícím příkladu využijeme soubor „test5.xml“, který vznikl spuštěním demonstračního příkladu ze sedmé kapitoly. Tento soubor – který neobsahuje specifikaci verze XML, kódování, o schématu ani nemluvě atd. – dokáže knihovna lxml bez problémů načíst a zparsovat. Výsledkem bude objekt představující rekonstruovaný strom:
import lxml.etree as ET xml = "test5.xml" tree = ET.parse(xml) root = tree.getroot() print(ET.tostring(root))
Takto se vypíše rekonstruovaný strom začínající kořenovým uzlem, který jsme získali metodou getroot::
b'<root atribut1="1" attribut2="2" popis="koren"><left popis="levy vnitrni poduzel"><left popis="list zcela nalevo"/><right popis="list"/></left><right popis="pravy vnitrni poduzel"><left popis="list"/><right popis="list zcela napravo"/></right></root>'
9. Přístup k atributům a poduzlům naparsovaného dokumentu
Ve chvíli, kdy máme k dispozici objekt představující kořen stromu vzniklého parsingem XML souboru, je možné postupně začít získávat atributy kořenového uzlu, jeho text a samozřejmě i potomky, tj. uzly ležící o jednu úroveň níže. Přístup k atributům:
print(root.get("atribut1")) print(root.get("popis"))
Získání potomků:
children = root.getchildren()
Tato metoda obecně vrací sekvenci, takže se k jednotlivým potomkům dostaneme například přes programovou smyčku typu for-each:
for child in children: print(child.get("popis"))
Celý demonstrační příklad, který zpracuje jednoduchý XML soubor a vypíše atributy kořenového uzlu i jeho potomky (resp. přesněji řečeno atribut „popis“ potomků), bude vypadat následovně:
import lxml.etree as ET xml = "test5.xml" tree = ET.parse(xml) root = tree.getroot() # print(ET.tostring(root)) print(root.get("atribut1")) print(root.get("popis")) children = root.getchildren() for child in children: print(child.get("popis"))
Výsledkem bude následujících pět řádků vypsaných na standardní výstup:
1 koren levy vnitrni poduzel prostredni vnitrni poduzel pravy vnitrni poduzel
10. Využití cesty (path) při přístupu k uzlům
V některých jednodušších případech si sice vystačíme se zpracováním stromu pomocí metody getchildren, ovšem knihovna lxml nám dává k dispozici mnohem silnější technologii nazvanou XPath neboli „XML Path Language“. Jedná se o doménově specifický jazyk (DSL), který primárně slouží k přístupu k elementům stromové struktury na základě absolutně nebo relativně zadané cesty. Možnosti nabízené XPathem jsou však větší; s některými z nich se seznámíme příště. Nyní si ukažme pravděpodobně nejjednodušší příklad, v němž použijeme cestu přesně popisující jediný uzel ve stromu. Cesta je popsána jmény značek (což je v našem stromu zcela jednoznačné, podobně jako můžeme popsat cestu k souboru v adresářové struktuře):
/root/right/right
Příklad získá všechny uzly odpovídající zadané cestě a vypíše popisy těchto uzlů:
import lxml.etree as ET xml = "test5.xml" tree = ET.parse(xml) # absolutni cesta nodes = tree.xpath("/root/right/right") print("Nodes: {}".format(len(nodes))) for node in nodes: print(node.get("popis"))
Vzhledem k tomu, že cesta přesně specifikuje jediný uzel/element, bude výpis vypadat takto:
Nodes: 1 list zcela napravo
Můžeme ovšem postupovat i jinak a metodu xpath volat přímo na kořenovém elementu a nikoli nad celým stromem. Zbytek příkladu bude stejný a stejný bude v tomto případě i výsledek:
import lxml.etree as ET xml = "test5.xml" tree = ET.parse(xml) root = tree.getroot() # absolutni cesta nodes = root.xpath("/root/right/right") print("Nodes: {}".format(len(nodes))) for node in nodes: print(node.get("popis"))
S výsledkem:
Nodes: 1 list zcela napravo
11. Použití relativních cest
Cesta může být zadána i relativně, tj. bez úvodního lomítka. Vzhledem k tomu, že metodu xpath opět voláme nad kořenovým uzlem, bude výsledek stejný, protože relativní cesta se bude vztahovat k tomuto uzlu:
import lxml.etree as ET xml = "test5.xml" tree = ET.parse(xml) root = tree.getroot() # relativni cesta nodes = root.xpath("right/right") print("Nodes: {}".format(len(nodes))) for node in nodes: print(node.get("popis"))
S výsledkem:
Nodes: 1 list zcela napravo
12. Přístup k hodnotám uzlů s využitím XPath
Možnosti doménově specifického jazyka XPath jsou ve skutečnosti mnohem větší. Pro zajímavost si ukažme, jak můžeme získat text (hodnotu) libovolného uzlu/elementu přímo specifikací cesty:
import lxml.etree as ET xml = "test2.xml" tree = ET.parse(xml) root = tree.getroot() text = root.xpath("//text()") print(text) text = root.xpath("left//text()") print(text) text = tree.xpath("/root/left//text()") print(text)
Povšimněte si, že v prvním případě se vrátí text všech nalezených uzlů, ve druhém případě text z uzlu „left“ (je použita relativní cesta) a v případě posledním textu taktéž z uzlu „left“, ovšem specifikovaného absolutní cestou:
['any text', 'ěščř', '<&>'] ['ěščř'] ['ěščř']
13. Získání cesty k vybranému uzlu pro pozdější použití
Při konstrukci stromu popř. při analýze naparsovaného XML může být užitečné zjistit, jak by vlastně mohla vypadat cesta k nějakém uzlu. I tuto možnost knihovna lxml nabízí, protože pro libovolný uzel (i uzel kořenový) je možné zavolat metodu getpath, která vrátí absolutní cestu k danému uzlu/elementu:
import lxml.etree as ET root = ET.Element("root", atribut1="1", attribut2="2", popis="koren") left1stLevel = ET.SubElement(root, "left", popis="levy vnitrni poduzel") right1stLevel = ET.SubElement(root, "right", popis="pravy vnitrni poduzel") left2ndLevelA = ET.SubElement(left1stLevel, "left", popis="list zcela nalevo") right2ndLevelA = ET.SubElement(left1stLevel, "right", popis="list") left2ndLevelB = ET.SubElement(right1stLevel, "left", popis="list") right2ndLevelB = ET.SubElement(right1stLevel, "right", popis="list zcela napravo") # konstrukce stromu tree = ET.ElementTree(root) print(tree.getpath(root)) print(tree.getpath(left1stLevel)) print(tree.getpath(right2ndLevelB))
Výsledkem je trojice absolutních cest:
/root /root/left /root/right/right
14. Základy zpracování HTML stránek
Knihovnu lxml je možné v případě potřeby použít i pro zpracování HTML stránek. Při zpracovávání HTML stránek se nevyžaduje (a popravdě řečeno ani neočekává) validita stránky, takže se specializovaný parser nazvaný HTMLParser snaží z dodaného zdrojového kódu stránky získat korektní stromovou strukturu, a to i v případě, že autor například neuzavírá značky, nevkládá uzavírací značky ve správném pořadí atd. Podívejme se nyní na jednoduchý příklad zpracovatelné HTML stránky, konkrétně na stránku dostupnou na adrese http://www.zyvra.org/html/simple.htm. Zdrojový kód této stránky vypadá následovně (stylem zápisu trošku připomíná minulé tisíciletí :-):
<html><head><title> Very simple HTML page. </title></head> <body> <p>You can look at the source of this page by: Right clicking anywhere out in space on this page then selecting "View" in the menu.</p> <p>This works on any page, but sometimes what you see may be very complex and seem confusing.</p> <p> <b>Please,</b> look at the source and what you see with the browser. You should understand and see the effect of every tag. Use the little Icons up in the right of your browser screen to change the size of the window and see the effect, and how the browser displays this page.</p> <p align="right"> Yes, this is a <b>Very Plain</b> page. <i>But it works!</i></p> <p><i><b>Remember. We are just getting started,</b> and I haven't used anything more than I have talked about in a couple pages!</i> Yes. You will want to be more fancy. Just be patient, we'll get there. <p align="center">Now create a page like this of your own. <b>Have fun!</b></p </body></html>
Povšimněte si například toho, že se ve stránce objevují neuzavřené značky <p>, chybí informace o verzi HTML, o kódování atd. I přesto je možné takovou stránku zpracovat, což je ukázáno v následující kapitole.
15. Použití objektu typu HTMLParser
Realizace příkladu pro zpracování HTML stránky je poměrně přímočará. Nejprve (přesněji řečeno po importu lxml.etree) vytvoříme instanci parseru HTML stránek:
import lxml.etree as ET parser = ET.HTMLParser()
Dále se pokusíme stránku načíst a ihned poté zparsovat. Povšimněte si, že funkce lxml.etree.parse rozpozná URL a stránku v případě potřeby stáhne (pokud ovšem není dostupná na serveru s HTTPS!):
url = "http://www.zyvra.org/html/simple.htm" tree = ET.parse(url, parser) root = tree.getroot()
Nyní, když máme k dispozici celý strom i kořenový prvek, se můžeme pokusit zpětně zrekonstruovat řetězec se zdrojovým kódem stránky:
print("\n\n\nContent:") result = ET.tostring(tree.getroot(), pretty_print=True, method="html") print(result)
S výsledkem:
Content: b'<html>\n<head><title>\nVery simple HTML page.\n</title></head>\n<body>\n\n<p>You can look at the source of this page by: Right clicking anywhere\nout in space on this page then selecting "View" in the menu.</p>\n<p>This works on any page, but sometimes\nwhat you see may be very complex\nand seem confusing.</p>\n\n<p>\n<b>Please,</b> look at the source and what you see with the browser.\nYou should understand and see the effect of every tag. Use the little\nIcons up in the right of your browser screen to change the size of the\nwindow and see the effect, and how the browser displays this page.</p>\n<p align="right">\nYes, this is a <b>Very Plain</b> page. <i>But it works!</i></p>\n<p><i><b>Remember. We are just getting started,</b> and I haven\'t used\nanything more than I have talked about in a couple pages!</i>\nYes. You will want to be more fancy.\nJust be patient, we\'ll get there.\n\n</p>\n<p align="center">Now create a page like\nthis of your own. <b>Have fun!</b></p>\n</body>\n</html>\n'
Popř. získat text titulku:
print("\n\n\nTitle:") title = root.xpath("/html/head/title/text()") print(title[0])
S výsledky:
Title: Very simple HTML page.
16. Přečtení obsahu všech odstavců, získání atributů prvků atd.
Dále můžeme získat všechny odstavce uzavřené do značky <p>, a to opět použitím metody xpath:
divs = root.xpath("body//p")
Vypsat můžeme textový obsah všech nalezených odstavců (obsah může být prázdný v případě nepárové značky):
print("\n\n\nText:") for div in divs: print(div.text)
S výsledky:
Text: You can look at the source of this page by: Right clicking anywhere out in space on this page then selecting "View" in the menu. This works on any page, but sometimes what you see may be very complex and seem confusing. Yes, this is a None Now create a page like this of your own.
Samozřejmě nám nikdo nebrání získat informace o libovolném atributu, například o atributu „class“, „style“ či „align“:
print("\n\n\nAlign:") for div in divs: print(div.get("align"))
S výsledky:
Align: None None None right None center
17. Úplný zdrojový kód příkladu pro parsing HTML stránky
Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:
import lxml.etree as ET parser = ET.HTMLParser() url = "http://www.zyvra.org/html/simple.htm" tree = ET.parse(url, parser) root = tree.getroot() print("\n\n\nContent:") result = ET.tostring(tree.getroot(), pretty_print=True, method="html") print(result) print("\n\n\nTitle:") title = root.xpath("/html/head/title/text()") print(title[0]) divs = root.xpath("body//p") print("\n\n\nText:") for div in divs: print(div.text) print("\n\n\nAlign:") for div in divs: print(div.get("align"))
18. Získávání informací z HTML stránek zajímavější cestou: knihovna Beautiful Soup
Pokud budete potřebovat vytvořit aplikaci získávající data ze složitěji strukturovaných HTML stránek, může být lepší namísto dnes popisované knihovny lxml použít další zajímavou a užitečnou knihovnu nazvanou Beautiful Soup. S možnostmi nabízenými touto knihovnou se seznámíme v samostatném článku, v němž si samozřejmě ukážeme i různé příklady (tentokrát již převzaté z praxe).
19. Repositář s demonstračními příklady
Všechny dnes popisované demonstrační příklady byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/lxml-examples. Příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý repositář:
20. Odkazy na Internetu
- lxml – XML and HTML with Python
https://lxml.de/index.html - lxml na PyPi
https://pypi.org/project/lxml/ - ElementTree and lxml
https://wiki.python.org/moin/Tutorials%20on%20XML%20processing%20with%20Python - ElementTree Overview
http://effbot.org/zone/element-index.htm - Elements and Element Trees
http://effbot.org/zone/element.htm - Python XML processing with lxml
http://infohost.nmt.edu/tcc/help/pubs/pylxml/web/index.html - Dive into Python 3: XML
http://www.diveintopython3.net/xml.html - Programovací jazyk Clojure – základy zpracování XML
https://www.root.cz/clanky/programovaci-jazyk-clojure-zaklady-zpracovani-xml/ - xml-zip
http://clojuredocs.org/clojure.zip/xml-zip - xml-seq
http://clojuredocs.org/clojure.core/xml-seq - Parsing XML in Clojure
https://www.root.cz/clanky/programovaci-jazyk-clojure-zaklady-zpracovani-xml/ - Tree structure
https://en.wikipedia.org/wiki/Tree_structure - Strom (datová struktura)
https://cs.wikipedia.org/wiki/Strom_(datov%C3%A1_struktura) - Element Library Functions
http://effbot.org/zone/element-lib.htm#prettyprint - The XML C parser and toolkit of Gnome
http://xmlsoft.org/ - XML Tutorial na zvon.org
http://www.zvon.org/comp/r/tut-XML.html - Extensible Markup Language (XML) 1.0 (Fifth Edition)
https://www.w3.org/TR/REC-xml/ - XML Processing Modules (pro Python)
https://docs.python.org/3/library/xml.html - Užitečné knihovny a moduly pro Python: knihovna Requests
https://mojefedora.cz/uzitecne-knihovny-pro-python-requests-1/ - Užitečné knihovny a moduly pro Python: další možnosti nabízené knihovnou Requests
https://mojefedora.cz/uzitecne-knihovny-a-moduly-pro-python-dalsi-moznosti-nabizene-knihovnou-requests/ - Extensible Markup Language
https://en.wikipedia.org/wiki/XML - Extensible Markup Language
https://cs.wikipedia.org/wiki/Extensible_Markup_Language - Slabikář XML – odkazy
https://www.interval.cz/clanky/slabikar-xml-odkazy/ - XML editors
http://www.xml-dev.com/ - lxml FAQ – Frequently Asked Questions
https://lxml.de/FAQ.html - XML pro začátečníky – 1. část
http://programujte.com/clanek/2007030501-xml-pro-zacatecniky-1-cast/ - XML pro web aneb od teorie k praxi, 2.díl
https://www.zive.cz/clanky/xml-pro-web-aneb-od-teorie-k-praxi-2dil/sc-3-a-109709/default.aspx - XML Schema
https://cs.wikipedia.org/wiki/XML_Schema - Meaning of – <?xml version=“1.0” encoding=“utf-8”?>
https://stackoverflow.com/questions/13743250/meaning-of-xml-version-1–0-encoding-utf-8#27398439 - Beautiful Soup
https://www.crummy.com/software/BeautifulSoup/