Obsah
1. Zpracování XML a HTML v Pythonu s využitím knihoven lxml a Beautiful Soup
2. Soubory POM (Project Object Model)
3. První demonstrační příklad – načtení XML souboru pom.xml
4. Použití XPath při přístupu ke konkrétnímu uzlu
5. Získání a výpis informací o modulech, na nichž závisí projekt
6. Podmínky použité při výběru uzlů pomocí cesty
7. Porovnání hodnoty uzlu s textovým řetězcem
8. Složitější cesta s relativní částí
10. Získání základních informací z HTML stránky
11. Zjednodušení předchozího příkladu pomocí tečkového operátoru
12. Nalezení všech značek <title> a výpis jejich obsahu
13. Výpis všech odkazů ve značkách <a>
14. Složitější příklad – získání klíčových slov přiřazených k balíčkům
15. Další zpracování klíčových slov přiřazených k balíčkům
16. Získání tučného textu z HTML stránky
17. Přečtení obsahu tabulek z HTML stránky
18. Úplný zdrojový kód předchozího demonstračního příkladu
19. Repositář s demonstračními příklady
1. Zpracování XML a HTML v Pythonu s využitím knihoven lxml a Beautiful Soup
V úvodním článku o knihovně xml jsme se seznámili se základními koncepty, na nichž je tato užitečná knihovna postavena. Připomeňme si, že pro reprezentaci dokumentu načítaného či ukládaného do XML jsou použity objekty typu 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ů. Instance třídy Element představuje jeden uzel vytvářeného či naparsovaného stromu dokumentu a může obsahovat celou řadu vlastností (properties), zejména pak samotnou značku, atributy, text (hodnotu umístěnou ve značce) a získat lze i reference na všechny přímé potomky uzlu. Naproti tomu třída ElementTree celý strom „obaluje“ a nabízí uživatelům další užitečné metody pro hledání uzlů, zápis stromu do XML apod.
Taktéž jsme si řekli základní informace o použití technologie XPath při vyhledávání uzlů na základě nějakých kritérií. Na tuto zajímavou část v dnešním článku částečně navážeme, protože si ukážeme některé další možnosti, které nám knihovna lxml v této oblasti nabízí.
Ovšem použití knihovny lxml nemusí být vždy to nejlepší řešení, i když zde nalezneme například specializovaný parser nazvaný příznačně HTMLParser. V případě, že je zapotřebí získávat informace z HTML stránek (web scraping), může být výhodnější použít knihovnu specializovanou právě na tuto oblast. Jednou z dostupných a často používaných knihoven pro web scraping a podobné činnosti je knihovna s poněkud záhadným názvem Beautiful Soup. I s některými možnostmi nabízenými touto knihovnou se dnes alespoň ve stručnosti seznámíme.
2. Soubory POM (Project Object Model)
Zejména vývojáři pracující s programovacím jazykem Java a systémem Maven se určitě již mnohokrát setkali se soubory nazvanými pom.xml. Jen ve stručnosti si řekněme, že tyto soubory obsahují informace o projektu a samozřejmě i konfiguraci celého projektu, popř. další informace používané některými Maven pluginy. Tyto soubory jsou primárně využívané nástrojem Apache Maven, přičemž zkratka POM znamená Project Object Model. Ve skutečnosti ovšem s těmito soubory pracuje i mnoho dalších nástrojů, ať již se jedná o integrovaná vývojová prostředí či o specializovanější nástroje určené pro kontrolu závislých knihoven, licencí použitých v knihovnách atd. atd.
Velmi jednoduchý soubor pom.xml může vypadat například následovně:
<project> <modelVersion>4.0.0</modelVersion> <groupId>org.tisnik.uberproject.test</groupId> <artifactId>test-app-junit-dependency</artifactId> <version>1.0</version> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> </dependency> <dependency> <groupId>foo</groupId> <artifactId>foo</artifactId> <version>1.0.0</version> </dependency> <dependency> <groupId>bar</groupId> <artifactId>bar</artifactId> <version>1.2.3</version> </dependency> </dependencies> </project>
3. První demonstrační příklad – načtení XML souboru pom.xml
Vzhledem k tomu, že soubory pom.xml jsou klasickými XML soubory, můžeme pro jejich načtení a parsing použít funkci lxml.etree.parse(), s níž jsme se již seznámili minule. Této funkci předáme jméno souboru. V případě, že se dokument podaří načíst (a to by neměl být problém, samozřejmě za předpokladu, že pom.xml nebyl nějak poškozen), lze získat kořenový uzel, rekurzivně vytisknout jeho obsah (tedy celý strom), přečíst přímé potomky kořenového uzlu a následně vypsat (například) jejich značky:
import lxml.etree as ET xml = "pom.xml" tree = ET.parse(xml) root = tree.getroot() print(ET.tostring(root)) print() children = root.getchildren() for child in children: print(child.tag)
Výsledek by pro náš pom.xml měl vypadat následovně. První část s rekurzivním výpisem celého stromu není příliš čitelná, ovšem to nevadí, protože vždy můžeme použít pretty printing:
b'<project>\n <modelVersion>4.0.0</modelVersion>\n <groupId>org.tisnik.uberproject.test</groupId>\n <artifactId>test-app-junit-dependency</artifactId>\n <version>1.0</version>\n <dependencies>\n <dependency>\n <groupId>junit</groupId>\n <artifactId>junit</artifactId>\n <version>3.8.1</version>\n </dependency>\n <dependency>\n <groupId>foo</groupId>\n <artifactId>foo</artifactId>\n <version>1.0.0</version>\n </dependency>\n <dependency>\n <groupId>bar</groupId>\n <artifactId>bar</artifactId>\n <version>1.2.3</version>\n </dependency>\n </dependencies>\n</project>' modelVersion groupId artifactId version dependencies
4. Použití XPath při přístupu ke konkrétnímu uzlu
Pro přístup ke konkrétnímu uzlu se většinou využívá technologie pojmenovaná XPath, s jejímiž základy jsme se seznámili minule. Podívejme se nyní na příklad použití xpathu v případě, že potřebujeme získat základní informace o projektu popsaného v souboru pom.xml. Mezi základní informace patří mj. i artifactId, groupId a version, přesněji řečeno obsah těchto tří uzlů (nikoli ovšem jejich atributy). Příslušné (absolutní) cesty budou vypadat takto:
/project/artifactId/text() /project/groupId/text() /project/version/text()
Připomeňme si, že pomocí „text()“ dokážeme přečíst textovou hodnotu zapsanou v uzlu/elementu.
Následuje výpis úplného zdrojového kódu demonstračního příkladu, který tyto cesty používá pro získání informací o projektu. Díky poměrně velké jistotě, že se tyto informace budou v pom.xml nacházet, použijeme přístup k prvnímu prvku vráceného seznamu přímo s využitím indexu:
import lxml.etree as ET xml = "pom.xml" tree = ET.parse(xml) root = tree.getroot() artifact_id = tree.xpath("/project/artifactId/text()")[0] print("artifact ID: {aid}".format(aid=artifact_id)) group_id = tree.xpath("/project/groupId/text()")[0] print("Group ID: {gid}".format(gid=group_id)) version = tree.xpath("/project/version/text()")[0] print("Version: {v}".format(v=version))
Výsledek by měl na standardním výstupu vypadat následovně:
Artifact ID: test-app-junit-dependency Group ID: org.tisnik.uberproject.test Version: 1.0
5. Získání a výpis informací o modulech, na nichž závisí projekt
Ve chvíli, kdy budeme potřebovat z našeho projektového souboru uloženého ve formátu XML získat všechny moduly, na nichž projekt přímo závisí, můžeme použít cestu (xpath), která bude vybírat všechny uzly nazvané „dependency“, které se nachází v poduzlu „dependencies“. Prozatím použijeme jednoduchou absolutní cestu, která bude vypadat následovně:
/project/dependencies/dependency
Pro každý nalezený uzel (či lépe řečeno element) pak přečteme text z poduzlu nazvaného „groupId“, a to opět s využitím cesty:
for dependency in dependencies: print(dependency.xpath("groupId/text()")[0])
Úplný kód demonstračního příkladu, který tuto činnost provádí, vypadá následovně:
import lxml.etree as ET xml = "pom.xml" tree = ET.parse(xml) root = tree.getroot() dependencies = tree.xpath("/project/dependencies/dependency") for dependency in dependencies: print(dependency.xpath("groupId/text()")[0])
Výsledek:
junit foo bar
Výše uvedený postup není příliš rychlý (spíše naopak), ovšem ve skutečnosti můžeme celý problém uspokojivě vyřešit i jiným způsobem, například cestou přímo vracející hodnoty všech poduzlů „groupId“ nalezených na všech cestách odpovídajících „/project/dependencies/dependency/“. Upravený příklad, který by měl být rychlejší, vypadá následovně:
import lxml.etree as ET xml = "pom.xml" tree = ET.parse(xml) root = tree.getroot() dependency_ids = tree.xpath("/project/dependencies/dependency/groupId/text()") for dependency_id in dependency_ids: print(dependency_id)
Výsledek by měl být totožný:
junit foo bar
6. Podmínky použité při výběru uzlů pomocí cesty
Při specifikaci cesty je dokonce možné používat i podmínky, v nichž se například testuje hodnota atributů, hodnota uložená v uzlu atd. Před vysvětlením základních podmínek si ukažme příklad, který budeme postupně upravovat. V tomto příkladu se používá cesta, která získá hodnoty všech uzlů „groupId“ (ty se ovšem mohou v dokumentu nacházet na více místech, což jsme již ostatně mohli vidět):
//groupId/text()
Získání a výpis hodnoty všech uzlů „groupId“ vypadá takto:
import lxml.etree as ET xml = "pom.xml" tree = ET.parse(xml) root = tree.getroot() dependency_ids = tree.xpath("//groupId/text()") for dependency_id in dependency_ids: print(dependency_id)
Výsledkem budou čtyři řádky, protože se tento uzel nachází jak v popisu vlastního projektu, tak i u všech závislostí (dependency):
org.tisnik.uberproject.test junit foo bar
7. Porovnání hodnoty uzlu s textovým řetězcem
Nyní se nalezené uzly pokusíme omezit pouze na ty, v nichž má „groupId“ hodnotu „junit“. Podmínky se zapisují do hranatých závorek, vlastní text uzlu lze zkrátit na tečku a pro porovnávání se používá jen jediný znak = (na to je zapotřebí si dát pozor, protože dvě == povedou k chybě):
import lxml.etree as ET xml = "pom.xml" tree = ET.parse(xml) root = tree.getroot() dependency_ids = tree.xpath('//groupId[.="junit"]/text()') for dependency_id in dependency_ids: print(dependency_id)
Nyní již získáme pouze jediný výsledek:
junit
Podobně můžeme postupovat i v případě, že budeme chtít omezit množinu uzlů pro testování pouze na informace o závislostech:
import lxml.etree as ET xml = "pom.xml" tree = ET.parse(xml) root = tree.getroot() dependency_ids = tree.xpath('/project/dependencies/dependency/groupId[.="junit"]/text()') for dependency_id in dependency_ids: print(dependency_id)
Jen pro jistotu – dostaneme naprosto stejný výsledek:
junit
8. Složitější cesta s relativní částí
V zápisu cesty je možné použít i dvojici teček (..) pro přesun do jiné části stromu. Tato část je umístěna relativně k uzlu, který byl aplikací cesty nalezen. Vlastně se nejedná o nic složitého, protože podobný styl zápisu používáme i pro přístup k souborům umístěným relativně k pwd. V případě, že budeme například chtít nalézt závislost s groupId nastavenou na „junit“ a následně vypsat verzi takto nalezené knihovny, může cesta vypadat takto:
import lxml.etree as ET xml = "pom.xml" tree = ET.parse(xml) root = tree.getroot() dependency_ids = tree.xpath('/project/dependencies/dependency/groupId[.="junit"]/../version/text()') for dependency_id in dependency_ids: print(dependency_id)
Výsledkem bude verze 3.8.1:
3.8.1
Jen pro doplnění si uveďme, že zápis ./ v cestě znamená „tentýž element“ a tudíž nebude mít vliv na to, jaké elementy budou vybrány popř. jak se bude vyhodnocovat podmínka. Ostatně si to můžete vyzkoušet sami na nepatrně upraveném příkladu se zbytečně komplikovaným zápisem cesty:
import lxml.etree as ET xml = "pom.xml" tree = ET.parse(xml) root = tree.getroot() dependency_ids = tree.xpath('/project/./././dependencies/dependency/groupId[.="junit"]/.././././version/text()') for dependency_id in dependency_ids: print(dependency_id)
9. Knihovna Beautiful Soup
Ve druhé části článku se ve stručnosti seznámíme se základními koncepty, na nichž je postavena knihovna nazvaná Beautiful Soup. Název této knihovny je převzatý ze slavné knihy Alenka v kraji divů (div ovšem nemá nic společného s HTML div-y :-). Tato knihovna dokáže v případě potřeby pracovat s nevalidními soubory XML a samozřejmě i s nevalidními HTML stránkami. Právě v tom ostatně spočívá užitečnost knihovny, protože mnoho HTML stránek (možná většinu?) není možné zpracovávat jako validní XML. Ve skutečnosti dokáže tato knihovna při zpracování HTML a XML používat větší množství parserů, které se od sebe liší rychlostí (či pomalostí) a samozřejmě i svými schopnostmi. Jedná se o tyto parsery:
Parser | Stručný popis |
---|---|
„html.parser“ | používá parser dodávaný společně s Pythonem |
„lxml“ | parser HTML převzatý z výše popisované knihovny lxml |
„lxml-xml“ | parser XML převzatý z výše popisované knihovny lxml |
„html5lib“ | určený pro parsing nevalidních HTML stránek |
10. Získání základních informací z HTML stránky
Podívejme se nyní na velmi jednoduchý příklad použití knihovny Beautiful Soup. Pokusíme se v něm získat titulek ze stránky umístěné na adrese „https://www.root.cz“. Vzhledem k tomu, že standardní moduly Pythonu mají problémy s protokolem HTTPS, použijeme pro načtení stránky známou knihovnu requests. Po načtení již můžeme stránku naparsovat, získat první značku title a vypsat její obsah:
import requests from bs4 import BeautifulSoup response = requests.get("https://www.root.cz") if response.status_code != 200: print("Chyba při přístupu na stránku: ", response.status_code) soup = BeautifulSoup(response.text, "html.parser") print(soup.find("title")) print(soup.find("title").text)
V tomto případě jsme použili standardní parser „html.parser“ a pro získání první značky metodu find. Výsledek:
<title>Root.cz - informace nejen ze světa Linuxu</title> Root.cz - informace nejen ze světa Linuxu
11. Zjednodušení předchozího příkladu pomocí tečkového operátoru
Předchozí příklad je samozřejmě možné různými způsoby vylepšit, například použitím tečkového operátoru, který zjednodušuje přístup ke značkám a jejich obsahu. Ostatně se o tom můžete přesvědčit sami, zejména po přečtení posledních dvou příkazů v tomto programu:
import requests from bs4 import BeautifulSoup response = requests.get("https://www.root.cz") if response.status_code != 200: print("Chyba při přístupu na stránku: ", response.status_code) soup = BeautifulSoup(response.text,"html.parser"); print(soup.title) print(soup.title.text)
Výsledek bude stejný, jako tomu bylo v předchozím příkladu:
<title>Root.cz - informace nejen ze světa Linuxu</title> Root.cz - informace nejen ze světa Linuxu
12. Nalezení všech značek <title> a výpis jejich obsahu
Stránka Roota ve skutečnosti obsahuje větší množství značek <title>. Je tomu tak z toho prostého důvodu, že přímo do stránky je vloženo několik souborů typu SVG. Pokud budeme chtít vyhledat všechny tyto značky, je zapotřebí namísto metody BeautifulSoup.find() použít metodu BeautifulSoup.find_all(). Opět se samozřejmě podíváme na demonstrační příklad:
import requests from bs4 import BeautifulSoup response = requests.get("https://www.root.cz") if response.status_code != 200: print("Chyba při přístupu na stránku: ", response.status_code) soup = BeautifulSoup(response.text, "html.parser") for anchor in soup.find_all("title"): print(anchor.text)
S výsledky:
Root.cz - informace nejen ze světa Linuxu Root.cz Root.cz Root.cz Vitalia.cz Vitalia.cz Vitalia.cz Lupa.cz Lupa.cz Podnikatel.cz Podnikatel.cz Podnikatel.cz Vitalia.cz Root.cz Mesec.cz Root.cz Lupa.cz Mesec.cz Vitalia.cz Root.cz Podnikatel.cz Vitalia.cz
13. Výpis všech odkazů ve značkách <a>
Podobným způsobem můžeme zpracovat stránku https://pypi.python.org/simple/ (pozor: je velmi rozsáhlá!) a získat z ní odkazy na všechny balíčky dostupné v repositáři PyPi. Pro zajímavost se pokusíme použít parser „lxml“ pro zpracování HTML stránek dodávaný právě knihovnou lxml:
import requests from bs4 import BeautifulSoup response = requests.get("https://pypi.python.org/simple/") if response.status_code != 200: print("Chyba při přístupu na stránku: ", response.status_code) soup = BeautifulSoup(response.text, "lxml") for a in soup.find_all("a"): print(a.text)
Výsledkem by měl být seznam zhruba 15000 abecedně seřazených balíčků:
0 0-._.-._.-._.-._.-._.-._.-0 0.0.1 00SMALINUX 01changer 021 02exercicio 0805nexter 0-core-client 0FELA 0-orchestrator 0wdg9nbmpm 0wned 0x 0x10c-asm 1 100bot 1020-nester 10daysweb 115wangpan 12factor-vault 131228_pytest_1 1337 153957-theme ... ... ...
14. Složitější příklad – získání klíčových slov přiřazených k balíčkům
Další demonstrační příklad, s nímž se dnes seznámíme, je rozdělen na dvě části. První část již známe – slouží k načtení seznamu balíčku z PyPi:
response = requests.get("https://pypi.python.org/simple/") soup = BeautifulSoup(response.text, "lxml") for a in soup.find_all("a")[:10]: package_name = a.text
Další část příkladu umístěná v programové smyčce se snaží pro každý balíček získat jeho stránku a tu naparsovat:
url = urljoin("https://pypi.python.org/pypi/project", package_name) response = requests.get(url) if response.status_code != 200: print("Chyba při přístupu na stránku: ", response.status_code) package_soup = BeautifulSoup(response.text, "lxml")
Ve třetím a současně i posledním kroku se pokusíme přečíst obsah odstavců („p“) s třídou „class“ nastavenou na hodnotu „tags“. Právě v těchto odstavcích jsou umístěna klíčová slova popisující balíček:
meta_keywords = package_soup.find_all("p", attrs={"class": "tags"}) if len(meta_keywords) < 1: print("Failed to parse and find keywords for '{p}'".format(p=package_name)) continue print(meta_keywords)
Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:
import requests from bs4 import BeautifulSoup from urllib.parse import urljoin response = requests.get("https://pypi.python.org/simple/") if response.status_code != 200: print("Chyba při přístupu na stránku: ", response.status_code) soup = BeautifulSoup(response.text, "lxml") for a in soup.find_all("a")[:10]: package_name = a.text url = urljoin("https://pypi.python.org/pypi/project", package_name) response = requests.get(url) if response.status_code != 200: print("Chyba při přístupu na stránku: ", response.status_code) package_soup = BeautifulSoup(response.text, "lxml") meta_keywords = package_soup.find_all("p", attrs={"class": "tags"}) if len(meta_keywords) < 1: print("Failed to parse and find keywords for '{p}'".format(p=package_name)) continue print(meta_keywords)
Z výsledků je patrné, že u některých balíčků nejsou klíčová slova uvedena:
Failed to parse and find keywords for '0' Failed to parse and find keywords for '0-._.-._.-._.-._.-._.-._.-0' [<p class="tags"> <i aria-hidden="true" class="fa fa-tags"></i> <span class="sr-only">Tags:</span> <span class="package-keyword"> tensorflow, </span> <span class="package-keyword"> tfrecord </span> </p>, <p class="tags"> <i aria-hidden="true" class="fa fa-tags"></i> <span class="sr-only">Tags:</span> <span class="package-keyword"> tensorflow, </span> <span class="package-keyword"> tfrecord </span> </p>] Failed to parse and find keywords for '00SMALINUX' Failed to parse and find keywords for '01changer' Failed to parse and find keywords for '021' Failed to parse and find keywords for '02exercicio' Failed to parse and find keywords for '0805nexter' Failed to parse and find keywords for '0-core-client' Failed to parse and find keywords for '0FELA' Failed to parse and find keywords for '0-orchestrator' Failed to parse and find keywords for '0wdg9nbmpm' Failed to parse and find keywords for '0wned' Failed to parse and find keywords for '0x' [<p class="tags"> <i aria-hidden="true" class="fa fa-tags"></i> <span class="sr-only">Tags:</span> <span class="package-keyword"> notch, </span> <span class="package-keyword"> asm, </span> <span class="package-keyword"> dcpu-16, </span> <span class="package-keyword"> dcpu, </span> <span class="package-keyword"> assembly, </span> <span class="package-keyword"> asm </span> </p>, <p class="tags"> <i aria-hidden="true" class="fa fa-tags"></i> <span class="sr-only">Tags:</span> <span class="package-keyword"> notch, </span> <span class="package-keyword"> asm, </span> <span class="package-keyword"> dcpu-16, </span> <span class="package-keyword"> dcpu, </span> <span class="package-keyword"> assembly, </span> <span class="package-keyword"> asm </span> </p>] Failed to parse and find keywords for '1'
15. Další zpracování klíčových slov přiřazených k balíčkům
Ve skutečnosti je možné informace s klíčovými slovy dále zpracovat, protože z předchozího výpisu bylo patrné, že se uvnitř odstavců s třídou „tags“ nachází jednotlivá klíčová slova v samostatných značkách <span> s třídou pojmenovanou „package-keyword“. Těchto značek může být pro jeden balíček několik, takže celé zpracování klíčových slov může vypadat takto:
print("Keywords for package '{p}'".format(p=package_name)) keywords_spans = meta_keywords[0].find_all("span", attrs={"class": "package-keyword"}) for span in keywords_spans: for word in span.contents: print([k.strip().lower() for k in word.split(",") if k.strip() != ""])
Opět si ukažme úplný zdrojový kód tohoto příkladu:
import requests from bs4 import BeautifulSoup from urllib.parse import urljoin response = requests.get("https://pypi.python.org/simple/") if response.status_code != 200: print("Chyba při přístupu na stránku: ", response.status_code) soup = BeautifulSoup(response.text, "lxml") for a in soup.find_all("a")[:20]: package_name = a.text url = urljoin("https://pypi.python.org/pypi/project", package_name) response = requests.get(url) if response.status_code != 200: print("Chyba při přístupu na stránku: ", response.status_code) package_soup = BeautifulSoup(response.text, "lxml") meta_keywords = package_soup.find_all("p", attrs={"class": "tags"}) if len(meta_keywords) < 1: print("Failed to parse and find keywords for '{p}'".format(p=package_name)) continue print("Keywords for package '{p}'".format(p=package_name)) keywords_spans = meta_keywords[0].find_all("span", attrs={"class": "package-keyword"}) for span in keywords_spans: for word in span.contents: print([k.strip().lower() for k in word.split(",") if k.strip() != ""])
Výsledky by pro několik prvních balíčků měly vypadat takto:
Failed to parse and find keywords for '0' Failed to parse and find keywords for '0-._.-._.-._.-._.-._.-._.-0' Keywords for package '0.0.1' ['tensorflow'] ['tfrecord'] Failed to parse and find keywords for '00SMALINUX' Failed to parse and find keywords for '01changer' Failed to parse and find keywords for '021' Failed to parse and find keywords for '02exercicio' Failed to parse and find keywords for '0805nexter' Failed to parse and find keywords for '0-core-client' Failed to parse and find keywords for '0FELA' Failed to parse and find keywords for '0-orchestrator' Failed to parse and find keywords for '0wdg9nbmpm' Failed to parse and find keywords for '0wned' Failed to parse and find keywords for '0x' Keywords for package '0x10c-asm' ['notch'] ['asm'] ['dcpu-16'] ['dcpu'] ['assembly'] ['asm'] Failed to parse and find keywords for '1' Failed to parse and find keywords for '100bot' Failed to parse and find keywords for '1020-nester' Keywords for package '10daysweb' ['web'] ['framework'] ['async'] Keywords for package '115wangpan' ['115'] ['wangpan'] ['pan'] ['cloud'] ['lixian'] Failed to parse and find keywords for '12factor-vault' Failed to parse and find keywords for '131228_pytest_1' Failed to parse and find keywords for '1337' Keywords for package '153957-theme' ['photo album'] ['theme'] ['sigal'] ['galleria']
16. Získání tučného textu z HTML stránky
Vraťme se nyní opět ke stránkám Rootu. Další příklad (resp. jeho část) slouží pro zobrazení všech textů umístěných do značky <strong>. Tuto značku často používám pro zvýraznění textu, takže si vyzkoušejme, kolikrát je použita v minulém článku Základy použití režimu org-mode v Emacsu:
response = requests.get("https://www.root.cz/clanky/zaklady-pouziti-rezimu-org-mode-v-emacsu/") if response.status_code != 200: print("Chyba při přístupu na stránku: ", response.status_code) soup = BeautifulSoup(response.text, "html.parser") for p in soup.find_all("strong"): print(p.text)
Výsledky budou vypadat následovně:
Hlavní navigace Základy použití režimu org-mode v Emacsu 17 minut org-mode vim-orgmode org-mode Install rsync TAB TAB TABu C-c - C-c * M-RETURN C-c | C-c - M-šipka nahoru M-šipka dolů C-SPACE C-c ^ C-c C-c C-c C-c C-c C-c C-c C-x C-b C-c # C-c # TAB TAB TAB TAB C-c * C-u C-c * A1 @2$3 @2$+1 @2$-1 $< $> $3 @2 C-c } C-c } M-šipka M-šipka doprava M-šipka dolů M-šipka dolů C-c C-l vim-orgmode org-mode org-mode Root.cz
17. Přečtení obsahu tabulek z HTML stránky
Druhá část příkladu je již mnohem zajímavější, protože se v něm pokusíme zpravovat obsah všech nalezených tabulek. Samotné získání všech tabulek je snadné:
soup = BeautifulSoup(response.text, "html.parser") for table in soup.find_all("table"): ... ... ...
Pro každou tabulku ve vnitřní smyčce získáme všechny řádky představované značkou <tr>:
for tr in table.find_all("tr"): ... ... ...
A konečně přichází nejzajímavější část celého příkladu – získání všech značek <th> a současně <td>. Povšimněte si, že metodě find_all() můžeme předat seznam značek:
for item in tr.find_all(["th", "td"]): print(item.text + "\t|\t", end="")
Celé zpracování tabulek je záležitost několika programových řádků:
response = requests.get("https://www.root.cz/clanky/zaklady-pouziti-rezimu-org-mode-v-emacsu/") if response.status_code != 200: print("Chyba při přístupu na stránku: ", response.status_code) soup = BeautifulSoup(response.text, "html.parser") for table in soup.find_all("table"): for tr in table.find_all("tr"): for item in tr.find_all(["th", "td"]): print(item.text + "\t|\t", end="") print() print("-------------------")
Výsledek v případě, že jsme se příliš nesnažili o formátování (pouze používáme znak \t):
Tables Klávesa | Význam klávesy (příkazu) | TAB | změna viditelnosti konkrétního vybraného podstromu (postupně se rotuje mezi různými úrovněmi) | S-TAB | změna viditelnosti obsahu celého bufferu (dokumentu) | C-u C-u TAB | výchozí nastavení viditelnosti | C-u C-u C-u TAB | zobrazení obsahu celého souboru, tj. celé jeho struktury | ------------------- Klávesa | Význam | M-RET | přidání dalšího prvku do (ne)číslovaného seznamu | | | C-c – | postupná změna typu prvku (číslovaný seznam atd.) | C-c ^ | seřazení seznamu | C-c * | převedení aktivního prvku na nadpis | C-c | | převod seznamu (i jiného bloku) na tabulku | ------------------- Klávesa | Význam | M-šipka nahoru | přesun prvku v rámci seznamu nahoru | M-šipka dolů | přesun prvku v rámci seznamu dolů | ------------------- Objekt | Klávesová zkratka | Význam klávesové zkratky | strom/podstrom | TAB | změna viditelnosti konkrétního vybraného podstromu (postupně se rotuje mezi různými úrovněmi) | strom/podstrom | S-TAB | změna viditelnosti obsahu celého bufferu (dokumentu) | celý dokument | C-u C-u TAB | výchozí nastavení viditelnosti | celý dokument | C-u C-u C-u TAB | zobrazení obsahu celého souboru, tj. celé jeho struktury | seznam | M-RET | přidání dalšího prvku do (ne)číslovaného seznamu | seznam | C-c – | postupná změna typu prvku (číslovaný seznam atd.) | seznam | C-c ^ | seřazení seznamu | seznam | C-c * | převedení aktivního prvku na nadpis | seznam | C-c | | převod seznamu (i jiného bloku) na tabulku | seznam | M-šipka nahoru | přesun prvku v rámci seznamu nahoru | seznam | M-šipka dolů | přesun prvku v rámci seznamu dolů | checkbox | C-c C-c | zaškrtnutí/zrušení zaškrtnutí políčka | checkbox | C-c C-x C-b | zaškrtnutí/zrušení více políček | checkboxy | C-c # | výpočet % nebo zlomku dokončených úkolů | tabulka | C-c * | přepočet jednoho řádku | tabulka | C-u C-c * | přepočet celé tabulky | tabulka | C-c } | zobrazení indexů řádků i sloupců | tabulka | M-šipka doleva | prohození dvou sloupců | tabulka | M-šipka doprava | prohození dvou sloupců | tabulka | M-šipka nahoru | prohození dvou řádků | tabulka | M-šipka dolů | prohození dvou řádků | dokument | C-c C-l | vložení odkazu (linku) | -------------------
18. Úplný zdrojový kód předchozího demonstračního příkladu
Úplný zdrojový kód demonstračního příkladu, který načte jeden článek z Roota a vypíše všechny tučné texty i obsah všech tabulek, vypadá takto:
import requests from bs4 import BeautifulSoup from urllib.parse import urljoin response = requests.get("https://www.root.cz/clanky/zaklady-pouziti-rezimu-org-mode-v-emacsu/") if response.status_code != 200: print("Chyba při přístupu na stránku: ", response.status_code) soup = BeautifulSoup(response.text, "html.parser") for p in soup.find_all("strong"): print(p.text) print("\n\n\nTables") for table in soup.find_all("table"): for tr in table.find_all("tr"): for item in tr.find_all(["th", "td"]): print(item.text + "\t|\t", end="") print() print("-------------------")
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 - Knihovna 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/ - Web scraping
https://en.wikipedia.org/wiki/Web_scraping - Introduction to the POM
https://maven.apache.org/guides/introduction/introduction-to-the-pom.html - Super POM
https://maven.apache.org/guides/introduction/introduction-to-the-pom.html#Super_POM - Maven – POM
https://www.tutorialspoint.com/maven/maven_pom.htm - XPath examples
https://www.w3schools.com/xml/xpath_examples.asp - XPath Axes
https://www.w3schools.com/xml/xpath_axes.asp - Guide to naming conventions on groupId, artifactId, and version
http://maven.apache.org/guides/mini/guide-naming-conventions.html - What is meaning of .// in XPath?
https://stackoverflow.com/questions/31375091/what-is-meaning-of-in-xpath - Using „//“ And „.//“ Expressions In XPath XML
https://www.bennadel.com/blog/2142-using-and-expressions-in-xpath-xml-search-directives-in-coldfusion.htm - Using parent dot notation in xpath to find another branch in the XML tree
https://stackoverflow.com/questions/5370544/using-parent-dot-notation-in-xpath-to-find-another-branch-in-the-xml-tree#5370817