学习笔记
文章目录
- Ajax动态加载网站数据抓取
- 动态加载的类型
- 那么该如何抓取数据?
- 如何得到JSON文件的地址?
- 观察JSON文件URL地址的查询参数
- JSON格式数据转换成python字典
- 如何获取科研数据
Ajax动态加载网站数据抓取
前几天小伙伴在写报告时,和我讨论了一下爬取某生态网站的统计数据问题,我看了一下,这个网站是动态加载的,想了一想,很多数据网站的数据都是动态加载的,那么脆写一个案例吧,方便大家进行数据收集和整理。
在爬取数据之前,我先讲几个关于动态加载网站的知识点,方便大家理解代码。
动态加载的类型
- 部分页面刷新的动态加载
我们首先看一个动态加载的网站(这个网站也是我们之后要爬取的网站):
当我们进行翻页操作时,URL地址并没有变化,网站只进行了部分页面的刷新,只有表格内的数据有变化,页面的其他部分并无改变:
- 滚动页面自动触发页面加载
我们再看一个大家都很熟悉的动态加载的网站,某东。当我们在某东上搜索【华章数学时】会出现如下页面:
我们审查元素,查看当前页面中的最后一个商品的HTML结构:
通过观察,我们发现每一个商品被包裹在一个li标记中,那么当前页面的最后一个商品,应该是被包裹在最后一个li标签中。现在我们滑动鼠标滑轮,边滑动页面边观察HTML页面结构:
哎呀!我们发现,当滑倒底部时,页面自动加载出了一大堆新的被li标签包裹的商品,此时网页的URL地址依然没有改变。
那么该如何抓取数据?
我们看到在动态加载的页面下,URL是如此无力,我们无法沿用静态匹配的方法去爬取动态加载页面里的数据,那么我们该如何爬取信息呢?
需要注意的是!只要是动态加载的网站,当我们与网站发生交互,那么网站就会返回给我们JSON格式的信息,这些信息中包含了我们要爬取的数据。此时,我们只要找到这些JSON文件的地址,就成功了一大步。
如何得到JSON文件的地址?
我们以安#省生态环境厅的数据(http://sthjt.ah.gov.cn/site/tpl/5391?cityCode=340100)为例,我们发现在这个网页中进行翻页操作,只有部分页面信息被刷新,网页URL地址不变,因此该网站应该是动态加载数据的。
打开安#省生态环境厅的网页后,右键打开审查元素–>点击Network–>点击All–>进行翻页操作,获取网站和我们进行交互的数据包–>点开preview,判断该数据包中是否有我们要的数据–>点开Headers,查看数据包的头部信息:
我们关注一下Headers中基本请求信息(General)里的Request URL , 这个URL地址,就是我们需要的JSON文件的URL地址,我们打开这个地址验证一下:
很好!就是你啦。但是…这些JSON数据的格式看起来很混乱,还有很多\转义符是怎么回事?虽然这个问题不影响我们之后的数据爬取,但是我先标记一下这个问题,以后再解决
我们再回到请求头Headers,查看一下查询参数:
这些查询参数,就是JSON文件URL地址的查询参数,不信的话,我们将这些查询参数与JSON文件的URL地址对比一下:
#JSON文件的URL地址http://sthjt.ah.gov.cn/site/label/8888?IsAjax=1&dataType=json&_=0.6245771911926585&isJson=true&cityCode=340100&type=1&num=2&isPage=true&pageIndex=3&pageSize=10&labelName=airQuality
#查询参数
IsAjax:1
dataType:json
_:0.6245771911926585
isJson:true
cityCode:340100
type:1
num:2
isPage:true
pageIndex:3
pageSize:10
labelName:airQuality
嗯!没错了!
那么我们要这些查询参数有啥用呢?就像我们在之前爬取静态网页的数据时,需要观察多个URL地址的查询参数的规律一样。如果我们要爬取动态页面里的数据,这些数据可能不止在一两个JSON文件中,而是可能在大量的JSON文件中。这时,就需要通过观察查询参数的规律,来批量得到我们需要的JSON文件的URL地址,从而获取JSON文件里的数据。
观察JSON文件URL地址的查询参数
我们进行多次翻页操作,观察查询参数规律。
第一页:
IsAjax:1dataType:json
_:0.9632859283976705
isJson:true
cityCode:340100
type:1
num:2
isPage:true
pageIndex:1
pageSize:10
labelName:airQuality
第二页:
IsAjax:1dataType:json
_:0.699042424499402
isJson:true
cityCode:340100
type:1
num:2
isPage:true
pageIndex:2
pageSize:10
labelName:airQuality
第三页:
IsAjax:1dataType:json
_:0.6245771911926585
isJson:true
cityCode:340100
type:1
num:2
isPage:true
pageIndex:3
pageSize:10
labelName:airQuality
我们发现除了_和pageIndex在翻页时有所改变,其他查询参数不变。
这个_查询参数的规律我有点捉摸不透啊,我们看看,在URL地址中把这个查询参数去掉,能不能访问JSON文件。
去掉_查询参数后的第3页的JSON文件的URL地址:
http://sthjt.ah.gov.cn/site/label/8888?IsAjax=1&dataType=json&isJson=true&cityCode=340100&type=1&num=2&isPage=true&pageIndex=3&pageSize=10&labelName=airQuality访问结果:
可以!删除_查询参数后不妨碍我们得到该JSON文件;同时,我们对比网页中第3页的数据,一毛一样。
现在我们观察pageIndex查询参数,可以看到第一页pageIndex的参数值为1,第二页为2,第三页为3, 则我们推断第页pageIndex查询参数的值为. 现在我把pageIndex的参数值改为4,并访问此URL地址:
对比网页中第4页的数据,一模一样。
JSON格式数据转换成python字典
现在我们理清了JSON文件的URL地址,就可以爬取网页,获取网页内容了。但是!需要注意的是,我们如何获取JSON文件里的数据呢?我们看到JSON的数据格式,有点像python里的字典和列表。其中要爬取的数据,被data键所对应的值(一个列表)包裹;每一条记录在列表中,被一个字典包裹:
{data: [{
aqi: "54",
area: "合肥市",
cityCode: 340100,
co: "0.363",
level: "II级",
measure: "",
no2: "30",
o3: "115",
pm10: "57",
pm25: "16",
primaryPollutant: "颗粒物(PM10)",
quality: "良",
so2: "7",
timePoint: "2020-04-15 18:00:00",
unheathful: ""
},
{
aqi: "49",
area: "合肥市",
cityCode: 340100,
co: "0.334",
level: "I级",
measure: "",
no2: "22",
o3: "123",
pm10: "49",
pm25: "15",
primaryPollutant: "—",
quality: "优",
so2: "7",
timePoint: "2020-04-15 17:00:00",
unheathful: ""
}],
pageCount: 5,
pageIndex: 4,
pageSize: 10,
startNumber: 40,
total: 47
}
但仔细观察我们又发现, 这些数据格式和字典又有细微差别,不能直接用字典的索引方式去获取数据. 所以,我们就想将JSON格式的数据转换为python字典类型,方便获取数据。
但是要怎么进行转换呢?
requests模块是我们的好帮手,使用requests.get().json()方法可以直接返回python的数据类型。
语法:
html_str = requests.get(url, headers=headers).json()按理来说,应该是返回一个字典的,但是!不知道为啥,它给我返回了一个包含着字典的字符串???我不想要字符串,我想要字典!现在我在网上查到了2种把字符串转换为字典类型的方法,演示一下:
In [43]: str_01 = '{"a":1}'In [44]: type(json.loads(str_01))
Out[44]: dict
In [45]: type(eval(str_01))
Out[45]: dict
如何获取科研数据
好了,现在回到我们该如何获取科研数据这个问题。这里,我就以我和小伙伴想要爬取的网站为例,就是我们刚才一直在讲解的安徽省生态环境厅的网站:
在这个案例中,我就获取网页中aqi(质量指数)、cityCode(城市代码)、pm25(PM2.5)这几个变量值.
PS:因为我太懒了,不想打那么多代码,大家可以举一反三,将其他变量值获取下来。
好,开始敲代码:
# -*- coding: utf-8 -*-import requests
import time
import csv
import json
class DeeSpider:
def __init__(self):
self.url = 'http://sthjt.ah.gov.cn/site/label/8888?IsAjax=1&dataType=json&isJson=true&cityCode=340100&type=1&num=2&isPage=true&pageIndex={}&pageSize=10&labelName=airQuality'
self.headers = {'Accept':'application/json, text/javascript, */*; q=0.01',
'Accept-Language':'zh-CN,zh;q=0.9',
'Connection':'keep-alive',
'Host':'sthjt.ah.gov.cn',
'User-Agent':'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36',
'X-Requested-With':'XMLHttpRequest'}
def get_data(self, url):
html = requests.get(url, headers=self.headers).json()
#得到的是字符串
print(type(html))
html_dict = json.loads(html)["data"]
#html_dict = html["data"]
#for循环遍历每条记录的数据
data_list = []
for data in html_dict:
aqi = data['aqi']
cityCode = data['cityCode']
pm25 = data['pm25']
#print(cityCode)
data_list.append([aqi, cityCode, pm25])
self.write_data(data_list)
def write_data(self, data_list):
with open('./test/my_DeeData.csv', 'a', newline = '') as f:
writer = csv.writer(f)
#writer.writerow(['aqi', 'cityCode', 'pm25'])
writer.writerows(data_list)
def main(self):
for item in range(1, 5):
url = self.url.format(item)
self.get_data(url)
if __name__ == '__main__':
start = time.time()
spider = DeeSpider()
spider.main()
end = time.time()
print('执行时间:%.2f' % (end-start))
控制台输出:
<class 'str'><class 'str'>
<class 'str'>
<class 'str'>
执行时间:1.15
爬取下来的部分数据(共37条):
85,340100,6393,340100,69
94,340100,70
92,340100,68
88,340100,65
85,340100,60
85,340100,49
82,340100,43
84,340100,44
87,340100,41
87,340100,35
83,340100,31
很好!数据都爬取下来了。
但是注意! 这个数据网站的页面虽然标明了有47条数据,并共有5页,但实际上只有37条数据,第5页我是咋地都点不开:
设置JSON的URL地址中查询参数pageIndex为5,得到的响应是系统繁忙:
。。。嗯
这个案例写完啦,欢迎补充,纠错,指导。
备注:虽然看上去,我们花了那么多心思,而爬取的数据并不多,但重要的是理解+举一反三。