一.介紹:
Beautiful Soup 是一個可以從HTML或XML文件中提取數據的Python庫.它能夠通過你喜歡的轉換器實現慣用的文檔導航,查找,修改文檔的方式.Beautiful Soup會幫你節省數小時甚至數天的工作時間.你可能在尋找 Beautiful Soup3 的文檔,Beautiful Soup 3 目前已經停止開發,官網推薦在現在的項目中使用Beautiful Soup 4, 移植到BS4。安裝步驟如下:
#安裝 Beautiful Soup 我們在爬蟲中一般推薦使用lxml解析器,因為其爬取效率比較高 pip install beautifulsoup4 #安裝解析器 Beautiful Soup支持Python標准庫中的HTML解析器,還支持一些第三方的解析器,其中一個是 lxml .根據操作系統不同,可以選擇下列方法來安裝lxml: $ apt-get install Python-lxml $ easy_install lxml $ pip install lxml 另一個可供選擇的解析器是純Python實現的 html5lib , html5lib的解析方式與瀏覽器相同,可以選擇下列方法來安裝html5lib: $ apt-get install Python-html5lib $ easy_install html5lib $ pip install html5lib
下表列出了主要的解析器,以及它們的優缺點,官網推薦使用lxml作為解析器,因為效率更高. 在Python2.7.3之前的版本和Python3中3.2.2之前的版本,必須安裝lxml或html5lib, 因為那些Python版本的標准庫中內置的HTML解析方法不夠穩定.
| 解析器 | 使用方法 | 優勢 | 劣勢 |
|---|---|---|---|
| Python標准庫 | BeautifulSoup(markup, "html.parser") |
|
|
| lxml HTML 解析器 | BeautifulSoup(markup, "lxml") |
|
|
| lxml XML 解析器 | BeautifulSoup(markup, ["lxml", "xml"]) BeautifulSoup(markup, "xml") |
|
|
| html5lib | BeautifulSoup(markup, "html5lib") |
|
|
中文文檔:https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html
二.基本使用
html_doc = """ <html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></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>
""" #基本使用:容錯處理,文檔的容錯能力指的是在html代碼不完整的情況下,使用該模塊可以識別該錯誤。使用BeautifulSoup解析上述代碼,能夠得到一個 BeautifulSoup 的對象,並能按照標准的縮進格式的結構輸出 from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') #具有容錯功能 res=soup.prettify() #處理好縮進,結構化顯示 print(res)
1.通過標簽尋定位,如果有多個標簽,那么只返回第一個,看下面的例子:
# 遍歷文檔樹:即直接通過標簽名字選擇,特點是選擇速度快,但如果存在多個相同的標簽則只返回第一個 html_doc = """ <html><head><title>The Dormouse's story</title></head>
<body>
<p id="my p" class="title"><b id="bbb" class="boldest">The Dormouse's story</b></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>
""" # 1、用法 from bs4 import BeautifulSoup soup = BeautifulSoup(html_doc, 'lxml') # 第一個參數直接是一個標簽文檔字符串 # soup=BeautifulSoup(open('a.html'),'lxml') # 第一個參數是一個文件句柄對象 print(soup.p) # 存在多個相同的標簽則只返回第一個 print(soup.a) # 存在多個相同的標簽則只返回第一個
打印結果如下:
print(soup.p) 上面有2個p標簽,只返回找到的第一個p標簽 <p class="title" id="my p"><b class="boldest" id="bbb">The Dormouse's story</b></p>
print(soup.a) 只返回找到的第一個a標簽 <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
2.獲取標簽的屬性:
# _*_ coding:utf-8 _*_ # 遍歷文檔樹:即直接通過標簽名字選擇,特點是選擇速度快,但如果存在多個相同的標簽則只返回第一個 html_doc = """ <html><head><title>The Dormouse's story</title></head>
<body>
<p id="my p" class="title"><b id="bbb" class="boldest">The Dormouse's story</b>111</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>
""" # 1、用法 from bs4 import BeautifulSoup soup = BeautifulSoup(html_doc, 'lxml') # 第一個參數直接是一個標簽文檔字符串 # # soup=BeautifulSoup(open('a.html'),'lxml') # 第一個參數是一個文件句柄對象 # # print(soup.p) # 存在多個相同的標簽則只返回第一個 # # print(soup.a) # 存在多個相同的標簽則只返回第一個 # # # 2、獲取標簽的名稱 # print(soup.p.name) # 打印 p # # # 3、獲取標簽的屬性 # """ # 打印找到的第一個p標簽的屬性,屬性被封裝為字典: {'class': ['title'], 'id': 'my p'} # 原始找到的p標簽為:<p class="title" id="my p"><b class="boldest" id="bbb">The Dormouse's story</b></p>
# """ # print(soup.p.attrs) # # 4、獲取標簽的內容 """ 表示查看p標簽下的文本,只尋找一個,如果里面有多個標簽或者文本,則打印None 當第一個p標簽如下所示時:<p id="my p" class="title"><b id="bbb" class="boldest">The Dormouse's story</b></p>
打印出The Dormouse's story
當p標簽如下所示時:<p id="my p" class="title"><b id="bbb" class="boldest">The Dormouse's story</b>
</p> 相當於多了一個換行符,那么就打印出None 又或者當p標簽如下所示時:<p id="my p" class="title"><b id="bbb" class="boldest">The Dormouse's story</b>111</p>
此時有兩個文本內容The Dormouse's story和111,此時soup不知道要找哪個,所以為None
""" # print(soup.p.string) # p下的文本只有一個時,取到,否則為None # print(soup.p.strings) # 拿到一個生成器對象, 取到p標簽下下所有的文本內容 # # 打印結果:<generator object _all_strings at 0x000002A324FAF3B8> # print(list(soup.p.strings)) # 打印結果: ["The Dormouse's story", '111'] """ 查看soup文檔對象中的所有標簽文本: 使用stripped_strings去除很多空行: 打印結果如下: The Dormouse's story
The Dormouse's story
111 Once upon a time there were three little sisters; and their names were Elsie , Lacie and Tillie ; and they lived at the bottom of a well. ... """ # for line in soup.stripped_strings: # 去掉空白 # print(line) ''' 如果tag包含了多個子節點,tag就無法確定 .string 方法應該調用哪個子節點的內容, .string 的輸出結果是 None,如果只有一個子節點那么就輸出該子節點的文本,比如下面的這種結構,soup.p.string 返回為None,但soup.p.strings就可以找到所有文本 <p id='list-1'> 哈哈哈哈 <a class='sss'>
<span>
<h1>aaaa</h1>
</span>
</a>
<b>bbbbb</b>
</p>
'''
接着看下面的:
# _*_ coding:utf-8 _*_ # 遍歷文檔樹:即直接通過標簽名字選擇,特點是選擇速度快,但如果存在多個相同的標簽則只返回第一個 html_doc = """ <html><head><title>The Dormouse's story</title></head>
<body>
<p id="my p" class="title"> aaa<b id="bbb" class="boldest">The Dormouse's story</b>ddd
<ul> spark很重要 <li>python</li>
</ul>
</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>
""" # 1、用法 from bs4 import BeautifulSoup soup = BeautifulSoup(html_doc, 'lxml') # 第一個參數直接是一個標簽文檔字符串 # # soup=BeautifulSoup(open('a.html'),'lxml') # 第一個參數是一個文件句柄對象 # # print(soup.p) # 存在多個相同的標簽則只返回第一個 # # print(soup.a) # 存在多個相同的標簽則只返回第一個 # # # 2、獲取標簽的名稱 # print(soup.p.name) # 打印 p # # # 3、獲取標簽的屬性 # """ # 打印找到的第一個p標簽的屬性,屬性被封裝為字典: {'class': ['title'], 'id': 'my p'} # 原始找到的p標簽為:<p class="title" id="my p"><b class="boldest" id="bbb">The Dormouse's story</b></p>
# """ # print(soup.p.attrs) # # 4、獲取標簽的內容 """ 表示查看p標簽下的文本,只尋找一個,如果里面有多個標簽或者文本,則打印None 當第一個p標簽如下所示時:<p id="my p" class="title"><b id="bbb" class="boldest">The Dormouse's story</b></p>
打印出The Dormouse's story
當p標簽如下所示時:<p id="my p" class="title"><b id="bbb" class="boldest">The Dormouse's story</b>
</p> 相當於多了一個換行符,那么就打印出None 又或者當p標簽如下所示時:<p id="my p" class="title"><b id="bbb" class="boldest">The Dormouse's story</b>111</p>
此時有兩個文本內容The Dormouse's story和111,此時soup不知道要找哪個,所以為None
""" # print(soup.p.string) # p下的文本只有一個時,取到,否則為None # print(soup.p.strings) # 拿到一個生成器對象, 取到p標簽下下所有的文本內容 # # 打印結果:<generator object _all_strings at 0x000002A324FAF3B8> # print(list(soup.p.strings)) # 打印結果: ["The Dormouse's story", '111'] """ 查看soup文檔對象中的所有標簽文本: 使用stripped_strings去除很多空行: 打印結果如下: The Dormouse's story
The Dormouse's story
111 Once upon a time there were three little sisters; and their names were Elsie , Lacie and Tillie ; and they lived at the bottom of a well. ... """ # for line in soup.stripped_strings: # 去掉空白 # print(line) ''' 如果tag包含了多個子節點,tag就無法確定 .string 方法應該調用哪個子節點的內容, .string 的輸出結果是 None,如果只有一個子節點那么就輸出該子節點的文本,比如下面的這種結構,soup.p.string 返回為None,但soup.p.strings就可以找到所有文本 <p id='list-1'> 哈哈哈哈 <a class='sss'>
<span>
<h1>aaaa</h1>
</span>
</a>
<b>bbbbb</b>
</p>
''' # 5、嵌套選擇 打印結果:The Dormouse's story
# print(soup.head.title.string) # 由於此時有三個a標簽,但是這樣嵌套尋找只找到第一個,所以只打印出Elsie # print(soup.body.a.string) # 6、子節點、子孫節點 """ 打印結果如下所示: [<b class="boldest" id="bbb">The Dormouse's story</b>, '111']
可以看到這樣也只是定位到第一個p標簽: <p id="my p" class="title"><b id="bbb" class="boldest">The Dormouse's story</b>111</p>
相當於打印的就是第一個p標簽內部的所有元素,包括子節點和文本,我們再來測試下: <p id="my p" class="title"> aaa<b id="bbb" class="boldest">The Dormouse's story</b>ddd
<ul> spark很重要 <li>python</li>
</ul>
</p> 這是文檔中的第一個p標簽,那么print(soup.p.contents)的打印結果如下: ['\n aaa', <b class="boldest" id="bbb">The Dormouse's story</b>, 'ddd\n ']
可以看到它找到的是第一個子標簽和子文本 """ # print(soup.p.contents) #p下所有子節點 # print(soup.p.children) #得到一個迭代器,包含p下所有子節點 # print(list(soup.p.children)) # <list_iterator object at 0x000001FD6AA47A20> # ['\n aaa', <b class="boldest" id="bbb">The Dormouse's story</b>, 'ddd\n '] """ 打印結果如下: 0 aaa 1 <b class="boldest" id="bbb">The Dormouse's story</b>
2 ddd """ # for i,child in enumerate(soup.p.children): # print(i,child) """ 打印結果如下: <generator object descendants at 0x0000017D7863F3B8> ['\n aaa', <b class="boldest" id="bbb">The Dormouse's story</b>, "The Dormouse's story", 'ddd\n '] """ # print(soup.p.descendants) # 獲取子孫節點,p下所有的標簽都會選擇出來 # print(list(soup.p.descendants)) # for i, child in enumerate(soup.p.descendants): # print(i, child) # # #7、父節點、祖先節點 # print(soup.a.parent) # 獲取a標簽的父節點,其父節點是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>
""" # print(soup.a.parents) # 找到a標簽所有的祖先節點,父親的父親,父親的父親的父親... # print(list(soup.a.parents)) # 8、兄弟節點 # print('=====>') # print(soup.a.next_sibling) #下一個兄弟 # print(soup.a.previous_sibling) #上一個兄弟 # print(list(soup.a.next_siblings)) #下面的兄弟們=>生成器對象 print(soup.a.previous_siblings) #上面的兄弟們=>生成器對象
繼續看bs中的五種過濾器
# _*_ coding:utf-8 _*_ # 搜索文檔樹:BeautifulSoup定義了很多搜索方法,這里着重介紹2個: find() 和 find_all() .其它方法的參數和用法類似 html_doc = """ <html><head><title>The Dormouse's story</title></head>
<body>
<p id="my p" class="title"><b id="bbb" class="boldest">The Dormouse's story</b>
</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_doc, 'lxml') # 1、五種過濾器: 字符串、正則表達式、列表、True、方法 # 1.1、字符串:即標簽名 # print(soup.find_all('b')) """ 結果是一個列表: [<b class="boldest" id="bbb">The Dormouse's story</b>] """ # 1.2、正則表達式 import re # # print(soup.find_all(re.compile("^b"))) # 找出b開頭的標簽,結果有body和b標簽 """ 打印結果如下: [<body>
<p class="title" id="my p"><b class="boldest" id="bbb">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>, <b class="boldest" id="bbb">The Dormouse's story</b>]
""" # # # 1.3、列表:如果傳入列表參數,Beautiful Soup會將與列表中任一元素匹配的內容返回.下面代碼找到文檔中所有<a>標簽和<b>標簽: # print(soup.find_all(['a', 'b'])) """ 返回結果仍然是一個列表: [<b class="boldest" id="bbb">The Dormouse's story</b>,
<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>] """ # # # 1.4、True:可以匹配任何值,下面代碼查找到所有的tag,但是不會返回字符串節點 # print(soup.find_all(True)) # 例如我再需要查找到文檔中所有包含id的標簽: # print(soup.find_all(id=True)) """ 返回結果如下,是一個列表: [<p class="title" id="my p"><b class="boldest" id="bbb">The Dormouse's story</b>
</p>, <b class="boldest" id="bbb">The Dormouse's story</b>,
<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>] """ """ 找到文檔中的所有標簽: """ # for tag in soup.find_all(True): # print(tag.name) # # # # 1.5、方法:如果沒有合適過濾器,那么還可以定義一個方法,方法只接受一個元素參數 ,如果這個方法返回 True 表示當前元素匹配並且被找到,如果不是則反回 False """ 下面方法表示尋找到標簽中有class屬性,但是沒有id屬性的標簽,打印結果如下: [<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>] 可以看到兩個p標簽只有class屬性,但是沒有id屬性 """ def has_class_but_no_id(tag): return tag.has_attr('class') and not tag.has_attr('id') print(soup.find_all(has_class_but_no_id))
重點方法之find_all( name , attrs , recursive , text , **kwargs )
