Python异步爬虫之协程抓取妹子图片(aiohttp、aiofiles) 您所在的位置:网站首页 async协程 Python异步爬虫之协程抓取妹子图片(aiohttp、aiofiles)

Python异步爬虫之协程抓取妹子图片(aiohttp、aiofiles)

#Python异步爬虫之协程抓取妹子图片(aiohttp、aiofiles)| 来源: 网络整理| 查看: 265

 

 

目录

前言

一、什么是协程?

二、协程的优势

三、代码分析

1.引入库

2.获取所有时间线的链接

3.获取一个时间线中所有相册的链接

4.获取一个相册中所有的图片链接以及相册的名字

5.下载并保存图片

6.main函数

7.主方法

四、完整代码

前言

在爬虫的过程中,效率是一个很关键的问题,最常用的是多线程、多进程、线程池、进程池等等。这篇文章主要介绍使用协程来完成抓取妹子图片。

 

一、什么是协程?

协程,英文Coroutines,是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程 。最重要的是,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是用户执行)。

 

  二、协程的优势

优势就是性能得到了很大的提升,不会像线程切换那样消耗资源。协程的开销远远小于线程的开销。协程本质是单线程,在不占用更多系统资源的情况下,将IO操作进行了挂起,然后继续执行其他任务,等待IO操作完成之后,再返回原来的任务继续执行。

 

三、代码分析 1.引入库 import requests #同步的网络请求模块 import re #正则模块,用于提取数据 import asyncio #创建并管理事件循环的模块 import aiofiles #可异步的文件操作模块 import aiohttp #可异步的网络请求模块 import os #可调用操作系统的模块 2.获取所有时间线的链接 #定义一个同步函数,使用requests库进行请求 def get_date_list(): #目标链接 url='https://zhaocibaidicaiyunjian.ml/' #伪装请求头 header={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36'} #发送请求 r=requests.get(url=url,headers=header) #获取网页页面源代码 htm=r.text #使用正则模块re解析获得时间线的链接 results=re.findall('''(.*?)''',htm,re.S) date_list_str=str(re.findall('''.*?''',str(results))) date_list=re.findall('''\\'(.*?)\\\\\\\\\'''',date_list_str) #返回一个所有时间线链接的列表 return date_list 3.获取一个时间线中所有相册的链接 #判断一个时间线页面是否含有第二页(在下面的协程函数get_album_urls中调用) def hasnext(Responsetext): if re.findall('''下一页''', Responsetext): nextpage = re.findall('''下一页''',Responsetext)[0] return nextpage else: return None #async 定义一个协程函数,参数为一个时间线的链接 async def get_album_urls(date_url): header = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36'} #创建一个上下文管理器,并创建一个可异步的session对象 async with aiohttp.ClientSession() as session: #session的get()的方法与requests库中的用法一样(注:requests库中get()方法的代理参数为字典形式proxies=dict,支持http和https。而session中get()方法的代理参数为字符串,proxy=str。且仅支持http,不支持https) async with session.get(url=date_url,headers=header) as Response: #获取网页页面源代码,使用await将网络IO请求挂起,程序继续执行其他任务,等待内容返回后再跳转到此处继续执行 htm=await Response.text() #使用正则提取所有相册的链接 album_urls=re.findall('''继续阅读.*?''',htm) #判断此时间线页面是否含有第二页 nextpage=hasnext(htm) #如果有第二页则提取第二页所有的相册链接 if nextpage: async with session.get(url=nextpage,headers=header) as Response1: htm1=await Response1.text() #列表生成器,将第二页中的所有相册链接加入到列表album_urls中 [album_urls.append(album_url) for album_url in re.findall('''继续阅读.*?''',htm1)] #返回一个时间线中所有的相册链接 return album_urls 4.获取一个相册中所有的图片链接以及相册的名字 #async 定义一个协程函数,参数为一个相册链接 async def get_pic_urls_and_title(album_url): header = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36'} #创建一个上下文管理器,并创建一个可异步的session对象 async with aiohttp.ClientSession() as session: #session的get()的方法与requests库中的用法一样(注:requests库中get()方法的代理参数为字典形式proxies=dict,支持http和https。而session中get()方法的代理参数为字符串,proxy=str。且仅支持http,不支持https) async with session.get(url=album_url,headers=header) as Response: #获取网页页面源代码,使用await将网络IO请求挂起,程序继续执行其他任务,等待内容返回后再跳转到此处继续执行 htm=await Response.text() #使用正则提取出所有的图片地址以及相册的名字 pic_urls=re.findall('''.*?''',htm,re.S) title=re.findall('''(.*?)''',htm,re.S)[0] #返回所有的图片地址以及相册的名字 return pic_urls,title 5.下载并保存图片 #定义一个协程函数,参数为一个相册的所有图片地址以及相册的名字 async def download(pic_urls,title): header = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36'} #为一个相册创建一个文件夹 dir_name = title #判断是否有这个文件夹,有则结束,否则创建一个文件夹并将图片放入 if os.path.exists(dir_name): print(dir_name + '这个图片夹已存在') return False elif not os.path.exists(dir_name): os.mkdir(dir_name) print(f'--------正在下载: {dir_name}--------') #对每张照片进行异步请求 for pic_url in pic_urls: #图片的名字取图片的链接地址 pic_name = pic_url.split('/')[-1] async with aiohttp.ClientSession() as session: async with session.get(url=pic_url, headers=header) as Response: #创建一个上下文管理器,并创建一个可异步的文件对象 async with aiofiles.open(file=dir_name + '/' + pic_name, mode='wb') as f: #因为Response对象的read()和text()方法会将响应一次性全部读入内存,这会导致内存爆满,导致卡顿,影响效率。 #因此采取字节流的形式,每次读取4096个字节并写入文件 while True: #遇到IO阻塞的情况则挂起,等待内容返回再跳转到此处继续执行 pic_stream = await Response.content.read(4096) #如果读取完毕之后,则跳出此次循环 if not pic_stream: break #文件写入为IO操作,挂起后执行其他任务,写入完成后跳转到此处继续执行 await f.write(pic_stream) 6.main函数 #定义一个协程函数,参数为一个时间线链接 async def main(date_url): #获取一个时间线中所有的相册链接,使用await进行挂起操作(因为此处get_album_urls为一个协程对象,并且内部有IO等待) album_urls=await get_album_urls(date_url) #获取每个相册中的相册名字以及所有图片地址 for album_url in album_urls: #获取一个相册中的所有图片地址以及相册名字,使用await进行挂起操作(因为此处get_pic_urls_and_title为一个协程对象,并且内部有IO等待) pic_urls,title=await get_pic_urls_and_title(album_url) #下载一个相册,使用await进行挂起操作(因为此处download为一个协程对象,并且内部有IO等待) await download(pic_urls,title) 7.主方法 if __name__=="__main__": #创建一个任务列表 tasks=[] #使用同步获取所有时间线的地址(因为后面的所有的操作都基于获取到的时间线链接,所以使用同步操作将所有链接获取完毕后再进行接下来的操作) date_list=get_date_list() #为每个时间线链接都创建为一个任务对象 for date_url in date_list: #此处不是立即执行main函数,而是创建了一个协程对象 task=main(date_url) #将任务添加到任务列表中 tasks.append(task) #创建一个事件循环,用户接收信息(接收某个任务的状态,未执行?,执行中?,执行完毕?) loop=asyncio.get_event_loop() #此处是真正执行任务,等待所有任务执行结束(可以认为是一种固定的写法) loop.run_until_complete(asyncio.wait(tasks)) #所有任务执行完毕后关闭事件循环,释放资源 loop.close()   四、完整代码 import requests #同步的网络请求模块 import re #正则模块,用于提取数据 import asyncio #创建并管理事件循环的模块 import aiofiles #可异步的文件操作模块 import aiohttp #可异步的网络请求模块 import os #可调用操作系统的模块 #定义一个同步函数,使用requests库进行请求 def get_date_list(): #目标链接 url='https://zhaocibaidicaiyunjian.ml/' #伪装请求头 header={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36'} #发送请求 r=requests.get(url=url,headers=header) #获取网页页面源代码 htm=r.text #使用正则模块re解析获得时间线的链接 results=re.findall('''(.*?)''',htm,re.S) date_list_str=str(re.findall('''.*?''',str(results))) date_list=re.findall('''\\'(.*?)\\\\\\\\\'''',date_list_str) #返回一个所有时间线链接的列表 return date_list #判断一个时间线页面是否含有第二页(在下面的协程函数get_album_urls中调用) def hasnext(Responsetext): if re.findall('''下一页''', Responsetext): nextpage = re.findall('''下一页''',Responsetext)[0] return nextpage else: return None #async 定义一个协程函数,参数为一个时间线的链接 async def get_album_urls(date_url): header = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36'} #创建一个上下文管理器,并创建一个可异步的session对象 async with aiohttp.ClientSession() as session: #session的get()的方法与requests库中的用法一样(注:requests库中get()方法的代理参数为字典形式proxies=dict,支持http和https。而session中get()方法的代理参数为字符串,proxy=str。且仅支持http,不支持https) async with session.get(url=date_url,headers=header) as Response: #获取网页页面源代码,使用await将网络IO请求挂起,程序继续执行其他任务,等待内容返回后再跳转到此处继续执行 htm=await Response.text() #使用正则提取所有相册的链接 album_urls=re.findall('''继续阅读.*?''',htm) #判断此时间线页面是否含有第二页 nextpage=hasnext(htm) #如果有第二页则提取第二页所有的相册链接 if nextpage: async with session.get(url=nextpage,headers=header) as Response1: htm1=await Response1.text() #列表生成器,将第二页中的所有相册链接加入到列表album_urls中 [album_urls.append(album_url) for album_url in re.findall('''继续阅读.*?''',htm1)] #返回一个时间线中所有的相册链接 return album_urls #async 定义一个协程函数,参数为一个相册链接 async def get_pic_urls_and_title(album_url): header = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36'} #创建一个上下文管理器,并创建一个可异步的session对象 async with aiohttp.ClientSession() as session: #session的get()的方法与requests库中的用法一样(注:requests库中get()方法的代理参数为字典形式proxies=dict,支持http和https。而session中get()方法的代理参数为字符串,proxy=str。且仅支持http,不支持https) async with session.get(url=album_url,headers=header) as Response: #获取网页页面源代码,使用await将网络IO请求挂起,程序继续执行其他任务,等待内容返回后再跳转到此处继续执行 htm=await Response.text() #使用正则提取出所有的图片地址以及相册的名字 pic_urls=re.findall('''.*?''',htm,re.S) title=re.findall('''(.*?)''',htm,re.S)[0] #返回所有的图片地址以及相册的名字 return pic_urls,title #定义一个协程函数,参数为一个相册的所有图片地址以及相册的名字 async def download(pic_urls,title): header = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36'} #为一个相册创建一个文件夹 dir_name = title #判断是否有这个文件夹,有则结束,否则创建一个文件夹并将图片放入 if os.path.exists(dir_name): print(dir_name + '这个图片夹已存在') return False elif not os.path.exists(dir_name): os.mkdir(dir_name) print(f'--------正在下载: {dir_name}--------') #对每张照片进行异步请求 for pic_url in pic_urls: #图片的名字取图片的链接地址 pic_name = pic_url.split('/')[-1] async with aiohttp.ClientSession() as session: async with session.get(url=pic_url, headers=header) as Response: #创建一个上下文管理器,并创建一个可异步的文件对象 async with aiofiles.open(file=dir_name + '/' + pic_name, mode='wb') as f: #因为Response对象的read()和text()方法会将响应一次性全部读入内存,这会导致内存爆满,导致卡顿,影响效率。 #因此采取字节流的形式,每次读取4096个字节并写入文件 while True: #遇到IO阻塞的情况则挂起,等待内容返回再跳转到此处继续执行 pic_stream = await Response.content.read(4096) #如果读取完毕之后,则跳出此次循环 if not pic_stream: break #文件写入为IO操作,挂起后执行其他任务,写入完成后跳转到此处继续执行 await f.write(pic_stream) #定义一个协程函数,参数为一个时间线链接 async def main(date_url): #获取一个时间线中所有的相册链接,使用await进行挂起操作(因为此处get_album_urls为一个协程对象,并且内部有IO等待) album_urls=await get_album_urls(date_url) #获取每个相册中的相册名字以及所有图片地址 for album_url in album_urls: #获取一个相册中的所有图片地址以及相册名字,使用await进行挂起操作(因为此处get_pic_urls_and_title为一个协程对象,并且内部有IO等待) pic_urls,title=await get_pic_urls_and_title(album_url) #下载一个相册,使用await进行挂起操作(因为此处download为一个协程对象,并且内部有IO等待) await download(pic_urls,title) if __name__=="__main__": #创建一个任务列表 tasks=[] #使用同步获取所有时间线的地址(因为后面的所有的操作都基于获取到的时间线链接,所以使用同步操作将所有链接获取完毕后再进行接下来的操作) date_list=get_date_list() #为每个时间线链接都创建为一个任务对象 for date_url in date_list: #此处不是立即执行main函数,而是创建了一个协程对象 task=main(date_url) #将任务添加到任务列表中 tasks.append(task) #创建一个事件循环,用户接收信息(接收某个任务的状态,未执行?,执行中?,执行完毕?) loop=asyncio.get_event_loop() #此处是真正执行任务,等待所有任务执行结束(可以认为是一种固定的写法) loop.run_until_complete(asyncio.wait(tasks)) #所有任务执行完毕后关闭事件循环,释放资源 loop.close()

 



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有