100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > 【第四章-2】Python爬虫教程(协程 多任务异步协程 aiohttp模块 异步爬虫实战:爬

【第四章-2】Python爬虫教程(协程 多任务异步协程 aiohttp模块 异步爬虫实战:爬

时间:2020-01-15 14:56:29

相关推荐

【第四章-2】Python爬虫教程(协程 多任务异步协程 aiohttp模块 异步爬虫实战:爬

本课程共五个章节,课程地址:

【Python爬虫教程】花9888买的Python爬虫全套教程完整版现分享给大家!(已更新项目)——附赠课程与资料_哔哩哔哩_bilibili

第四章

本章内容梗概多线程多进程线程池和进程池抓取广州江南果菜批发市场菜价协程多任务异步协程aiohttp模块异步爬虫实战:扒光一本电子书综合训练:抓取一部电影

目录

第四章

(六)协程

(七)多任务异步协程

python编写协程的程序

官方推荐写法

在爬虫领域的应用

(八)aiohttp模块

安装

代码框架

例子

(九)异步爬虫实战:扒光一本电子书

思路

第一步:同步操作

第二步:异步操作

(六)协程

协程能够更加高效的利用CPU

其实,我们能够高效的利用多线程来完成爬虫已经很厉害了。但是,从某种角度讲,线程的执行效率真的就无敌了吗?我们真的充分利用CPU资源了吗?非也

比如,我们来看下面这个例子。我们单独的用一个线程来完成某一个操作,看看它的效率是否真的能把CPU完全利用起来

import timedef func():print("我爱黎明")time.sleep(3) # 让当前的线程处于阻塞状态,CPU是不为我工作的print("我真的爱黎明")if __name__ == '__main__':func()# 其他能让线程处于阻塞状态的语句(一般情况下,当程序处于IO操作的时候,线程都会处于阻塞状态) # input() 输入# requests.get() 发送请求(在网络请求返回数据之前,程序也是处于阻塞状态的)

在该程序中,我们的func()实际在执行的时候至少需要3s的时间来完成操作,中间的3s需要让我当前的线程处于阻塞状态,阻塞状态的线程CPU是不会来执行你的,那么此时CPU很可能会切换到其他程序上去执行。此时,对于你来说,在这3s内CPU并没有为你工作。那么我们能不能通过某种手段,让CPU一直为我工作,尽量不要去管其他人

我们知道CPU一般抛开执行周期不谈,如果一个线程遇到了IO操作,CPU就会自动地切换到其他线程进行执行。那么,如果我想办法让我的线程遇到了IO操作就挂起,留下的都是运算操作,那CPU是不是就会长时间的来照顾我

以此为目的,程序员就发明了一个新的执行过程:当线程中遇到了IO操作的时候,将线程中的任务进行切换,切换成非IO操作,等原来的IO执行完了,再恢复到原来的任务中

协程:当程序遇见了IO操作(费时不费力的操作)的时候,可以选择性的切换到其他任务上(程序完成的,不是操作系统完成的)

在微观上是一个任务一个任务的进行切换,切换条件一般就是IO操作在宏观上,我们能看到的其实是多个任务一起在执行(多任务异步操作)

上方所讲的一切,都是在单线程的条件下

(七)多任务异步协程

python编写协程的程序

import asyncioasync def func():print("你好啊,我叫赛利亚")if __name__ == '__main__':# print(func()) 此时拿到的是一个协程对象,和生成器差不多,该函数默认是不会这样执行的g = func() # 此时的函数是异步协程函数,函数执行得到的是一个协程对象asyncio.run(g) # 协程程序运行需要asyncio模块的支持

但上面这个代码是单任务的,下面写一个多任务的:

import asyncioimport timeasync def func1():print("你好啊,我叫潘金莲")time.sleep(3)print("你好啊,我叫潘金莲")async def func2():print("你好啊,我叫王建国")time.sleep(2)print("你好啊,我叫王建国")async def func3():print("你好啊,我叫李雪琴")time.sleep(4)print("你好啊,我叫李雪琴")if __name__ == '__main__':f1 = func1() # 此时的函数是异步协程函数,函数执行得到的是一个协程对象f2 = func2()f3 = func3()# 把三个任务统一放到列表里去tasks = [f1,f2,f3]t1 = time.time() # 执行之前记录一下时间# 一次性启动多个任务(协程)asyncio.run(asyncio.wait(tasks)) # 协程程序运行需要asyncio模块的支持t2 = time.time() # 执行之后记录一下时间print(t2-t1)

异步操作,但跑出来的是同步效果

异步效果的:

await:当该任务被挂起后,CPU会自动切换到其他任务中

import asyncioimport timeasync def func1():print("你好啊,我叫潘金莲")# time.sleep(3) # 当程序出现了同步操作的时候,异步就中断了 还有如requests.get()也会造成阻塞,碰到的时候也要换成对应的异步操作的代码await asyncio.sleep(3) # 异步操作的代码print("你好啊,我叫潘金莲")async def func2():print("你好啊,我叫王建国")# time.sleep(2)await asyncio.sleep(2)print("你好啊,我叫王建国")async def func3():print("你好啊,我叫李雪琴")# time.sleep(4)await asyncio.sleep(4)print("你好啊,我叫李雪琴")if __name__ == '__main__':f1 = func1() # 此时的函数是异步协程函数,函数执行得到的是一个协程对象f2 = func2()f3 = func3()# 把三个任务统一放到列表里去tasks = [ # 协程任务列表f1,f2,f3 # 创建协程任务]t1 = time.time() # 执行之前记录一下时间# 一次性启动多个任务(协程)asyncio.run(asyncio.wait(tasks)) # 协程程序运行需要asyncio模块的支持t2 = time.time() # 执行之后记录一下时间print(t2-t1)

官方推荐写法:

import asyncioimport timeasync def func1():print("你好啊,我叫潘金莲")await asyncio.sleep(3) # 异步操作的代码print("你好啊,我叫潘金莲")async def func2():print("你好啊,我叫王建国")await asyncio.sleep(2)print("你好啊,我叫王建国")async def func3():print("你好啊,我叫李雪琴")await asyncio.sleep(4)print("你好啊,我叫李雪琴")async def main(): # 协程函数# 第一种写法# f1 = func1()# await f1 # 一般await挂起操作放在协程对象前面# 第二种写法(推荐)tasks = [# 组成列表func1(),func2(),func3()]await asyncio.wait(tasks) # 一次性把所有任务都执行if __name__ == '__main__':t1 = time.time()asyncio.run(main()) # run协程对象t2 = time.time()print(t2-t1)

python3.8之后的版本运行上述代码时可能会有警告,将代码稍作修改:

# py3.8之后需要我们手动将协程对象包装成task对象tasks = [asyncio.create_task(func1()), # py3.8以后加上asyncio.create_task()asyncio.create_task(func2()),asyncio.create_task(func3())]await asyncio.wait(tasks)

在爬虫领域的应用:

import asyncio# 在爬虫领域的应用async def download(url):print("准备开始下载")await asyncio.sleep(2) # 模拟网络请求 不能写requests.get()print("下载完成")async def main():urls = ["","",""]tasks = []for url in urls:d = download(url) # 协程对象tasks.append(d)await asyncio.wait(tasks)if __name__ == '__main__':asyncio.run(main())

python3.8之后的版本运行上述代码时可能会有警告,将代码稍作修改:

# 准备异步协程对象列表tasks = []for url in urls:d = asycio.create_task(download(url))tasks.append(d)tasks = [asyncio.create_task(download(url)) for url in urls] # 这么干也行哦~

多线程和异步一个是程序执行模式,一个是IO模式。协程是单线程,事实上只是节省了CPU切栈的时间

(八)aiohttp模块

requests.get() 是同步的代码,如何换成异步的操作? 借助模块aiohttp(第三方库,需安装)

aiohttp是python的一个非常优秀的第三方异步http请求库,我们可以用aiohttp来编写异步爬虫(协程)

安装:

pip install aiohttp --trusted-host pypi.tuna.

代码框架:

import asyncioimport aiohttpurls = ["",""]# 异步下载async def aiodownload(url):passasync def main():tasks = [] # 添加下载任务for url in urls:tasks.append(aiodownload(url))await asyncio.wait(tasks) # 等待所有任务下载完成if __name__ == '__main__':asyncio.run(main())

例子:

【唯美壁纸】桌面壁纸唯美小清新_唯美手机壁纸_电脑桌面壁纸高清唯美大全 - 优美图库

import asyncio # 异步协程import aiohttp # 异步协程的http请求urls = [ # 复制图片地址"http://kr.shanghai-/file//1031/191468637cab2f0206f7d1d9b175ac81.jpg","http://kr.shanghai-/file//1031/563337d07af599a9ea64e620729f367e.jpg","http://kr.shanghai-/file//1031/774218be86d832f359637ab120eba52d.jpg"]async def aiodownload(url):# 发送请求 ——> 得到图片内容 ——> 保存到文件'''s = aiohttp.ClientSession() 等价于 requestsrequests.get() .post()s.get() .post()'''name = url.rsplit("/", 1)[1] # 从右边切, 切一次. 得到[1]位置的内容# 有了with会自动closeasync with aiohttp.ClientSession() as session: # session对象相当于requests对象async with session.get(url) as resp: # 发送请求,相当于resp = requests.get()# 请求回来了. 写入文件# 写入文件可以自己去学习一个模块aiofileswith open(name, mode="wb") as f: # 创建文件# resp.content.read() 等价于 resp.content 读取图片# resp.text() 等价于 resp.text 读取文本,如页面源代码# resp.json() 等价于 resp.json() 读取jsonf.write(await resp.content.read()) # 读取内容是异步的. 需要await挂起print(name, "搞定")async def main():tasks = []for url in urls:tasks.append(aiodownload(url))await asyncio.wait(tasks)if __name__ == '__main__':asyncio.run(main())

运行后能爬下图片,但是代码会报错:

asyncio.run()改为asyncio.get_event_loop().run_until_complete(main())即可。具体可参考下面这个帖子:

【Python自学笔记】asyncio.run()报错RuntimeError:Event loop is closed的原因以及解决办法_xiaoqiangclub的博客-CSDN博客_asyncio.run

if __name__ == '__main__':asyncio.get_event_loop().run_until_complete(main())

从最终运行的结果中能非常直观地看到用异步IO完成爬虫的效率明显高了很多

(九)异步爬虫实战:扒光一本电子书

百度小说

右键 ——> 查看网页源代码,如图:

说明页面的数据是通过 ajax 异步操作返回的数据(第二次加载的),故 f12 ,点击页面上 “全部章节(共100章)” 后的 “查看全部”

# 所有章节的内容(名称、cid)# 同步的方式即可/api/pc/getCatalog?data={"book_id":"4306063500"}

随便点进某一回的链接,如图:

# 章节内部的内容# 需要异步(100个章节即100个任务)/api/pc/getChapterContent?data={"book_id":"4306063500","cid":"4306063500|1569782244","need_bookinfo":1}

思路:

同步操作:访问getCatolog 拿到所有章节的cid和名称异步操作:访问getChapterContent 下载所有的文章内容

第一步:同步操作

怎么拿cid?

import requestsdef getCatalog(url):resp = requests.get(url)dic = resp.json()for item in dic['data']['novel']['items']: # item就是对应每一个章节的名称和cidtitle = item['title']cid = item['cid']print(title,cid)if __name__ == '__main__':b_id = "4306063500"url = '/api/pc/getCatalog?data={"book_id":"' + b_id + '"}'getCatalog(url)

第二步:异步操作

每一个cid就是一个异步任务

如何获取章节内容?

代码运行前,先创建一个文件夹,并把文件夹标记成 Excluded

# /api/pc/getCatalog?data={"book_id":"4306063500"} => 所有章节的内容(名称, cid)# /api/pc/getChapterContent?data={"book_id":"4306063500","cid":"4306063500|11348571","need_bookinfo":1} => 章节内部的内容import requestsimport asyncioimport aiohttpimport aiofiles # 异步的文件读写import json"""1. 同步操作: 访问getCatalog 拿到所有章节的cid和名称2. 异步操作: 访问getChapterContent 下载所有的文章内容"""async def aiodownload(cid, b_id, title):data = {"book_id":b_id,"cid":f"{b_id}|{cid}","need_bookinfo":1}data = json.dumps(data) # 将对象变为json字符串url = f"/api/pc/getChapterContent?data={data}"async with aiohttp.ClientSession() as session: # 准备好sessionasync with session.get(url) as resp:# session发送请求dic = await resp.json() # 从发送的请求里读取jsonasync with aiofiles.open(title, mode="w", encoding="utf-8") as f:await f.write(dic['data']['novel']['content']) # 把小说内容写出async def getCatalog(url):# 同步(此时还没有其他任务会和该任务一起并行执行,所以完全没必要用异步)resp = requests.get(url)dic = resp.json()# 得到要的内容之后,再异步tasks = []for item in dic['data']['novel']['items']: # item就是对应每一个章节的名称和cidtitle = item['title']cid = item['cid']# 准备异步任务tasks.append(aiodownload(cid, b_id, title))await asyncio.wait(tasks)if __name__ == '__main__':b_id = "4306063500" # 百度小说的书籍idurl = '/api/pc/getCatalog?data={"book_id":"' + b_id + '"}' # 首页urlasyncio.get_event_loop().run_until_complete(getCatalog(url))

运行后,将产生的文件全部移入之前建立好的文件夹里

怎么一次性选中多个文件?

左键选中一个文件,按住Ctrl+Shift,选中想要范围内的文件

【第四章-2】Python爬虫教程(协程 多任务异步协程 aiohttp模块 异步爬虫实战:爬取《西游记》全部章节内容)

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。