Python公开课 - 数据抓取之Ajax

前言

目前HTML页面分为静态网页和动态网页,动态页面是指前端页面的主要代码一样,但是动态展现部分是根据后台的数据不同而表现各异。

对于静态页面的抓取比较简单,但是往往动态页面的价值更大,因为它联系着后端的结构化数据。

其中Ajax技术的出现又让前端动态页面的展现更加灵活,用户体验更好,同时给爬虫的抓取也带来的更大的技术难度。

什么是Ajax

Ajax,全称为Asynchronous JavaScript and XML,即异步的JavaScript和XML。 它不是一门编程语言,而是利用JavaScript在保证页面不被刷新、页面链接不改变的情况下与服务器交换数据并更新部分网页的技术。

简单来说就是数据加载是一种异步加载方式,原始的页面初始加载的时候并不含有该数据,而是待页面加载完后或用户触发后,前端再向服务器请求某个接口获取数据,然后数据才被处理从而渲染到网页上,这整个过程,就是发送了一个Ajax请求。

实战新浪微博 - 抓取每日热门

第一步:分析抓取源页面

我们首先要做的就是比较H5页面与PC页面,评估抓取难度。

目前网站建设一般来说会有普通HTML页面,适合用户在PC下的浏览器宽屏模式进行访问,另外也会建设一个H5页面,让用户在手机的小屏浏览器上也有不错的体验。

但是这个对于我们来说都不重要,因为它们两周的后台数据源都是一样的,我们需要的是比较哪种页面对于我们来说更容易抓取。

新浪微的首页https://weibo.com/?category=0,可以直接访问不用登陆

新浪热门微博

新浪的热门微博在H5下的地址是https://m.weibo.cn/,也可以直接访问不用登陆

新浪微博热门

但是比较之后,我们发H5页面的结构更简单些,这样对于后期的页面抽取也是有帮助的,因此我们选择以H5页面作为抓取源

第二步:分析Ajax请求

传统页面的翻页是通过下一页按钮来实现,新浪微博也是类似,不过是使用的Ajax技术来实现。

新浪微博翻页

通过Chrome浏览器来抓包分析,我们每次将页面拖到底部,并不断滑动页面,可以看到页面底部有一条条新的微博被刷出,而开发者工具下方也一个个地出现Ajax请求, 如图所示:

新浪微博翻页请求

第三步:分析响应内容

点击并展开该请求,看看新浪的后台都响应了啥内容呢,该响应返回了Json数据,其中核心数据放在了data字段中,内嵌的微博内容是List结构构成。详细内容如下:

新浪微博Ajax响应

第四步:提取内容

由于是Json数据,所以提取内容非常方便,我们只用选择我们需要的字段数据,并进行裁剪即可。

例如我们需要转发数,评论数,id,内容等信息,直接可以通过json的key, value值来获取

代码如下:

def parse_page(json):
    items = json.get('data').get('cards')
    for item in items:
        item = item.get('mblog')
        if item == None:
            continue
        weibo = {}
        weibo['id'] = item.get('id')
        weibo['text'] = pq(item.get('text')).text()
        weibo['attitudes'] = item.get('attitudes_count')
        weibo['comments'] = item.get('comments_count')
        weibo['reposts'] = item.get('reposts_count')
        yield weibo

提取后内容如下:

{'reposts': 39467, 'attitudes': 45754, 'comments': 24316, 'id': '4330663866478723', 'text': '刚才且且在讨论吴秀波小三的私人生活,就去看了看陈昱霖的Ins,当场就昏迷了,基本上是比佛利娇妻网红版。\n\n之前陈昱霖在爆料时,说得凄凄惨惨戚戚,被吴秀波圈养在横店的房间里,给他洗衣搓背炖汤侍寝。几年不羁情,一把辛酸泪。\n\n结果看Ins感觉一年365天都在全世界旅游,入住的全是安曼四季范思哲酒 ...全文'}
{'reposts': 29327, 'attitudes': 62777, 'comments': 11528, 'id': '4330532781865649', 'text': '【这是地球的#十年对比挑战#】地球的变化,可能比我们想象中更快!敬畏生命,关爱自然,一起守护我们共同的家。支持请转! NowThis的秒拍视频'}
{'reposts': 14562, 'attitudes': 50973, 'comments': 5229, 'id': '4330596832125262', 'text': '【回忆杀!#手机十年对比挑战#:2009年,人们都用什么手机?】2009年到2019年这十年间,科技发展迅速,诺基亚手机从行业巨头跌入谷底,三星,小米,华为手机崛起,电脑升级换代迅速,各种新的科技产品不断出世。2029年,会有什么样的高科技呢?@全球视频大魔王 全球视频大魔王的秒拍视频'}
{'reposts': 12206, 'attitudes': 37263, 'comments': 9191, 'id': '4330686271455274', 'text': '【逆转获胜!国足2-1胜泰国闯进八强!】#2019年亚洲杯#1/8决赛,中国队迎战泰国!上半场国足全面处于被动,第31分钟被对手利用角球机会打进一球,0-1落后!下半场国足展开绝地反击,里皮第63分钟的换人成为比赛转折点,第67分钟,刚刚替补出场的肖智头顶脚踢连续两脚射门将球打进,1-1!3分钟之后,郜 ...全文'}
{'reposts': 29706, 'attitudes': 59720, 'comments': 12003, 'id': '4330598409696735', 'text': '来来来,新鲜出炉,《飞驰人生》剧情版预告片电影飞驰人生的微博视频'}
{'reposts': 64645, 'attitudes': 207631, 'comments': 23075, 'id': '4330615652923843', 'text': '明兰解除刘海封印,开启新副本'}
{'reposts': 5472, 'attitudes': 26382, 'comments': 58447, 'id': '4330589911599564', 'text': '#娱乐圈好男人#这两天,吴秀波国民大叔的形象彻底崩塌,有人提问:娱乐圈还有哪些好男人?网友纷纷提名:潘粤明、邓超、周杰伦、陈信宏、靳东。你心中的娱乐圈好男人是谁?'}
{'reposts': 241449, 'attitudes': 179559, 'comments': 59618, 'id': '4330665946062381', 'text': '今晚 爽\n演唱会见'}
{'reposts': 79853, 'attitudes': 108235, 'comments': 13535, 'id': '4330546173624314', 'text': '1、节目里大概只是关于我1/3的呈现,宅和养生是我认为的舒适并且享受的生活方式,但是并不代表我只宅在家里养生,我们的生活需要给自己留独立的空间去放松调整,当然也需要在独立的空间之外,去积极阳光的生活。\n2、我的确是一个容易动感情的人,跟朋友一起更是容易情绪激动。一激动就会哭...但是哭其 ...全文'}

小结

本章介绍了什么是Ajax,并且以新浪微博为例介绍了如果来抓取Ajax的数据内容,实际情况会更复杂,会在后面的章节中进行介绍。

完整代码

import requests
from urllib.parse import urlencode
from pyquery import PyQuery as pq

def get_page(page):
    params = {
      'containerid':'102803', 'page': page}
    url = 'https://m.weibo.cn/api/container/getIndex?openApp=0' + urlencode(params)
    r = requests.get(url)
    return r.json()

def parse_page(json):
    items = json.get('data').get('cards')
    for item in items:
        item = item.get('mblog')
        if item == None:
            continue
        weibo = {}
        weibo['id'] = item.get('id')
        weibo['text'] = pq(item.get('text')).text()
        weibo['attitudes'] = item.get('attitudes_count')
        weibo['comments'] = item.get('comments_count')
        weibo['reposts'] = item.get('reposts_count')
        yield weibo

for i in range(1, 10):
    json = get_page(i)
    results = parse_page(json)
    for result in results:
        print(result)


相关阅读