3.2 BeautifulSoup
和lxml
一樣,BeautifulSoup
也是一個HTML/XML的解析器,跟XPath
的功能是一樣的。
區別在哪裏呢?lxml
只會局部遍歷,如果你想提取a
標籤的內容,那麼我們需要寫出相關的xpath
語法,此時lxml
只會對全部的a
標籤進行遍歷,找出滿足條件的a
標籤。
而BeautifulSoup
會載入整個文檔,將文檔內容組織成類似於DOM樹的結構,然後我們可以根據它提供的API
來作出相應的操作,此時BeautifulSoup
會遍歷整個樹,找出滿足條件的標籤。因此它的開銷比較大,速度慢。
使用BeautifulSoup
前需要先安裝:pip install bs4
。
那現在來看一下幾大解析工具對比:
解析工具 | 解析速度 | 使用難度 |
---|---|---|
BeautifulSoup | 最慢 | 最簡單 |
lxml | 快 | 簡單 |
正則 | 最快 | 最難 |
簡單使用:
# 第一步:從bs4中導入BeautifulSoup
from bs4 import BeautifulSoup
import requests
r = requests.get("http://python123.io/ws/demo.html")
demo = r.text
# 將內容組織成DOM樹
soup = BeautifulSoup(demo, 'html.parser') # 第二個參數是指定一個解析器
print(soup.text) # 輸出純文本
print(soup.prettify()) # 用比較美化得方式打印出來
soup = beatifulsoup(text, pattern)
-
參數text 爲要提取的內容
-
參數pattern爲解析器(可選)
html.parser
(默認)html5lib
(容錯性最強)lxml
(平時使用最多)xml
這裏值得一提的是,bs4
庫將任何讀入的html
文件或字符串都轉換爲utf-8
編碼。
提升案例:
from bs4 import BeautifulSoup
import requests
rp = requests.get("http://www.baidu.com")
text = rp.text
soup = BeautifulSoup(text, "lxml")
# 獲取30個div標籤
trs = soup.find_all('div', limit=30) # 返回的是列表
for ts in trs:
print(ts)
print("-"*30)
# 獲取第二個div標籤
tr = soup.find_all('div',limit = 3)[1]
print(tr)
# 獲取class等於head_wrapper的div標籤
# trs = soup.find_all('div',class_='head_wrapper') # 注意這裏是class_ ,因爲class在python中是關鍵字
# 上下這兩種方式等價
trs = soup.find_all('div',attrs={'class': 'head_wrapper'})
for tr in trs:
print(tr)
# 將所有class=lb,並且name=tj_login的a標籤
trs = soup.find_all('a', attrs={'class': 'lb', 'name': 'tj_login'})
for tr in trs:
print(tr)
# 所有a標籤的href屬性
alist = soup.find_all('a')
for a in alist:
# 獲取標籤屬性的方法
href = a['href']
print(href)
# 獲取第一個tr下面的全部文本內容
# infos = tr.strings # 包含空白符
infos = list(tr.stripped_strings) # 去掉空白符
print(infos[0])
# 獲取所有的職位信息(假設網址中有這個信息)
# trs = soup.find_all('tr')[1:] # 獲取第一個以後的tr標籤
# message = [] # 存放職位信息
# for tr in trs:
# mess = {} # 存放單條職位信息
# # 找到tr下面所有的td標籤
# tds = tr.find_all('td')
#
# mess['title'] = tds[0].string # 獲取第一個td的內容
# mess['category'] = tds[1].string # 獲取第二個td的內容
# message.append(mess)
# print(message)
soup.find(tag) 查找 第一個 匹配的tag標籤等價於soup.tag,對應的有soup.find_all(tag[,limit]) 查找 所有 tag標籤,limit表示提取多少條。
string 獲取某個標籤下的直接內容。無法獲取多行內容。
strings (返回的是生成器,可以轉換成list) 獲取所有子孫非標籤的字符串,有多行時獲取不到。
stripped_strings 同上,但會去掉空白文本。
get_text() 獲取所有子孫非標籤的文本,返回的是字符串。
CSS選擇器:select方式
此種方式通過CSS語法
來進行提取特定標籤。
import requests
from bs4 import BeautifulSoup
rp = requests.get("http://www.baidu.com")
text = rp.text
soup = BeautifulSoup(text, "lxml")
# 查找p標籤 select功能跟find_all一樣,就是不能同時篩選類名和id
print(soup.select('p'))
# 查找div下面的所有滿足class=‘cp-feedback’的標籤
print(soup.select('div .cp-feedback'))
# 查找div下面的所有滿足id=‘cp’的標籤
print(soup.select('div #cp'))
# 獲取class=‘lg’的a標籤 注意,中間沒有空格
a = soup.select('a.lb')
# a = soup.select('a[class="lb"]') 等價於上面的寫法
print(a)
# 查找name="tj_trnews"的a標籤
print(soup.select('a[name="tj_trnews"]'))
# 查找div下的子p標籤
print(soup.select('div > p'))
小結:
- 中間有空格:表示在該標籤下找滿足篩選條件的標籤
- 中間沒空格:表示查找滿足篩選條件的該標籤。
select
功能跟find_all一樣,返回的都是列表,但select
無法同時篩選類名和id>
表示在該標籤的子元素中查找
四個常用對象:
Beautiful Soup
將複雜的HTML文檔轉換成一個複雜的樹形結構,每個節點都是python對象,這些對象可以分爲4種:
- Tag :就是一個個的HTML標籤(擁有name、class等屬性)。
- NavigableString:繼承自python本身的str類型。代表標籤中的內容字符串。
- BeautifulSoup:繼承自Tag,本質上就是Tag。所以上面
soup對象
使用的find_all
等方法實際上是Tag類中的方法,與此同時也說明了 爲什麼標籤可以像soup一樣直接使用find_all等方法 。它表示的是一個文檔的全部內容。 - Comment:特殊的
NavigableString
,表示註釋的部分。
案例:
本次案例是用requests庫跟bs4結合使用,然後爬取CSDN推薦文章列表的一些信息。
其他要爬取的內容的方式跟這兩個方式差不多,這裏就不一一截圖出來了。。。
下面來看看代碼吧。
import requests
from bs4 import BeautifulSoup
url = 'https://blog.csdn.net/nav/python'
# 僞裝
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/82.0.4077.0 Safari/537.36'
}
res = requests.get(url, headers=headers)
text = res.text
# 建立文檔樹
soup = BeautifulSoup(text, 'lxml')
# 提取所有文章的li
lis = soup.find_all('li', attrs={'class': 'clearfix', 'data-type': 'blog'})
# 遍歷lis,單獨對每個li進行提取
for li in lis:
title_div = li.find('div', attrs={'class': 'title'})
# 獲取標題內容,因爲stripped_strings返回的是迭代器所以要先轉成list
# "".join()的作用是把列表內容變成字符串
title = "".join(list(title_div.find('a').stripped_strings))
# 獲取作者名稱所在的dd標籤
name_dd = li.find('dd', attrs={'class': 'name'})
# 獲取作者名稱
name = "".join(list(name_dd.find('a').stripped_strings))
# 獲取閱讀量所在的dd標籤
readNum_dd = li.find('dd', attrs={'class': 'read_num'})
# 獲取閱讀量
readNum = "".join(list(readNum_dd.stripped_strings))
# 獲取評論數所在的dd標籤
commonNum_dd = li.find('dd', attrs={'class': 'common_num'})
# 獲取評論數
# 因爲有些文章是沒有評論的,所以會缺少此標籤
if commonNum_dd != None:
commonNum = "".join(list(commonNum_dd.stripped_strings))
else:
commonNum = 0
print('文章標題:{}\t作者名稱:{}\t瀏覽量:{}\t評論數:{}'
.format(title, name, readNum, commonNum))
當然,你還可以自己獲取更多更好玩的內容,沖沖衝。
至於如何爬取每篇文章的內容詳情,後續教程中將會講到,一步一步來。
那這一節就到這裏啦~~~