Piękna zupa - nawigacja za pomocą tagów

W tym rozdziale omówimy nawigację za pomocą tagów.

Poniżej znajduje się nasz dokument HTML -

>>> html_doc = """
<html><head><title>Tutorials Point</title></head>
<body>
<p class="title"><b>The Biggest Online Tutorials Library, It's all Free</b></p>
<p class="prog">Top 5 most used Programming Languages are:
<a href="https://www.tutorialspoint.com/java/java_overview.htm" class="prog" id="link1">Java</a>,
<a href="https://www.tutorialspoint.com/cprogramming/index.htm" class="prog" id="link2">C</a>,
<a href="https://www.tutorialspoint.com/python/index.htm" class="prog" id="link3">Python</a>,
<a href="https://www.tutorialspoint.com/javascript/javascript_overview.htm" class="prog" id="link4">JavaScript</a> and
<a href="https://www.tutorialspoint.com/ruby/index.htm" class="prog" id="link5">C</a>;
as per online survey.</p>
<p class="prog">Programming Languages</p>
"""
>>>
>>> from bs4 import BeautifulSoup
>>> soup = BeautifulSoup(html_doc, 'html.parser')
>>>

Na podstawie powyższego dokumentu spróbujemy przejść z jednej części dokumentu do drugiej.

Zejście w dół

Jednym z ważnych elementów każdego fragmentu dokumentu HTML są tagi, które mogą zawierać inne tagi / ciągi znaków (dzieci tagu). Beautiful Soup zapewnia różne sposoby nawigacji i iteracji po elementach potomnych tagu.

Nawigacja przy użyciu nazw tagów

Najłatwiejszym sposobem przeszukania drzewa parsowania jest wyszukanie znacznika według jego nazwy. Jeśli chcesz mieć tag <head>, użyj soup.head -

>>> soup.head
<head>&t;title>Tutorials Point</title></head>
>>> soup.title
<title>Tutorials Point</title>

Aby uzyskać określony tag (taki jak pierwszy tag <b>) w tagu <body>.

>>> soup.body.b
<b>The Biggest Online Tutorials Library, It's all Free</b>

Użycie nazwy tagu jako atrybutu da ci tylko pierwszy tag o tej nazwie -

>>> soup.a
<a class="prog" href="https://www.tutorialspoint.com/java/java_overview.htm" id="link1">Java</a>

Aby uzyskać wszystkie atrybuty tagu, możesz użyć metody find_all () -

>>> soup.find_all("a")
[<a class="prog" href="https://www.tutorialspoint.com/java/java_overview.htm" id="link1">Java</a>, <a class="prog" href="https://www.tutorialspoint.com/cprogramming/index.htm" id="link2">C</a>, <a class="prog" href="https://www.tutorialspoint.com/python/index.htm" id="link3">Python</a>, <a class="prog" href="https://www.tutorialspoint.com/javascript/javascript_overview.htm" id="link4">JavaScript</a>, <a class="prog" href="https://www.tutorialspoint.com/ruby/index.htm" id="link5">C</a>]>>> soup.find_all("a")
[<a class="prog" href="https://www.tutorialspoint.com/java/java_overview.htm" id="link1">Java</a>, <a class="prog" href="https://www.tutorialspoint.com/cprogramming/index.htm" id="link2">C</a>, <a class="prog" href="https://www.tutorialspoint.com/python/index.htm" id="link3">Python</a>, <a class="prog" href="https://www.tutorialspoint.com/javascript/javascript_overview.htm" id="link4">JavaScript</a>, <a class="prog" href="https://www.tutorialspoint.com/ruby/index.htm" id="link5">C</a>]

.contents i .children

Możemy wyszukiwać dzieci tagu na liście według jego .contents -

>>> head_tag = soup.head
>>> head_tag
<head><title>Tutorials Point</title></head>
>>> Htag = soup.head
>>> Htag
<head><title>Tutorials Point</title></head>
>>>
>>> Htag.contents
[<title>Tutorials Point</title>
>>>
>>> Ttag = head_tag.contents[0]
>>> Ttag
<title>Tutorials Point</title>
>>> Ttag.contents
['Tutorials Point']

Sam obiekt BeautifulSoup ma dzieci. W tym przypadku tag <html> jest elementem podrzędnym obiektu BeautifulSoup -

>>> len(soup.contents)
2
>>> soup.contents[1].name
'html'

Ciąg nie zawiera .contents, ponieważ nie może niczego zawierać -

>>> text = Ttag.contents[0]
>>> text.contents
self.__class__.__name__, attr))
AttributeError: 'NavigableString' object has no attribute 'contents'

Zamiast pobierać je jako listę, użyj generatora .children, aby uzyskać dostęp do dzieci tagu -

>>> for child in Ttag.children:
print(child)
Tutorials Point

.potomków

Atrybut .descendants umożliwia iterację po wszystkich elementach podrzędnych tagu, rekurencyjnie -

jego bezpośrednie dzieci i dzieci jego bezpośrednich dzieci i tak dalej -

>>> for child in Htag.descendants:
print(child)
<title>Tutorials Point</title>
Tutorials Point

Tag <head> ma tylko jedno dziecko, ale ma dwóch potomków: tag <title> i tag podrzędny tagu <title>. Obiekt beautifulsoup ma tylko jedno bezpośrednie dziecko (tag <html>), ale ma wiele potomków -

>>> len(list(soup.children))
2
>>> len(list(soup.descendants))
33

.strunowy

Jeśli tag ma tylko jedno dziecko, a to dziecko jest NavigableString, element podrzędny jest udostępniany jako .string -

>>> Ttag.string
'Tutorials Point'

Jeśli jedynym elementem podrzędnym tagu jest inny tag, a ten tag ma .string, to uznaje się, że tag nadrzędny ma taki sam .string, jak jego element podrzędny -

>>> Htag.contents
[<title>Tutorials Point</title>]
>>>
>>> Htag.string
'Tutorials Point'

Jeśli jednak tag zawiera więcej niż jedną rzecz, nie jest jasne, do czego ma się odnosić .string, więc .string jest zdefiniowane jako None -

>>> print(soup.html.string)
None

.strings i stripped_strings

Jeśli w tagu jest więcej niż jedna rzecz, nadal możesz patrzeć tylko na ciągi. Użyj generatora .strings -

>>> for string in soup.strings:
print(repr(string))
'\n'
'Tutorials Point'
'\n'
'\n'
"The Biggest Online Tutorials Library, It's all Free"
'\n'
'Top 5 most used Programming Languages are: \n'
'Java'
',\n'
'C'
',\n'
'Python'
',\n'
'JavaScript'
' and\n'
'C'
';\n \nas per online survey.'
'\n'
'Programming Languages'
'\n'

Aby usunąć dodatkowe spacje, użyj generatora .stripped_strings -

>>> for string in soup.stripped_strings:
print(repr(string))
'Tutorials Point'
"The Biggest Online Tutorials Library, It's all Free"
'Top 5 most used Programming Languages are:'
'Java'
','
'C'
','
'Python'
','
'JavaScript'
'and'
'C'
';\n \nas per online survey.'
'Programming Languages'

Wchodzę w górę

W analogii „drzewo genealogiczne” każdy znacznik i każdy ciąg ma rodzica: znacznik, który go zawiera:

.rodzic

Aby uzyskać dostęp do elementu nadrzędnego elementu, użyj atrybutu .parent.

>>> Ttag = soup.title
>>> Ttag
<title>Tutorials Point</title>
>>> Ttag.parent
<head>title>Tutorials Point</title></head>

W naszym html_doc sam ciąg tytułu ma rodzica: znacznik <title>, który go zawiera

>>> Ttag.string.parent
<title>Tutorials Point</title>

Rodzicem tagu najwyższego poziomu, takiego jak <html>, jest sam obiekt Beautifulsoup -

>>> htmltag = soup.html
>>> type(htmltag.parent)
<class 'bs4.BeautifulSoup'>

Element .parent obiektu Beautifulsoup jest zdefiniowany jako None -

>>> print(soup.parent)
None

.rodzice

Aby iterować po wszystkich elementach nadrzędnych, użyj atrybutu .parents.

>>> link = soup.a
>>> link
<a class="prog" href="https://www.tutorialspoint.com/java/java_overview.htm" id="link1">Java</a>
>>>
>>> for parent in link.parents:
if parent is None:
print(parent)
else:
print(parent.name)
p
body
html
[document]

Idąc bokiem

Poniżej znajduje się jeden prosty dokument -

>>> sibling_soup = BeautifulSoup("<a><b>TutorialsPoint</b><c><strong>The Biggest Online Tutorials Library, It's all Free</strong></b></a>")
>>> print(sibling_soup.prettify())
<html>
<body>
   <a>
      <b>
         TutorialsPoint
      </b>
      <c>
         <strong>
            The Biggest Online Tutorials Library, It's all Free
         </strong>
      </c>
   </a>
</body>
</html>

W powyższym dokumencie znaczniki <b> i <c> są na tym samym poziomie i oba są elementami podrzędnymi tego samego znacznika. Zarówno tag <b>, jak i <c> są rodzeństwem.

.next_sibling i .previous_sibling

Użyj .next_sibling i .previous_sibling, aby nawigować między elementami strony, które znajdują się na tym samym poziomie drzewa parsowania:

>>> sibling_soup.b.next_sibling
<c><strong>The Biggest Online Tutorials Library, It's all Free</strong></c>
>>>
>>> sibling_soup.c.previous_sibling
<b>TutorialsPoint</b>

Znacznik <b> ma .next_sibling, ale nie ma .previous_sibling, ponieważ nie ma nic przed znacznikiem <b> na tym samym poziomie drzewa, to samo dotyczy tagu <c>.

>>> print(sibling_soup.b.previous_sibling)
None
>>> print(sibling_soup.c.next_sibling)
None

Te dwa łańcuchy nie są rodzeństwem, ponieważ nie mają tego samego rodzica.

>>> sibling_soup.b.string
'TutorialsPoint'
>>>
>>> print(sibling_soup.b.string.next_sibling)
None

.next_siblings i .previous_siblings

Aby iterować po rodzeństwie tagu, użyj .next_siblings i .previous_siblings.

>>> for sibling in soup.a.next_siblings:
print(repr(sibling))
',\n'
<a class="prog" href="https://www.tutorialspoint.com/cprogramming/index.htm" id="link2">C</a>
',\n'
>a class="prog" href="https://www.tutorialspoint.com/python/index.htm" id="link3">Python</a>
',\n'
<a class="prog" href="https://www.tutorialspoint.com/javascript/javascript_overview.htm" id="link4">JavaScript</a>
' and\n'
<a class="prog" href="https://www.tutorialspoint.com/ruby/index.htm"
id="link5">C</a>
';\n \nas per online survey.'
>>> for sibling in soup.find(id="link3").previous_siblings:
print(repr(sibling))
',\n'
<a class="prog" href="https://www.tutorialspoint.com/cprogramming/index.htm" id="link2">C</a>
',\n'
<a class="prog" href="https://www.tutorialspoint.com/java/java_overview.htm" id="link1">Java</a>
'Top 5 most used Programming Languages are: \n'

Chodzić tam i z powrotem

Wróćmy teraz do pierwszych dwóch wierszy w naszym poprzednim przykładzie „html_doc” -

&t;html><head><title>Tutorials Point</title></head>
<body>
<h4 class="tagLine"><b>The Biggest Online Tutorials Library, It's all Free</b></h4>

Parser HTML bierze powyższy ciąg znaków i zamienia go w serię zdarzeń, takich jak „otwórz tag <html>”, „otwórz tag <head>”, „otwórz tag <title>”, „dodaj ciąg”, „Zamknij tag </title>”, „zamknij tag </head>”, „otwórz tag <h4>” i tak dalej. BeautifulSoup oferuje różne metody rekonstrukcji początkowej analizy dokumentu.

.next_element i .previous_element

Atrybut .next_element tagu lub ciągu wskazuje na wszystko, co zostało przeanalizowane natychmiast po tym. Czasami wygląda podobnie do .next_sibling, jednak nie jest całkowicie taki sam. Poniżej znajduje się ostatni znacznik <a> w naszym przykładowym dokumencie „html_doc”.

>>> last_a_tag = soup.find("a", id="link5")
>>> last_a_tag
<a class="prog" href="https://www.tutorialspoint.com/ruby/index.htm" id="link5">C</a>
>>> last_a_tag.next_sibling
';\n \nas per online survey.'

Jednak .next_element tego znacznika <a>, rzecz, która została przeanalizowana bezpośrednio po znaczniku <a>, nie jest resztą tego zdania: jest to słowo „C”:

>>> last_a_tag.next_element
'C'

Powyższe zachowanie wynika z tego, że w oryginalnym oznaczeniu litera „C” pojawiła się przed średnikiem. Parser napotkał znacznik <a>, następnie literę „C”, następnie tag zamykający </a>, a następnie średnik i resztę zdania. Średnik znajduje się na tym samym poziomie co znacznik <a>, ale najpierw napotkano literę „C”.

Atrybut .previous_element jest dokładnym przeciwieństwem .next_element. Wskazuje na dowolny element, który został przeanalizowany bezpośrednio przed tym.

>>> last_a_tag.previous_element
' and\n'
>>>
>>> last_a_tag.previous_element.next_element
<a class="prog" href="https://www.tutorialspoint.com/ruby/index.htm" id="link5">C</a>

.next_elements i .previous_elements

Używamy tych iteratorów, aby przejść do przodu i do tyłu do elementu.

>>> for element in last_a_tag.next_e lements:
print(repr(element))
'C'
';\n \nas per online survey.'
'\n'
<p class="prog">Programming Languages</p>
'Programming Languages'
'\n'