前面介紹了正規表示式的相關用法,但是一旦正規表示式寫的有問題,得到的可能就不是我們想要的結果了。而且對於一個網頁來說,都有一定的特殊結構和層級關係,而且很多節點都有id
或class
來作區分,所以藉助它們的結構和屬性來提取不也可以嗎?
這一節中,我們就來介紹一個強大的解析工具Beautiful Soup,它藉助網頁的結構和屬性等特性來解析網頁。有了它,我們不用再去寫一些複雜的正規表示式,只需要簡單的幾條語句,就可以完成網頁中某個元素的提取。
廢話不多說,接下來就來感受一下Beautiful Soup的強大之處吧。
1. 簡介
簡單來說,Beautiful Soup就是Python的一個HTML或XML的解析庫,可以用它來方便地從網頁中提取資料。官方解釋如下:
Beautiful Soup提供一些簡單的、Python式的函式來處理導航、搜尋、修改分析樹等功能。它是一個工具箱,通過解析文件為使用者提供需要抓取的資料,因為簡單,所以不需要多少程式碼就可以寫出一個完整的應用程式。
Beautiful Soup自動將輸入文件轉換為Unicode編碼,輸出文件轉換為UTF-8編碼。你不需要考慮編碼方式,除非文件沒有指定一個編碼方式,這時你僅僅需要說明一下原始編碼方式就可以了。
Beautiful Soup已成為和lxml、html6lib一樣出色的Python直譯器,為使用者靈活地提供不同的解析策略或強勁的速度。
所以說,利用它可以省去很多煩瑣的提取工作,提高了解析效率。
2. 準備工作
在開始之前,請確保已經正確安裝好了Beautiful Soup和lxml,如果沒有安裝,可以參考第1章的內容。
3. 解析器
Beautiful Soup在解析時實際上依賴解析器,它除了支援Python標準庫中的HTML解析器外,還支援一些第三方解析器(比如lxml)。表4-3列出了Beautiful Soup支援的解析器。
表4-3 Beautiful Soup支援的解析器
通過以上對比可以看出,lxml解析器有解析HTML和XML的功能,而且速度快,容錯能力強,所以推薦使用它。
如果使用lxml,那麼在初始化Beautiful Soup時,可以把第二個引數改為lxml
即可:
from bs4 import BeautifulSoup
soup = BeautifulSoup('<p>Hello</p>', 'lxml')
print(soup.p.string)
複製程式碼
在後面,Beautiful Soup的用法例項也統一用這個解析器來演示。
4. 基本用法
下面首先用例項來看看Beautiful Soup的基本用法:
html = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title" name="dromouse"><b>The Dormouse's story</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 story
</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
),此時就完成了BeaufulSoup
物件的初始化。然後,將這個物件賦值給soup
變數。
接下來,就可以呼叫soup
的各個方法和屬性解析這串HTML程式碼了。
首先,呼叫prettify()
方法。這個方法可以把要解析的字串以標準的縮排格式輸出。這裡需要注意的是,輸出結果裡面包含body
和html
節點,也就是說對於不標準的HTML字串BeautifulSoup
,可以自動更正格式。這一步不是由prettify()
方法做的,而是在初始化BeautifulSoup
時就完成了。
然後呼叫soup.title.string
,這實際上是輸出HTML中title
節點的文字內容。所以,soup.title
可以選出HTML中的title
節點,再呼叫string
屬性就可以得到裡面的文字了,所以我們可以通過簡單呼叫幾個屬性完成文字提取,這是不是非常方便?
5. 節點選擇器
直接呼叫節點的名稱就可以選擇節點元素,再呼叫string
屬性就可以得到節點內的文字了,這種選擇方式速度非常快。如果單個節點結構層次非常清晰,可以選用這種方式來解析。
選擇元素
下面再用一個例子詳細說明選擇元素的方法:
html = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title" name="dromouse"><b>The Dormouse's story</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 story</b></p>
複製程式碼
這裡依然選用剛才的HTML程式碼,首先列印輸出title
節點的選擇結果,輸出結果正是title
節點加里面的文字內容。接下來,輸出它的型別,是bs4.element.Tag
型別,這是Beautiful Soup中一個重要的資料結構。經過選擇器選擇後,選擇結果都是這種Tag
型別。Tag
具有一些屬性,比如string
屬性,呼叫該屬性,可以得到節點的文字內容,所以接下來的輸出結果正是節點的文字內容。
接下來,我們又嘗試選擇了head
節點,結果也是節點加其內部的所有內容。最後,選擇了p
節點。不過這次情況比較特殊,我們發現結果是第一個p
節點的內容,後面的幾個p
節點並沒有選到。也就是說,當有多個節點時,這種選擇方式只會選擇到第一個匹配的節點,其他的後面節點都會忽略。
提取資訊
上面演示了呼叫string
屬性來獲取文字的值,那麼如何獲取節點屬性的值呢?如何獲取節點名呢?下面我們來統一梳理一下資訊的提取方式。
(1)獲取名稱
可以利用name
屬性獲取節點的名稱。這裡還是以上面的文字為例,選取title
節點,然後呼叫name
屬性就可以得到節點名稱:
print(soup.title.name)
複製程式碼
執行結果如下:
title
複製程式碼
(2)獲取屬性
每個節點可能有多個屬性,比如id
和class
等,選擇這個節點元素後,可以呼叫attrs
獲取所有屬性:
print(soup.p.attrs)
print(soup.p.attrs['name'])
複製程式碼
執行結果如下:
{'class': ['title'], 'name': 'dromouse'}
dromouse
複製程式碼
可以看到,attrs
的返回結果是字典形式,它把選擇的節點的所有屬性和屬性值組合成一個字典。接下來,如果要獲取name
屬性,就相當於從字典中獲取某個鍵值,只需要用中括號加屬性名就可以了。比如,要獲取name
屬性,就可以通過attrs['name']
來得到。
其實這樣有點煩瑣,還有一種更簡單的獲取方式:可以不用寫attrs
,直接在節點元素後面加中括號,傳入屬性名就可以獲取屬性值了。樣例如下:
print(soup.p['name'])
print(soup.p['class'])
複製程式碼
執行結果如下:
dromouse
['title']
複製程式碼
這裡需要注意的是,有的返回結果是字串,有的返回結果是字串組成的列表。比如,name
屬性的值是唯一的,返回的結果就是單個字串。而對於class
,一個節點元素可能有多個class
,所以返回的是列表。在實際處理過程中,我們要注意判斷型別。
(3)獲取內容
可以利用string
屬性獲取節點元素包含的文字內容,比如要獲取第一個p
節點的文字:
print(soup.p.string)
複製程式碼
執行結果如下:
The Dormouse's story
複製程式碼
再次注意一下,這裡選擇到的p
節點是第一個p
節點,獲取的文字也是第一個p
節點裡面的文字。
巢狀選擇
在上面的例子中,我們知道每一個返回結果都是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
屬性,也就是節點裡的文字內容。
關聯選擇
在做選擇的時候,有時候不能做到一步就選到想要的節點元素,需要先選中某一個節點元素,然後以它為基準再選擇它的子節點、父節點、兄弟節點等,這裡就來介紹如何選擇這些節點元素。
(1)子節點和子孫節點
選取節點元素之後,如果想要獲取它的直接子節點,可以呼叫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>
"""
複製程式碼
執行結果如下:
['\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>, ' \n and\n ', <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>, '\n and 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 0x1064f7dd8>
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 0x10650e678>
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
會遞迴查詢所有子節點,得到所有的子孫節點。
(2)父節點和祖先節點
如果要獲取某個節點元素的父節點,可以呼叫parent
屬性:
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>
</p>
<p class="story">...</p>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.a.parent)
複製程式碼
執行結果如下:
<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">
<span>Elsie</span>
</a>
</p>
複製程式碼
這裡我們選擇的是第一個a
節點的父節點元素。很明顯,它的父節點是p
節點,輸出結果便是p
節點及其內部的內容。
需要注意的是,這裡輸出的僅僅是a
節點的直接父節點,而沒有再向外尋找父節點的祖先節點。如果想獲取所有的祖先節點,可以呼叫parents
屬性:
html = """
<html>
<body>
<p class="story">
<a href="http://example.com/elsie" class="sister" id="link1">
<span>Elsie</span>
</a>
</p>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(type(soup.a.parents))
print(list(enumerate(soup.a.parents)))
複製程式碼
執行結果如下:
<class 'generator'>
[(0, <p class="story">
<a class="sister" href="http://example.com/elsie" id="link1">
<span>Elsie</span>
</a>
</p>), (1, <body>
<p class="story">
<a class="sister" href="http://example.com/elsie" id="link1">
<span>Elsie</span>
</a>
</p>
</body>), (2, <html>
<body>
<p class="story">
<a class="sister" href="http://example.com/elsie" id="link1">
<span>Elsie</span>
</a>
</p>
</body></html>), (3, <html>
<body>
<p class="story">
<a class="sister" href="http://example.com/elsie" id="link1">
<span>Elsie</span>
</a>
</p>
</body></html>)]
複製程式碼
可以發現,返回結果是生成器型別。這裡用列表輸出了它的索引和內容,而列表中的元素就是a
節點的祖先節點。
(3)兄弟節點
上面說明了子節點和父節點的獲取方式,如果要獲取同級的節點(也就是兄弟節點),應該怎麼辦呢?示例如下:
html = """
<html>
<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>
Hello
<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>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print('Next Sibling', soup.a.next_sibling)
print('Prev Sibling', soup.a.previous_sibling)
print('Next Siblings', list(enumerate(soup.a.next_siblings)))
print('Prev Siblings', list(enumerate(soup.a.previous_siblings)))
複製程式碼
執行結果如下:
Next Sibling
Hello
Prev Sibling
Once upon a time there were three little sisters; and their names were
Next Siblings [(0, '\n Hello\n '), (1, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>), (2, ' \n and\n '), (3, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>), (4, '\n and they lived at the bottom of a well.\n ')]
Prev Siblings [(0, '\n Once upon a time there were three little sisters; and their names were\n ')]
複製程式碼
可以看到,這裡呼叫了4個屬性,其中next_sibling
和previous_sibling
分別獲取節點的下一個和上一個兄弟元素,next_siblings
和previous_siblings
則分別返回所有前面和後面的兄弟節點的生成器。
(4)提取資訊
前面講解了關聯元素節點的選擇方法,如果想要獲取它們的一些資訊,比如文字、屬性等,也用同樣的方法,示例如下:
html = """
<html>
<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">Bob</a><a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
</p>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print('Next Sibling:')
print(type(soup.a.next_sibling))
print(soup.a.next_sibling)
print(soup.a.next_sibling.string)
print('Parent:')
print(type(soup.a.parents))
print(list(soup.a.parents)[0])
print(list(soup.a.parents)[0].attrs['class'])
複製程式碼
執行結果如下:
Next Sibling:
<class 'bs4.element.Tag'>
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
Lacie
Parent:
<class 'generator'>
<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">Bob</a><a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
</p>
['story']
複製程式碼
如果返回結果是單個節點,那麼可以直接呼叫string
、attrs
等屬性獲得其文字和屬性;如果返回結果是多個節點的生成器,則可以轉為列表後取出某個元素,然後再呼叫string
、attrs
等屬性獲取其對應節點的文字和屬性。
6. 方法選擇器
前面所講的選擇方法都是通過屬性來選擇的,這種方法非常快,但是如果進行比較複雜的選擇的話,它就比較煩瑣,不夠靈活了。幸好,Beautiful Soup還為我們提供了一些查詢方法,比如find_all()
和find()
等,呼叫它們,然後傳入相應的引數,就可以靈活查詢了。
find_all()
find_all
,顧名思義,就是查詢所有符合條件的元素。給它傳入一些屬性或文字,就可以得到符合條件的元素,它的功能十分強大。
它的API如下:
find_all(name , attrs , recursive , text , **kwargs)
複製程式碼
(1)name
我們可以根據節點名來查詢元素,示例如下:
html='''
<div class="panel">
<div class="panel-heading">
<h4>Hello</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(name='ul'))
print(type(soup.find_all(name='ul')[0]))
複製程式碼
執行結果如下:
[<ul class="list" id="list-1">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>, <ul class="list list-small" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>]
<class 'bs4.element.Tag'>
複製程式碼
這裡我們呼叫了find_all()
方法,傳入name
引數,其引數值為ul
。也就是說,我們想要查詢所有ul
節點,返回結果是列表型別,長度為2,每個元素依然都是bs4.element.Tag
型別。
因為都是Tag
型別,所以依然可以進行巢狀查詢。還是同樣的文字,這裡查詢出所有ul
節點後,再繼續查詢其內部的li
節點:
for ul in soup.find_all(name='ul'):
print(ul.find_all(name='li'))
複製程式碼
執行結果如下:
[<li class="element">Foo</li>, <li class="element">Bar</li>, <li class="element">Jay</li>]
[<li class="element">Foo</li>, <li class="element">Bar</li>]
複製程式碼
返回結果是列表型別,列表中的每個元素依然還是Tag
型別。
接下來,就可以遍歷每個li
,獲取它的文字了:
for ul in soup.find_all(name='ul'):
print(ul.find_all(name='li'))
for li in ul.find_all(name='li'):
print(li.string)
複製程式碼
執行結果如下:
[<li class="element">Foo</li>, <li class="element">Bar</li>, <li class="element">Jay</li>]
Foo
Bar
Jay
[<li class="element">Foo</li>, <li class="element">Bar</li>]
Foo
Bar
複製程式碼
(2)attrs
除了根據節點名查詢,我們也可以傳入一些屬性來查詢,示例如下:
html='''
<div class="panel">
<div class="panel-heading">
<h4>Hello</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1" name="elements">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(attrs={'id': 'list-1'}))
print(soup.find_all(attrs={'name': 'elements'}))
複製程式碼
執行結果如下:
[<ul class="list" id="list-1" name="elements">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>]
[<ul class="list" id="list-1" name="elements">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>]
複製程式碼
這裡查詢的時候傳入的是attrs
引數,引數的型別是字典型別。比如,要查詢id
為list-1
的節點,可以傳入attrs={'id': 'list-1'}
的查詢條件,得到的結果是列表形式,包含的內容就是符合id
為list-1
的所有節點。在上面的例子中,符合條件的元素個數是1,所以結果是長度為1的列表。
對於一些常用的屬性,比如id
和class
等,我們可以不用attrs
來傳遞。比如,要查詢id
為list-1
的節點,可以直接傳入id
這個引數。還是上面的文字,我們換一種方式來查詢:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(id='list-1'))
print(soup.find_all(class_='element'))
複製程式碼
執行結果如下:
[<ul class="list" id="list-1">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>]
[<li class="element">Foo</li>, <li class="element">Bar</li>, <li class="element">Jay</li>, <li class="element">Foo</li>, <li class="element">Bar</li>]
複製程式碼
這裡直接傳入id='list-1'
,就可以查詢id
為list-1
的節點元素了。而對於class
來說,由於class
在Python裡是一個關鍵字,所以後面需要加一個下劃線,即class_='element'
,返回的結果依然還是Tag
組成的列表。
(3)text
text
引數可用來匹配節點的文字,傳入的形式可以是字串,可以是正規表示式物件,示例如下:
import re
html='''
<div class="panel">
<div class="panel-body">
<a>Hello, this is a link</a>
<a>Hello, this is a link, too</a>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(text=re.compile('link')))
複製程式碼
執行結果如下:
['Hello, this is a link', 'Hello, this is a link, too']
複製程式碼
這裡有兩個a
節點,其內部包含文字資訊。這裡在find_all()
方法中傳入text
引數,該引數為正規表示式物件,結果返回所有匹配正規表示式的節點文字組成的列表。
find()
除了find_all()
方法,還有find()
方法,只不過後者返回的是單個元素,也就是第一個匹配的元素,而前者返回的是所有匹配的元素組成的列表。示例如下:
html='''
<div class="panel">
<div class="panel-heading">
<h4>Hello</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find(name='ul'))
print(type(soup.find(name='ul')))
print(soup.find(class_='list'))
複製程式碼
執行結果如下:
<ul class="list" id="list-1">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>
<class 'bs4.element.Tag'>
<ul class="list" id="list-1">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>
複製程式碼
這裡的返回結果不再是列表形式,而是第一個匹配的節點元素,型別依然是Tag
型別。
另外,還有許多查詢方法,其用法與前面介紹的find_all()
、find()
方法完全相同,只不過查詢範圍不同,這裡簡單說明一下。
find_parents()
和find_parent()
:前者返回所有祖先節點,後者返回直接父節點。find_next_siblings()
和find_next_sibling()
:前者返回後面所有的兄弟節點,後者返回後面第一個兄弟節點。find_previous_siblings()
和find_previous_sibling()
:前者返回前面所有的兄弟節點,後者返回前面第一個兄弟節點。find_all_next()
和find_next()
:前者返回節點後所有符合條件的節點,後者返回第一個符合條件的節點。find_all_previous()
和find_previous()
:前者返回節點後所有符合條件的節點,後者返回第一個符合條件的節點。
7. CSS選擇器
Beautiful Soup還提供了另外一種選擇器,那就是CSS選擇器。如果對Web開發熟悉的話,那麼對CSS選擇器肯定也不陌生。如果不熟悉的話,可以參考www.w3school.com.cn/cssref/css_…瞭解。
使用CSS選擇器時,只需要呼叫select()
方法,傳入相應的CSS選擇器即可,示例如下:
html='''
<div class="panel">
<div class="panel-heading">
<h4>Hello</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.select('.panel .panel-heading'))
print(soup.select('ul li'))
print(soup.select('#list-2 .element'))
print(type(soup.select('ul')[0]))
複製程式碼
執行結果如下:
[<div class="panel-heading">
<h4>Hello</h4>
</div>]
[<li class="element">Foo</li>, <li class="element">Bar</li>, <li class="element">Jay</li>, <li class="element">Foo</li>, <li class="element">Bar</li>]
[<li class="element">Foo</li>, <li class="element">Bar</li>]
<class 'bs4.element.Tag'>
複製程式碼
這裡我們用了3次CSS選擇器,返回的結果均是符合CSS選擇器的節點組成的列表。例如,select('ul li')
則是選擇所有ul
節點下面的所有li
節點,結果便是所有的li
節點組成的列表。
最後一句列印輸出了列表中元素的型別。可以看到,型別依然是Tag
型別。
巢狀選擇
select()
方法同樣支援巢狀選擇。例如,先選擇所有ul
節點,再遍歷每個ul
節點,選擇其li
節點,樣例如下:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
for ul in soup.select('ul'):
print(ul.select('li'))
複製程式碼
執行結果如下:
[<li class="element">Foo</li>, <li class="element">Bar</li>, <li class="element">Jay</li>]
[<li class="element">Foo</li>, <li class="element">Bar</li>]
複製程式碼
可以看到,這裡正常輸出了所有ul
節點下所有li
節點組成的列表。
獲取屬性
我們知道節點型別是Tag
型別,所以獲取屬性還可以用原來的方法。仍然是上面的HTML文字,這裡嘗試獲取每個ul
節點的id
屬性:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
for ul in soup.select('ul'):
print(ul['id'])
print(ul.attrs['id'])
複製程式碼
執行結果如下:
list-1
list-1
list-2
list-2
複製程式碼
可以看到,直接傳入中括號和屬性名,以及通過attrs
屬性獲取屬性值,都可以成功。
獲取文字
要獲取文字,當然也可以用前面所講的string
屬性。此外,還有一個方法,那就是get_text()
,示例如下:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
for li in soup.select('li'):
print('Get Text:', li.get_text())
print('String:', li.string)
複製程式碼
執行結果如下:
Get Text: Foo
String: Foo
Get Text: Bar
String: Bar
Get Text: Jay
String: Jay
Get Text: Foo
String: Foo
Get Text: Bar
String: Bar
複製程式碼
可以看到,二者的效果完全一致。
到此,Beautiful Soup的用法基本就介紹完了,最後做一下簡單的總結。
- 推薦使用lxml解析庫,必要時使用html.parser。
- 節點選擇篩選功能弱但是速度快。
- 建議使用
find()
或者find_all()
查詢匹配單個結果或者多個結果。 - 如果對CSS選擇器熟悉的話,可以使用
select()
方法選擇。
本資源首發於崔慶才的個人部落格靜覓: Python3網路爬蟲開發實戰教程 | 靜覓
如想了解更多爬蟲資訊,請關注我的個人微信公眾號:進擊的Coder
weixin.qq.com/r/5zsjOyvEZ… (二維碼自動識別)