本文代碼片段和部分內容轉載自Python123的木下瞳的專欄,由本人進行改動與整理,並且增加部分注釋。
上節我們是用各個方法獲取一個頁面中指定的一個內容,這次我們實現多個頁面,同一組數據的獲取。
1.BeautifulSoup().find_all()方法(select()的升級版)
我們是爬取酷狗音樂TOP500 的‘音樂名’,‘歌手’,‘歌名’,‘播放時間’這幾個數據網址如下:
酷狗TOP500_排行榜_樂庫頻道_酷狗網
打開后只能看到前 22 名的數據,我們可以看到,在網址中有一個 1-8888 這個參數,打開上述網址后我們只能看到前 22 首歌,想繼續查看后面的歌曲就得翻頁,就像“淘寶”那樣查看下一頁商品需要翻頁,這里也是一樣的道理,把 1-8888 改成 2-8888 ,就會看到下一頁的 22 首歌。
本次使用的方法,就是把上一篇的 select 方法換成 find_all 方法
find_all(標簽名,class_=類名)
第一個參數是標簽名,我們來看排名的標簽如下:
可以看到它的標簽名為 span,class 屬性名為 pc_tempnum,所以find_all()中填入'span',class_='pc_temp_num',同理,歌手,歌名,播放時間的如下:
# 歌手 + 歌名
names = html.find_all('a',class_='pc_temp_songname')
# 播放時間
times = html.find_all('span',class_='pc_temp_time')
具體代碼如下:
import requests
import time
from bs4 import BeautifulSoup
import lxml
def get_html(url):
'''
獲得 HTML,並且構造一個請求頭,有了請求頭之后,服務器就會默認程序是通過瀏覽器訪問的
請求頭中的信息其實在網頁按F12后,點network,然后f5刷新一下,看主要文件在右邊顯示的信息里
一般是有index字樣的文件,信息在user-agent中
'''
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/53\
7.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
return response.text
#這是判斷網頁返回的狀態碼,200代表連接成功,大家常見的應該是404和503
#狀態碼一般在爬蟲程序出錯時,作為檢測工具,用斷點然后打印出來,判斷連接網站時出現的錯誤
#具體的各個狀態碼代表什么意思,出現問題后,自行百度即可
else:
return
def get_infos(html):
'''
提取數據
'''
html = BeautifulSoup(html,'lxml')
# 排名
ranks = html.find_all('span',class_='pc_temp_num')
# 歌手 + 歌名
names = html.find_all('a',class_='pc_temp_songname')
# 播放時間
times = html.find_all('span',class_='pc_temp_time')
# 打印信息
for r,n,t in zip(ranks,names,times):
r = r.get_text().replace('\n','').replace('\t','').replace('\r','')
n = n.get_text()
t = t.get_text().replace('\n','').replace('\t','').replace('\r','')
data = {
'排名': r,
'歌名-歌手': n,
'播放時間': t
}
print(data)
def main():
'''
主接口
'''
urls = ['https://www.kugou.com/yy/rank/home/{}-8888.html?from=rank'
.format(str(i)) for i in range(1, 24)]
for url in urls:
html = get_html(url)
get_infos(html)
time.sleep(1)
#每過一秒,再次執行下一個網頁,避免給服務器造成壓力,也避免自己的計算機能力不夠,運算過快死機
if __name__ == '__main__':
main()
用了 zip 函數,意思是把對應的排名,歌名歌手,播放時間打包,可以這樣理解 zip 函數的結果是一個列表 [(排名,歌手歌名,播放時間),(排名,歌手歌名,播放時間)... ...],而每一次循環的 r,n,t 一次對應元組中的元素。
我們提取到的是這個數據所在的標簽信息,並不是實際數據,所以需要使用 get_text() 獲得實際數據。
.replace('\n','').replace('\t','').replace('\r','')是為了去掉實際數據中多余的字符串,最后把數據打包成字典打印。
輸出結果為:
{'排名': '1', '歌名-歌手': '楊胖雨 - 這就是愛嗎', '播放時間': '4:04'}
{'排名': '2', '歌名-歌手': '魏新雨 - 百花香', '播放時間': '2:47'}
{'排名': '3', '歌名-歌手': '棉子 - 勇氣', '播放時間': '4:01'}
{'排名': '4', '歌名-歌手': '阿悠悠 - 你若三冬', '播放時間': '4:18'}
……
2.etree.HTML().xpath()方法
爬取中國大學2019的排名信息,爬取‘排名’,‘學校名’,‘省份’,‘總分’,這四個字段信息。
右鍵-檢查,查看元素如下圖:
可以看到,我們所需要的數據是畫紅線部分的數據,我們把它折疊起來,tr 標簽左邊有個小箭頭,點擊折疊,如圖:
折疊后我們可以看到,高亮條對應的部分,意思是每一條‘排名’‘學校名’‘省份’‘總分’都對應一個 ... 標簽。
我們拉到網頁最底部,可以看到有 549 個學校,就是說這樣的標簽有 549 條,我們需要先提取它們,再從每一條標簽提取信息。提取所有的學校信息的標簽,使用 xpath 方法選擇標簽在 html 源碼里的路徑,// 是選擇此 html 源碼里所有 tr 標簽並且 class 屬性為 alt 的標簽。
import requests
import time
from lxml import etree
def get_html(url):
'''
獲得 HTML
'''
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/53\
7.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
response.encoding = 'utf-8'#響應的結果的 html 源碼的編碼格式設置成 utf-8,也可以設成上一節的自動識別網頁編碼
return response.text
else:
return
def get_infos(html):
'''
提取數據
'''
html = etree.HTML(html)
# 提取所有的大學標簽信息
ls = html.xpath('//tr[@class="alt"]')
for info in ls:
# 排名
rank = info.xpath('./td[1]/text()')[0]
# 學校名
name = info.xpath('./td[2]/div/text()')[0]
# 省份
province = info.xpath('./td[3]/text()')[0]
# 總分
score = info.xpath('./td[4]/text()')[0]
data = {
'排名' : rank,
'校名' : name,
'省份' : province,
'總分' : score,
}
print(data)
def main():
'''
主接口
'''
url = 'http://www.zuihaodaxue.com/zuihaodaxuepaiming2019.html'
html = get_html(url)
get_infos(html)
time.sleep(1)
if __name__ == '__main__':
main()
輸出結果為:
{'排名': '1', '校名': '清華大學', '省份': '北京', '總分': '94.6'}
{'排名': '2', '校名': '北京大學', '省份': '北京', '總分': '76.5'}
{'排名': '3', '校名': '浙江大學', '省份': '浙江', '總分': '72.9'}
{'排名': '4', '校名': '上海交通大學', '省份': '上海', '總分': '72.1'}
……
3.正則表達式匹配網頁內容
我們這次的目標是一個小說網站的的小說章節的標題的爬取,我們使用正則來提取我最愛的玄幻小說《斗破蒼穹》的章節標題。
網頁的整體結構很簡單,所以爬取起來,也比較容易:
正則表達式匹配的方式就是在網頁源碼中,抓取需要的字符串,可能大家想,匹配第XXX章並且截止到之前就好了,但是我們這次使用匹配前面的鏈接就好了。而獲取到的網頁源碼,只要不進行編譯,其實和txt文本一樣,亂糟糟的沒有規律和美感可言,所以我們選擇匹配標簽里的網頁屬性就好了,正好它的數字也是隨着章數的變化而變化。
我們使用 findall() 方法
pat:正則表達式,html:網頁源碼,指的是 response.text,re.S:是一種匹配的模式,是指允許換行匹配,因為在構造正則時,網頁源碼可能換行了就需要它,它返回的是一個列表,包含了在此 html 源碼中匹配的符合的結果。
import requests
import re
from fake_useragent import UserAgent
def get_html(url):
'''
請求 html
:return:
'''
headers = {
'User-Agent' : UserAgent().random
}
response = requests.get(url,headers=headers)
if response.status_code == 200:
response.encoding = 'utf-8'
return response.text
else:
return
def get_info(html):
'''
提取文章標題
:param html:
:return:
'''
pat = '<dd> <a style="" href="/book/390/\d+.html">(.*?)</a></dd>'
titles = re.findall(pat,html,re.S)
for title in titles:
print(title.replace("VIP章節目錄 ",""))#中間部分章節前面帶有“VIP章節目錄”字樣,所以處理一下
if __name__ == '__main__':
'''
主接口
'''
url = 'https://www.jx.la/book/390/'
html = get_html(url)
if html == None:
print('請求失敗!')
get_info(html)
輸出結果為:
第一章 隕落的天才
第二章 斗氣大陸
第三章 客人
第四章 雲嵐宗
……
總結
通過以上三種方法獲取網站中一組數據,我們進行總結這三種方法,分析它們的長處。
首先是處理方法上,第三種最簡單,只是對於網頁中的字符串進行正則匹配,不需要進行網頁渲染,適合獲取網站結構簡單,並且標簽內容相似的網站。
而第二種尋找XPath的方法,則適合匹配內容組多,但是網頁結構不復雜的網頁。
而第一種相較於第二種,更適合網頁結構復雜,同種標簽太多並且需要獲取多組數據的網頁。
所以如果只是針對同一個網頁進行數據獲取,很難體現它們的不同和優點來,故三個代碼片段我就直接搬運了過來,並且修正了部分代碼,增加一些注釋,如果覺得代碼太長難以一下子理解,可以去代碼原作者那里去看分步解釋。