網絡爬蟲實踐(二)-動態頁面

背景

我們可以採用查看網頁源代碼的方式,獲取網頁信息,但是,對於動態頁面,很可能無法在源代碼中,找到目標信息。比如,蝦米精選集中,當精選集中的歌曲數目超過50首,點擊加載更多後,直接查看網頁源代碼,依然無法看到第50首後的歌曲信息。
這是因爲,使用了Ajax(Asynchronous JavaScript and XML)技術。在不重新加載整個頁面的情況下,web與服務器實現數據交互。Ajax請求數據還是通過http協議傳輸,加載到的是json數據,然後在瀏覽器進行渲染。

實踐

編程環境:python 2.7
爬取對象:蝦米中,歌曲數超過50首的精選集(如果不超過50首,則無需加載新歌曲)

一、分析請求與響應

分析交互需藉助工具,可使用fiddler等抓包工具,亦可使用Chrome自帶的調試器。以下以Chrome調試器爲工具。
觸發頁面加載更多數據是點擊“點擊加載更多”的時候。所以,先右鍵網頁→審查元素→Network→清空。接着,點擊”點擊加載更多“。此時,出現“ajax-get-list”字樣的GET鏈接,點擊對應的text/html,則可以查看到請求和響應的相關信息。

查看瀏覽器發送的url,發現已不是原url。複製新url到瀏覽器搜索欄,得到json數據--這就是我們要找的東西!

{"state":0,"message":"","result":{"total_page":23,"data":[{"gmt_create":1431664142,"list_id":101007112,"song_id":1770385747,"description":"","status":0,"user_id":0,"thirdparty_flag":0,"is_check":0,"artist_name":"Wolfgang Amadeus Mozart","artist_id":103608,"name":"Wolfgang Amadeus Mozart: Symphony No.40 in G minor, K.550 - 1. Molto allegro","ordering":51},{"gmt_create":1431664142,"list_id":101007112,"song_id":1770421675,"description":"","status":0,"user_id":0,"thirdparty_flag":0,"is_check":0,"artist_name":"Murray Perahia","artist_id":61885,"name":"no.3 in e major","ordering":52},……]}}

繼續分析:
第一次點擊加載發送的url:
=1448693039684&id=101007112&p=2”>http://www.xiami.com/collect/ajax-get-list?=1448693039684&id=101007112&p=2

第二次點擊加載發送的url:
=1448693039684&id=101007112&p=3”>http://www.xiami.com/collect/ajax-get-list?=1448693039684&id=101007112&p=3
……
規律分析看來,Ajax地址不同處在與“&p=”後的數字。

二、解析json數據

訪問Ajax地址得到json數據,肉眼看上去是dict。試着打印key對應的value。

state = content['state']
TypeError: string indices must be integers, not str

錯誤提示的意思是:字符的索引必須是整數。難道json數據不是”“肉眼看到的dict”?!嘗試用type()看下類型。
結果顯示,這個看起來很像dict的數據,確實是

三、保存文件

保存文件不難,分享下我遇到的寫入文件過程中報錯的問題。先簡單引入:
運行:

fp.write(u'中')  

結果:

UnicodeEncodeError: 'ascii' codec can't encode character u'\u4e2d' in position 0: ordinal not in range(128)

報錯信息的意思是,ASCII無法對“中”進行編碼。ASCII只能處理一個字節,而處理中文至少要2個字節,這個錯誤不難理解。關鍵是,我們從這個報錯信息中,可以推測出, Python2.7基於ascii去處理字符流。而ASCII編碼實際上可以被看成是UTF-8編碼的一部分,所以,只要將字符轉換成UTF-8編碼格式,就可以解決問題。即:

f.write((u'中').encode('utf-8'))

“先編碼後寫入”是一種解決方式。也可以,利用codecs()模塊,先指明寫入的數據流就是unicode string,如下:

import codecs
f = codecs.open('filecode.txt','a','utf-8') 
f.write(u'中')

BTW,數據只能以字符形式寫入,如果要寫入數字,需先使用str()將數字轉型。

感想

以前不理解,爲什麼有些崗位明明是要招黑盒測試工程師,但是要求有編程基礎。也經常看到有些人吐槽說,做手工測試用不上編程……寫這個代碼,看到1000+多首歌瞬間就爬下來,突然就理解了。
假設現在測試工程師接到測試蝦米歌曲列表這個模塊的任務,“點擊加載更多”這個基本功能是要測的,像這個有1110首歌的精選集,需點擊button22次才能在頁面顯示完精選集內所有歌曲,要怎麼測試?“點擊一次兩次button”這個功能就算完成測試,還是出於保守起見,“點擊22次”,加載完所有歌曲纔算測試完全?對於前者,我只問一句,如何保證服務器會正確返回後面的數據?對於後者,我誇獎下你的耐心,你是個用心工作的人,但請問,“點擊22次”需要多長時間?耗時乘以送測版本數,等於“點點點”機械勞動的人力成本。對比:寫個腳本,每個版本運行幾秒,就可以知道服務器是否正確返回數據。是不是瞬間明白手工測試工程師會寫代碼的理由?!

附:代碼

#! /usr/bin/env python
#coding: utf-8

import urllib2
import json
import os
import re

def  visitServer(url):
    req = urllib2.Request(url)
    req.add_header('User-Agent','Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.72 Safari/537.36')
    try:
        respone = urllib2.urlopen(req)
        html = respone.read()
        respone.close()
    except URLError, e:
        print e.reason
    return html


url = "http://www.xiami.com/collect/101007112"
html = visitServer(url)
#一次加載最多顯示幾首歌
rex1 = re.compile("trackid")
limit = rex1.findall(html)
#精選集的歌曲數
rex = re.compile(r'<span>歌曲數:</span>(.+)&nbsp')
songNO = rex.findall(html)[0]
ajaxpageNO = int(songNO) / len(limit)
if songNO % limit != 0:
    ajaxpageNO = ajaxpageNO + 1
ajaxurl = "http://www.xiami.com/collect/ajax-get-list?_=1448693039684&id=101007112&p="
if os.path.exists('loadmore.txt'):
    os.remove('loadmore.txt')
fp = open ('loadmore.txt','a')  
for i in range(1,ajaxpageNO+1):
    url = ajaxurl + str(i)
    content = visitServer(url)
    data = json.loads(content)
    resultdict = data['result']
    datalist = resultdict['data']           
    for i in range(len(datalist)):
        datadict = datalist[i]
        artistname = datadict['name']
        artistid = datadict['ordering']
        fp.write((unicode(artistid) + u'、' + unicode(artistname) + u'\n').encode('utf-8'))
fp.close()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章