网络爬虫实践(二)-动态页面

来源:互联网 发布:shell编程 ${ 编辑:程序博客网 时间:2024/06/14 05:27

背景

我们可以采用查看网页源代码的方式,获取网页信息,但是,对于动态页面,很可能无法在源代码中,找到目标信息。比如,虾米精选集中,当精选集中的歌曲数目超过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 codecsf = codecs.open('filecode.txt','a','utf-8') f.write(u'中')

BTW,数据只能以字符形式写入,如果要写入数字,需先使用str()将数字转型。

感想

以前不理解,为什么有些岗位明明是要招黑盒测试工程师,但是要求有编程基础。也经常看到有些人吐槽说,做手工测试用不上编程……写这个代码,看到1000+多首歌瞬间就爬下来,突然就理解了。
假设现在测试工程师接到测试虾米歌曲列表这个模块的任务,“点击加载更多”这个基本功能是要测的,像这个有1110首歌的精选集,需点击button22次才能在页面显示完精选集内所有歌曲,要怎么测试?“点击一次两次button”这个功能就算完成测试,还是出于保守起见,“点击22次”,加载完所有歌曲才算测试完全?对于前者,我只问一句,如何保证服务器会正确返回后面的数据?对于后者,我夸奖下你的耐心,你是个用心工作的人,但请问,“点击22次”需要多长时间?耗时乘以送测版本数,等于“点点点”机械劳动的人力成本。对比:写个脚本,每个版本运行几秒,就可以知道服务器是否正确返回数据。是不是瞬间明白手工测试工程师会写代码的理由?!

附:代码

#! /usr/bin/env python#coding: utf-8import urllib2import jsonimport osimport redef  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 htmlurl = "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 + 1ajaxurl = "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()
0 0
原创粉丝点击