這是日常學python的第16篇原創文章
經過了前面幾篇文章的學習,估計你已經會爬不少中小型網站了。但是有人說,前面的正則很難唉,學不好。正則的確很難,有人說過:如果一個問題用正則解決,那麼就變成了兩個問題。所以說學不會是很正常的,不怕,除了正則,我們還可以用另外一個強大的庫來解析html。所以,今天的主題就是來學習這個強大的庫--BeautifulSoup,不過正則還是需要多多練習下的。
因為是第三方庫所以我們需要下載,在命令列敲下以下程式碼進行下載
pip install beautifulsoup4
安裝第三方解析庫
pip install lxml
pip install html5lib
如果不知道有什麼用請往下看
1.相關解析庫的介紹
這裡官方推薦解析庫為lxml,因為它的效率高。下面都是用lxml解析庫來進行解析的。
2.詳細語法介紹
本文是進行解析豆瓣圖書首頁book.douban.com/
1)建立bs物件
from bs4 import BeautifulSoup
import requests
response = requests.get('https://book.douban.com/').text
# print(response)
# 建立bs物件
soup = BeautifulSoup(response, 'lxml') # 使用到了lxml解析庫
2)獲取相關標籤
標籤:
<a data-moreurl-dict='{"from":"top-nav-click-main","uid":"0"}' href="https://www.douban.com" target="_blank">豆瓣</a>
上面的a就是一個標籤名字,最簡單的就是<a></a>這樣,可以簡單理解為<>裡面的第一個單詞就是標籤名
# 獲取標籤
print(soup.li) # 這個只是獲取第一個li標籤
# 結果
<li class="">
<a data-moreurl-dict='{"from":"top-nav-click-main","uid":"0"}' href="https://www.douban.com" target="_blank">豆瓣</a>
</li>
3)獲取標籤的名字和內容
標籤的名字和內容:
<a >豆瓣</a>
如上面所說,a就是標籤名字,而兩個標籤之中所夾雜的內容就是我們所說的內容,如上,豆瓣就是該標籤的內容
# 獲取標籤名字
print(soup.li.name)
# 獲取標籤內容
print(soup.li.string) # 這個只能是這個標籤沒有子標籤才能正確獲取,否則會返回None
# 結果
li
None
由於這個li標籤裡面還有個子標籤,所以它的文字內容為None
下面這個就可以獲取它的文字內容
# 獲取標籤內的標籤
print(soup.li.a)
print(soup.li.a.string) # 這個標籤沒有子標籤所以可以獲取到內容
# 結果
<a data-moreurl-dict='{"from":"top-nav-click-main","uid":"0"}' href="https://www.douban.com" target="_blank">豆瓣</a>
豆瓣
4)獲取標籤屬性,有兩種方法
標籤屬性:
<a href="https://www.douban.com" target="_blank">豆瓣</a>
可以簡單理解為屬性就是在標籤名字旁邊而且在前一個<>符號裡面的,還有是有等號來進行體現的。所以上面的href就是標籤屬性名字,等號右邊的就是屬性的值,上面的值是個網址
# 獲取標籤屬性
print(soup.li.a['href']) # 第一種
print(soup.li.a.attrs['href']) # 第二種
# 結果
https://www.douban.com
https://www.douban.com
5)獲取標籤內的子標籤
子標籤:
<li><a>豆瓣</a></li>
比如我們現在獲取的li標籤,所以a標籤就是li標籤的子標籤
# 獲取標籤內的標籤
print(soup.li.a)
# 結果
<a data-moreurl-dict='{"from":"top-nav-click-main","uid":"0"}' href="https://www.douban.com" target="_blank">豆瓣</a>
6)獲取所有子節點
子節點:這個和子標籤是差不多的,只不過這裡是獲取一個標籤下的所有子標籤,上面的只是獲取最接近該標籤的子標籤
# 獲取子節點
print(soup.div.contents) # 返回一個列表 第一種方法
for n, tag in enumerate(soup.div.contents):
print(n, tag)
# 結果
['\n', <div class="bd">
<div class="top-nav-info">
<a class="nav-login" href="https://www.douban.com/accounts/login?source=book" rel="nofollow">登入</a>
...
0
1 <div class="bd">
<div class="top-nav-info">
...
這個是獲取div下的所有子節點,.content就是獲取子節點的屬性
7)第二種方法獲取所有子節點
# 第二種方法
print(soup.div.children) # 返回的是一個迭代器
for n, tag in enumerate(soup.div.children):
print(n, tag)
這個是用.children獲取所有的子節點,這個方法返回的是一個迭代器
8)獲取標籤的子孫節點,就是所有後代
子孫節點:
<ul>
<li>
<a>豆瓣</a>
</li>
</ul>
從上面知道,li標籤是ul標籤的子標籤,a標籤是li標籤的子標籤,若此時我們獲取的是ul標籤,所以li標籤和a標籤都是ul標籤的子孫節點
# 獲取標籤的子孫節點
print(soup.div.descendants) # 返回的是一個迭代器
for n, tag in enumerate(soup.div.descendants):
print(n, tag)
# 結果
...
<generator object descendants at 0x00000212C1A1E308>
0
1 <div class="bd">
<div class="top-nav-info">
<a class="nav-login" href="https://www.douban.com/accounts/login?source=book" rel="nofollow">登入</a>
...
這裡用到了.descendants屬性,獲取的是div標籤的子孫節點,而且返回結果是一個迭代器
9)獲取父節點和所有祖先節點
既然有了子節點和子孫節點,反過來也是有父節點和祖先節點的,所以都很容易理解的
# 獲取父節點
print(soup.li.parent) # 返回整個父節點
# 獲取祖先節點
print(soup.li.parents) # 返回的是一個生成器
for n, tag in enumerate(soup.li.parents):
print(n, tag)
.parent屬性是獲取父節點,返回來的是整個父節點,裡面包含該子節點。.parents就是獲取所有的祖先節點,返回的是一個生成器
10)獲取兄弟節點
兄弟節點:
<ul>
<li>
<a>豆瓣1</a>
</li>
<li>
<a>豆瓣2</a>
</li>
<li>
<a>豆瓣3</a>
</li>
</ul>
比如上面的html程式碼,裡面的li標籤都是ul標籤的子節點,而li標籤都是處於同級的,所以上面的li標籤都是各自的兄弟。這就是兄弟節點。
# 獲取兄弟節點
print(soup.li.next_siblings) # 獲取該標籤的所有同級節點,不包括本身 返回的是一個生成器
for x in soup.li.next_siblings:
print(x)
# 結果
<generator object next_siblings at 0x000002A04501F308>
<li class="on">
<a data-moreurl-dict='{"from":"top-nav-click-book","uid":"0"}' href="https://book.douban.com">讀書</a>
</li>
...
.next_siblings屬性是獲取該標籤的所有在他後面的兄弟節點,不包括他本身。同時返回結果也是一個迭代器
同理,既然有獲取他的下一個所有兄弟標籤,也有獲取他前面的所有兄弟標籤
soup.li.previous_siblings
如果只是獲取一個即可,可以選擇把上面的屬性後面的s字母去掉即可,如下
soup.li.previous_sibling # 獲取前一個兄弟節點
soup.li.next_sibling # 獲取後一個兄弟節點
3.bs庫的更高階的用法
在前面我們可以獲取標籤的名字、屬性、內容和所有的祖孫標籤。但是當我們需要獲取任意一個指定屬性的標籤還是有點困難的,所以,此時有了下面這個方法:
soup.find_all( name , attrs , recursive , text , **kwargs )
name:需要獲取的標籤名
attrs:接收一個字典,為屬性的鍵值,或者直接用關鍵字引數來替代也可以,下面
recursive:設定是否搜尋直接子節點
text:對應的字串內容
limit:設定搜尋的數量
1)先使用name引數來進行搜尋
# 先使用name引數
print(soup.find_all('li')) # 返回一個列表,所有的li標籤名字
# 結果
[<li class="">
<a data-moreurl-dict='{"from":"top-nav-click-main","uid":"0"}' href="https://www.douban.com" target="_blank">豆瓣</a>
</li>, <li class="on">
...
這裡獲取了所有標籤名字為li的標籤
2)使用name和attrs引數
# 使用name和attrs引數
print(soup.find_all('div', {'class': 'more-meta'})) # 這個對上個進行了篩選,屬性引數填的是一個字典型別的
# 結果
[<div class="more-meta">
<h4 class="title">
刺
</h4>
...
這裡搜尋了具有屬性為class='more-meta'的div標籤
3)根據關鍵字引數來搜尋
# 對相關屬性進行進行查詢也可以這樣
print(soup.find_all(class_='more-meta')) # 使用關鍵字引數,因為class是python關鍵字,所以關鍵字引數時需要加多一個下劃線來進行區別
# 結果
和上面的結果一樣
...
這裡注意,我們找的是class屬性為more-meta的標籤,用了關鍵字引數,但是python裡面有class關鍵字,所以為了不使語法出錯,所以需要在class加個下劃線
其他引數的就不再介紹了,可以自行去官網檢視
4)find()方法
此方法與find_all()方法一樣,只不過這個方法只是查詢一個標籤而已,後者是查詢所有符合條件的標籤。
還有很多類似的方法,用法都差不多,就不再一一演示了,需要的可以去官網檢視
5)select()方法
這個方法是使用css選擇器來進行篩選標籤的。
css選擇器:就是根據標籤的名字,id和class屬性來選擇標籤。
通過標籤名:直接寫該標籤名,如 li a ,這個就是找li標籤下的a標籤
通過class屬性:用. 符號加class屬性值,如 .title .time 這個就是找class值為title下的class值為time的標籤
通過id屬性:用# 加id屬性值來進行查詢,如 #img #width 這個就是找id值為img下的id值為width的標籤
上面三者可以混合使用,如 ul .title #width
如果還不太會的話,可以直接在瀏覽器上按下f12來檢視
位置在箭頭所指的位置就是選擇器的表達
程式碼如下
# 還可以用標籤選擇器來進行篩選元素, 返回的都是一個列表
print(soup.select('ul li div')) # 這個是根據標籤名進行篩選
print(soup.select('.info .title')) # 這個是根據class來進行篩選
print(soup.select('#footer #icp')) # 這個是根據id來進行篩選
# 上面的可以進行混合使用
print(soup.select('ul li .cover a img'))
這裡的獲取屬性和文字內容
# 獲取屬性
for attr in soup.select('ul li .cover a img'):
# print(attr.attrs['alt'])
# 也可以這樣
print(attr['alt'])
# 獲取標籤的內容
for tag in soup.select('li'):
print(tag.get_text()) # 裡面可以包含子標籤,會將子標籤的內容連同輸出
.get_tex()方法和前面的.string屬性有點不一樣哈,這裡的他會獲取該標籤的所有文字內容,不管有沒有子標籤
寫在最後
以上的這些都是個人在學習過程中做的一點筆記。還有點不足,如果有錯誤的話歡迎大佬指出哈。如果想要檢視更多相關用法可以去官方文件檢視:beautifulsoup.readthedocs.io/zh_CN/lates…
學習參考資料:https://edu.hellobi.com/course/157
如果這篇文章對你有用,點個贊,轉個發如何?
還有,祝大家今天愚人節快樂
◐◑爬取《The Hitchhiker’s Guide to Python!》python進階書並製成pdf
日常學python
程式碼不止bug,還有美和樂趣