爬虫学习(5)

requests获取的是静态页面,是初始的HTML,但正常我们通过浏览器看到的页面是经过JS处理数据后生成的结果,这些数据的来源可能是Ajax加载的,也可能是HTML文档中的,还有可能是通过JS和特定算法计算生产的。
对于Ajax加载,这是一种异步加载方式,原始页面加载完毕后,再向服务器请求某个接口获取数据,然后数据才被处理呈现在网页上,这其实就是一个Ajax请求,这种方法可以做到前后端分离,降低服务器直接渲染页面带来的压力。
遇到这种情况,就需要我们用requests模拟Ajax并抓取Ajax请求,这一章主要就介绍了如何分析和抓取Ajax请求。

Ajax介绍

Asynchronous JaveScript and XML,即异步的JS和XML
利用JS在保证页面不被刷新、链接不改变的情况下与服务器交换数据并更新部分网页
比如说刷微博的时候,刷到底后,会出现一个加载中,然后继续出现新的微博内容,这就是Ajax加载的过程,页面并没有被刷新。
发送Ajax请求到网页更新的鬼才可以分为以下三步:
1.发送请求
2.解析内容
3.渲染网页

Ajax分析

以微博为例,来查看一下拖动刷新的Ajax请求
这里以刘若英的微博为例:
首先打开开发者工具,查看network选项卡并刷新页面,可以看到出现了很多条目,其实就是页面加载过程中浏览器与服务器之间发送请求和接受响应的所有记录:

Ajax的请求类型是xhr,随便点开一个Ajax请求呀,可以看到相关的详细信息:

可以看到有一个信息是X-Requested-With:XMLHttpRequest,这就是标记了该请求为Ajax请求
打开preview,可以看到响应的内容,格式是JSON:

这里是渲染个人主页所使用的一些数据,JS接收到这些数据后,执行相应的渲染方法,整个页面就渲染出来了。
Reponse选项卡里,是真实的返回数据。
这时候我们回到第一个请求,它的Reponse如下:

可以看到,这里大概只有五十几行代码,结构简单,执行了一些JS,所以说我们看到的微博页面的真是数据并不是最原始的页面返回的,而是后来执行了JS后再次向后台发送了Ajax请求,浏览器拿到数据后进一步渲染出来的。

所以说,我们可以用python实现Ajax请求的模拟,从而实现数据的抓取。

Ajax结果提取

分析请求

打开Ajax的XHR过滤器,滑动页面家在新的微博内容,就不断有Ajax请求发出,随机选取几个请求,看看url:

1
2
3
4
Request URL:https://m.weibo.cn/api/container/getIndex?type=uid&value=1681213010&containerid=1076031681213010
Request URL:https://m.weibo.cn/api/container/getIndex?type=uid&value=1681213010&containerid=1076031681213010&page=2
Request URL:https://m.weibo.cn/api/container/getIndex?type=uid&value=1681213010&containerid=1076031681213010&page=3
Request URL:https://m.weibo.cn/api/container/getIndex?type=uid&value=1681213010&containerid=1076031681213010&page=4

可以看到,url的参数一致,除了第一页省略了page参数,其他页面都是四个参数,而且type、value、containerid的值一直不变,进一步分析可以知道value的值就是i用户的id。唯一改变的参数值就是page,用来控制分页。

分析响应

选取一个Ajax请求,分析它的响应内容:

该内容是JSON大学,cardlistInfo是一个信息汇总,在这里就是微博的总数量,可以用来估算分页数;cards是一个列表,包含了10个元素,其中有一个比较重要的字段是mblog,里面包含了一些微博信息:

比如:attitudes_count代表赞的个数,comments_count是评论个数,reposts_count是转发个数,text是微博正文,created_at是发文时间。
这样,一个Ajax请求可以获取10条微博信息,而且只要改变page参数就可以获取其他页的微博,利用python,我们很方便就能获取所有的微博。

爬取微博

模拟Ajax请求,爬取刘若英微博前10页的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

import requests
import random
from pyquery import PyQuery as pq
headers = [
{'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:34.0) Gecko/20100101 Firefox/34.0'},
{'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'},
{'User-Agent': 'Mozilla/5.0 (Windows NT 6.2) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.12 Safari/535.11'},
{'User-Agent': 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0)'},
{'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:40.0) Gecko/20100101 Firefox/40.0'},
{'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/44.0.2403.89 Chrome/44.0.2403.89 Safari/537.36'}
]

def get_pages(url):
try:
response = requests.get(url,headers=random.choice(headers))
if response.status_code == 200:
return response.json()
except requests.ConnectionError as e:
print('Error',e.args)
def parse_page(json):
items = json.get('data').get('cards')
for item in items:
item = item.get('mblog')
if item:
weibo = {}
weibo['data'] = item['created_at']
weibo['id'] = item['id']
weibo['text'] = pq(item['text']).text()
weibo['attitudes'] = item['attitudes_count']
weibo['comments'] = item['comments_count']
weibo['reposts'] = item['reposts_count']
yield weibo

if __name__ == '__main__'
for page in range(1,11):
base_url = 'https://m.weibo.cn/api/container/getIndex?type=uid&value=1681213010&containerid=1076031681213010'
url = base_url+'&page='+str(page)
json = get_pages(url)
result = parse_page(json)
for each in result:
print(each)

爬取的内容如下:

还可以进一步优化,把结果存到数据库或是文本里,这里就不再赘述了。

最后,下一节我们就分析Ajax来爬取今日头条街拍美图😄