网站开发手机销售网站用例图,动易手机网站模板,公司网站建设的不足,网站如何做导航条Python编程之异步爬虫
协程的基本原理
要实现异步机制的爬虫#xff0c;自然和协程脱不了关系。
案例引入
先看一个案例网站#xff0c;地址为https://www.httpbin.org/delay/5#xff0c;访问这个链接需要先等5秒钟才能得到结果#xff0c;这是因为服务器强制等待5秒时…Python编程之异步爬虫
协程的基本原理
要实现异步机制的爬虫自然和协程脱不了关系。
案例引入
先看一个案例网站地址为https://www.httpbin.org/delay/5访问这个链接需要先等5秒钟才能得到结果这是因为服务器强制等待5秒时间才返回响应。下面来测试一下用requests写一个遍历程序直接遍历100次案例网站看看效果代码如下
import requests
import logging
import timelogging.basicConfig(levellogging.INFO, format%(asctime)s - %(levelname)s:%(message)s)TOTAL_NUMBER 100
URL https://www.httpbin.org/delay/5
start_time time.time()
for _ in range(1, TOTAL_NUMBER 1):logging.info(scraping %s, URL)response requests.get(URL)end_time time.time()
logging.info(total time %s seconds, end_time - start_time)使用的是requests单线程在爬取之前和爬取之后分别记录了时间最后输出了爬取100个页面消耗的总时间。运行结果如下
2024-03-23 18:45:12,159 - INFO:scraping https://www.httpbin.org/delay/5 2024-03-23 18:45:18,693 - INFO:scraping https://www.httpbin.org/delay/5 2024-03-23 18:45:24,865 - INFO:scraping https://www.httpbin.org/delay/5 2024-03-23 18:45:30,957 - INFO:scraping https://www.httpbin.org/delay/5 2024-03-23 18:45:37,544 - INFO:scraping https://www.httpbin.org/delay/5 …. ….
2024-03-23 18:55:19,929 - INFO:scraping https://www.httpbin.org/delay/5 2024-03-23 18:55:26,069 - INFO:scraping https://www.httpbin.org/delay/5 2024-03-23 18:55:32,186 - INFO:total time 620.0276908874512 seconds
由于每个页面至少等待5秒钟100个页面至少花费500秒加上网站本身负载问题总时间大约620秒10分钟多。
基础知识
协程的基础概念
1. 阻塞和非阻塞
阻塞当一个任务执行时如果需要等待某个操作完成才能继续执行这个任务就会被阻塞。在阻塞状态下任务无法执行其他操作。非阻塞相对于阻塞非阻塞任务在等待某个操作完成时可以继续执行其他操作。
2. 同步和异步
同步指的是程序按照代码顺序依次执行一个操作完成之后才会进行下一个操作。异步异步编程允许程序在等待某个操作的同时继续执行其他操作操作完成后通过回调或者事件通知来处理结果。
3. 多进程和协程
多进程每个进程有自己独立的内存空间系统为每个进程分配资源进程间通信开销较大。协程协程coroutine是一种轻量级的线程可以看作是在同一个线程内部进行切换执行不同任务共享同一个进程的资源更高效利用 CPU 和内存。
协程的特点
轻量级 协程不需要像线程那样创建新的进程或者线程因此比多线程的切换开销更小。灵活性 协程可以根据需要暂停和恢复执行可以实现任务的合理调度。高效性 由于不需要进行系统调用、进程/线程切换协程可以更高效地利用计算资源。
在 Python 中使用 asyncio 库可以实现协程。通过 async 和 await 关键字可以定义异步函数和阻塞点在适当的时机挂起和恢复函数的执行。
协程的优点在于它们可以解决异步编程中的并发性问题并且能够提供更好的性能和资源利用率。通过合理地使用协程可以实现高效的并发编程尤其在 I/O 密集型应用中表现突出。 协程的用法 在 Python 中可以使用 asyncio 库来实现协程。以下是协程的基本用法示例 定义一个异步函数 使用 async def 关键字定义一个异步函数该函数可以包含 await 表达式来挂起执行。 import asyncioasync def greet():print(Hello)await asyncio.sleep(1)print(World) b. 运行协程任务 使用 asyncio.run() 函数来运行协程任务并且保证事件循环的创建和销毁。 asyncio.run(greet()) c. 创建并发任务 使用 asyncio.create_task() 函数创建多个并发任务让它们同时运行。 async def task1():print(Task 1 start)await asyncio.sleep(2)print(Task 1 end)async def task2():print(Task 2 start)await asyncio.sleep(1)print(Task 2 end)async def main():taskA asyncio.create_task(task1())taskB asyncio.create_task(task2())await taskAawait taskBasyncio.run(main()) d. 并发等待多个任务完成 使用 asyncio.gather() 函数等待多个任务完成后再继续执行。 async def main():tasks [task1(), task2()]await asyncio.gather(*tasks)asyncio.run(main()) e. 异步IO操作 在协程中可以进行异步的IO操作例如网络请求、文件读写等操作以提高应用程序的性能和效率。 通过上述示例您可以了解到如何定义、运行和管理协程以及如何利用协程来处理并发任务和异步IO操作。在实际应用中协程可以帮助降低资源消耗提高程序响应性并简化复杂的并发编程任务。 定义协程 import asyncioasync def execute(x):print(Number:, x)coroutine execute(1)
print(Coroutine:, coroutine)
print(After calling excute)loop asyncio.get_event_loop()
loop.run_until_complete(coroutine)
print(After calling loop)运行结果如下
Coroutine: coroutine object execute at 0x10f5b37c0
After calling excute
Number: 1
After calling loop导入asyncio包这样才可以使用async和await关键字。然后使用async定义一个execute方法该方法接收一个数字参数x执行之后会打印这个数字。 随后直接执行execute方法然而这个方法没有执行而是返回了一个coroutine协程对象。之后我们使用了get_event_loop方法创建了一个事件循环loop调用loop对象的run_until_complete方法将协程对象注册到了事件循环中接着启动。可见async定义的方法会变成一个无法直接执行的协程对象必须将此对象注册到事件循环中才可以执行。 当我们把协程对象coroutine传递给run_until_complete方法的时候实际上它进行了一个操作就是将coroutine封装成task对象。显示声明代码如下 import asyncioasync def execute(x):print(Number:, x)return xcoroutine execute(1)
print(Coroutine:, coroutine)
print(After calling execute)loop asyncio.get_event_loop()
task loop.create_task(coroutine)
print(Task:,task)
loop.run_until_complete(task)
print(Task:, task)
print(After calling loop)运行结果如下
Coroutine: coroutine object execute at 0x10faf37c0
After calling execute
Task: Task pending nameTask-1 coroexecute() running at /Users/bruce_liu/PycharmProjects/崔庆才--爬虫/第6章异步爬虫/协程用法4.py:3
Number: 1
Task: Task finished nameTask-1 coroexecute() done, defined at /Users/bruce_liu/PycharmProjects/崔庆才--爬虫/第6章异步爬虫/协程用法4.py:3 result1
After calling loop定义task对象还有另外一种方法就是直接调用asyncio包的ensure_future方法返回结果也是task对象写法如下 import asyncioasync def execute(x):print(Number:, x)return xcoroutine execute(1)
print(Coroutine:, coroutine)
print(After calling execute)task asyncio.ensure_future(coroutine)
print(Task:, task)
loop asyncio.get_event_loop()
loop.run_until_complete(task)
print(Task:, task)
print(After calling loop)运行结果如下
Coroutine: coroutine object execute at 0x10c3737c0
After calling execute
Task: Task pending nameTask-1 coroexecute() running at /Users/bruce_liu/PycharmProjects/崔庆才--爬虫/第6章异步爬虫/协程用法5.py:3
Number: 1
Task: Task finished nameTask-1 coroexecute() done, defined at /Users/bruce_liu/PycharmProjects/崔庆才--爬虫/第6章异步爬虫/协程用法5.py:3 result1
After calling loop绑定回调 为某个task对象绑定一个回调方法如下所示 import asyncio
import requestsasync def request():url https://www.baidu.comstatus requests.get(url)return statusdef callback(task):print(Status:, task.result())coroutine request()
task asyncio.ensure_future(coroutine)
task.add_done_callback(callback)
print(Task:, task)loop asyncio.get_event_loop()
loop.run_until_complete(task)
print(Task:, task)定义了request方法在这个方法里请求了百度并获取了其状态码随后我们定义了callback方法这个方法接收一个参数参数是task对象在这个方法中调用print方法打印出task对象的结果。这样就定义好了一个协程对象和一个回调方法我们希望达到的效果是当协程对象执行完毕后就去执行声明的callback方法。如何关联的呢只要调用add_done_callback方法就行。将callback方法传递给封装好的task对象。这样当task执行完之后就可以调用callback方法了。同时task对象还会作为参数传递给callback方法调用task对象的result方法就可以获取返回结果了。运行结果如下 Task: Task pending nameTask-1 cororequest() running at /Users/bruce_liu/PycharmProjects/崔庆才--爬虫/第6章异步爬虫/绑定回调.py:4 cb[callback() at /Users/bruce_liu/PycharmProjects/崔庆才--爬虫/第6章异步爬虫/绑定回调.py:9]
status: Response [200]
task: Task finished nameTask-1 cororequest() done, defined at /Users/bruce_liu/PycharmProjects/崔庆才--爬虫/第6章异步爬虫/绑定回调.py:4 resultResponse [200]实际上即使不使用回调方法在task运行完毕后也可以直接调用result方法获取结果代码如下 import asyncio
import requestsasync def request():url https://www.baidu.comstatus requests.get(url)return statuscoroutine request()
task asyncio.ensure_future(coroutine)
print(Task:, task)loop asyncio.get_event_loop()
loop.run_until_complete(task)
print(Task:, task)
print(Task Result:, task.result())运行结果如下
Task: Task pending nameTask-1 cororequest() running at /Users/bruce_liu/PycharmProjects/崔庆才--爬虫/第6章异步爬虫/绑定回调1.py:5
Task: Task finished nameTask-1 cororequest() done, defined at /Users/bruce_liu/PycharmProjects/崔庆才--爬虫/第6章异步爬虫/绑定回调1.py:5 resultResponse [200]
Task Result: Response [200]多任务协程 如果想执行多次请求应该怎么办可以定义一个task列表然后使用asyncio包中的wait方法执行如下所示 import asyncio
import requestsasync def request():url https://www.baidu.comstatus requests.get(url)return statustasks [asyncio.ensure_future(request()) for _ in range(5)]
print(Tasks:, tasks)loop asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))for task in tasks:print(Task Result:, task.result())运行结果如下
Tasks: [Task pending nameTask-1 cororequest() running at /Users/bruce_liu/PycharmProjects/崔庆才--爬虫/第6章异步爬虫/多任务协程.py:5, Task pending nameTask-2 cororequest() running at /Users/bruce_liu/PycharmProjects/崔庆才--爬虫/第6章异步爬虫/多任务协程.py:5, Task pending nameTask-3 cororequest() running at /Users/bruce_liu/PycharmProjects/崔庆才--爬虫/第6章异步爬虫/多任务协程.py:5, Task pending nameTask-4 cororequest() running at /Users/bruce_liu/PycharmProjects/崔庆才--爬虫/第6章异步爬虫/多任务协程.py:5, Task pending nameTask-5 cororequest() running at /Users/bruce_liu/PycharmProjects/崔庆才--爬虫/第6章异步爬虫/多任务协程.py:5]
Task Result: Response [200]
Task Result: Response [200]
Task Result: Response [200]
Task Result: Response [200]
Task Result: Response [200]协程实现 协程在解决IO密集型任务方面的优势耗时等待一般都是IO操作例如文件读取、网络请求等。协程在处理这种操作时是有很大优势的当遇到需要等待的情况时程序可以暂时挂起转而执行其他操作避免浪费时间。 以https://www.httpbin.org/delay/5为例体验一下协程的效果。示例代码如下 import asyncio
import requests
import timestart time.time()async def request():url https://www.httpbin.org/delay/5print(waiting for, url)response requests.get(url)print(Get response from, url, response, response)tasks [asyncio.ensure_future(request()) for _ in range(10)]
loop asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))end time.time()
print(Cost time:, end - start)运行结果如下
waiting for https://www.httpbin.org/delay/5
Get response from https://www.httpbin.org/delay/5 response Response [200]
waiting for https://www.httpbin.org/delay/5
Get response from https://www.httpbin.org/delay/5 response Response [200]
waiting for https://www.httpbin.org/delay/5
Get response from https://www.httpbin.org/delay/5 response Response [200]
...
waiting for https://www.httpbin.org/delay/5
Get response from https://www.httpbin.org/delay/5 response Response [200]
waiting for https://www.httpbin.org/delay/5
Get response from https://www.httpbin.org/delay/5 response Response [200]
waiting for https://www.httpbin.org/delay/5
Get response from https://www.httpbin.org/delay/5 response Response [200]
Cost time: 63.61974787712097可以发现与正常的顺序请求没有啥区别。那么异步处理的优势呢要实现异步处理先得有挂起操作当一个任务需要等待IO结果的时候可以挂起当前任务转而执行其他任务这样才能充分利用好资源。 使用aiohttp aiohttp是一个支持异步请求的库它和asyncio配合使用可以使我们非常方便地实现异步请求操作。 aiohttp分为两部分一部分是Client一部分是Server。 下面我们将aiohttp投入使用将代码改成如下 import asyncio
import aiohttp
import timestart time.time()async def get(url):session aiohttp.ClientSession()response await session.get(url)await response.text()await session.close()return responseasync def request():url https://www.httpbin.org/delay/5print(Waiting for, url)response await get(url)print(Get response from, url, response, response)tasks [asyncio.ensure_future(request()) for _ in range(10)]
loop asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))end time.time()
print(Cost time:, end - start)运行结果如下
Waiting for https://www.httpbin.org/delay/5
Waiting for https://www.httpbin.org/delay/5
Waiting for https://www.httpbin.org/delay/5
Waiting for https://www.httpbin.org/delay/5
Waiting for https://www.httpbin.org/delay/5
Waiting for https://www.httpbin.org/delay/5
...
Get response from https://www.httpbin.org/delay/5 response ClientResponse(https://www.httpbin.org/delay/5) [200 OK]
CIMultiDictProxy(Date: Sat, 23 Mar 2024 13:42:05 GMT, Content-Type: application/json, Content-Length: 367, Connection: keep-alive, Server: gunicorn/19.9.0, Access-Control-Allow-Origin: *, Access-Control-Allow-Credentials: true)Get response from https://www.httpbin.org/delay/5 response ClientResponse(https://www.httpbin.org/delay/5) [200 OK]
CIMultiDictProxy(Date: Sat, 23 Mar 2024 13:42:05 GMT, Content-Type: application/json, Content-Length: 367, Connection: keep-alive, Server: gunicorn/19.9.0, Access-Control-Allow-Origin: *, Access-Control-Allow-Credentials: true)
...
Get response from https://www.httpbin.org/delay/5 response ClientResponse(https://www.httpbin.org/delay/5) [200 OK]
CIMultiDictProxy(Date: Sat, 23 Mar 2024 13:42:05 GMT, Content-Type: application/json, Content-Length: 367, Connection: keep-alive, Server: gunicorn/19.9.0, Access-Control-Allow-Origin: *, Access-Control-Allow-Credentials: true)Get response from https://www.httpbin.org/delay/5 response ClientResponse(https://www.httpbin.org/delay/5) [200 OK]
CIMultiDictProxy(Date: Sat, 23 Mar 2024 13:42:05 GMT, Content-Type: application/json, Content-Length: 367, Connection: keep-alive, Server: gunicorn/19.9.0, Access-Control-Allow-Origin: *, Access-Control-Allow-Credentials: true)Cost time: 6.868626832962036这里将请求库由requests改成了aiohttp利用aiohttp库里ClientSession类的get方法进行请求。 测试一下并发量分别为1、3、5、10、….、500时的耗时情况代码如下 import asyncio
import aiohttp
import timedef test(number):start time.time()async def get(url):session aiohttp.ClientSession()response await session.get(url)await response.text()await session.close()return responseasync def request():url https://www.baidu.com/await get(url)tasks [asyncio.ensure_future(request()) for _ in range(number)]loop asyncio.get_event_loop()loop.run_until_complete(asyncio.wait(tasks))end time.time()print(Number:, number, Cost time:, end - start)for number in [1, 3, 5, 10, 15, 30, 50, 75, 100, 200, 500]:test(number)运行结果如下
Number: 1 Cost time: 0.23929095268249512
Number: 3 Cost time: 0.19086170196533203
Number: 5 Cost time: 0.20035600662231445
Number: 10 Cost time: 0.21305394172668457
Number: 15 Cost time: 0.25495195388793945
Number: 30 Cost time: 0.769071102142334
Number: 50 Cost time: 0.3470029830932617
Number: 75 Cost time: 0.4492309093475342
Number: 100 Cost time: 0.586918830871582
Number: 200 Cost time: 1.0910720825195312
Number: 500 Cost time: 2.4768006801605225