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字元串(各種正則式匹配) 的標籤

要參考屬性提取標籤時,只有findfind_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類型,如何轉為漢字?在線等

TAG:Python | python爬蟲 | 爬蟲計算機網路 |