Python爬蟲:分析AJAX傳遞的JSON獲取數據-初步分析動態網頁(1)

在第一篇文章中,我總結了最近學到的利用requests和bs4第三方庫共同作用,基本可以應對python獲取靜態網頁數據的相關問題。但是如果現實中的網頁往往比想象中複雜的多,網頁也早已不再是純靜態網頁。

就比如在第一篇文章中爬取的網易雲課堂計算機專業大學課程中,如果我們進一步爬取計算機專業可以就業的崗位信息時,通過開發者工具,我們發現,我們所需要的數據位於id="j-smartSpec" 的div中,

然而,我們利用之前的方法進行會發現最後得到的list為空,那麼我們檢查一下源代碼,好不容易找到了意料之中的標籤id,但是我們驚奇的發現,裡面什麼都沒有呀:

好氣呀!~可是對於渴望獲取想要的數據的決心,我們當然要有一探究竟的耐心的啦~經過多種方式,我們肯定會了解到這是AJAX在搗鬼,AJAX 是一種用於創建快速動態網頁的技術。 這種技術使我們可以通過在後臺與服務器進行少量數據交換,從而使網頁實現異步更新。這意味著可以在不重新加載整個網頁的情況下,瀏覽器可以對網頁的某部分進行更新。

此時想要獲取數據,就要考慮它是通過什麼傳遞新的信息給我們。

實際就是python對動態網頁、異步加載的爬取。

————————————————————————————————真^分界線

以上引出本文的主題。

正文

一、方法分析

其實任何動態產生的內容,要麼是本地計算,要麼是從服務器獲取的。前者看js,後者需要抓包。而後者經常配上各種參數加密,不過既然瀏覽器能正確發送參數,那麼就證明肯定有辦法模擬(當然不容易)。如果有能力,模擬發包。如果嫌麻煩,用現成的包來模擬操作瀏覽器。

那麼通常來講,獲取動態數據有兩種思路或者說是方法:

1. 分析頁面請求 2. 利用selenium模擬瀏覽器行為或其他抓包工具直接獲取(比較暴力有沒有)

效率最高的就是分析出請求數據的URL 一般都可以 而selenium 實在沒轍的時候再用。

本篇文章也僅對第一種方法進行介紹(當然是要實貨,不動手是沒有用滴),若之後幾天仍然很閒,會繼續介紹下一種方法

二、開始戰鬥(目標:股票|上海證券交易所)

說了半天,總算要開始了。一年之計在於春,一天之計在於晨。

我們起碼要先確定一個方向,看了一下目標頁面:

有用的就是公司代碼,公司簡稱,A股代碼,A股簡稱以及A股總資本和A股流通資本這幾項。

所以我們的目標就是爬它30頁,這些信息全部都要。

三、尋找數據位置

還是以前的基本思路,首先在頁面找準數據位置檢查,找到標籤所在位置,在前言中我們大概也有了些許經驗,這時我們試探地打開源代碼,這次看到什麼都沒有似乎也不那麼生氣了~

接下來是沒有介紹過的東西!!即分析出AJAX加載出的文件是哪一個:

如圖,在開發者工具Network中的JS中分析,如果感覺實在太多文件不好分析,那麼我們發現上面有上市A股點擊會小範圍刷新的現象,最終可以完全明確目標

這樣一來,我們可以說是完成了一半了(其他如果輕車熟路就基本沒有什麼了)

下一步就是打開目標驗證一下有沒有我們需要的數據

What????這又是啥情況

嘿嘿,這真的心裡又是一驚,403碼錶示什麼呢,就是我們沒有權限瀏覽目標地址。這是網站的自我保護行為。

那咋辦呢?我們沒有權限啊,可是我們在自己原來的頁面不是可以獲取這些的嘛!

所以,這裡就用到了讓我們的蟲去模擬人的操作。在前篇一筆帶過,其實就是通過修改Request-Headers中Cookie,User-Agent,Referer等信息來使我們的訪問請求就像是真人訪問一般。而需要修改的內容可以在Headers中查看:

則可保存如下:

headers={'Cookie':'yfx_c_g_u_id_10000042=_ck18012900250116338392357618947; VISITED_MENU=%5B%228528%22%5D; yfx_f_l_v_t_10000042=f_t_1517156701630__r_t_1517314287296__v_t_1517320502571__r_c_2',

'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36',

'Referer':'http://www.sse.com.cn/assortment/stock/list/share/'

}

通常包含這三個元素即足以證明是‘人’。這樣我們已經可以找的到數據了。

四、處理分析數據(將數據JSON格式化並解析JSON)

好了,數據找到了,並且我們可以在開發者工具preview中看到,數據儲存為JSON格式(JSON格式的數據本質上是一種被格式化了的字符串,遵循一定的語法規則),現在我們先按照原來的方法先利用requests先獲取到數據:

import requests

url='http://query.sse.com.cn/security/stock/getStockListData2.do?&jsonCallBack=jsonpCallback99887&isPagination=true&stockCode=&csrcCode=&areaName=&stockType=1&pageHelp.cacheSize=1&pageHelp.beginPage=1&pageHelp.pageSize=25&pageHelp.pageNo=1&_=1517320503161'

response=requests.get(url,headers=headers) #注意,這一步即將我們‘人’的信息傳入請求中

那麼,下一步就要獲取目標中的JSON數據,此時我們將開發者工具中Response複製後,粘貼到json在線解析及格式化驗證驗證是否是格式化的標準JSON數據。

檢驗後發現結果出錯

那麼我們就需要分析哪裡語法有問題,此處不過多贅述,直接貼出刪除和添加的部分:

刪除部分:開頭的以及結尾的

添加部分:添加至開頭,並在結尾添加‘}’即可看到解析出的JSON結構:

好了,到這裡數據JSON格式化也基本完成。而在解析時,我們需要用到python自帶的json庫以及jsonpath第三方庫(若是windows系統直接在cmd輸入pip install jsonpath 即可安裝):

import json

from jsonpath import jsonpath #從jsonpath庫中導入jsonpath方法

json_str='{"content":'+response.text[19:-1]+'}' #即將我們剛才分析出的結果進行格式化

unicodestr=json.loads(json_str) #json的loads()方法用於將json的字符串轉換成python默認的unicode字符串,還有一個dumps()方法是將python對象轉換成json字符串,其中的轉換之間的關係不再贅述,有興趣自行查閱相關資料

接下來就是通過jsonpath尋找我們需要的數據(類似於之前的soup.select()尋找的思想,但是這裡是基於jsonpath的查詢)

通過分析兩個,我們可以輕易地發現其規律性,而jsonpath的使用可以參照jsonpath的簡單入門,或者自行查閱官方文檔。

由於A股中A股名稱代碼與公司名稱代碼均一致,故:

COMPANY_CODE=jsonpath(a,'$..pageHelp..COMPANY_CODE')#公司/A股代碼

COMPANY_ABBR=jsonpath(a,'$..pageHelp..COMPANY_ABBR')#公司/A股簡稱

totalShares=jsonpath(a,"$..pageHelp..totalShares") #A股總資本

totalFlowShares=jsonpath(a,'$..pageHelp..totalFlowShares') #A股流動資本

至此,解析數據也完成了。

五、整理打印數據

print('公司/A股代碼','\\t','公司/A股簡稱','\\t','A股總資本','\\t','A股流動資本')

L1=list()

L2=list()

L3=list()

L4=list()

for x in COMPANY_CODE:

L1.append(x)

for x in COMPANY_ABBR:

L2.append(x)

for x in totalShares:

L3.append(x)

for x in totalFlowShares:

L4.append(x)

#由於同時解四個包太過複雜,python不幹,故拆分開來

x=0

while(x

print(L1[x],'\\t','\\t',L2[x],'\\t','\\t',L3[x],'\\t','\\t',L4[x])

x+=1

這樣我們就爬下一頁了:經驗證無誤。

六、擴大戰果(兒時吹的牛皮還是要補的)

前面誇下海口要抓30頁,怎麼就能沒有了呢?其實後面已經基本沒有什麼了,有興趣的朋友可以和我一起補補課。

感覺內容有些多,我在這裡簡單描述思路,就是我們要分析第一頁第二頁第三頁等之間的目標數據地址的url的相似之處,或者說其中的規律,比如:

第二頁:http://query.sse.com.cn/security/stock/getStockListData2.do?&jsonCallBack=jsonpCallback46762&isPagination=true&stockCode=&csrcCode=&areaName=&stockType=1&pageHelp.cacheSize=1&pageHelp.beginPage=2&pageHelp.pageSize=25&pageHelp.pageNo=2&pageHelp.endPage=21&_=1517320503162

第三頁:http://query.sse.com.cn/security/stock/getStockListData2.do?&jsonCallBack=jsonpCallback61233&isPagination=true&stockCode=&csrcCode=&areaName=&stockType=1&pageHelp.cacheSize=1&pageHelp.beginPage=3&pageHelp.pageSize=25&pageHelp.pageNo=3&pageHelp.endPage=31&_=1517320503163

很輕鬆就可以對比出不同和相似之處,可以說僅僅在個別關鍵字部分進行了修改。

故提取三十頁的代碼,以及之前的各種步驟,我們可以封裝到函數以便調取使用:

def find_pageA(c): #根據傳遞參數c(提取的頁數)來選擇目標url地址

return 'http://query.sse.com.cn/security/stock/getStockListData2.do?&jsonCallBack=jsonpCallback13897&isPagination=true&stockCode=&csrcCode=&areaName=&stockType=1&pageHelp.cacheSize=1&pageHelp.beginPage='+str(c)+'&pageHelp.pageSize=25&pageHelp.pageNo='+str(c)+'&pageHelp.endPage='+str(c)+'1&_=151731428806'+str(c)

def datascreenA(a):#封裝解析輸出的部分

COMPANY_CODE=jsonpath(a,'$..pageHelp..COMPANY_CODE')

COMPANY_ABBR=jsonpath(a,'$..pageHelp..COMPANY_ABBR')

totalShares=jsonpath(a,"$..pageHelp..totalShares")

totalFlowShares=jsonpath(a,'$..pageHelp..totalFlowShares')

print('公司/A股代碼','\\t','公司/A股簡稱','\\t','A股總資本','\\t','A股流動資本')

L1=list()

L2=list()

L3=list()

L4=list()

for x in COMPANY_CODE:

L1.append(x)

for x in COMPANY_ABBR:

L2.append(x)

for x in totalShares:

L3.append(x)

for x in totalFlowShares:

L4.append(x)

x=0

while(x

print(L1[x],'\\t','\\t',L2[x],'\\t','\\t',L3[x],'\\t','\\t',L4[x])

x+=1

def collect_30_pagesA():#調取30頁,相當於主函數

c=1

while(c<31):

time.sleep(2)

print('第', c, '頁:')

response=requests.get(find_pageA(c),headers=headers)

a='{"content":'+response.text[19:-1]+'}'

b=json.loads(a)

datascreenA(b)

c+=1

終結

不知不覺寫了這麼多,真的是閒的太慌了。其實除了A股還有B股,有興趣也可以繼續爬下去,雖然我不知道有什麼價值目前……另外就是前言部分的爬取也可以嘗試一下,可能會發現那個文件中的數據又是從其他地方獲取的……恕我能力有限,原理不是特別清晰,故而沒有深入講解那個,望各位指教。當然暴力破解是可行的,但是……又不著急幹啥能不用就不用唄,^_^。

————————————————

原文鏈接:https://blog.csdn.net/qq_36779888/article/details/79210713


分享到:


相關文章: