异步 & ajax | 下载unplash高清大图
写在前面:
第一篇关于爬虫的文章,主要涉及异步操作和带ajax网页的分析。
- 操作系统:win10
- IDE:VS Code
- Python 3.6
- 异步库:asyncio
- 异步请求库:aiohttp
- 同步请求库:urllib、requests
- 其他相关库:os、time
这里限于篇幅原因,可能无法对每一行代码进行详细的剖析,在后面的文章中,我也将不断地总结、记录一些python库地用法。
大家都知道,python的第三方库正在不断地扩大,因此,对于常用库的积累和对相应库的学习,都是非常重要的。(在我看来,自己造轮子挺不现实的)
Ajax是什么?
Ajax 即 A synchronous Javascript And XML(异步 JavaScript 和 XML),是指一种创建交互式网页应用的网页开发技术。
Ajax = 异步 JavaScript 和 XML 或者是 HTML(标准通用标记语言的子集)。
Ajax 是一种用于创建快速动态网页的技术。
Ajax 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。
通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
传统的网页(不使用 Ajax)如果需要更新内容,必须重载整个网页页面。
——《百度百科》
关键一点:Ajax请求的文件格式是XHR。
举例子说,当你访问unplash的时候,往下滑动图片,你会发现在你滑动的过程中,有些图片还没加载出来,需要等待一小段时间,这些图片就是通过Ajax加载的。
关于Ajax的更多信息,可以看一下崔庆才大佬的博客:
1. 分析并访问Ajax:
(因为用的是火狐浏览器,所以开发者选项是中文的,不过还是建议用谷歌浏览器)
首先,按下F12,打开开发者选项,选中Network(网络)这一项。一开始,当我们还为向下滑动鼠标时,只显示了一个get请求,如下图。

当我们开始向下滑动鼠标的时候,下方的图片开始通过Ajax加载出来,生成许多的CSS、JS等格式的文件。而我们知道Ajax的文件格式是XHR,可以通过选择器选择仅显示XHR的文件,方便我们分析。




可以看到,这些Ajax的请求头都有两个参数, page和per_page。
page表示第几页,per_page表示每一页有多少张图片。
通过对比,我们发现只有page这个参数在变,而per_page一直等于12。因此,我们仅需要改变page参数的值,就可以访问到不同的Ajax。
这里主要的难点是如何构造出URL中特定的参数,这里主要有两种方式,一种是利用urllib中的urlencode(),另一种是requests中的params参数。
【requests的params参数】
# 对ajax进行请求,返回JSON响应
def __get_img_links(self, page):
url = 'https://unsplash.com/napi/photos'
data = {
'page': page,
'per_page': 12,
}
response = requests.get(url, headers=self.headers,params=data)
if response.status_code == 200:
return response.json()
else:
print(f'请求失败,状态码为{response.status_code}')
【urllib的urlencode()函数】
import requests
from urllib.parse import urlencode
def __get_img_links(self, page):
params = {
'page': page,
'per_page': 12,
}
url = 'https://unsplash.com/napi/photos/?' + urlencode(params)
response = requests.get(url,headers=self.headers)
if response.status_code == 200:
return response.json()
else:
print(f'请求失败,状态码为{response.status_code}')
2. 分析Ajax返回的JSON响应
我们可以在开发者选项中响应中查看返回的JSON响应。

也可以将其中一个Ajax的请求在新的标签页中打开,如下图。我个人是比较喜欢在新的标签页中打开,然后进行分析。(主要是受网页大小的限制)


这里,我们仅需要图片的ID和下载链接这两个信息。
- ID:作为图片的文件名
- 下载链接:用于下载图片
至此,对unplash这个网页的Ajax分析就已经完成了,接下来就是代码实现了。
3. 代码实现
import requests, os, time
import aiohttp, asyncio
class Spider(object):
# 初始化
def __init__(self):
self.headers = {
'Referer':'https://unsplash.com/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'}
self.num = 1
if '图片' not in os.listdir('.'):
os.mkdir('图片')
self.path = os.path.join(os.path.abspath('.'), '图片')
os.chdir(self.path) # 进入文件下载路径
# 获取ajax返回的JSON中链接,并进行请求
async def __get_content(self, link):
async with aiohttp.ClientSession() as session:
response = await session.get(link,headers=self.headers)
content = await response.read()# 读取图片的二进制数据
return content
# 对ajax进行请求,返回JSON响应
def __get_img_links(self, page):
url = 'https://unsplash.com/napi/photos'
data = {
'page': page,
'per_page': 12,
}
response = requests.get(url, headers=self.headers,params=data)
if response.status_code == 200:
return response.json()
else:
print(f'请求失败,状态码为{response.status_code}')
# 下载图片
# img:tuple元组
# img[0]:图片的id
# img[1]:图片的下载链接
async def __download_img(self, img):
# 调用__get_content()方法,返回图片的二进制数据
content = await self.__get_content(img[1])
with open(img[0]+'.jpg', 'wb') as f:
f.write(content)
print(f'下载第{self.num}张图片成功')
self.num += 1
# 调度函数,整合前面的函数
def run(self):
start = time.time()
for x in range(1, 101): # 下载一百页的图片就可以了,或者自己更改页数
links = self.__get_img_links(x)
tasks = [asyncio.ensure_future(self.__download_img((link['id'], link['links']['download']))) for link in links]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
# 测试速度使用,如需要下载多张图片可以注释这段代码
if self.num >= 10:
break
end = time.time()
print(f'共运行了{end-start}秒')
def main():
spider = Spider()
spider.run()
if __name__ == '__main__':
main()
4. 结果展示


写在最后:
参考资料:
python爬虫涉及的东西相对较多,需要不断地积累、总结,可以成为我们学习python的一个不错的方法。
限于篇幅,代码无法一一细讲,还望各位大佬多包涵。
人生苦短,重拾Python。
Per Week,Hard Work!