Menu Zamknij

Web scraping i MySQL część 4 – wybieranie elementów stron po znacznikach HTML, typ danych lista i instrukcja iteracyjna (pętla) for.

Jak na razie potrafisz za pomocą Pythona odczytać całą stronę. Tylko co z taką całą stroną masz zrobić? Przepisać to co Cię interesuje? A co jeśli potrzebujesz zebrać dane z 1 000 stron? Warto się nauczyć wybierać ze strony tylko interesujące Cię elementy, oddzielać ziarno od plew. Zatrzymajmy się jeszcze przy stronie z poprzedniej części.

.find() – wybieranie elementów stron po znacznikach HTML

Powiedzmy, że interesują cię nagłówki najwyższego rzędu – h1. Żeby taki nagłówek znaleźć w całej treści strony możesz użyć metody .find(), argumentem funkcji będzie poszukiwany znacznik HTML. Popatrz:

from urllib.request import urlopen
from bs4 import BeautifulSoup

strona = "https://inferiordatascience.com/przyklad.html"
otwarta_strona = urlopen(strona)
html = BeautifulSoup(otwarta_strona.read(), "html.parser")
naglowek = html.find("h1")

print(naglowek)

W 7 linijce, na uprzednio otwartej i odczytanej stronie, zapisanej pod zmienna html. używasz metody .find(), której argumentem jest „h1”, całość podstawiamy pod zmienną naglowek. W 9 linijce zmienna naglowek jest drukowana. Zobacz efekt:

==================== RESTART: F:\python38\Web scraping 03.py ===================
<h1 align="center"><font face="Verdana, Arial, Helvetica, sans-serif"><b>This 
        Is The Very Last Page On The Internet</b></font></h1>
>>>

Dobrze, mamy nagłówek. Niestety nie tylko jego treść i tylko pierwszy. Poprawmy nieco program.

from urllib.request import urlopen
from bs4 import BeautifulSoup

strona = "https://inferiordatascience.com/przyklad.html"
otwarta_strona = urlopen(strona)
html = BeautifulSoup(otwarta_strona.read(), "html.parser")
naglowek = html.find("h1").get_text()

print(naglowek)

W 7 linijce dopisaliśmy drugą metodę .get_text(). To przyjemna właściwość programowania w Pythonie, możesz wykonać kilka operacji w jednej linijce i całość podstawić pod zmienną. Metoda .get_text() pozwala na wydobycie samego tekstu. W naszym przypadku powinna wydobyć wszystko co jest między znacznikami > <. Sprawdźmy działanie:

==================== RESTART: F:/python38/Web scraping 04.py ===================
This 
        Is The Very Last Page On The Internet
>>> 

Super! Pierwsza rzecz gotowa. Teraz trzeba znaleźć sposób na wydrukowanie wszystkich nagłówków h1 ze strony. Do tego użyjesz metody .findAll(). Zamieńmy metodę .find() na .findAll():

from urllib.request import urlopen
from bs4 import BeautifulSoup

strona = "https://inferiordatascience.com/przyklad.html"
otwarta_strona = urlopen(strona)
html = BeautifulSoup(otwarta_strona.read(), "html.parser")
naglowek = html.findAll("h1").get_text()

print(naglowek)

Zobaczmy efekt działania programu:

================ RESTART: C:/python38/Scripts/Web scraping 04.py ===============
Traceback (most recent call last):
  File "C:/python38/Scripts/Web scraping 04.py", line 7, in <module>
    naglowek = html.findAll("h1").get_text()
  File "C:\python38\lib\site-packages\bs4\element.py", line 2160, in __getattr__
    raise AttributeError(
AttributeError: ResultSet object has no attribute 'get_text'. You're probably treating a list of elements like a single element. Did you call find_all() when you meant to call find()?
>>> 

Zamiast wyniku otrzymałeś błąd: AttributeError: ResultSet object has no attribute 'get_text’. Błąd ten oznacza, że nie można użyć metody .get_text() z funkcją findAll(), co z resztą jest napisane w kolejnych linijkach błędu. Podejrzymy jak wygląda zawartość samego findAll()?:

from urllib.request import urlopen
from bs4 import BeautifulSoup

strona = "https://inferiordatascience.com/przyklad.html"
otwarta_strona = urlopen(strona)
html = BeautifulSoup(otwarta_strona.read(), "html.parser")
naglowek = html.findAll("h1")

print(naglowek)

W 7 linijce pozbywasz się metody .get_text(). Zobaczmy co z tego wyniknie:

[<h1 align="center"><font face="Verdana, Arial, Helvetica, sans-serif"><b>This 
        Is The Very Last Page On The Internet</b></font></h1>, <h1 align="center"> </h1>, <h1 align="center"><b><font color="#666666" face="Verdana, Arial, Helvetica, sans-serif">Please 
        turn off your computer!!!</font></b></h1>, <h1 align="center"> </h1>, <h1 align="center"><font color="#666666"><b><font face="Verdana, Arial, Helvetica, sans-serif">Go 
        outside and play!!!</font></b></font></h1>, <h1 align="center"> </h1>, <h1 align="center"><b><font color="#FF0000" face="Verdana, Arial, Helvetica, sans-serif">The 
        End.</font><font color="#666666" face="Verdana, Arial, Helvetica, sans-serif">
</font></b></h1>]
>>> 

Przyjrzyj się efektowi, zauważ kilka rzeczy:

  • Wszystkie nagłówki h1, wraz z tagami są wypisane
  • Nagłówki są rozdzielone przecinkami
  • Cała zawartość zmiennej naglowek znajduje się w kwadratowych nawiasach []

By poprawić czytelność rozbiję na osobne linijki każdy nagłówek:

[<h1 align="center"><font face="Verdana, Arial, Helvetica, sans-serif"><b>This 
        Is The Very Last Page On The Internet</b></font></h1>,
<h1 align="center"> </h1>, <h1 align="center"><b><font color="#666666" face="Verdana, Arial, Helvetica, sans-serif">Please 
        turn off your computer!!!</font></b></h1>,
<h1 align="center"> </h1>,
<h1 align="center"><font color="#666666"><b><font face="Verdana, Arial, Helvetica, sans-serif">Go 
        outside and play!!!</font></b></font></h1>,
<h1 align="center"> </h1>,
<h1 align="center"><b><font color="#FF0000" face="Verdana, Arial, Helvetica, sans-serif">The 
        End.</font><font color="#666666" face="Verdana, Arial, Helvetica, sans-serif">
</font></b></h1>]

Typ danych lista

Poznałeś już kilka typów danych (np. string, integer). Dodajmy do tego kolejny typ danych – listę. Listę zapisujesz pomiędzy kwadratowymi nawiasami [], a elementy listy rozdzielasz przecinkami, czy przypomina ci to już analizowany efekt działania .findAll()? Poznajmy ten typ danych trochę dokładniej:

Jedna lista może zawierać dane typu tekstowego, liczbowego itd., dlatego nie zapomnij o cudzysłowie gdy podajesz dane tekstowe. Przykładem listy może być spis seriali do obejrzenia:
["Wataha", "Wiedźmin", "Narcos"]. Napiszmy program, w którym zadeklarujemy zmienną o nazwie seriale z wartościami z poprzedniego zdania. Następnie sprawdźmy działania funkcji print(), type() i len() na naszej liście:

seriale = ['Wataha', 'Wiedźmin', 'Narcos']

print(seriale)
print(type(seriale))
print(len(seriale))
===================== RESTART: F:/python38/zmienna lista.py ====================
['Wataha', 'Wiedźmin', 'Narcos']
<class 'list'>
3
>>> 

Funkcja print() wydrukowała wszystkie elementy listy zaznaczając pojedynczym cudzysłowem, że każdy jest tekstem:
['Wataha', 'Wiedźmin', 'Narcos']
funkcja type() potwierdziła, że mamy do czynienia z listą:
<class 'list'>
natomiast funkcja len() nie podała długości znaków, ale liczbę elementów listy. Trzy wartości oddzielone przecinkiem, trzy seriale, trzy elementy:
3

Instrukcja iteracyjna (pętla) for

Wiesz już jak wyświetlić listę jako całość. Poznasz teraz trzy sposoby wyświetlania elementów listy, jeden będzie bardzo zły, drugi trochę zły a trzeci dobry.

bardzo zły sposób

Lista to uporządkowane dane. Na liście każdy element listy ma swoje określone miejsce. Jeśli chcesz wyświetlić tylko pierwszy element listy, musisz podać jego indeks w kwadratowych nawiasach [], indeks jest liczbą i podajesz go zaraz za listą, lub zmienną zawierającą listę bez żadnych dodatkowych znaków. Popatrz na przykład:

seriale = ['Wataha', 'Wiedźmin', 'Narcos']

print(seriale[1])

Mamy zdefiniowaną listę 3 elementów. W 3 linijce drukujemy element listy znajdujący się pod indeksem nr 1. Zobaczmy wynik:

==================== RESTART: F:\python38\Web scraping 04.py ===================
Wiedźmin
>>> 

Zupełnie nie to, czego się spodziewałeś. Python zaczyna numerację od zera. Pierwszy element ma indeks 0, drugi element ma indeks 1 itd. Poprawmy więc kod programu, dodajmy jeszcze wydrukowanie typu danych.

print(seriale[0])
print(type(seriale[0]))

I zobaczmy efekt:

==================== RESTART: F:\python38\Web scraping 04.py ===================
Wataha
<class 'str'>
>>> 

Teraz mamy wydrukowany pierwszy element listy, na liście umieściliśmy stringi i mamy potwierdzenie, że pierwszym elementem listy jest string.
Wróćmy do naszej listy nagłówków h1 i wydrukujmy je w najgorszy możliwy sposób.

from urllib.request import urlopen
from bs4 import BeautifulSoup

strona = "https://inferiordatascience.com/przyklad.html"
otwarta_strona = urlopen(strona)
html = BeautifulSoup(otwarta_strona.read(), "html.parser")
naglowek = html.findAll("h1")

print(len(naglowek))
print(naglowek[0])
print(naglowek[1])
print(naglowek[2])
print(naglowek[3])
print(naglowek[4])
print(naglowek[5])
print(naglowek[6])

W 9 linijce sprawdzamy długość listy, potem w linijkach 10-16 drukujemy poszczególne nagłówki. Zobaczmy efekt:

==================== RESTART: F:\python38\Web scraping 04.py ===================
7
<h1 align="center"><font face="Verdana, Arial, Helvetica, sans-serif"><b>This 
        Is The Very Last Page On The Internet</b></font></h1>
<h1 align="center"> </h1>
<h1 align="center"><b><font color="#666666" face="Verdana, Arial, Helvetica, sans-serif">Please 
        turn off your computer!!!</font></b></h1>
<h1 align="center"> </h1>
<h1 align="center"><font color="#666666"><b><font face="Verdana, Arial, Helvetica, sans-serif">Go 
        outside and play!!!</font></b></font></h1>
<h1 align="center"> </h1>
<h1 align="center"><b><font color="#FF0000" face="Verdana, Arial, Helvetica, sans-serif">The 
        End.</font><font color="#666666" face="Verdana, Arial, Helvetica, sans-serif">
</font></b></h1>
>>> 

Jeśli chcesz zapoznać się z dokładniejszym spojrzeniem na listy zapraszam do innego kursu i części poświęconej listom: Porównaj styl pisarzy część 6 – Indeksy dla typu danych lista. Oczywiście teraz na poszczególnych elemencie listy możemy użyć metody .get_text():

from urllib.request import urlopen
from bs4 import BeautifulSoup

strona = "https://inferiordatascience.com/przyklad.html"
otwarta_strona = urlopen(strona)
html = BeautifulSoup(otwarta_strona.read(), "html.parser")
naglowek = html.findAll("h1")

print(len(naglowek))

print(naglowek[0].get_text())
print(naglowek[1].get_text())
print(naglowek[2].get_text())
print(naglowek[3].get_text())
print(naglowek[4].get_text())
print(naglowek[5].get_text())
print(naglowek[6].get_text())

Efekt będzie taki:

==================== RESTART: F:\python38\Web scraping 04.py ===================
7
This 
        Is The Very Last Page On The Internet
 
Please 
        turn off your computer!!!
 
Go 
        outside and play!!!
 
The 
        End.

>>> 

Z 7 nagłówków wydrukowały się 4, pozostałe nie zawierały tekstu, co możesz sprawdzić w źródle strony, przechowywanym pod zmienną html. Dlaczego jest to bardzo zły sposób? Pomyśl, co jeśli strona, którą chcesz automatycznie odczytać, ma 100 nagłówków? A co jeśli pobierasz dane z wielu stron i na każdej liczba nagłówków jest inna? Programowanie to w dużej mierze sztuka ułatwiania sobie życia, zazwyczaj nie musisz pisać podobnych linijek kodu. My musieliśmy napisać print() 7 razy! Poznasz teraz sposób jak napisać polecenie print() tylko jeden raz i osiągnąć ten sam efekt!

trochę zły sposób

Tutaj użyjemy pętli for. Jeśli spojrzysz raz jeszcze na 7 linijek print(), to zauważysz, że różnią się jedynie indeksem, czyli liczbą w kwadratowym nawiasie. Użyjesz pętli for, by wygenerować cyfry od 0 do 6. Ta pętla pozwala na powtarzanie polecenia określoną ilość razy, bądź na każdym kolejnym elemencie zmiennej. Zajmijmy się pierwszym przypadkiem. Popatrz na przykład:

for indeks in range(7):
    print(indeks)

Tu spotykasz się z pętlą po raz pierwszy, zobacz jakie znaczenia mają poszczególne słowa

  1. for indeks in range(7): tu zaczyna się nasza pętla. for indeks in mówi nam dla indeksu w. range() informuje o zakresie w jakim porusza się nasza pętla, argumentem range() zawsze będzie liczba całkowita, lub liczby całkowite. range(7) oznacza w zakresie do 7. Każda pętla po określeniu czego dotyczy ma dwukropek :, wszystko co pojawia się jako polecenie dla zdefiniowanej pętli jest po dwukropku, w nowej linijce kodu i musi być wcięte, do wcięcia najlepiej użyć klawisza Tab (znajdziesz go koło litery Q na klawiaturze). IDLE po wciśnięciu enter po dwukropku sam doda potrzebne wcięcie. Całość 1 linijki można przeczytać dla indeksu w zakresie 0-6 (pamiętaj, Python zaczyna liczenie od zera, 0, 1, 2, 3, 4, 5, 6, to 7 cyfr).
  2. print(indeks) – wydrukuj indeks.

Indeks jest po prostu zmienną, która podstawiana jest w funkcji print(), możesz nazwać ją dowolnie, ale dobrze użyć nazwy, której cel łatwo zrozumieć. Zobaczmy efekt:

==================== RESTART: F:\python38\Web scraping 04.py ===================
0
1
2
3
4
5
6
>>> 

No i fajnie! Zamieńmy teraz bardzo zły sposób na trochę zły sposób:

from urllib.request import urlopen
from bs4 import BeautifulSoup

strona = "https://inferiordatascience.com/przyklad.html"
otwarta_strona = urlopen(strona)
html = BeautifulSoup(otwarta_strona.read(), "html.parser")
naglowek = html.findAll("h1")

for indeks in range(7):
    print(naglowek[indeks].get_text())

W 9 linijce zaczynamy pętlę for, która pod zmienną indeks podstawia wartości od 0 do 6 a następnie dla każdego takiego podstawienia drukujemy tekst nagłówka h1. Efekt:

==================== RESTART: F:\python38\Web scraping 04.py ===================
This 
        Is The Very Last Page On The Internet
 
Please 
        turn off your computer!!!
 
Go 
        outside and play!!!
 
The 
        End.

>>> 

No i niby wszystko fantastycznie, ale rozwiązanie nie jest optymalne. Popatrz, za każdym razem musisz sprawdzać ile elementów jest na liście i za każdym razem generujesz jakąś liczbę do podstawiania. Oczywiście możesz trochę uładnić kod i zamiast na sztywno podawać długość listy, to tą długość podać z wykorzystaniem funkcji len(). Skoro to potrafimy, to zróbmy to:

from urllib.request import urlopen
from bs4 import BeautifulSoup

strona = "https://inferiordatascience.com/przyklad.html"
otwarta_strona = urlopen(strona)
html = BeautifulSoup(otwarta_strona.read(), "html.parser")
naglowek = html.findAll("h1")

for indeks in range(len(naglowek)):
    print(naglowek[indeks].get_text())

Jedyna zmiana została wprowadzona w 9 linijce, zamiast wpisać w argument funkcji range() cyfrę 7 wpisaliśmy tam inną funkcję len() zwracającą liczbowo długość listy z nagłówkami. Sprawdźmy efekt:

==================== RESTART: F:\python38\Web scraping 04.py ===================
This 
        Is The Very Last Page On The Internet
 
Please 
        turn off your computer!!!
 
Go 
        outside and play!!!
 
The 
        End.

>>> 
Dobry sposób

Przy opisie pętli for napisałem, że ta pętla pozwala na powtarzanie polecenia określoną liczbę razy, bądź na każdym kolejnym elemencie zmiennej. Przyszedł czas na ten drugi przypadek. Zacznijmy od przykładu z serialami:

seriale = ['Wataha', 'Wiedźmin', 'Narcos']

for tytul in seriale:
    print(tytul)

W 3 linijce mamy zmianę, nie piszemy nigdzie in range(). Tym razem pętla for będzie podstawiać pod zmienną tytul kolejne elementy zmiennej seriale, czyli kolejne elementy listy. Linijkę 3 możemy przeczytać tak: dla każdego elementu listy seriale. w 4 linijce każdy z tych elementów drukujemy. Zobacz efekt:

==================== RESTART: F:\python38\Web scraping 04.py ===================
Wataha
Wiedźmin
Narcos
>>> 

Działa! Przeniesienie tego pomysłu na nasze nagłówki to już prościzna:

from urllib.request import urlopen
from bs4 import BeautifulSoup

strona = "https://inferiordatascience.com/przyklad.html"
otwarta_strona = urlopen(strona)
html = BeautifulSoup(otwarta_strona.read(), "html.parser")
naglowki = html.findAll("h1")

for h1 in naglowki:
    print(h1.get_text())

W 7 linijce poprawiłem nazwę zmiennej, by lepiej oddawała rzeczywistość. W linijkach 9-10 używamy pętli, tutaj też nazwa zmiennej została poprawiona. Zobaczmy efekt:

==================== RESTART: F:\python38\Web scraping 04.py ===================
This 
        Is The Very Last Page On The Internet
 
Please 
        turn off your computer!!!
 
Go 
        outside and play!!!
 
The 
        End.

>>> 

I to wszystko w tej części. Poznałeś sporo nowych pojęć i metod praktycznego wykorzystania znajomości znaczników HTML, list i pętli.

Zadanie domowe

Podążanie za instrukcjami, nawet jeśli je wszystkie wykonujesz samodzielnie, nie zrobi z ciebie programisty. Zadania domowe mogą wydawać się na początku trudne. Tu nie ma rozwiązania podanego na talerzu, użyj dowolnych źródeł, by znaleźć odpowiedź.

  1. Jaką zmianę musisz wprowadzić w argumentach metody .findAll(), by wydrukować tytuł strony (tag <title>) i wszystkie nagłówki h1?
  2. Znajdź sposób, by ze strony https://pl.wikipedia.org/wiki/Python wydrukować treść paragrafów (<p>) od piątego do dziesiątego.
0 0 votes
Ocena artykułu

0 komentarzy
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
0
Chcesz podzielić się komentarzem?x