医疗机构网站模板,小广告的胶怎么清理,有哪个网站是成都中科大旗做的,做网站实验报告异步爬虫
一、协程的基本原理
1、案例
案例网站#xff1a;https://www.httpbin.org/delay/5、这个服务器强制等待了5秒时间才返回响应
测试#xff1a;用requests写一个遍历程序#xff0c;遍历100次案例网站#xff1a;
import requests
import logging
import time…异步爬虫
一、协程的基本原理
1、案例
案例网站https://www.httpbin.org/delay/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/5start_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)# 爬取总时间约为11分钟2、基本知识
2.1、阻塞
阻塞状态指程序未得到所需计算资源时被挂起的状态。程序在等待某个操作完成期间自身无法继续干别的事情则称该程序在该操作上市阻塞的 。
常见的阻塞形式有网络I/O阻塞、磁盘I/O阻塞、用户输入阻塞等。阻塞是无处不在的包括在CPU切换上下文时所有进程都无法真正干事情它们也会被阻塞。在多核CPU的情况下正在执行上下文切换操作的核不可被利用。
2.2、非阻塞
程序在等待某操作的过程中自身不被阻塞可以继续干别的事情则称该程序在该操作上是非阻塞的。
非阻塞并不是在任何程序级别、任何情况下都存在的。仅当程序封装的级别可以囊括独立的子程序单元时程序才可能存在非阻塞状态。
非阻塞因阻塞的存在而存在正因为阻塞导致程序运行时的耗时增加与效率低下我们才要把它变成非阻塞的。
2.3、同步
不同单元为了共同完成某个任务在执行过程中需要靠某种通信方式保持协调一致此时这些程序单元是同步执行的。
同步意味着有序。
2.4、异步
为了完成某个任务有时不同程序单元之间戊戌通信协调也能完成任务此时不相关的程序单元之间可以是异步的。
异步意味着无序。
2.5、多进程
多进程就是利用CPU的多核优势在同一时间并行执行多个任务可以大大提高执行效率。
2.6、协程
协程英文叫做coroutine又称微线程、纤程是一种运行在用户太的轻量级线程。
协程拥有自己的寄存器上下文和栈。协程在调度切换时将寄存器上下文和栈保存到其他地方等切回来的时候再恢复先前保存的寄存器上下文和栈。因此协程能保留上一次调用的状态即所有局部状态的一个特定组合每次过程重入就相当于上一次调用的状态。
协程本质是单进程相对于多进程来说它没有线程上下文切换的开销没有原子操作锁定及同步的开销编程模型也非常简单。
3、协程的用法
Python3.4开始Python中加入了协程的概念但这个版本的协程还是以生成器对象为基础。Python3.5中增加了asyncio、await使得协程的实现更为方便。
Python中使用协程最常用的莫过于asyncio库。
event_loop事件循环相当于一个无限循环我们可以把一些函数注册到这个事件循环上、当满足发生条件的时候就调用对应的处理方法。coroutine中文翻译叫协程在Python中长指代协程对象类型我们可以将协程对象注册到事件循环中它会被事件循环调用。我们可以使用async关键字来定义一个方法这个方法在调用时不会立即被执行而是会返回一个协程对象。task任务这是对协程对象的进一步封装包含协程对象的各个状态。future代表将来执行或者没有执行的任务的结果实际上和task没有本质。
4、准备工作
确保安装的Python版本为3.5以上。
5、定义协程
import asyncio # 引入asyncio包这样才能使用async和await关键字。async def execute(x): # 使用async定义了一个execute方法该方法接受一个数字参数x执行之后会打印这个数字。print(Number:,x)
coroutine execute(1) # 调用这execute方法然而没有被执行而是返回了一个coroutine协程对象。
print(Coroutine:,coroutine)
print(After calling execute)loop asyncio.get_event_loop() # 使用get_event_loop方法创建了一个事件循环loop
loop.run_until_complete(coroutine) # 调用loop对象的run_until_complete方法将协程对象注册到了事件循环中接着启动。才看见了这个数字。
print(After calling loop)可见async定义的方法会变成一个无法执行的协程对象必须将此对象注册到事件循环中才可以执行。前面提到的task是对协程对象的进一步封装比协程对象多了个运行状态例如runing、finished等我们可以利用这些状态获取协程对象的执行情况。在上述例子中我们把协程对象coroutine传递给run_untill_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) # 调用loop对象的create_task方法将协程对象转为task对象随后打印输出一下发现它处于pending状态
print(Task:,task) # pending
loop.run_until_complete(task) # 将task对象加入到事件循环中执行后发现状态变为finished
print(Task:,task) # finished
print(After calling loop)定义task对象还有另外一种方式就是直接调用asyncio包的ensure_future方法返回结果也是task对象这样的话就可以不借助loop对象。即使还没有声明loop也可以提取定义好task对象
async 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)6、绑定回调
我们也可以为某个task对象绑定一个回调方法
import asyncio
import requestsasync def request(): # 定义request方法请求百度返回状态码。url https://www.baidu.comstatus requests.get(url)return statusdef callback(task): # 定义callback方法接受一个task对象参数打印task对象的结果。print(Status:,task.result())coroutine request()
task asyncio.ensure_future(coroutine)
task.add_done_callback(callback) # 将callback方法传递给封装好的task对象这样当task执行完毕后就可以调用callback方法了。同时task对象还会作为参数传递给callback方法调用task对象的result方法就可以获取返回结果。
print(Task:,task)loop asyncio.get_event_loop()
loop.run_until_complete(task)
print(Task:,task)实际上即使不适用回调方法在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(Result:,task.result())7、多任务协程
如果想执行多次请求可以定义一个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(Tasl result:,task.result())使用一个for循环创建了5个task它们组成一个列表列表推导式然后把这个列表首先传递给asyncio包的wait方法再将其注册到事件循环中就可以发起5个任务了。
8、协程实现
await不能和requests返回的Response对象一起使用。await后面的对象必须是以下
一个原生协程对象一个由types.coroutine修饰的生成器这个生成器可以返回协程对象由一个包含_await_方法的对象返回一个迭代器
import asyncio
import requests
import timestart time.time()
async def get(url):return requests.get(url)async 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)报错说明仅仅将涉及I/O操作的代码封装到async修饰的方法是不可行的。只有使用支持异步操作的请求方式才可以实现真正的异步。aiohttp就派上用场了
9、使用aiohttp
aiohttp是一个支持异步请求的库它和asyncio配合使用可以使我们方便的实现异步请求操作。
pip3 install aiohttpimport asyncio
import requests
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 requestsasync 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) # 6秒二、aiohttp的使用
1、基本介绍
aiohttp是一个基于asyncio的异步HTTP网络模块它既提供了服务端又提供了客户端。
其中我们用服务端可以搭建一个支持异步处理的服务器这个服务器就是用来处理请求并返回响应的类似Djaongo、Flask等。而客户端可以用来发起请求类似于使用requests发起一个HTTP请求然后获得响应但requests发起的是同步的网络请求aiohttp是异步的。
2、基本实例
import aiohttp
import asyncioasync def fetch(session,url):async with session.get(url) as response:return await response.text(),response.statusasync def main():async with aiohttp.ClientSession() as session:html,status await fetch(session,https://cuiqingcai.com)print(fhtml:{html[:100]}...)print(fstatus:{status})if __name__ __main__:loop asyncio.get_event_loop()loop.run_until_complete(main())
# asyncio.run(main()) aiohttp的请求方法与之前的差别
必须引入aiohttp库和asyncio库。因为实现异步爬取需要启动协程而协程需要借助asyncio里面的事件循环才能执行。除了事件循环aasyncio里面也提供了许多基础的异步操作。异步爬取方法的定义不同每个异步方法的前面都要统一加async来修饰。with as 语句前面同样需要加async来修饰。在Python中with as 语句用来声明一个上下文管理器能够帮我们自动分配和释放资源。而在异步方法中with as前面加上async代表声明一个支持异步的上下文管理器。对于一些返回协程对象的操作前面需要加await来修饰。定义完爬取方法之后实际上是main方法调用了fetch方法。要运行的话必须启用事件循环。
在Python3.7及以后的版本我们可以使用asyncio.run(main())代替最后的启动操作不需要显示声明事件循环run()方法内部会自动启用一个事件循环。
3、URL参数设置
对于URL参数的设置我们可以借助params参数传入一个字典即可
import aiohttp
import asyncioasync def main():params {name:germey,age:25}async with aiohttp.ClientSession() as session:async with session.get(https://www.httpbin.org/get,paramsparams) as response:print(await response.text())if __name__ __main__:asyncio.get_event_loop().run_until_complete(main())实际请求的URL为https://www.httpbin.org/get?namegermeyage25、其中的参数对应params的内容
4、其他请求类型
aiohttp还支持其他请求类型
async with session.get(http://www.httpbin.org/get,databdata)
async with session.put(http://www.httpbin.org/get,databdata)
async with session.delete(http://www.httpbin.org/get,)
async with session.head(http://www.httpbin.org/get,)
async with session.options(http://www.httpbin.org/get,)
async with session.patch(http://www.httpbin.org/get,databdata)5、POST请求
对于POST表单提交其对应的请求头中的Content-Type为application/x-www-form-urlencoded实现
import asyncio
import aiohttpasync def main():data {name:germey,age:25}async with aiohttp.ClientSession() as session:async with session.post(https://www.httpbin.org/post,datadata) as response:print(await response.text())if __name__ __main__:asyncio.get_event_loop().run_until_complete(main())对于POST JSON数据提交其对应的请求头中的Content-Type为application/json将post方法里的data参数改成json即可
async def main():data {name:germey,age:25}async with aiohttp.ClientSession() as session:async with session.post(https://www.httpbin.org/post,jsondata) as response:print(await response.text())6、响应
对于响应来说我们可以用如下方法分别获取其中的状态码、响应头、响应体响应体二进制内容、响应体JSON结果
import aiohttp
import asyncioasync def main():data {name:germey,age:25}async with aiohttp.ClientSession() as session:async with session.post(https://www.httpbin.org/post,datadata) as response:print(status:,response.status)print(headers:,response.headers)print(body:,await response.text())print(bytes:,await response.read())print(json:,await response.json())if __name__ __main__:asyncio.get_event_loop().run_until_complete(main())有些字段需要加await的原因是如果返回的是一个协程对象如async修饰的方法那么前面就要加await。
7、超时设置
可以借助ClientTimeout对象设置超时例如要设置1秒的超时时间
import aiohttp
import asyncioasync def main():timeout aiohttp.ClientTimeout(total1)async with aiohttp.ClientSession(timeouttimeout) as session:async with session.get(https://www.httpbin.org/get) as response:print(status:,response.status)if __name__ __main__:asyncio.get_event_loop().run_until_complete(main())如果超时则抛出TimeoutRrror异常。
8、并发限制
由于aiohttp可以支持非常高的并发量面对高的并发量目标网站可能无法在短时间内响应而且有瞬间将目标网站爬挂掉。因此需要借助asyncio的Semaphore控制一下爬取的并发量
import asyncio
import aiohttpCONCURRENCY 5
URL https://www.baidu.comsemaphore asyncio.Semaphore(CONCURRENCY)
session Noneasync def scrap_api():async with semaphore:print(scraping,URL)async with session.get(URL) as response:await asyncio.sleep(1)return await response.text()
async def main():global sessionsession aiohttp.ClientSession()scrape_index_tasks [asyncio.ensure_future(scrap_api()) for _ in range(10000)]await asyncio.gather(*scrape_index_tasks)if __name__ __main__:asyncio.get_event_loop().run_until_complete(main())这里声明CONCURRENCY代表爬取的最大并发量为5同时声明爬取的目标为百度…
三、aiohttp异步爬取实战
1、案例介绍
网站https://spa5.scrape.center/
2、准备工作
安装好了Python了解Ajax爬取的一些基本原理和模拟方法了解异步爬虫的基本原理和asyncio库的基本用法了解asiohttp库的基本用法
3、页面分析
列表页的Ajax请求接口格式为https://spa5.scrape.center/api/book/?limit18offset{offset}在列表页的Ajax接口返回的数据里results字段包含当前页里18本图书的信息其中每本书的数据里都含有一个id字段这个id就是图书本身的ID详情页的Ajax请求格式为https://spa5.scrape.center/api/book{id}
4、实现思路
第一阶段异步爬取所有列表页将所有列表页的爬取任务集合在一起并将其声明为由task组成的列表进行异步爬取。第二阶段拿到上一步列表页的所有内容解析将所有图书的ID信息组合为所有详情页的爬取任务集合并将其声明为task组成的列表。进行异步爬取结果也以异步新式存储到数据库。
5、基本配置
import asyncio
import aiohttp
import logginglogging.basicConfig(levellogging.INFO,format%(asctime)s - %(levelname)s:%(message)s)
INDEX_URL https://spa5.scrape.center/api/book/?limit18offset{offset}
DETAIL_URL https://spa5.scrape.center/api/book/{id}
PAGE_SIZE 18
PAGE_NUMBER 100
CONCURRENCY 56、爬取列表页
爬取列表页先定义一个通用的爬取方法
semaphore asyncio.Semaphore(CONCURRENCY) # 声明信号量控制最大并发数量
session Noneasync def scrape_api(url): # 定义scrape_api方法接受一个参数apiasync with semaphore: # 用async with语句引入信号量作为上下文try:logging.info(scraping %s,url)async with session.get(url) as response: # 调用session的get方法请求urlreturn await response.json() # 返回响应的JSON格式except aiohttp.ClientError: # 进行异常处理logging.error(error occurred while scraping %s,url,exc_infoTrue)爬取列表页
async def scrape_index(page): # 爬取列表页方法接受一个参数pageurl INDEX_URL.format(offsetPAGE_SIZE * (page - 1)) # 构造一个列表页的URLreturn await scrape_api(url) # scripe_api调用之后本身会返回一个协程对象所以加await定义main()方法将上面的方法串联起来调用
async def main():global session # 声明最初声明的全局变量sessionsession aiohttp.ClientSession() scrape_index_tasks [asyncio.ensure_future(scrape_index(page)) for page in range(1,PAGE_NUMBER 1)] # 用于爬取列表页所有的task组成的列表results await asyncio.gather(*scrape_index_tasks) # 调用gather方法将task列表传入其参数将结果赋值为results它是由所有task返回结果组成的列表。logging.info(results %s,json.dumps(results,ensure_asciiFalse,indent2))if __name__ __main__: # 调用main方法开启事件循环。asyncio.get_event_loop().run_until_complete(main())7、爬取详情页
在main方法里增加results的解析代码
ids []
for index_data in results:if not index_data: continuefor item in index_data.get(results):ids.append(item.get(id))在定义两个方法用于爬取详情页和保存数据
async def save_data(data):logging.info(saving data %s,data)... # 以后再补async def scrape_detail(id):url DETAIL_URL.format(idid)data await scrape_api(url)await save_data(data)接着在main方法里面增加对scrape_detail方法的调用即可爬取详情页
scrape_detail_tasks [asyncio.ensure_future(scrape_detail(id)) for id in ids]await asyncio.wait(scrape_detail_tasks)await session.close()