動態(tài)加載頁面信息的提取
當(dāng)我們?yōu)g覽一個新聞類的網(wǎng)站,例如微博纳击,今日頭條续扔,知乎等,由于它的內(nèi)容極多焕数,當(dāng)我們搜索某一關(guān)鍵詞的信息后纱昧,服務(wù)器只會向我們返回少量的數(shù)據(jù),微博和頭條是返回指定數(shù)量的數(shù)據(jù)堡赔,當(dāng)我們再次向下刷新的時候识脆,會再次通過Ajax請求返回指定數(shù)目的數(shù)據(jù)(如果你的網(wǎng)絡(luò)不好時,會出現(xiàn)一個表示正在加載的小圓圈的動畫效果)善已。知乎是當(dāng)瀏覽器的滾動條觸底時存璃,再次提取數(shù)據(jù)。這就產(chǎn)生了一個問題雕拼,通過爬蟲如何來提取通過Ajax請求動態(tài)加載的數(shù)據(jù)呢纵东?
模擬Ajax請求
這時需要通過Chrome等瀏覽器的開發(fā)者工具,利用Chrome開發(fā)者工具的篩選功能篩選出所有的Ajax請求啥寇。選擇network選項偎球,直接點擊XHR分析網(wǎng)頁后臺向接口發(fā)送的Ajax請求,用requests來模擬Ajax請求辑甜,那么就可以成功抓取信息了
待爬取網(wǎng)站的Ajax請求的分析
這里選擇今日頭條來搞事情衰絮,通過爬蟲來下載頭條上街拍關(guān)鍵詞的圖片,搜索關(guān)鍵詞街拍磷醋,分析Ajax請求猫牡。下面是提取到的Ajax的主要信息
Request URL: https://www.toutiao.com/search_content/?
offset=0&
format=json&
keyword=%E8%A1%97%E6%8B%8D&
autoload=true&
count=20&
cur_tab=1&
from=search_tab
1.分析請求
https://www.toutiao.com/search_content/ 這個是請求的鏈接,后面還帶了一個 ? 號邓线,之后的都是請求所帶的參數(shù)
通過刷新新內(nèi)容不斷的發(fā)送Ajax請求淌友,對比不同的幾個ajax請求煌恢,對比他們的不變的地方和改變的地方,為寫程序做好準(zhǔn)備震庭。
可以發(fā)現(xiàn)每加載一次內(nèi)容參數(shù)offset加20瑰抵,表示偏移量,每次取20條數(shù)據(jù)
format是不變的器联,表示格式是json格式的二汛,
Keyword是我們搜索的關(guān)鍵字
%E8%A1%97%E6%8B%8D& ,,可能是中文的某種加密方式加密后的結(jié)果發(fā)現(xiàn)offset加20就可以了拨拓,其他參數(shù)照搬肴颊,因為都是不變的參數(shù)
2.分析響應(yīng)
點擊Preview分析響應(yīng)內(nèi)容。我們要下載圖片渣磷,發(fā)現(xiàn)圖片鏈接都在image_list里苫昌,一篇文章的一張或多張圖片都在里面,而外層是data屬性幸海,標(biāo)題在title屬性里,這里獲取標(biāo)題名作為文件夾名稱進行存儲
代碼實現(xiàn)
- 首先奥务,實現(xiàn)方法get_page()來加載單個Ajax請求的結(jié)果物独。其中唯一變化的參數(shù)就是offset,所以我們將它當(dāng)作參數(shù)傳遞氯葬,代碼實現(xiàn)如下:
import requests
from urllib.parse import urlencode #Python內(nèi)置的HTTP請求庫
def get_page(offset):
params = {
'offset':offset,
'format': 'json',
'keyword':'街拍',
'autoload':'true',
'count':'20',
'cur_tab':'1',
'from' : 'search_tab',
}
url = 'https://www.toutiao.com/search_content/?'+ urlencode(params) #拼接URL
try:
r = requests.get(url)
if r.status_code == 200:
return r.json() # 返回json格式的響應(yīng)內(nèi)容
except:
return None
- urllib庫
Python內(nèi)置的HTTP請求庫挡篓,通常我們使用的是功能更為強大的requests庫,用到urllib的parse工具模塊帚称,提供了許多URL處理方法官研,比如拆分、解析闯睹、合并等戏羽。用urlencode()方法構(gòu)造請求的GET參數(shù)。
- 接下來楼吃,再實現(xiàn)一個解析方法:提取每條數(shù)據(jù)的image_list字段中的每一張圖片鏈接始花,將圖片鏈接和圖片所屬的標(biāo)題一并返回,同時構(gòu)造一個生成器孩锡。實現(xiàn)代碼如下:
def get_images(jsondata):
if jsondata.get('data'):
for item in jsondata.get('data'):
title = item.get('title')
images = item.get('image_list')
for image in images:
yield {
'image' : image.get('url'),
'title' : title
}
- 接下來酷宵,實現(xiàn)一個保存圖片的方法save_image(),其中item就是前面get_images()方法返回的一個字典躬窜。在該方法中浇垦,首先根據(jù)item的title來創(chuàng)建文件夾,然后請求這個圖片鏈接荣挨,獲取圖片的二進制數(shù)據(jù)男韧,以二進制的形式寫入文件朴摊。圖片的名稱可以使用其內(nèi)容的MD5值,這樣可以去除重復(fù)煌抒。
def save_image(item):
if not os.path.exists(item.get('title')):
os.mkdir(item.get('title'))
try:
image_url = item.get('image')
print(image_url)
r = requests.get('http:'+image_url)
if r.status_code == 200:
file_path = '{0}/{1}.{2}'.format(item.get('title'),md5(r.content).hexdigest(),'jpg')
if not os.path.exists(file_path):
with open(file_path,'wb') as f:
f.write(r.content)
else:
print('Already Downloaded', file_path)
except:
print('Faild to Save Image')
最后仍劈,構(gòu)造一個offset數(shù)組,遍歷offset寡壮,提取圖片鏈接贩疙,并將其下載:
def main(offset):
jsondata = get_page(offset)
for item in get_images(jsondata):
print(item)
save_image(item)
num_start = 1
num_end = 20
if __name__ == '__main__':
pool = Pool()
num = ([x * 20 for x in range(num_start,num_end + 1)])
pool.map(main,num)
pool.close()
pool.join()
- 整體代碼
import requests
from urllib.parse import urlencode
import os
from hashlib import md5
from multiprocessing.pool import Pool
def get_page(offset):
params = {
'offset':offset,
'format': 'json',
'keyword':'街拍',
'autoload':'true',
'count':'20',
'cur_tab':'1',
'from' : 'search_tab',
}
url = 'https://www.toutiao.com/search_content/?'+ urlencode(params)
try:
r = requests.get(url)
if r.status_code == 200:
return r.json()
except:
return None
def get_images(jsondata):
if jsondata.get('data'):
for item in jsondata.get('data'):
title = item.get('title')
images = item.get('image_list')
for image in images:
yield {
'image' : image.get('url'),
'title' : title
}
def save_image(item):
if not os.path.exists(item.get('title')):
os.mkdir(item.get('title'))
try:
image_url = item.get('image')
r = requests.get('http:'+ image_url)
if r.status_code == 200:
file_path = '{0}/{1}.{2}'.format(item.get('title'),md5(r.content).hexdigest(),'jpg')
if not os.path.exists(file_path):
with open(file_path,'wb') as f:
f.write(r.content)
else:
print('Already Downloaded', file_path)
except:
print('Faild to Save Image')
def main(offset):
jsondata = get_page(offset)
for item in get_images(jsondata):
print(item)
save_image(item)
num_start = 1
num_end = 20
if __name__ == '__main__':
pool = Pool()
num = ([x * 20 for x in range(num_start,num_end + 1)])
pool.map(main,num)
pool.close()
pool.join()
這里定義了分頁的起始頁數(shù)和終止頁數(shù),分別為num_start和num_end况既,還利用了多線程的線程池这溅,調(diào)用其map()方法實現(xiàn)多線程下載