Python爬虫之异步讲解
1 异步爬虫
1.1 异步了解
使用高性能爬虫可以缩短爬取用时,提供爬取效率
目的:在爬虫中使用异步实现高性能的数据爬取操作
异步爬虫的方式有:
- 多线程和多进程
好处:可以为相关阻塞的操作单独开启线程或者进程,阻塞操作就可以异步执行
坏处:无法无限制的开启多线程或者多进程(如果不限制的开启了,会严重消耗CPU
资源,这样会导致响应外界效率变慢) - 线程池和进程池
好处:我们可以降低系统对进场或者线程创建和销毁的一个频率,从而很好的降低系统的开销
坏处:池中线程或者进程的数量是有上限的,倘若远远超过了上限,爬取效率就会下降
2 多线程
2.1 多线程讲解
多线程类似于同时执行多个不同程序,多线程运行,使用线程可以把占据长时间的程序中的任务放到后台去处理。
每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。
指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存。
线程可以被抢占(中断)。
在其他线程正在运行时,线程可以暂时搁置(也称为睡眠) — 这就是线程的退让。
线程可以分为:
- 内核线程:由操作系统内核创建和撤销。
- 用户线程:不需要内核支持而在用户程序中实现的线程。
2.2 thread模块
thread
模块已被废弃。用户可以使用threading
模块代替。所以,在 Python3
中不能再使用thread
模块。为了兼容性,Python3
将 thread
重命名为 _thread
调用 _thread
模块中的start_new_thread()
函数来产生新线程。语法如下:
_thread.start_new_thread ( function, args[, kwargs] )
参数说明:
function
– 线程函数。args
– 传递给线程函数的参数,它必须是个tuple
类型kwargs
– 可选参数
使用例子:
2.3 threading
Python3
通过两个标准库 _thread
和 threading
提供对线程的支持
_thread
提供了低级别的、原始的线程以及一个简单的锁,它相比于 threading
模块的功能还是比较有限的。
threading
模块除了包含 _thread
模块中的所有方法外,还提供的其他方法:
threading.currentThread():
返回当前的线程变量。threading.enumerate():
返回一个包含正在运行的线程的list
。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。threading.activeCount():
返回正在运行的线程数量,与len(threading.enumerate())
有相同的结果。
除了使用方法外,线程模块同样提供了Thread类
来处理线程,Thread类
提供了以下方法:
run():
用以表示线程活动的方法start():
启动线程活动join([time]):
等待至线程中止
join
:让主线程
等待子线程
结束之后才能继续运行,比如如下程序,看着是thread2
调用了join
方法,其实是当前线程在运行,所以当前main
线程要等待thread2
运行完毕后,才能运行main
线程
isAlive():
返回线程是否活动的getName():
返回线程名setName():
设置线程名
使用例子:
3 线程池
3.1 单线程串行
单线程串行就是阻塞连续执行命令,假如有一个耗时时间长,就会一直等待到执行完毕,如下操作大概耗时8秒
3.2 使用线程池
导入线程池使用:from multiprocessing.dummy import Pool
如下操作,就是使用线程池后大概2秒
4 协程操作
最推荐的不是线程池,而是单线程和协程一起操作
4.1 协程基本概念
使用协程中的一般概念:
event_loop:
事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上,当满足某些条件的时候,函数就会被循环执行coroutine:
协程对象,我们可以将协程对象注册到事件循环中,它会被事件循环调用。可以使用async
关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回一个协程对象task:
任务,它是对协程对象的进一步封装,包含了任务的各个状态future:
代表将来执行或还没有执行的任务,实际上和task
没有本质区别async:
定义一个协程,不会立即执行
await:
用来挂起阻塞方法的执行
4.2 协程基本操作
4.2.1 协程对象
使用async
定义一个协程对象,并创建一个事件循环对象
4.2.2 task对象
task对象需要loop对象基础上建立起来
4.2.3 future对象
future对象与task对象不同的是创建基于asyncio空间来创建的
4.2.4 绑定回调
在使用task
或者future
绑定回调时,需要先定义回调函数
4.2.4.1 定义回调函数
回调函数中返回的result
方法就是任务对象
中封装的协程对象
对应的函数返回值
注意:
回调函数必须有返回值,不然result
方法就没有值
4.2.4.2 绑定回调
在使用task
或者future
绑定回调时,都可以使用方法绑定task.add_done_callback(callback_func)
4.2.5 异步多任务
首先说明下async\await
的使用
正常的函数在执行时是不会中断的,所以要写一个能够中断的函数,就需要添加async
关键字
async
用来声明一个函数为异步函数
,异步函数的特点是能在函数执行过程中挂起,去执行其他异步函数,等到挂起条件(假设挂起条件是sleep(5))消失后,也就是5秒到了再回来执行。
await
用来用来声明程序挂起
,比如异步程序执行到某一步时需要等待的时间很长,就将此挂起,去执行其他的异步程序。await
后面只能跟异步程序或有__await__
属性的对象,因为异步程序与一般程序不同。假设有两个异步函数async a
,async b
,a
中的某一步有await
,当程序碰到关键字await b()
后,异步程序挂起后去执行另一个异步b
程序,就是从函数内部跳出去执行其他函数,当挂起条件消失后,不管b
是否执行完,要马上从b
程序中跳出来,回到原程序执行原来的操作。
如果await
后面跟的b
函数不是异步函数,那么操作就只能等b执行
完再返回,无法在b
执行的过程中返回。如果要在b
执行完才返回,也就不需要用await
关键字了,直接调用b函数
就行。所以这就需要await``后面跟的是异步函数了。 在一个异步函数中,可以不止一次挂起,也就是可以用多个``await
另外多任务时,对于run_until_complete
方法需要这样用asyncio.wait()
方法处理:loop.run_until_complete(asyncio.wait(task_list))
代码示例:
4.2.6 aiohttp模块
由于在使用异步多任务时,就不能用request.get()
,因为此方法是同步的,需要使用aiohttp
模块了
在使用aiohttp
模块先安装环境:pip intall aiohttp
,使用该模块中的ClientSession
使用时需要用async
修饰为异步,并用await
修饰耗时操作
微信赞赏
支付宝赞赏