【Python3 爬蟲學習筆記】解析庫的使用 3 —— Beautiful Soup 1
Beautiful Soup可以藉助網頁的結構和屬性等特性來解析網頁。有了Beautiful Soup,我們不用再去寫一些複雜的正則表示式,只需要簡單的幾條語句,就可以完成網頁中某個元素的提取。
Beautiful Soup是Python的一個HTML或XML的解析庫,可以用它來方便地從網頁中提取資料。官方解釋如下:
Beautiful Soup提供一些簡單的、Python式的函式來處理導航、搜尋、修改分析樹等功能。它是一個工具箱,通過解析文件為使用者提供需要抓取的資料,因為簡單,所以不需要多少程式碼就可以寫出一個完整的應用程式。
Beautiful Soup自動將輸入文件轉換為Unicode編碼,輸出文件轉換為UTF-8編碼。你不需要考慮編碼方式,除非文件沒有指定一個編碼方式,這時你僅僅需要說明一下原始編碼方式就可以了。
Beautiful Soup已成為和lxml、html6lib一樣出色的Python直譯器,為使用者靈活地提供不同的接卸策略或強勁的速度。
Beautiful Soup在解析時實際上依賴解析器,它除了支援Python標準庫中的HTML解析器外,還支援一些第三方解析器(比如lxml)。
解析器 | 使用方法 | 優勢 | 劣勢 |
---|---|---|---|
Python標準庫 | BeautifulSoup(markup,“html.parser”) | Python的內建標準庫、執行速度適中、文件容錯能力強 | Python2.7.3及Python3.2.2之前的版本文件容錯能力差 |
lxml HTML解析器 | BeautifulSoup(markup,“lxml”) | 速度快、文件容錯能力強 | 需要安裝C語言庫 |
lxml XML解析器 | BeautifulSoup(markup,“xml”) | 速度快、唯一支援XML的解析器 | 需要安裝C語言庫 |
html5lib | BeautifulSoup(markup,“html5lib”) | 最好的容錯性、以瀏覽器的方式解析文件、生成HTML5格式的文件 | 速度慢、不依賴外部擴充套件 |
通過以上對比可以看出,lxml解析器有解析HTML和XML的功能,而且速度快,容錯能力強。
一個例項展示Beautiful Soup的基本用法:
html = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title" name="dromouse"><b>The Dormouse's</b></p>
<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"><!-- Elsie --></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>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.prettify())
print(soup.title.string)
執行結果如下:
<html>
<head>
<title>
The Dormouse's story
</title>
</head>
<body>
<p class="title" name="dromouse">
<b>
The Dormouse's
</b>
</p>
<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>
<p class="story">
...
</p>
</body>
</html>
The Dormouse's story
這裡首先宣告變數html,它是一個HTML字串。但是需要注意的是,它並不是一個完成的HTML字串,因為body和html節點都沒有閉合。接著,我們將它當做第一個引數傳給BeautifulSoup物件,該物件的第二個引數為解析器的型別(這裡使用lxml),此時就完成了BeautifulSoup物件的初始化。然後,將這個物件賦值給soup變數。
接下來,就可以呼叫soup的各個方法和屬性解析這串HTML程式碼了。
首先,呼叫prettify()方法。這個方法可以把要解析的字串以標準的縮排格式輸出。這裡需要注意的是,輸出結果裡面包含body和html節點,也就是說對於不標準的HTML字串BeautifulSoup,可以自動更正格式。這一步不是由prettify()方法做的,而是在初始化BeautifulSoup時就完成了。
然後呼叫soup.title.string,這實際上是輸出HTML中title節點的文字內容。所以,soup.title可以選出HTML中的title節點,再呼叫string屬性就可以得到裡面的文字了。
節點選擇器
直接呼叫節點的名稱就可以選擇節點元素,再呼叫string屬性就可以得到節點內的文字了,這種選擇方式速度非常快。如果單個節點結構層次非常清晰,可以選用這種方式來解析。
選擇元素
html = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title" name="dromouse"><b>The Dormouse's</b></p>
<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"><!-- Elsie --></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>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.title)
print(type(soup.title))
print(soup.title.string)
print(soup.head)
print(soup.p)
執行結果如下:
<title>The Dormouse's story</title>
<class 'bs4.element.Tag'>
The Dormouse's story
<head><title>The Dormouse's story</title></head>
<p class="title" name="dromouse"><b>The Dormouse's</b></p>
這裡依然選用剛才的HTML程式碼,首先列印輸出title節點的選擇結果,輸出結果正是title節點加里面的文字內容。接下來,輸出它的型別,是bs4.element.Tag型別,這時Beautiful Soup中一個重要的資料結構。經過選擇器選擇後,選擇結果都是這種Tag型別。Tag具有一些屬性,比如string屬性,呼叫該屬性,可以得到節點的文字內容,所以接下來的輸出結果正是節點的文字內容。
接下來,我們又嘗試選擇了head節點,結果也是節點加其內部的所有內容。最後,選擇了p節點。不過這次情況比較特殊,我們發現結果是第一個p節點的內容,後面的幾個p節點並沒有選到。
提取資訊
獲取名稱
可以利用name屬性獲取節點的名稱。這裡還是以上面文字為例,選取title節點,然後呼叫name屬性就可以得到節點名稱:
print(soup.title.name)
執行結果如下:
title
獲取屬性
每個節點可能有多個屬性,比如id和class等,選擇這個節點元素後,可以呼叫attrs獲取所有屬性:
print(soup.p.attrs)
print(soup.p.attrs['name'])
執行結果如下:
{'class':['title'], 'name':'dromouse'}
dromouse
可以看到,attrs的返回結果是字典形式,它把選擇的節點的所有屬性和屬性值組合成一個字典。接下來,如果要獲取name屬性,就相當於從字典中獲取某個鍵值,只需要用中括號加屬性名就可以了。
還有一種更簡單的獲取方式:可以不用寫attrs,直接在節點元素後面加中括號,傳入屬性名就可以獲取屬性值了。
print(soup.p['name'])
print(soup.p['class'])
執行結果如下:
dromouse
['title']
獲取內容
可以利用string屬性獲取節點元素包含的內容,比如要抓取第一個p節點的文字:
print(soup.p.string)
執行結果如下:
The Dormouse's story
巢狀選擇
在上面的例子中,我們知道每一個返回結果都是bs4.element.Tag型別,它同樣可以繼續呼叫節點進行下一步的選擇。比如,我們獲取了head節點元素,我們可以繼續呼叫head選取其內部的head節點元素:
html = """
<html><head><title>The Dormouse's story</title></head>
<body>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.head.title)
print(type(soup.head.title))
print(soup.head.title.string)
執行結果如下:
<title>The Dormouse's story</title>
<class 'bs4.element.Tag'>
The Dormouse's story
第一行結果是呼叫head之後再次呼叫title而選擇的title節點元素。然後列印輸出了它的型別,可以看到,它仍然是bs4.element.Tag型別。也就是說,我們在Tag型別的基礎上再次選擇得到的依然還是Tag型別,每次返回的結果都相同,所以這樣就可以做巢狀選擇了。
最後,輸出它的string屬性,也就是節點裡的文字內容。
關聯選擇
在做選擇的時候,有時候不能做到一步就選到想要的節點元素,需要先選中某一個節點元素,然後以它為基準再選擇它的子節點、父節點、兄弟節點等。
子節點和子孫節點
選取節點元素之後,如果想要獲取它的直接子節點,可以呼叫contents屬性,示例如下:
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>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.p.contents)
執行結果如下:
['\n Once upon a time there were three little sisters; and their names were\n ', <a class="sister" href="http://example.com/elsie" id="link1">
<span>Elsie</span>
</a>, '\n', <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, '\nand\n', <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>, '\nand they lived at the bottom of a well.\n']
可以看到,返回結果是列表形式。p節點裡既包含文字,又包含節點,最後會將它們以列表形式統一返回。
需要注意的是,列表中的每個元素都是p節點的直接子節點。比如第一個a節點裡麵包含一層span節點,這相當於孫子節點了,但是返回結果並沒有單獨把span節點選出來。所以說,contents屬性得到的結果是直接子節點的列表。
同樣,我們可以呼叫children屬性得到相應的結果:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.p.children)
for i, child in enumerate(soup.p.children):
print(i, child)
執行結果如下:
<list_iterator object at 0x000002347B2FD748>
0
Once upon a time there were three little sisters; and their names were
1 <a class="sister" href="http://example.com/elsie" id="link1">
<span>Elsie</span>
</a>
2
3 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
4
and
5 <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
6
and they lived at the bottom of a well.
還是同樣的HTML文字,這裡呼叫了children屬性來選擇,返回結果是生成器型別。接下來,我們用for迴圈輸出相應的內容。
如果要得到所有的子孫節點的話,可以呼叫descendants屬性:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.p.descendants)
for i,child in enumerate(soup.p.descendants):
print(i, child)
執行結果如下:
<generator object descendants at 0x000002347B603AF0>
0
Once upon a time there were three little sisters; and their names were
1 <a class="sister" href="http://example.com/elsie" id="link1">
<span>Elsie</span>
</a>
2
3 <span>Elsie</span>
4 Elsie
5
6
7 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
8 Lacie
9
and
10 <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
11 Tillie
12
and they lived at the bottom of a well.
此時返回結果還是生成器。遍歷輸出一下可以看到,這次的輸出結果就包含了span節點。descendants會遞迴查詢所有子節點,得到所有的子孫節點。