BeautifulSoup全面總結
(註:網頁解析庫的代碼都比較通俗易懂,看理論講解不如直接看代碼,自己多寫就能對常用方法瞭然於胸。本文是從整體框架上進行總結,更適合在對庫有基本的了解之後再詳細跟著文章思路查缺補漏。所以建議本文閱讀順序為:先不看文字,挑代碼來看(這時挑簡單的來看,看不懂的代碼不要管),知道那些代碼都是做什麼的,自己動手寫,多試錯,然後看後面的實戰。了解庫的基本使用流程之後,能自己解析一些東西了,再跟著文章的思路把細節補上,之後就能運用自如了。)
beautifulsoup庫應該是初學爬蟲聽的最多的一個解析庫了,本文就來講解一下這個庫的用法。
本文分為如下幾個部分
- 解析html流程說明
- 識別標籤
- 提取內容
- 查看標籤信息
- 其他提取細節
解析html流程說明
爬取網頁信息,可以理解成從html代碼中抽取我們需要的信息,而html代碼由眾多標籤組成,我們要做的就分為兩個部分
- 精確定位標籤
- 從標籤中提取內容
先說第二個,在找到對應標籤位置後,我們要的信息一般會存儲在標籤的兩個位置中
- 標籤中的內容
- 屬性值
比如下面這行標籤
<p><a href=www.wenzi.com>文字</a></p>
一般要提取「文字」部分,或者那個鏈接www.wenzi.com
部分
那麼如何精確定位到標籤呢?只能依靠標籤名和標籤屬性來識別。下面一個例子提供識別的大致思路,之後會具體總結
<title>標題</title><body> <ul class=list1> <li>列表1第1項</li> <li>列表1第2項</li> </ul> <p class=first>文字1</p> <p class=second>文字2</p> <ul class=list2> <li>列表2第1項</li> <li>列表2第2項</li> </ul></body>
- 如果要提取「標題」,只需要使用
title
標籤名來識別,因為只出現過一次title
標籤 - 如果要提取「文字1」,不能只使用
p
標籤,因為「文字2」也對應了p
標籤,所以要用p
標籤且class
屬性是second
來識別 - 如果「文字1」和「文字2」都要,就可以循環獲取所有
p
標籤提取內容 - 如果想提取列表1中的兩項,就不能直接循環獲取
li
標籤了,因為列表2中也有li
標籤。此時需要先識別其父節點,先定位到<ul class=list1>
這個標籤上(通過ul
標籤和class
屬性是list1
定位)。此時在這個標籤里,所有li
都是我們想要的了,直接循環獲取就可以了
這裡展示一下使用beautifulsoup實現上述提取的代碼,先對這個庫的提取思路有一個大概的印象
a = <title>標題</title><body> <ul class=list1> <li>列表1第1項</li> <li>列表1第2項</li> </ul> <p class=first>文字1</p> <p class=second>文字2</p> <ul class=list2> <li>列表2第1項</li> <li>列表2第2項</li> </ul></body>from bs4 import BeautifulSoupsoup = BeautifulSoup(a, "html.parser")soup.title.text # 標題soup.find(p, attrs={class:first}).text # 文字1soup.find_all(p) # [<p class="first">文字1</p>, <p class="second">文字2</p>], 再分別從中提取文字soup.find(ul, attrs={class:list1}).find_all(li) # [<li>列表1第1項</li>, <li>列表1第2項</li>]
識別的大致思路就是這樣。
從細節上看,一個完整解析庫需要實現三個部分的功能
從提取內容上看
- 提取標籤內容
- 提取標籤屬性值
從識別標籤上看
- 只根據標籤來識別
- 找到名為a的標籤(只有一個a標籤時)
- 找到所有名為a的標籤
- 找到名為a或b的標籤
- 根據正則表達式提取標籤
- 同時根據標籤和屬性識別
- (要求在能識別標籤的前提下,能相似地識別屬性)比如
- 找到 標籤名為a且B屬性的值是b 的標籤
- 找到 標籤名為a且B屬性的值是b且C屬性是c 的標籤
- 找到 標籤名為a且B屬性的值是b或c 的標籤
- 找到 標籤名為a且(不)包含B屬性 的標籤
- 找到 標籤名為a且B屬性的值包含b字元串(各種正則式匹配) 的標籤
- 根據標籤內內容來識別,也是類似如上劃分,最好能用正則表達式匹配內容
- 根據位置識別
- 找到 第i個a標籤
- 找到 第i個和第j個a標籤
提供一些查看當前標籤信息的方法,方便調試
- 獲得標籤名
- 獲得標籤所有屬性及值
- 檢查標籤是否有某屬性
除此之外這個庫還有一些提取細節
- 多層嵌套標籤問題
find_all
的簡寫方法find_all
的其他參數
下面按照上述實現的功能順序來講解
(其實到最後會發現這個庫唯一一個要重點掌握的方法是find_all
)
首先載入庫
from bs4 import BeautifulSoup
識別標籤
本節分為如下幾個部分
- 只根據標籤來識別
- 根據標籤和屬性識別
- 根據標籤內內容來識別
- 根據位置識別
只根據標籤來識別
這部分分為如下幾種情況
- 找到名為a的標籤(查找唯一標籤)
- 找到所有名為a的標籤
- 找到名為a或b的標籤
- 根據正則表達式提取標籤
查找唯一標籤時,beautifulsoup庫中提供三種方法
- 對象的屬性調用,直接提取該名字的標籤,但是如果有很多該標籤只能找到第一個
find
方法,也只能找到第一個find_all
方法,找到所有該標籤,返回一個list,如果只找到一個也是返回list,用[0]
提取
a = <h1>標題1</h1><h2>標題2</h2><h2>標題3</h2># 將提取到的字元串轉化為beautifulsoup的對象soup = BeautifulSoup(a, "html.parser")# 提取唯一標籤soup.h1soup.find(h1)soup.find_all(h1)[0]# 上面三條結果都是# <h1>標題1</h1>
如果要找到所有某標籤,或者某幾種標籤及根據正則表達式提取,只能用find_all
,返回一個列表
soup.find_all(h2)# [<h2>標題2</h2>, <h2>標題3</h2>]soup.find_all([h1,h2])# [<h1>標題1</h1>, <h2>標題2</h2>, <h2>標題3</h2>]# 使用正則表達式import resoup.find_all(re.compile(^h))# [<h1>標題1</h1>, <h2>標題2</h2>, <h2>標題3</h2>]
根據標籤和屬性識別
這部分分為如下幾種情況
- (要求在能識別標籤的前提下,能用相似的方法識別屬性)比如
- 找到 標籤名為a且B屬性的值是b 的標籤
- 找到 標籤名為a且B屬性的值是b且C屬性是c 的標籤
- 找到 標籤名為a且B屬性的值是b或c 的標籤
- 找到 標籤名為a且(不)包含B屬性 的標籤
- 找到 標籤名為a且B屬性的值包含b字元串(各種正則式匹配) 的標籤
要參考屬性提取標籤時,只有find
和find_all
兩種方法,find
總是返回找到的第一個,而find_all
會返回所有,如果想要第一個就提取即可,因此這裡全用find_all
來講,其實只是加一個attrs
參數
a = <p id=p1>段落1</p><p id=p2>段落2</p><p class=p3>段落3</p><p class=p3 id=pp>段落4</p>soup = BeautifulSoup(a, "html.parser")# 第一種,直接將屬性名作為參數名,但是有些屬性不行,比如像a-b這樣的屬性soup.find_all(p, id = p1) # 一般情況soup.find_all(p, class_=p3) # class是保留字比較特殊,需要後面加一個_# 最通用的方法soup.find_all(p, attrs={class:p3}) # 包含這個屬性就算,而不是只有這個屬性soup.find_all(p, attrs={class:p3,id:pp}) # 使用多個屬性匹配soup.find_all(p, attrs={class:p3,id:False}) # 指定不能有某個屬性soup.find_all(p, attrs={id:[p1,p2]}) # 屬性值是p1或p2# 正則表達式匹配import resoup.find_all(p, attrs={id:re.compile(^p)}) # 使用正則表達式soup.find_all(p, attrs={class:True}) # 含有class屬性即可
根據標籤內內容來識別
這部分還是使用find_all
函數,增加text
參數
a = <p id=p1>段落1</p><p class=p3>段落2</p><p class=p3>文章</p><p></p>soup = BeautifulSoup(a, "html.parser")soup.find_all(p, text=文章)soup.find_all(p, text=[段落1,段落2])# 正則表達式import resoup.find_all(p, text=re.compile(段落))soup.find_all(p,text=True)# 傳入函數def nothing(c): return c not in [段落1,段落2,文章]soup.find_all(p,text=nothing)# 同上def nothing(c): return c is Nonesoup.find_all(p,text=nothing)
最後使用的傳入函數的方法,不止在這裡識別內容上可以這樣用,在識別標籤名和屬性時都可以使用。都是輸入要判斷東西返回布爾值,結果是True的才會被選中。只是函數定義時有點不同
- 第一個參數識別標籤,函數的參數應該是標籤,比如這樣用
def has_class_but_no_id(tag): return tag.has_attr(class) and not tag.has_attr(id)
attrs
參數可以字典中的屬性鍵的值中使用函數,傳入字元串text
參數也是傳入字元串
根據位置識別
- 找到 第i個a標籤
- 找到 第i個和第j個a標籤
有時三個標籤的標籤屬性全都一樣,所有東西都一樣(內容可能不一樣,但是類型是一樣的),但是我們只想要第二個,這時就不能只通過標籤屬性內容這些方法提取了,可能它的位置是特殊的就可以用位置來提取。這裡其實可以用find_all
提取出列表,然後在列表中根據位置再提取一次
提取內容
本節分為兩個部分
- 提取標籤內容:使用
.text
- 提取標籤屬性值,像字典一樣提取
a = <body> <h><a href=www.biaoti.com>標題</a></h> <p>段落1</p> <p>段落2</p></body>soup = BeautifulSoup(a, html.parser)# 提取內容soup.p.textfor p in soup.find_all(p): print(p.text)soup.h.text # 多層嵌套也可以直接返回soup.h.a.text # 也可以這樣soup.body.text # 裡面有多個內容時
標題
段落1
段落2
# 提取屬性值,像字典一樣提取,以下兩種方法等價soup.h.a[href]soup.h.a.get(href)
查看標籤信息
本節分為如下內容
- 獲得標籤名
- 獲得標籤所有屬性的字典
- 檢查標籤是否有某屬性
a = <body> <h><a href=www.biaoti.com>標題</a></h> <p>段落1</p> <p></p></body>soup = BeautifulSoup(a, html.parser)for i in soup.body.find_all(True): print(i.name) # 提取標籤名 print(i.attrs) # 提取標籤所有屬性值 print(i.has_attr(href)) # 檢查標籤是否有某屬性
其他提取細節
本節包括以下幾個部分
- 多層嵌套標籤時的
find_all
——滲透到所有層次子節點全部提取出來 find_all
的簡寫方法——可以省去find_all
這8個字母find_all
的其他參數- 通過css選擇器來提取
- 庫中的其他函數
示例代碼如下
a = <span> <span id=s>內容1</span></span><span>內容2</span>soup = BeautifulSoup(a, html.parser)# 多層嵌套標籤soup.find_all(span) # 3個元素 # [<span> # <span>內容1</span> # </span>, <span>內容1</span>, <span>內容2</span>]# find_all的簡寫,標籤直接加括弧soup.span(span,id=s) # 相當於調用find_all返回list# find_all的其他參數soup.find_all(span, limit=2) # 限制只返回前兩個soup.find_all(span, recursive=False) # 只查找子節點,不查找孫節點
總結下來,Beautifulsoup庫其實主要使用find_all
方法進行網頁解析,審查不同元素使用不同參數即可實現,不同參數使用方法都相同,無論是列表、正則pattern還是函數都支持,讓提取信息非常得心應手。
除此之外,Beautifulsoup庫還提供了css選擇器的select
方法,這裡不多講,用css選擇器提取的方法會在後面的pyquery庫中講解。
另外,對於庫中的其他方法做如下說明:
findAll
是之前版本的find_all
,如果看到有其他教程用這個不要覺得奇怪- 庫中還提供了一些查詢兄弟節點等查詢方法,還有對html代碼進行修改的方法(有時不要的部分才有特點,就可以把不要的部分替換掉,就可以自由提取想要的了),詳情可以見官網
- 解析器的選擇
BeautifulSoup(a, "html.parser")
這裡的第二個參數表示使用的解析器,BeautifulSoup提供了三個解析器,它們各自的優缺點如下
html.parser
內置不依賴擴展,容錯能力強,速度適中lxml
速度最快,容錯能力強,但是依賴C擴展html5hib
速度最慢,容錯能力最強,依賴擴展
它們不僅在這些性能方面有些許差異,而且在對文檔的處理上也有差異,雖然這些差異很多時候不影響我們解析,詳情見官網
使用beautifulsoup抓取網頁實戰可以看這篇文章 beautifulsoup+json抓取stackoverflow實戰
專欄信息
專欄主頁:python編程
專欄目錄:目錄
爬蟲目錄:爬蟲系列目錄
版本說明:軟體及包版本說明
推薦閱讀:
※四、BeautifulSoup庫
※初識Scrapy,在充滿爬蟲的世界裡做一個好公民
※輪帶逛終極版! 抓取輪子哥(vczh)全部知乎動態
※八、示例
※爬蟲 get 請求返回的內容是byte類型,如何轉為漢字?在線等