beautifulsoup(以下簡稱bs),是一款網(wǎng)頁結(jié)構(gòu)解析模塊,它支持傳統(tǒng)的Xpath,css selector 語法,可以說很強(qiáng)大了,下面我們就來著重介紹下它的用法。
bs 可以使用pip 或者easy_install安裝,方便快捷。
pip install Beautifulsoup4
一般就是先由requests 獲取到網(wǎng)頁源碼后然后對頁面進(jìn)行解析,如圖:
這樣就基本上拿到了頁面的源碼了。
1.根據(jù)標(biāo)簽直接獲取元素,如下圖:
2.根據(jù)find,find_all方法查找
前者返回一個(gè)結(jié)果,后者返回所有結(jié)果
find( name , attrs , recursive , text , **kwargs )
name :要查找的標(biāo)簽名(字符串、正則、方法、True)
attrs: 標(biāo)簽的屬性
recursive: 遞歸
text: 查找文本
**kwargs :其它 鍵值參數(shù)
因?yàn)閏lass是關(guān)鍵字,所以要寫成class_="value", 等同于attrs={"class":"value"}
這里的參數(shù)適用于find find_all兩種方法。
只不過find_all 還有其他參數(shù),比如限制查找返回?cái)?shù)量 的limit方法,標(biāo)簽內(nèi)容string方法。
3.根據(jù)select方法查找
soup.select('div') | 所有名為<div>的元素 |
soup.select('#aa') | 所有 id 屬性名為aa的元素 |
soup.select('.oo') | 所有class 屬性名為oo的元素 |
soup.select('div p') | 所有在<div>元素之內(nèi)的<p>元素 |
soup.select('div >p') | 所有直接在<div>元素之內(nèi)的<p>元素,中間沒有其他元素 |
soup.select('input[name]') | 所有名為<input>,并有一個(gè) name 屬性,其值無所謂的元素 |
soup.select('input[type="button"]') | 所有名為<input>,并有一個(gè) type 屬性,其值為 button 的元素 |
soup.select('a')[0].get_text() # 獲取首個(gè)a元素的文本
soup.select('a')[0].attrs['href'] # 獲取首個(gè)a元素的鏈接地址
4.關(guān)系節(jié)點(diǎn)名
find_parents()返回所有祖先節(jié)點(diǎn)的列表,find_parent()返回直接父節(jié)點(diǎn)
print(soup.title.find_parent())
print(soup.title.find_parent().find_all('link')[1])
print(soup.title.find_parents())
find_next_siblings()返回后面所有兄弟節(jié)點(diǎn)的列表,find_next_sibling()返回后面第一個(gè)兄弟節(jié)點(diǎn)
print(soup.title.find_next_sibling())
print(soup.title.find_next_siblings())
find_previous_siblings()返回前面所有兄弟節(jié)點(diǎn)的列表,find_previous_sibling()返回前面第一個(gè)兄弟節(jié)點(diǎn)
print(soup.title.find_previous_sibling())
print(soup.title.find_previous_siblings())
find_all_next()返回節(jié)點(diǎn)后所有符合條件的節(jié)點(diǎn)的列表, find_next()返回節(jié)點(diǎn)后第一個(gè)符合條件的節(jié)點(diǎn)
print(soup.title.find_next('link'))
print(soup.title.find_all_next('link'))
find_all_previous()返回節(jié)點(diǎn)前所有符合條件的節(jié)點(diǎn), find_previous()返回節(jié)點(diǎn)前第一個(gè)符合條件的節(jié)點(diǎn)
print(soup.title.find_previous('link'))
print(soup.title.find_all_previous('link'))
5.對象種類
tag(標(biāo)簽) navigablestring(標(biāo)簽內(nèi)字符串) beautifulsoup(對象) comment(備注)
rep=requests.get('https://book.qidian.com/info/1014243481#Catalog',timeout=3)
soup=BeautifulSoup(rep.text,'html.parser')
print(soup.name) #beautifulsoup 對象
tr=soup.div
print(type(tr),tr) #tag對象 標(biāo)簽
print(tr.get_attribute_list('class')) #獲取屬性對應(yīng)列表
print(tr.a.string) #navigablestring 對象,獲取標(biāo)簽內(nèi)文字,可使用str()方法將她轉(zhuǎn)換為unicode字符串
print(soup.a.string.replace_with('fdf')) #替換navigablestring
comment 即為提取的注釋內(nèi)容,一般為!--xxxxxxx--! 包裹的內(nèi)容就是了
三、使用案例
爬取起點(diǎn)小說主頁第一頁所有小說名字和鏈接,如圖:
import requests
from bs4 import BeautifulSoup
rep=requests.get('https://www.qidian.com/all',timeout=3)
soup=BeautifulSoup(rep.text,'html.parser')
#按照步驟一步一步來爬取
ul=soup.find_all('ul','all-img-list cf')
for y in ul:
for z in y.find_all('div','book-mid-info'):
for x in z.find_all('h4'):
for v in x.find_all('a'):
print(v.get_text(),'https:'+v.attrs['href']) #獲取a標(biāo)簽的內(nèi)容和href屬性值
最后就可以得出正確結(jié)果,如圖:
關(guān)于bs大致就這么多,大家學(xué)會了嗎??
今天就講這么多,關(guān)于BS的強(qiáng)大之處,遠(yuǎn)不止于此,本文只是介紹了它的安裝和基本用法,并通過一個(gè)案例來幫助大家加深理解,希望大家好好利用,在爬蟲路上可以事倍功半!
------------------- End -------------------
最后多說一句,小編是一名python開發(fā)工程師,這里有我自己整理了一套最新的python系統(tǒng)學(xué)習(xí)教程,包括從基礎(chǔ)的python腳本到web開發(fā)、爬蟲、數(shù)據(jù)分析、數(shù)據(jù)可視化、機(jī)器學(xué)習(xí)等。想要這些資料的可以關(guān)注小編,并在后臺私信小編:“01”即可領(lǐng)取。
上一節(jié)中,認(rèn)識了Python中的lxml庫,可以通過XPath來尋找頁面中的位置,這也是僅僅對于結(jié)構(gòu)完整的頁面,但是對于有針對性的獲取內(nèi)容的時(shí)候并不很友好,比如說鏈接中以XXX開頭或者結(jié)尾,而且中間符合某些特定規(guī)則,所以這時(shí)候需要認(rèn)識一個(gè)新朋友,那就是另外一個(gè)很強(qiáng)大的解析庫——Beautiful Soup。
與 lxml 一樣,Beautiful Soup 也是一個(gè)HTML/XML的解析器,通過解析文檔為用戶提供需要抓取的數(shù)據(jù)的功能。
Beautiful Soup也有很多版本,不過Beautiful Soup3已經(jīng)停止更新了,目前最新的都是Beautiful Soup4,而且也已經(jīng)移植到bs4庫中,我們安裝bs4庫后就可以直接使用。安裝庫使用pip安裝,安裝命令:
pip install beautifulsoup4
Beautiful Soup中支持的解析器有很多種,不僅僅支持Python標(biāo)準(zhǔn)庫中的HTML解析器,還可以使用一些第三方的解析器,比如說lxml等,如表所示,是幾種常見的解析器的優(yōu)缺點(diǎn)。
解析器 | 使用方式 | 優(yōu)點(diǎn) | 缺點(diǎn) |
Python標(biāo)準(zhǔn)庫 | BeautifulSoup(html, "html.parser") | Python的內(nèi)置標(biāo)準(zhǔn)庫、文檔容錯性較強(qiáng) | 執(zhí)行速度適中 |
lxml解析器 | BeautifulSoup(html, "lxml") | 速度快、文檔容錯性較強(qiáng) | 依賴C語言庫 |
html5lib | BeautifulSoup(html, "html5lib") | 以瀏覽器的方式解析文檔、容錯性最好 | 執(zhí)行速度慢 |
一般情況下可以使用Python標(biāo)準(zhǔn)庫或者lxml作為常用的解析器,對于爬蟲來說,比起速度來說,準(zhǔn)確性的要求并不是很高。如果在解析文檔上花費(fèi)的時(shí)間太多,必然會導(dǎo)致爬蟲的效率低。
Python標(biāo)準(zhǔn)庫解析器并不需要安裝,因?yàn)楸旧碜詭У模琹xml解析器在上一節(jié)使用它作為解析器時(shí)候已經(jīng)安裝過了,也不需要額外安裝,直接使用即可。html5lib的安裝跟BeautifulSoup一樣,使用pip安裝:
pip install html5lib
from bs4 import BeautifulSoup
from lxml import etree
text='''
<html>
<head>
<title>實(shí)例HTML</title>
</head>
<body>
<div>
<h1>這是標(biāo)題</h1>
</div>
<div>
<ul>
<li class="c1"><a href="link1.html" title="鏈接1">第一個(gè)鏈接</a></li>
<li class="c2"><a href="link2.html" title="鏈接2">第二個(gè)鏈接</a></li>
<li class="c3"><a href="link3.html" title="鏈接3">第三個(gè)鏈接</a></li>
</ul>
</div>
</body>
</html>
'''
# 生成一個(gè)BeautifulSoup對象
soup=BeautifulSoup(text, 'html.parser')
# 對象類型
print(type(soup))
#代碼結(jié)果:
<class 'bs4.BeautifulSoup'>
現(xiàn)在就獲得了一個(gè)BeautifulSoup的對象,Beautiful Soup其實(shí)是將HTML文檔轉(zhuǎn)換成一個(gè)復(fù)雜的樹形結(jié)構(gòu),每個(gè)節(jié)點(diǎn)都是Python中的對象,所有對象可以歸納為 4 種:Tag、NavigableString、BeautifulSoup、Comment,后兩種根本上講也是前面兩種的特殊情況。下面我們簡要講解這幾個(gè)對象。
Tag
Tag是最容易理解的,跟字面意思一樣,就是HTML中的標(biāo)簽。比如:一個(gè)a標(biāo)簽就是一個(gè)對象:
<a href="link1.html" title="鏈接1">第一個(gè)鏈接</a>
在tag對象中比較重要的兩個(gè)屬性name和attrs。通過這兩個(gè)屬性可以獲取到標(biāo)簽中的信息:
print(soup.a.name)
print(soup.a.attrs)
#代碼結(jié)果:
a
{'href': 'link1.html', 'title': '鏈接1'}
name其實(shí)就是獲取標(biāo)簽的名稱,這個(gè)是使用的不多,畢竟在日常使用的時(shí)候都會知道需要找哪些標(biāo)簽中的內(nèi)容。attrs獲取是標(biāo)簽中的屬性,結(jié)果是一個(gè)字典類型的集合。
NavigableString
在上面兩個(gè)屬性中,并沒法獲取標(biāo)簽中的內(nèi)容,那么NavigableString就是用來獲取標(biāo)簽中文本內(nèi)容的,用法也比較簡單,直接使用string即可。
print(soup.a.string)
print(type(soup.a.string))
#代碼結(jié)果:
第一個(gè)鏈接
<class 'bs4.element.NavigableString'>
BeautifulSoup
這個(gè)對象在前面提到過,表示一個(gè)頁面(文檔)的內(nèi)容,可以作為一個(gè)特殊的Tag。
print(type(soup))
#代碼結(jié)果:
<class 'bs4.BeautifulSoup'>
Comment
Comment對象也是一個(gè)特殊的NavigableString,讀取的內(nèi)容是注釋里面的內(nèi)容。把上面示例中的第一個(gè)a標(biāo)簽的內(nèi)容更改成如下:
<a href="link1.html" title="鏈接1"><!--Hello--></a>
print(soup.a.string)
print(type(soup.a.string))
#代碼結(jié)果:
Hello
<class 'bs4.element.Comment'>
注意:如果在標(biāo)簽內(nèi)的文本既有正常文字也有注釋,這時(shí)候string屬性就無法獲取到內(nèi)容:
<a href="link1.html" title="鏈接1">第一個(gè)鏈接<!--Hello--></a>
print(soup.a.string)
#代碼結(jié)果:
None
獲取文本內(nèi)容可以使用text方法,雖然text和string結(jié)果都是字符串,但是兩個(gè)對象其實(shí)并不相同。
<a href="link1.html" title="鏈接1">第一個(gè)鏈接<!--Hello--></a>
print(soup.a.text)
print(type(soup.a.text))
#代碼結(jié)果:
第一個(gè)鏈接
<class 'str'>
把HTML內(nèi)容解析成為一個(gè)BeautifulSoup對象后,對這個(gè)對象的操作才是BeautifulSoup這個(gè)模塊所能體驗(yàn)的強(qiáng)大之處。本身BeautifulSoup本身有著豐富的節(jié)點(diǎn)遍歷功能,包括父節(jié)點(diǎn)、子節(jié)點(diǎn)、子孫節(jié)點(diǎn)的獲取和逐個(gè)元素的遍歷。
不過在實(shí)際應(yīng)用上,我們使用遍歷的還是少數(shù),使用搜索的還是多數(shù),現(xiàn)在很多網(wǎng)頁中的元素很豐富,我們很少會把一個(gè)頁面中的所有內(nèi)容都獲取下來,基本是需要的重點(diǎn)內(nèi)容,這對于遍歷來說,搜索更加顯得便捷實(shí)用。
find_all()
說到搜索,最常使用的肯定是BeautifulSoup的find_all()方法,它會搜索當(dāng)前 tag 的所有 tag 子孫節(jié)點(diǎn),并判斷每個(gè)節(jié)點(diǎn)是否符合過濾器的條件。
find_all()方法的完整參數(shù)為find_all(name, attrs, recursive, text,limit, **kwargs):
name:標(biāo)簽名稱的過濾,支持正則
attrs:標(biāo)簽的屬性條件的過濾,支持正則;
recursive:bool選項(xiàng),如果為True,find_all()將遍歷所有節(jié)點(diǎn),否則只有子節(jié)點(diǎn),默認(rèn)為True;
text:標(biāo)簽中的文本過濾,;
limit:搜索限制過濾,如果不為空,表示找到指定數(shù)量的元素后將停止搜索,默認(rèn)為空,將搜索全部;
kwargs:表示可以添加多個(gè)屬性值參數(shù)過濾。
1.name參數(shù)
搜索所有a標(biāo)簽
links=soup.find_all('a')
print(links)
代碼結(jié)果:
[<a href="link1.html" title="鏈接1">第一個(gè)鏈接</a>, <a href="link2.html" title="鏈接2">第二個(gè)鏈接</a>, <a href="link3.html" title="鏈接3">第三個(gè)鏈接</a>]
搜索所有名字帶“a”標(biāo)簽
links=soup.find_all(re.compile(".*a.*"))
print(links)
代碼結(jié)果(head和a標(biāo)簽都符合)
[<head>
<title>實(shí)例HTML</title>
</head>, <a href="link1.html" title="鏈接1">第一個(gè)鏈接</a>, <a href="link2.html" title="鏈接2">第二個(gè)鏈接</a>, <a href="link3.html" title="鏈接3">第三個(gè)鏈接</a>]
2. arrts參數(shù)
搜索所有a標(biāo)簽中title值為“鏈接1”
links=soup.find_all('a', attrs={"title": "鏈接1"})
print(links)
代碼結(jié)果:
[<a href="link1.html" title="鏈接1">第一個(gè)鏈接</a>]
3. kwargs參數(shù):
搜索所有a標(biāo)簽中herf中帶“1”的標(biāo)簽
links=soup.find_all('a', href=re.compile(".*1.*"))
print(links)
代碼結(jié)果:
[<a href="link1.html" title="鏈接1">第一個(gè)鏈接</a>]
4. text參數(shù):
#搜索所有a標(biāo)簽中,文本帶“二”的標(biāo)簽
links=soup.find_all('a', text=re.compile(".*二.*"))
print(links)
代碼結(jié)果:
[<a href="link2.html" title="鏈接2">第二個(gè)鏈接</a>]
如果不加a標(biāo)簽,搜索的內(nèi)容則僅僅是文本。
#搜索所有a標(biāo)簽中,文本帶“二”的標(biāo)簽
links=soup.find_all('text=re.compile(".*二.*"))
print(links)
代碼結(jié)果:
['第二個(gè)鏈接']
5. limit參數(shù)
#搜索所有a標(biāo)簽中,超鏈接以link開頭,最多2個(gè)
links=soup.find_all('a', href=re.compile("link.*"), limit=2)
print(links)
代碼結(jié)果:
[<a href="link1.html" title="鏈接1">第一個(gè)鏈接</a>, <a href="link2.html" title="鏈接2">第二個(gè)鏈接</a>]
find()
find()方法相當(dāng)于給find_all()方法默認(rèn)添加limit=1,僅僅發(fā)揮符合條件的第一個(gè)Tag。方便有時(shí)候我們僅僅需要一個(gè)值的時(shí)候,直接可以調(diào)用。參數(shù)跟find_all()一樣,用法也是相同。
Beautiful Soup中用select()方法來CSS樣式的進(jìn)行篩選,當(dāng)然也可以篩選標(biāo)簽。在標(biāo)簽的屬性中,class的屬性就是當(dāng)前標(biāo)簽的CSS樣式,返回的結(jié)果同樣也是list。
1.通過標(biāo)簽名查找
查找所有a標(biāo)簽
links=soup.select('a')
print(links)
代碼結(jié)果:
[<a href="link1.html" title="鏈接1">第一個(gè)鏈接</a>, <a href="link2.html" title="鏈接2">第二個(gè)鏈接</a>]
2.通過CSS樣式類名查找
查找樣式類名為c1的標(biāo)簽
links=soup.select('.c1')
print(links)
代碼結(jié)果:
[<li class="c1"><a href="link1.html" title="鏈接1">第一個(gè)鏈接</a></li>]
3.通過標(biāo)簽屬性查找
查找屬性中href="link1.html"的a標(biāo)簽
links=soup.select('a[href="link1.html"]')
print(links)
代碼結(jié)果:
[<a href="link1.html" title="鏈接1">第一個(gè)鏈接</a>]
在標(biāo)簽+屬性組合中,屬性不支持正則表達(dá)式。
4.獲取查找到的內(nèi)容
除了以上集中還可以使用標(biāo)簽的id等元素來進(jìn)行查找,但是不管使用哪種方式,最終的是回去標(biāo)簽的內(nèi)容或者屬性中的值,那么找到相應(yīng)的標(biāo)簽后,怎么取值呢?如果是去標(biāo)簽屬性值,跟使用字典取值方式一樣。如果是獲取標(biāo)簽的文本,直接使用get_text()方法,可以獲取到標(biāo)簽的文本內(nèi)容。
查找屬性中href="link1.html"的a標(biāo)簽
links=soup.select('a[href="link1.html"]')
#打印標(biāo)簽中的超鏈接值
print(links[0][‘href])
#打印標(biāo)簽文本內(nèi)容
print(links[0].get_text())
代碼結(jié)果:
第一個(gè)鏈接
link1.html
不管是使用lxml還是Beautiful Soup,多數(shù)結(jié)果都是獲取文本內(nèi)容或者是標(biāo)簽的屬性值。文本內(nèi)容多數(shù)是需要獲取的內(nèi)容,整理下來放到list中,最后可能保存本地文件或者數(shù)據(jù)庫,而標(biāo)簽的中屬性值多數(shù)可以找到子鏈接(詳情鏈接),知道了怎么定位和獲取頁面的元素,下面我們就可以動手爬取頁面的內(nèi)容了。
eautifulsoup介紹:
第一步:安裝BeautifulSoup4,lxml
pip install BeautifulSoup4
BeautifulSoup 是一個(gè)可以從HTML或XML文件中提取數(shù)據(jù)的Python庫
pip install lxml
lxml 是一種使用 Python 編寫的解析庫,可以迅速、靈活地處理 XML 和 HTML
第二步:導(dǎo)包,from bs4 import BeautifulSoup
第三步:實(shí)例化對象
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>
<p class="story">...</p>
"""
soup=BeautifulSoup(html, 'lxml') # h:要解析的內(nèi)容 lxml:解析器
知識補(bǔ)充:print(soup.prettify()) # 代碼補(bǔ)全
第四步:打印
一、通過標(biāo)簽選取,會返回包含標(biāo)簽本身及其里面的所有內(nèi)容
# print(soup.head) # 包含head標(biāo)簽在內(nèi)的所有內(nèi)容
# print(soup.p) # 返回匹配的第一個(gè)結(jié)果
1.print(soup.head)打印結(jié)果:
<head>
<title>The Dormouse's story</title>
</head>
2.print(soup.p)打印結(jié)果:
<p class="title" name="dromouse"><b><span>The Dormouse's story</span></b></p>
二、打印標(biāo)簽中間的文本內(nèi)容,不包含<>
# .string是屬性,作用是獲取字符串文本
print(soup.html.head.title.string)
print(soup.title.string)
打印結(jié)果都為:The Dormouse's story
三、打印標(biāo)簽名
.name --獲取標(biāo)簽本身名稱
print(soup.title.name)
打印結(jié)果為:title
四、打印屬性的值
.attrs[] --通過屬性拿屬性的值
print(soup.p.attrs['name'])# 獲取p標(biāo)簽name屬性的屬性值
print(soup.a.attrs['id']) # 獲取p標(biāo)簽id屬性的屬性值
print(soup.a['id']) #第二種寫法
print(soup.p['class']) # 以列表得形式保存
print(soup.a['href']) # 也是只返回第一個(gè)值
1.print(soup.p.attrs['name'])打印結(jié)果:
dromouse
2.print(soup.p.attrs['id'])和print(soup.a['id'])打印結(jié)果:
link1
3.print(soup.p['class'])打印結(jié)果:
['title', 'asdas']
4.print(soup.a['href'])打印結(jié)果:
http://example.com/elsie
五、打印父標(biāo)簽下的所有子標(biāo)簽
.contents 獲取標(biāo)簽子節(jié)點(diǎn),以列表形式返回
.children 獲取子節(jié)點(diǎn),返回的是一個(gè)list類型的迭代器
print(soup.body.contents) # a是p的子節(jié)點(diǎn),獲取P標(biāo)簽所有子節(jié)點(diǎn)內(nèi)容 返回一個(gè)list
print(soup.body.children) #返回的是一個(gè)list類型的迭代器
1.print(soup.body.contents)的打印結(jié)果:
['\n', <p class="title asdas" name="dromouse"><b><span>The Dormouse's story</span></b></p>, '\n', <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>, '\n', <p class="story">...</p>, '\n']
2.print(soup.body.children)的打印結(jié)果:
<list_iterator object at 0x000002035ECC7088>
.children 獲取子節(jié)點(diǎn)講解
1.和for循環(huán)一起使用
for i in soup.p.children:
print(i)
打印結(jié)果:
*請認(rèn)真填寫需求信息,我們會在24小時(shí)內(nèi)與您取得聯(lián)系。