注:本文章为学习过程中对知识点的记录,供自己复习使用,也给大家做个参考,如有错误,麻烦指出,大家共同探讨,互相进步。
借鉴出处:
该文章的路线和主要内容:崔庆才(第2版)python3网络爬虫开发实战
前言:爬虫属于IO密集型任务,例如使用request库来爬取某个站点,当发出一个请求后,程序必须等待网站返回响应,才能接着运行,而在等待响应的过程中,整个爬虫程序是一直在等待的,实际上没有做任何事情。
1.1 基础知识
- 阻塞
阻塞状态指程序未得到所需计算资源时被挂起的状态。程序在等待某个操作完成期间,自身无法继续干别的事情,则称该程序在操作上是阻塞的。 - 非阻塞
程序在等待某操作的过程中,自身不被阻塞,可以继续干别的事情,则称该程序在该操作上是非阻塞的。
非阻塞并不是在任何程序级别、任何情况下都存在的。仅当程序封装的级别可以囊括独立的子程序单元时,程序才可能存在非阻塞状态。
非阻塞因阻塞的存在而存在,正因为阻塞导致程序运行的耗时增加与效率低下,我们才要把它变成非阻塞的。 - 同步
不同程序单元为了共同完成某个任务,在执行过程中需要靠某种通信方式保持协调一致,此时这些程序单元是同步执行的。
例如在购物系统中更新商品库存时,需要用“行锁”作为通信信号,强制让不同的更新请求排队并按顺序执行,这里的更新库存操作就是同步的。 - 异步
为了完成某个任务,有时不同程序单元之间无须通信协调也能完成任务,此时不相关的程序单元之间可以是异步的。 - 多进程
多进程就是利用CPU多核的优势,在同一时间并执行多个任务,可以大大提高执行效率。 - 协程
协程(coroutine),又称作微线程、纤程,是一种运行在用户态的轻量级线程。
协程拥有自己的寄存器上下文和栈。协程在调度切换时,将寄存器上下文和栈保存到其他地方,等切换回来的时候,再恢复先前保存的寄存器上下文和栈。因此,协程能保留上一次调用时的状态。
协程本质上是个单进程,相对于多进程,它没有线程上下文切换的开销,没有原子操作锁定及同步的开销,编程模型也非常简单。
1.2 协程的用法
Python中使用协程最常用的库为asyncio。
- event_loop:事件循环,相当于一个无限虚幻,我们可以把一些函数注册到这个事情循环上,当满足发生条件时,就调用对应的处理方法。
- coroutine:中文翻译叫协程,在Python中常指代协程对象类型,我们可以将协程对象注册到时间循环中,它会被事件训话调用。我们可以使用async关键字来定义一个方法,这个方法在调用时不会立即被执行,而是会返回一个协程对象。
- task:任务,这是对协程对昂的进一步封装,包含协程对象的各个状态。
- future:代表将来执行或者没有执行的任务的结果,实际上和task没有本质区别。
1.2.1 定义协程
例子1:了解协程对象、事件循环
输入:
输出:
例子2:了解task任务
输出:
创建任务的另一种方式:
1.2.2 绑定回调
绑定回调的作用就是当协程对象执行完毕之后,就去执行声明的回调函数。
输入:
输出:
分析:实际上,即使不适用回调方法,在task运行完毕之后,也可以直接调用result方法获取结果。
1.2.2 多任务协程
执行多次请求,可以定义一个task列表,然后使用asyncio包中的wait方法执行。
输入:
输出:
分析:
loop函数不要放在定义函数内,否则会报错。
await后面的对象必须是如下格式之一:
1、一个原生协程对象;
2、一个由types.coroutine修饰的生成器,这个生成器可以返回协程对象;
3、由一个包含__await__方法的对象返回的一个迭代器。
上面声明的方式比较复杂,aiohttp是一个支持异步请求的库,它和asyncio配合使用,可以非常方便地实现异步请求操作。
安装
2.1 以一个例子开始aiohttp
输入:
输入:
分析:时间循环会运行第一个task,执行第一个get方法时,会被挂起,但get方法第一步是创建了ClientSession对象,是非阻塞的,挂起后会被立马唤醒。接着执行await session.get是会被挂起等待,此期间事件循环会寻找当前未被挂起的协程继续进行。都被挂起后,请求还没有响应,就继续等待直到获取到结果。
2.2 基本介绍
asynic模块内部实现了对TCP、UDP、SSL协议的异步操作,但是对于HTTP请求来说,就需要用aiohttp实现了。
aiphttp是一个基于asynico的异步HTTP网络模块,它既提供了服务端,有提供了客户端。其中,我们用服务器可以搭建一个支持异步处理的处理器,这个服务器就是用来处理请求并返回响应的,类似于Django、Flask等一些Web服务器。而客户端可以用来发送请求,类似于使用requests发起一个HTTP请求然后获得响应,但requests发起的是通的网络请求,aiohttp则是异步的。
输入:
aiohttp客户端例子
输出:
分析:
aiohttp实现的异步爬取,与之前的定义有明显的区别,主要包括以下几点:
- aiohttp是对http请求进行异步爬取的库,实现异步爬取,需要启动协程,而协程则需要借助asynico里面的事件循环才能执行。
- 每个异步方法的前面都要统一+async来修饰。(async定义的方法会变成一个无法直接执行的协程对象,必须将此对象注册到事件循环中才可以执行)
- with as前面加上async代表声明一个支持异步的上下文管理器。(with as能够自动分配和释放资源)
- 对于一些返回协程对象的操作,官方API文档里,response.text返回的是client对象,所以要在前面加上await;response.status返回的是数值,因此前面不要加await。参考官方文档https://docs.aiohttp.org/en/stable/client_reference.html
- 最后定义的协程对象要调用事件循环。
2.3 URL参数设置
响应,与requests响应基本一致。需要加await的要查看响应类型是否是协程对象(如async修饰的方法),具体查看apihttp的API官方文档。
设置超时
ClientTimeout对象还有其他connect、socket_connect等参数,详细API可以参考官方文档:https://docs.aiohttp.org/en/stable/client_quickstart.html#timeouts
并发限制
由于aiohttp可以支持非常高的并发量,百万量都是能做到的,所以部分网站如果响应不过来,有瞬间将目标网站爬挂掉的危,这是就要限制爬取的并发量。
一般情况下,借助asynico的Semaphore来控制并发量
分析:
asyncio.await && asyncio.gather
相同:从功能上看,asyncio.wait 和 asyncio.gather 实现的效果是相同的,都是把所有 Task 任务结果收集起来。
不同:asyncio.wait 会返回两个值:done 和 pending,done 为已完成的协程 Task,pending 为超时未完成的协程 Task,需通过 future.result 调用 Task 的 result;而asyncio.gather 返回的是所有已完成 Task 的 result,不需要再进行调用或其他操作,就可以得到全部结果。
爬取目标
1、爬取地址:https://spa5.scrape.center/
2、使用aiohttp爬取全站的图书数据;
3、将数据存储到数据库或独立文件中;