Beautiful Soup是 Python 的一個 HTML 或 XML 的解析庫,庫藉助網頁的結構和屬性等特性來解析網頁css
以上對比,lxml 解析器有解析 HTML 和 XML 的功能,速度’快,容錯能力強,因此推薦使用它。html
若是使用 lxml,那麼在初始化 Beautiful Soup 時,能夠把第二個參數改成 lxml。例:正則表達式
from bs4 import BeautifulSoup Soup = BeautifulSoup('<p>Hello</p>','lxml') print(Soup.p.string)
html = ''' <html><head><title>The Dormouse's story</title></head> <body> <p class ='title' name='dromouse'><b>The Dormouse's story</p></p> <p class = 'story'>Once upon a time there were three little sisters; and their names were <a href='http://example.com/elsie' class='sister' id='link1'><!--Elsie--></a>, <a href='http://example.com/lacie' class='sister' id='link2'>Lacie</a> and <a href='http://example.com/tillie'class='sister' id='link3'>Tillie</a>; and they lived at the bottom of a well.</p> <p class='story'>...</p> ''' from bs4 import BeautifulSoup soup=BeautifulSoup(html,'lxml') print(soup.prettify()) print(soup.title.string)
輸出: <html> <head> <title> The Dormouse's story </title> </head> <body> <p class="title" name="dromouse"> <b> The Dormouse's story </b> </p> <p class="story"> Once upon a time there were three little sisters; and their names were <a class="sister" href="http://example.com/elsie" id="link1"> <!--Elsie--> </a> , <a class="sister" href="http://example.com/lacie" id="link2"> Lacie </a> and <a class="sister" href="http://example.com/tillie" id="link3"> Tillie </a> ; and they lived at the bottom of a well. </p> <p class="story"> ... </p> </body> </html> The Dormouse's story
首先聲明變量 html,它是一個 HTML 字符串。須要注意的是,它並非一個完整的 HTML 字符串,由於 body 和 html 節點都沒有閉合。 接着,將它看成第一個參數傳給 BeautifulSoup 對 象,對象的第二個參數爲解析器的類型(這裏使用 lxml ),此時就完成了 BeaufulSoup 對象的初始化。 而後,將這個對象賦值給 soup 變量。數據結構
接下來,調用 soup 的各個方法和屬性解析HTML 代碼。ide
首先,調用 prettify()方法能夠把要解析的字符串以標準的縮進格式輸出。 注意:輸出結果裏面包含 body 和 html 節點,也就是說對於不標準的 HTML 字符串 Beautifol Soup , 能夠自動更正格式。 這一步不是由 prettify()方法作的,而是在初始化 Beautifol Soup 時就完成了。函數
而後調用 soup.title.string,這其實是輸出 HTML 中 title 節點的文本內容。因此,soup.title 能夠選出 HTML 中的 title 節點,再調用 string 屬性就能夠獲得裏面的文本了,因此咱們能夠經過 簡單調用幾個屬性完成文本提取。this
節點選擇器spa
直接調用節點的名稱就能夠選擇節點元素,再調用 string 屬性就能夠獲得節點內的文本。 若是單個節點結構層次很是清晰,能夠選用這種方式來解析。3d
選擇元素code
html = ''' --snip-- ''' from bs4 import BeautifulSoup soup=BeautifulSoup(html,'lxml') print(soup.title) print(type(soup.title)) print(soup.title.string) print(soup.head) print(soup.p)
輸出: <title>The Dormouse's story</title> <class 'bs4.element.Tag'> The Dormouse's story <head><title>The Dormouse's story</title></head> <p class="title" name="dromouse"><b>The Dormouse's story</b></p>
首先輸出 title 節點的選擇結果,輸出結果正是 title 節 點加里面的文字內容。
接下來,輸出它的類型,是 bs4.element.Tag 類型,這是 Beautiful Soup 中一個 重要的數據結構。 通過選擇器選擇後,選擇結果都是這種 Tag 類型。 Tag 具備一些屬性,好比 調用string 屬性能夠獲得節點的文本內容,因此接下來的輸出結果正是節點的文本內容。嘗試選擇了 head 節點,也是節點加其內部的全部內容。
選擇了 p 節點。 不過此次狀況比較特殊,咱們發現結果是第一個 p 節點的內容,後面的幾個 p 節點並無選到。 也就是說,當有多個節點時,這種選擇方式只會選擇到第一個匹配的節點,其餘的後面節點都會忽略
提取信息
獲取名稱
利用 name 屬性獲取節點的名稱,選取 title 節點,而後調用 name屬性就能夠獲得節點名稱:
print(soup.title.name) 輸出: title
獲取屬性
print(soup.p.attrs) print(soup.p.attrs['name']) 輸出: {'class':['title'], 'name':'dromouse'} dromouse
attrs 的返回結果是字典形式,把選擇的節點的全部屬性和屬性值組合成一個字典。 若是要獲取 name 屬性,就至關於從字典中獲取某個鍵值,只須要用中括號加屬性名就能夠 好比,要獲取 name 屬性,就能夠經過 attrs['name'] 來獲得
print(soup.p['name']) print(soup.p['class']) 輸出: dromouse ['title']
注意:有的返回結果是字符串,有的返回結果是字符串組成的列表。好比, name 屬性的值是惟一的,返回的結果就是單個字符串。而對於 class 一個節點元素可能有多個 class 因此 返回的是列表。
print(soup.p.string) 輸出: The Dormouse's story
注意:這裏選擇到的 p 節點是第一個 p 節點,獲取的文本也是第一個 p 節點裏面的文本。
嵌套選擇
以上例子,每一個返回結果都是 bs4 element.Tag 類型,一樣能夠繼續調用節點進行下一步的選擇。如:獲取了 head 節點元素,能夠繼續調用 head 來選取內部的 head 節點元素:
html = ''' <html><head><title>The Dormouse's story</title></head> <body> ''' from bs4 import BeautifulSoup soup=BeautifulSoup(html,'lxml') print(soup.head.title.string) print(type(soup.head.title)) 輸出: The Dormouse's story <class 'bs4.element.Tag'>
第一行結果是調用 head 以後再次調用 title 而選擇的 title 節點元素。輸出類型仍然是 bs4.element.Tag 類型。在 Tag 類型的基礎上再次選擇獲得的依然仍是 Tag 類型,每次返回的結果都相同。因此這樣就能夠作嵌套選擇了。 最後,輸出它的 string 屬性,也就是節點裏的文本內容。
關聯選擇
子節點和子孫節點
選取節點元素以後,若是想要獲取它的直接子節點,能夠調用 contents 屬性。示例:
html = ''' <html> <head> <title>The Dormouse's story</title> </head> <body> <p class='story'> Once upon a time there were three little sisters; and their names were <a href="http://example.com/elsie" class="sister" id="link1"> <span>Elsie</span> </a> <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a> and they lived at the bottom of a well. <p class="story">...</p> ''' from bs4 import BeautifulSoup soup=BeautifulSoup(html,'lxml') print(soup.p.contents) 輸出:
['\n Once upon a time there were three little sisters; and their names were\n ', <a class="sister" href="http://example.com/elsie" id="link1"> <span>Elsie</span> </a>, '\n', <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, '\nand\n', <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>, '\nand they lived at the bottom of a well.\n']
返回結果是列表形式。p 節點裏既包含文本,又含節點,最後會將它們以列表形式 統一返回
注意:列表中的每一個元素都是 p 節點的直接子節點。 如第一個a節點裏包含一層 span 節點,這至關於孫子節點了,可是返回結果並無單獨把 span 節點選出來。因此, contents 屬性獲得的結果是直接子節點的列表
一樣,能夠調用 children 屬性獲得相應的結果:
from bs4 import BeautifulSoup soup=BeautifulSoup(html,'lxml') print(soup.p.children) for i,child in enumerate(soup.p.children):
"""enumerate() 函數用於將一個可遍歷的數據對象(如列表、元組或字符串)組合爲一個索引序列,同時列出數據和數據下標,通常用在 for 循環當中""" print(i,child) 輸出:
<list_iterator object at 0x000002192389F630> 0 Once upon a time there were three little sisters; and their names were 1 <a class="sister" href="http://example.com/elsie" id="link1"> <span>Elsie</span> </a> 2 3 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> 4 and 5 <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a> 6 and they lived at the bottom of a well.
調用了 children 屬性來選擇,返回結果是生成器類型。再使用 for 循環輸出相應的內容。
若是要獲得全部的子孫節點的話,能夠調用 descendants 屬性:
from bs4 import BeautifulSoup soup=BeautifulSoup(html,'lxml') print(soup.p.descendants) for i,child in enumerate(soup.p.descendants): print(i,child) 輸出:
<generator object descendants at 0x0000022B542F9410> 0 Once upon a time there were three little sisters; and their names were 1 <a class="sister" href="http://example.com/elsie" id="link1"> <span>Elsie</span> </a> 2 3 <span>Elsie</span> 4 Elsie 5 6 7 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> 8 Lacie 9 and 10 <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a> 11 Tillie 12 and they lived at the bottom of a well.
返回結果是生成器。遍歷輸出能夠看到,輸出結果包含了 span 節點。descendants 會遞歸查詢全部子節點,獲得全部的子孫節點
父節點和祖先節點
若是要獲取某個節點元素的父節點,能夠調用 parent 屬性:
from bs4 import BeautifulSoup soup=BeautifulSoup(html,'lxml') print(soup.a.parent) 輸出:
<p class="story"> Once upon a time there were three little sisters; and their names were <a class="sister" href="http://example.com/elsie" id="link1"> <span>Elsie</span> </a> <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a> and they lived at the bottom of a well. </p>
這裏咱們選擇的是第一個 a節點的父節點元素。它的父節點是 p節點,輸出結果即是p節點及其內部的內容。
注意:這裏輸出的僅僅是 a 節點的直接父節點,而沒有再向外尋找父節點的祖先節點。若是想獲取全部的祖先節點,調用 parents 屬性:
html = ''' <html> <body> <p class='story'> <a href="http://example.com/elsie" class="sister" id="link1"> <span>Elsie</span> </a> </p> ''' from bs4 import BeautifulSoup soup=BeautifulSoup(html,'lxml') print(soup.a.parents) print(list(enumerate(soup.a.parents))) 輸出:
<generator object parents at 0x0000011701B69410> [(0, <p class="story"> <a class="sister" href="http://example.com/elsie" id="link1"> <span>Elsie</span> </a> </p>), (1, <body> <p class="story"> <a class="sister" href="http://example.com/elsie" id="link1"> <span>Elsie</span> </a> </p> </body>), (2, <html> <body> <p class="story"> <a class="sister" href="http://example.com/elsie" id="link1"> <span>Elsie</span> </a> </p> </body></html>), (3, <html> <body> <p class="story"> <a class="sister" href="http://example.com/elsie" id="link1"> <span>Elsie</span> </a> </p> </body></html>)]
返回結果是生成器類型。用列表輸出了它的索引和內容,而列表中的元素就是 a 節點的祖先節點。
兄弟節點
html = ''' <html> <body> <p class="story"> Once upon a time there were three sisters;and their names were <a href="http://example.com/elsie" class="sister" id="link1"> <span>Elsie</span> </a> Hello <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a> and they lived at the bottom of a well. </p> ''' from bs4 import BeautifulSoup soup = BeautifulSoup(html, 'lxml') print('Next Sibling', soup.a.next_sibling) print('Prev Sibling', soup.a.previous_sibling) print('Next Sibling', list(enumerate(soup.a.next_siblings))) print('Prev Sibling', list(enumerate(soup.a.previous_siblings))) 輸出:
Next Sibling Hello Prev Sibling Once upon a time there were three sisters;and their names were Next Sibling [(0, '\n Hello\n'), (1, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>), (2, '\n and\n'), (3, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>), (4, '\n and they lived at the bottom of a well.\n')] Prev Sibling [(0, '\n Once upon a time there were three sisters;and their names were \n')]
這裏調用了4 個屬性,其中 next_sibling和previous_sibling 獲取節點的下一個 上一個兄弟元素, next_siblings和 previous_siblings 則分別返回全部前面和後面 兄弟節點的生成器
html = ''' <html> <body> <p class="story"> Once upon a time there were three sisters;and their names were <a href="http://example.com/elsie" class="sister" id="link1">BOb</a><a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> </p> ''' from bs4 import BeautifulSoup soup = BeautifulSoup(html, 'lxml') print('Next Sibling:') print(type(soup.a.previous_sibling)) print(soup.a.next_sibling) print(soup.a.next_sibling.string) print('Parent:') print(type(soup.a.parents)) print(list(soup.a.parents)[0]) print(list(soup.a.parents)[0].attrs['class']) 輸出:
Next Sibling: <class 'bs4.element.NavigableString'> <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> Lacie Parent: <class 'generator'> <p class="story"> Once upon a time there were three sisters;and their names were <a class="sister" href="http://example.com/elsie" id="link1">BOb</a><a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> </p> ['story']
若是返回結果是單個節點,能夠直接調用 string attrs 等屬性得到其文本和屬性;若是返回結果是多個節點的生成器,則能夠轉爲列表後取出某個元素,而後再調用 string attrs 等屬性獲 取其對應節點的文本和屬性。
方法選擇器
前面所講選擇方法是經過屬性來選擇的,這種方法很是快,可是若是進行比較複雜的選擇的話,它就比較煩瑣,不夠靈活。
find_all()
是查詢全部符合條件的元素給它傳入一些屬性或文本,就能夠獲得符合條件的元素。API 以下:
find_all(name,attrs,recursive,text,**kwargs)
html = ''' <div class="panel"> <div class="panel-heading"> <h4>Hello</h4> </div> <div class="panel-body"> <ul class="list" id="list-1"> <li class="element">Foo</li> <li cass= "element">Bar</li> <li cass= "element">Jay</li> </ul> <ul class="list list-small" id="list-2"> <li class= "element">Foo</li> <li class="element">Bar</li> </ul> </div> </div> ''' from bs4 import BeautifulSoup soup=BeautifulSoup(html,'lxml') print(soup.find_all(name='ul')) print(type(soup.find_all(name='ul')[0])) 輸出:
[<ul class="list" id="list-1"> <li class="element">Foo</li> <li cass="element">Bar</li> <li cass="element">Jay</li> </ul>, <ul class="list list-small" id="list-2"> <li class="element">Foo</li> <li class="element">Bar</li> </ul>] <class 'bs4.element.Tag'>
調用了 find_all()方法,傳入 name 參數,其參數值爲 ul。也就是說,想要查詢全部 ul 節點,返回結果是列表類型,長度爲2,每一個元素依然都是 bs4.element.Tag類型。
由於都是 Tag 類型,因此依然能夠進行嵌套查詢 仍是一樣的文本,這裏查詢出全部 節點後, 再繼續查詢其內部的 li 節點:for ul in soup.find_all(name='ul'): print(ul.find_all(name='li')) 輸出: [<li class="element">Foo</li>, <li cass="element">Bar</li>, <li cass="element">Jay</li>] [<li class="element">Foo</li>, <li class="element">Bar</li>]
返回結果是列表類型,列表中的每一個元素依然仍是 ag 類型。
接下來,能夠遍歷每一個 li ,獲取它的文本了:for ul in soup.find_all(name='ul'): print(ul.find_all(name='li')) for li in ul.find_all(name='li'): print(li.string) 輸出:
[<li class="element">Foo</li>, <li cass="element">Bar</li>, <li cass="element">Jay</li>] Foo Bar Jay [<li class="element">Foo</li>, <li class="element">Bar</li>] Foo Bar
atrrs
除了根據節點名查詢,咱們也能夠傳入一些屬性來查詢,示例:
html = ''' <div class="panel"> <div class="panel-heading"> <h4>Hello</h4> </div> <div class="panel-body"> <ul class="list" id="list-1" name="elements"> <li class="element">Foo</li> <li cass= "element">Bar</li> <li cass= "element">Jay</li> </ul> <ul class="list list-small" id="list-2"> <li class= "element">Foo</li> <li class="element">Bar</li> </ul> </div> </div> ''' from bs4 import BeautifulSoup soup=BeautifulSoup(html,'lxml') print(soup.find_all(attrs={'id':'list-1'})) print(soup.find_all(attrs={'name':'elements'})) 輸出:
[<ul class="list" id="list-1" name="elements"> <li class="element">Foo</li> <li cass="element">Bar</li> <li cass="element">Jay</li> </ul>] [<ul class="list" id="list-1" name="elements"> <li class="element">Foo</li> <li cass="element">Bar</li> <li cass="element">Jay</li> </ul>]
查詢的時候傳入的是 attrs 參數,參數的類型是字典類型,如要查詢 id爲 list-1 的節 點,能夠傳入 attrs ={'id' :' list-1'}查詢條件,獲得的結果是列表形式,包含的內容就是符合id爲 list-1 的全部節點。以上示例,符合條件的元素個數是1,因此結果是長度爲 1 的列表。
一些經常使用的屬性,好比 id 和 class 等,能夠不用 attrs 來傳遞。如,要查詢 id爲 list-1 的節點,能夠直接傳人 id 這個參數。換種方式查詢:
html = ''' <div class="panel"> <div class="panel-heading"> <h4>Hello</h4> </div> <div class="panel-body"> <ul class="list" id="list-1" name="elements"> <li class="element">Foo</li> <li cass= "element">Bar</li> <li cass= "element">Jay</li> </ul> <ul class="list list-small" id="list-2"> <li class= "element">Foo</li> <li class="element">Bar</li> </ul> </div> </div> ''' from bs4 import BeautifulSoup soup=BeautifulSoup(html,'lxml') print(soup.find_all(id='list-1')) print(soup.find_all(class_ ='element')) 輸出:
[<ul class="list" id="list-1" name="elements"> <li class="element">Foo</li> <li cass="element">Bar</li> <li cass="element">Jay</li> </ul>] [<li class="element">Foo</li>, <li class="element">Foo</li>, <li class="element">Bar</li>]
直接傳入 id = 'list-1', 就能夠查詢 id爲list-1的節點元素。對於 class 來講,因爲 class在Python 裏是一個關鍵字,因此後面須要加一個下劃線,即 class_ = 'element' ,返回結果依然是Tag。
text
import re html = ''' <div class="panel"> <div class="panel-body"> <a>Hello,this is a link</a> <a>Hello,this is a link,too</a> </div> </div> ''' from bs4 import BeautifulSoup soup=BeautifulSoup(html,'lxml') print(soup.find_all(text=re.compile('link'))) 輸出:
['Hello,this is a link', 'Hello,this is a link,too']
這裏有兩個 a 節點,內部包含文本信息。這裏在 find_all() 方法傳人text 參數,該參數爲正則表達式對象,結果返回全部匹配正則表達式的節點文本組成的列表。
from bs4 import BeautifulSoup soup=BeautifulSoup(html,'lxml') print(soup.find(name='ul')) print(soup.find(class_ ='element')) print(type(soup.find(name='ul'))) 輸出:
<ul class="list" id="list-1"> <li class="element">Foo</li> <li cass="element">Bar</li> <li cass="element">Jay</li> </ul> <li class="element">Foo</li> <class 'bs4.element.Tag'>
返回結果再也不是列表形式,是第一個匹配的節點元素,類型依然是 Tag 類型。
還有許多查詢方法,其用方法與前面介紹的 find_all(), find() 方法徹底相同, 只不過查詢範圍不一樣html = ''' <div class="panel"> <div class="panel-heading"> <h4>Hello</h4> </div> <div class="panel-body"> <ul class="list" id="list-1"> <li class="element">Foo</li> <li cass= "element">Bar</li> <li cass= "element">Jay</li> </ul> <ul class="list list-small" id="list-2"> <li class= "element">Foo</li> <li class="element">Bar</li> </ul> </div> </div> ''' from bs4 import BeautifulSoup soup=BeautifulSoup(html,'lxml') print(soup.select('.panel .panel-heading')) print(soup.select('ul li')) print(soup.select('#list-2 .element')) print(type(soup.select('ul')[0])) 輸出:
[<div class="panel-heading"> <h4>Hello</h4> </div>] [<li class="element">Foo</li>, <li cass="element">Bar</li>, <li cass="element">Jay</li>, <li class="element">Foo</li>, <li class="element">Bar</li>] [<li class="element">Foo</li>, <li class="element">Bar</li>] <class 'bs4.element.Tag'>
這裏用了CSS 選擇器,返回的結果均是符合 css 選擇器的節點組成的列表 。如, select( ’ ul li ’)則是選擇全部 ul 節點下面的全部li 節點,結果全部的 li 節點組成的列表。輸出列表中元素的類型依然是 Tag 類型。
from bs4 import BeautifulSoup soup=BeautifulSoup(html,'lxml') for ul in soup.select('ul'): print(ul.select('li')) 輸出:
[<li class="element">Foo</li>, <li cass="element">Bar</li>, <li cass="element">Jay</li>] [<li class="element">Foo</li>, <li class="element">Bar</li>]
正常輸出了全部 ul 節點下全部 li 節點組成的列表
獲取屬性
from bs4 import BeautifulSoup soup=BeautifulSoup(html, 'lxml') for ul in soup.select('ul'): print(ul['id']) print(ul.attrs['id']) 輸出:
list-1 list-1 list-2 list-2
接傳入中括號和屬性名,以及經過 attrs 屬性獲取屬性值,均可以成功。
獲取文本
獲取文本,除了能夠用 string 屬性。還有一個方法,那就是 get_text() ,效果一致。示例:
from bs4 import BeautifulSoup soup=BeautifulSoup(html, 'lxml') for li in soup.select('li'): print('Get Text:',li.get_text()) print('string:',li.string) 輸出:
Get Text: Foo
string: Foo
Get Text: Bar
string: Bar
Get Text: Jay
string: Jay
Get Text: Foo
string: Foo
Get Text: Bar
string: Bar
小結: