1. 程式人生 > >Python從零開始寫爬蟲(二)BeautifulSoup庫使用

Python從零開始寫爬蟲(二)BeautifulSoup庫使用

Beautiful Soup 是一個可以從HTML或XML檔案中提取資料的Python庫, BeautifulSoup在解析的時候是依賴於解析器的,它除了支援Python標準庫中的HTML解析器,還支援一些第三方的解析器比如lxml等。可以從其官網得到更詳細的資訊:http://beautifulsoup.readthedocs.io/zh_CN/latest/#。在windows環境下,直接通過pip install beautifulsoup4安裝使用它把,同時也可以將lxml解析庫安裝上pip install lxml

一、BeautifulSoup基礎

1、開啟檔案

如下程式碼所示,用lxml方式解析檔案,也可以使用html.parser方式。如果擔心使用的檔案不規則,可以使用soup.prettify()

方法來格式化文件。

# coding=utf-8

import requests
from bs4 import BeautifulSoup

#建立BeautifulSoup物件,開啟檔案
r = requests.get('http://music.163.com/')
soup = BeautifulSoup(r.text, "lxml")
print(soup.title)
#<title>網易雲音樂</title>

soup2 = BeautifulSoup(open("D:\music.html",encoding="utf-8",errors="ignore"
),"lxml") print(soup2.title) #<title>網易雲音樂</title>

2、四大物件

Beautiful Soup將複雜HTML文件轉換成一個複雜的樹形結構,每個節點都是Python物件,所有物件可以歸納為4種: Tag , NavigableString , BeautifulSoup , Comment .

Tag物件:
Tag物件和xml、html原生文件中的tag相同。tag就是一個個標籤,如下面html片段當中的meta、title等等。

<meta name="baidu-site-verification"
content="cNhJHKEzsD" />
<meta charset="utf-8"> <meta property="qc:admins" content="27354635321361636375" /> <title>網易雲音樂</title> <meta name="applicable-device" content="pc,mobile"> <script type="text/javascript">

使用BeautifulSoup物件,可以方便的獲取tag,如下所示:

soup2 = BeautifulSoup(open("D:\\test.html",encoding="utf-8",errors="ignore"),"lxml")
#獲取meta這個tag物件
tag = soup2.meta
print(type(tag))
#<class 'bs4.element.Tag'>

可以看到,很方便的便獲取到了Tag物件,Tag物件有兩個重要的屬性name和attrs,怎麼獲取這兩個屬性呢?使用print(tag.name)、print(tag.attrs)即可,其輸出為:

meta
{'name': 'baidu-site-verification', 'content': 'cNhJHKEzsD'}

可以看見,預設輸出了第一個meta tag的資料,屬性是以dictionary形式輸出的,獲取特定的屬性就可以用字典的相關方法了:

print(tag.attrs["name"])
print(tag.attrs.get("content"))
#輸出為:
#baidu-site-verification
#cNhJHKEzsD

NavigableString物件:
這個物件呢,就是用來獲取tag中的文字的,還是用上面的例子:

tag = soup2.title
print(tag.string)
print(type(tag.string))
#網易雲音樂
#<class 'bs4.element.NavigableString'>

這樣我們就輕鬆獲取到了標籤裡面的內容。如果tag包含了多個子節點tag就無法確定 .string 方法應該呼叫哪個子節點的內容, .string 的輸出結果是 None。這時可以使用 .strings 來迴圈獲取。

BeautifulSoup物件:
BeautifulSoup 物件表示的是一個文件的全部內容.大部分時候,可以把它當作 Tag 物件,是一個特殊的 Tag。比如上面使用BeautifulSoup物件呼叫文件中的tag資訊。因為 BeautifulSoup 物件並不是真正的HTML或XML的tag,所以它沒有name和attribute屬性.但有時檢視它的 .name 屬性是很方便的,所以 BeautifulSoup 物件包含了一個值為 “[document]” 的特殊屬性 .name:

soup2 = BeautifulSoup(open("D:\\test.html",encoding="utf-8",errors="ignore"),"lxml")
print(soup2.name)
#[document]

Comment 物件:
Tag , NavigableString , BeautifulSoup 幾乎覆蓋了html和xml中的所有內容,但是還有一些特殊物件.就像文件的註釋部分。比如文件中含有這樣一行:<b><!--Hey, buddy. Want to buy a used parser?--></b>,使用以下程式碼來檢視輸出:

b_tag = soup2.b
print(b_tag.string)
print(type(b_tag.string))
#輸出為:
#Hey, buddy. Want to buy a used parser?
#<class 'bs4.element.Comment'>

可以看到,輸出的僅僅是註釋的文字部分,已經把註釋符號去掉了,要注意這個小細節。

二、遍歷文件樹

1、直接子節點

一個Tag可能包含多個字串或其它的Tag,這些都是這個Tag的子節點.Beautiful Soup提供了許多操作和遍歷子節點的屬性。

.contents 和 .children

tag的 .contents 屬性可以將tag的子節點以列表的方式輸出:

head_tag = soup.head
#輸出head tag中的子節點的個數
print(len(head_tag.contents))
#子節點的內容,列表形式返回
print(head_tag.contents)

tag的 .children屬性可以將tag的子節點迴圈輸出:

for child in soup.head.children:
    print(child)

遇到的問題,下面這個文件執行上面程式碼的輸出和期望的不太一致:

<html>
<head>
<meta charset="utf-8"/>
<meta name="baidu-site-verification" content="cNhJHKEzsD"/>
<meta property="qc:admins" content="27354635321361636375"/>
</head>
</html>

輸出為:

#為什麼會有那麼多換行符?
7
['\n', <meta charset="utf-8"/>, '\n', <meta content="cNhJHKEzsD" name="baidu-site-verification"/>, '\n', <meta content="27354635321361636375" property="qc:admins"/>, '\n']

2、所有子節點

.contents 和 .children 屬性僅包含tag的直接子節點,.descendants 屬性可以對所有tag的子孫節點進行遞迴迴圈。


3、節點內容

單節點:如果tag只有一個 NavigableString 型別子節點,那麼這個tag可以使用 .string 得到子節點。

多節點:如果tag中包含多個字串 ,可以使用 .strings 來迴圈獲取,使用 .stripped_strings 可以去除多餘空白內容。


4、其他節點

其他各種節點有很多,不逐個解釋了,這裡只列出它們:

節點名稱 屬性名稱
父節點 .parent 屬性
全部父節點 .parents 屬性
兄弟節點 .next_sibling .previous_sibling 屬性
全部兄弟節點 .next_siblings .previous_siblings 屬性
前後節點 .next_element .previous_element 屬性(針對所有節點,部分層次)
所有前後節點 .next_elements .previous_elements 屬性

~

這些屬性的使用和前面子節點的使用類似,後面會陸續用到。

三、搜尋文件樹

BeautifulSoup定義了很多搜尋方法,這裡著重介紹find_all() ,其它方法的引數和用法類似。

find

find_all( name , attrs , recursive , string , **kwargs )

find_all() 方法搜尋當前tag的所有tag子節點,並判斷是否符合過濾器的條件。常用的過濾器的型別有以下幾種:

  • 字串: 最簡單的過濾器是字串.在搜尋方法中傳入一個字串引數,Beautiful Soup會查詢與字串完整匹配的內容
  • 正則表示式: 如果傳入正則表示式作為引數,Beautiful Soup會通過正則表示式的 match() 來匹配內容
  • 列表:如果傳入列表引數,Beautiful Soup會將與列表中任一元素匹配的內容返回
  • True:True 可以匹配任何值,下面程式碼查詢到所有的tag,但是不會返回字串節點
  • 方法:如果沒有合適過濾器,那麼還可以定義一個方法,方法只接受一個元素引數 ,如果這個方法返回 True 表示當前元素匹配並且被找到,如果不是則反回 False

下面介紹一下find_all()方法中的引數:

1、name 引數:
可以查詢所有名字為 name 的tag,字串物件會被自動忽略掉。搜尋 name 引數的值可以使任一型別的 過濾器 ,字串,正則表示式,列表,方法或是 True。

3、recursive 引數:
呼叫tag的 find_all() 方法時,Beautiful Soup會檢索當前tag的所有子孫節點,如果只想搜尋tag的直接子節點,可以使用引數 recursive=False。

r = requests.get('http://music.163.com/#/discover/toplist')
soup = BeautifulSoup(r.text, "lxml")
print(soup.title)
#<title>網易雲音樂</title>
print(soup.html.find_all("title",recursive = False))
#[]

只有 find_all() 和 find() 支援 recursive 引數。

4、string 引數:
通過 string 引數可以搜搜文件中的字串內容.與 name 引數的可選值一樣, string 引數接受 字串 , 正則表示式 , 列表, True。

5、keyword 引數(kwargs ):
如果一個指定名字的引數不是搜尋內建的引數名,搜尋時會把該引數當作指定名字tag的屬性來搜尋,如果包含一個名字為 id 的引數,Beautiful Soup會搜尋每個tag的”id”屬性。

按CSS搜尋:從Beautiful Soup的4.1.1版本開始,可以通過 class_ 引數搜尋有指定CSS類名的tag。可以通過CSS值完全匹配,也可以使用各種過濾。

limit 引數:
使用 limit 引數限制返回結果的數量.效果與SQL中的limit關鍵字類似,當搜尋到的結果數量達到 limit 的限制時,就停止搜尋返回結果。

find_all() 的簡寫方法: find_all幾乎是Beautiful Soup中最常用的搜尋方法,所以我們定義了它的簡寫方法. BeautifulSoup 物件和 tag 物件可以被當作一個方法來使用,這個方法的執行結果與呼叫這個物件的 find_all() 方法相同,下面兩行程式碼是等價的:

soup.find_all("a")
soup("a")

這兩行程式碼也是等價的:

soup.title.find_all(string=True)
soup.title(string=True)

find()與 find_all() 方法的區別是 find_all() 方法的返回結果是值包含一個元素的列表,而 find() 方法直接返回結果。

四、CSS選擇器

Beautiful Soup支援大部分的CSS選擇器 ,在 Tag 或 BeautifulSoup 物件的 .select() 方法中傳入字串引數, 即可使用CSS選擇器的語法找到tag。
CSS選擇器的語言可以參考筆者之前的文章:Selenium自動化測試-入門
或者官方資料: CSS 選擇器參考手冊