猫哥教你写爬虫 045--协程

协程是什么

如果我们想要爬取的是成千上万条的数据,那么就会遇到一个问题:

因为程序是一行一行依次执行的缘故,要等待很久,我们才能拿到想要的数据

既然一个爬虫爬取大量数据要爬很久,那我们能不能让多个爬虫一起爬取?

一个人干不完的活儿,组个团队一起干,活一下被干完了

把三部电影都点击下载。哪一部先下载好了,就先看哪一部,还没有下载完的电影持续保持下载状态

在一个任务未完成时,就可以执行其他多个任务,彼此不受影响(在看第一部下载好的电影时,其他电影继续保持下载状态,彼此之间不受影响),叫异步

有异步的概念,也有同步的概念, 同步就是一个任务结束才能启动下一个(类比你看完一部电影,才能去看下一部电影)。

显然,异步执行任务会比同步更加节省时间,因为它能减少不必要的等待。如果你需要对时间做优化,异步是一个很值得考虑的方案。

如果我们把同步与异步的概念迁移到网络爬虫的场景中,那我们之前学的爬虫方式都是同步

爬虫每发起一个请求,都要等服务器返回响应后,才会执行下一步。而很多时候,由于网络不稳定,加上服务器自身也需要响应的时间,导致爬虫会浪费大量时间在等待上。这也是爬取大量数据时,爬虫的速度会比较慢的原因

我们可以采取异步的爬虫方式,让多个爬虫在执行任务时保持相对独立,彼此不受干扰,这样就可以免去等待时间, 显然这样爬虫的效率和速度都会提高。

计算机小知识

每台计算机都靠着CPU(中央处理器)干活。在过去,单核CPU的计算机在处理多任务时,会出现一个问题:每个任务都要抢占CPU,执行完了一个任务才开启下一个任务。CPU毕竟只有一个,这会让计算机处理的效率很低

为了解决这样的问题,一种非抢占式的异步技术被创造了出来,这种方式叫多协程

原理:一个任务在执行过程中,如果遇到等待,就先去执行其他的任务,当等待结束,再回来继续之前的那个任务。

在计算机的世界,这种任务来回切换得非常快速,看上去就像多个任务在被同时执行一样。

可以在等电饭煲蒸饭的时候去炒菜。而不是等饭做好,再去炒菜。你还是那个你,但工作时间就这样被缩短了

多协程的用法 gevent库

效果对比: 爬取8个网站(包括百度、新浪、搜狐、腾讯、网易、爱奇艺、天猫、凤凰)

同步的爬虫方式,是依次爬取网站,并等待服务器响应(状态码为200表示正常响应)后,才爬取下一个网站。比如第一个先爬取了百度的网址,等服务器响应后,再去爬取新浪的网址,以此类推,直至全部爬取完毕。

# 导入requests和time
import requests
import time
# 记录程序开始时间
start = time.time()
# 把8个网站封装成列表
url_list = [
    'https://www.baidu.com/',
    'https://www.sina.com.cn/',
    'http://www.sohu.com/',
    'https://www.qq.com/',
    'https://www.163.com/',
    'http://www.iqiyi.com/',
    'https://www.tmall.com/',
    'http://www.ifeng.com/'
]
# 遍历url_list
for url in url_list:
    # 用requests.get()函数爬取网站
    r = requests.get(url)
    # 打印网址和抓取请求的状态码
    print(url, r.status_code)
# 记录程序结束时间
end = time.time()
# end-start是结束时间减去开始时间,就是最终所花时间。
# 最后,把时间打印出来。
print(end-start)
复制代码

试试异步...

# 建议只要使用gevent记得把gevent相关的import语句放在所有其他import语句的前边
# 在最开头的地方gevent.monkey.patch_all();
# 把标准库中的thread/socket等给替换掉.
# 这样我们在后面使用socket的时候可以跟平常一样使用,无需修改任何代码,但是它变成非阻塞的了
import gevent
from gevent import monkey
monkey.patch_all()
import requests
import time
start = time.time()
url_list = [
    'https://www.baidu.com/',
    'https://www.sina.com.cn/',
    'http://www.sohu.com/',
    'https://www.qq.com/',
    'https://www.163.com/',
    'http://www.iqiyi.com/',
    'https://www.tmall.com/',
    'http://www.ifeng.com/'
]
def crawler(url):
    r = requests.get(url)
    print(url, time.time()-start, r.status_code)
tasks_list = []
for url in url_list:
    task = gevent.spawn(crawler, url)
    tasks_list.append(task)
gevent.joinall(tasks_list)
end = time.time()
print(end-start)
复制代码

我们案例爬取的数据量还比较小,不能直接体现出更大的速度差异。如果爬的是大量的数据,运用多协程会有更显著的速度优势

把爬取8个网站变成爬取80个网站,用同步的爬取方式大概需要花17.3秒,但用多协程异步爬取只需大概4.5秒,整个爬取效率提升了**280%+**。

安装 gevent ==> pip install gevent

之前的代码, 加上注释...

# 导入gevent、time、requests。
import requests
import time
import gevent
# 从gevent库里导入monkey模块。
from gevent import monkey
# monkey.patch_all()能把程序变成协作式运行,就是可以帮助程序实现异步。
monkey.patch_all()
# 记录程序开始时间。
start = time.time()
# 把8个网站封装成列表。
url_list = [
    'https://www.baidu.com/',
    'https://www.sina.com.cn/',
    'http://www.sohu.com/',
    'https://www.qq.com/',
    'https://www.163.com/',
    'http://www.iqiyi.com/',
    'https://www.tmall.com/',
    'http://www.ifeng.com/'
]
# 定义一个crawler()函数。
def crawler(url):
    # 用requests.get()函数爬取网站。
    r = requests.get(url)
    # 打印网址、请求运行时间、状态码。
    print(url, time.time()-start, r.status_code)
# 创建空的任务列表。
tasks_list = []
# 遍历url_list。
for url in url_list:
    # 用gevent.spawn()函数创建任务。
    task = gevent.spawn(crawler, url)
    # 往任务列表添加任务。
    tasks_list.append(task)
# 执行任务列表里的所有任务,就是让爬虫开始爬取网站。
gevent.joinall(tasks_list)
# 记录程序结束时间。
end = time.time()
# 打印程序最终所需时间。
print(end-start)
复制代码

gevent.spawn()的参数需为要调用的函数名及该函数的参数。比如,gevent.spawn(crawler,url)就是创建一个执行crawler函数的任务,参数为crawler函数名和它自身的参数url。

调用gevent库里的joinall方法,能启动执行所有的任务。gevent.joinall(tasks_list)就是执行tasks_list这个任务列表里的所有任务,开始爬取。

那如果我们要爬的不是8个网站,而是1000个网站,我们可以怎么做?

我们可以用gevent.spawn()创建1000个爬取任务,再用gevent.joinall()执行这1000个任务。

执行1000个任务,就是一下子发起1000次请求,这样子的恶意请求,会拖垮网站的服务器

既然这种创建1000个任务的方式不可取,我们能不能只创建成5个任务,每个任务爬取200个网站?

这5个任务之间是异步执行的,但是每个任务(爬取200个网站)内部是同步的

队列(银行叫号) queue模块

当我们用多协程来爬虫,需要创建大量任务时,我们可以借助queue模块

queue翻译成中文是队列的意思。我们可以用queue模块来存储任务,让任务都变成一条整齐的队列,就像银行窗口的排号做法。因为queue其实是一种有序的数据结构,可以用来存取数据。

这样,协程就可以从队列里把任务提取出来执行,直到队列空了,任务也就处理完了。就像银行窗口的工作人员会根据排号系统里的排号,处理客人的业务,如果已经没有新的排号,就意味着客户的业务都已办理完毕。

# 从gevent库里导入monkey模块。
import requests
import time
import gevent
from gevent.queue import Queue
from gevent import monkey
# monkey.patch_all()能把程序变成协作式运行,就是可以帮助程序实现异步。
monkey.patch_all()
# 导入gevent、time、requests
# 从gevent库里导入queue模块
# 记录程序开始时间
start = time.time()
url_list = [
    'https://www.baidu.com/',
    'https://www.sina.com.cn/',
    'http://www.sohu.com/',
    'https://www.qq.com/',
    'https://www.163.com/',
    'http://www.iqiyi.com/',
    'https://www.tmall.com/',
    'http://www.ifeng.com/'
]
# 创建队列对象,并赋值给work。
work = Queue()
# 遍历url_list
for url in url_list:
    # 用put_nowait()函数可以把网址都放进队列里。
    work.put_nowait(url)
def crawler():
    # 当队列不是空的时候,就执行下面的程序。
    while not work.empty():
        # 用get_nowait()函数可以把队列里的网址都取出。
        url = work.get_nowait()
        # 用requests.get()函数抓取网址。
        r = requests.get(url)
        # 打印网址、队列长度、抓取请求的状态码。
        print(url, work.qsize(), r.status_code)
#创建空的任务列表
tasks_list  = []
#相当于创建了8个爬虫
for x in range(8):
    #用gevent.spawn()函数创建执行crawler()函数的任务。
    task = gevent.spawn(crawler)
    #往任务列表添加任务。
    tasks_list.append(task)
#用gevent.joinall方法,执行任务列表里的所有任务,就是让爬虫开始爬取网站。
gevent.joinall(tasks_list)
end = time.time()
print(end-start)
复制代码

并行和并发

后来,我们的CPU从单核终于进化到了多核,每个核都能够独立运作。计算机开始能够真正意义上同时执行多个任务(术语叫并行执行),而不是在多个任务之间来回切换(术语叫并发执行

我们电脑一般都会是多核CPU。多协程,其实只占用了CPU的一个核运行,没有充分利用到其他核。利用CPU的多个核同时执行任务的技术,我们把它叫做“多进程”

真正大型的爬虫程序不会单单只靠多协程来提升爬取速度的。比如,百度搜索引擎,可以说是超大型的爬虫程序,它除了靠多协程,一定还会靠多进程,甚至是分布式爬虫

虽然爬虫是异步加多线程的,但是我们只能在一台主机上运行,所以爬取效率还是有限的,分布式爬虫则是将多台主机组合起来,共同完成一个爬取任务,这将大大提高爬取的效率

总结

同步和异步

多协程,是一种非抢占式的异步方式。使用多协程,就能让多个爬取任务用异步的方式交替执行。

快速跳转:

猫哥教你写爬虫 000--开篇.md
猫哥教你写爬虫 001--print()函数和变量.md
猫哥教你写爬虫 002--作业-打印皮卡丘.md
猫哥教你写爬虫 003--数据类型转换.md
猫哥教你写爬虫 004--数据类型转换-小练习.md
猫哥教你写爬虫 005--数据类型转换-小作业.md
猫哥教你写爬虫 006--条件判断和条件嵌套.md
猫哥教你写爬虫 007--条件判断和条件嵌套-小作业.md
猫哥教你写爬虫 008--input()函数.md
猫哥教你写爬虫 009--input()函数-人工智能小爱同学.md
猫哥教你写爬虫 010--列表,字典,循环.md
猫哥教你写爬虫 011--列表,字典,循环-小作业.md
猫哥教你写爬虫 012--布尔值和四种语句.md
猫哥教你写爬虫 013--布尔值和四种语句-小作业.md
猫哥教你写爬虫 014--pk小游戏.md
猫哥教你写爬虫 015--pk小游戏(全新改版).md
猫哥教你写爬虫 016--函数.md
猫哥教你写爬虫 017--函数-小作业.md
猫哥教你写爬虫 018--debug.md
猫哥教你写爬虫 019--debug-作业.md
猫哥教你写爬虫 020--类与对象(上).md
猫哥教你写爬虫 021--类与对象(上)-作业.md
猫哥教你写爬虫 022--类与对象(下).md
猫哥教你写爬虫 023--类与对象(下)-作业.md
猫哥教你写爬虫 024--编码&&解码.md
猫哥教你写爬虫 025--编码&&解码-小作业.md
猫哥教你写爬虫 026--模块.md
猫哥教你写爬虫 027--模块介绍.md
猫哥教你写爬虫 028--模块介绍-小作业-广告牌.md
猫哥教你写爬虫 029--爬虫初探-requests.md
猫哥教你写爬虫 030--爬虫初探-requests-作业.md
猫哥教你写爬虫 031--爬虫基础-html.md
猫哥教你写爬虫 032--爬虫初体验-BeautifulSoup.md
猫哥教你写爬虫 033--爬虫初体验-BeautifulSoup-作业.md
猫哥教你写爬虫 034--爬虫-BeautifulSoup实践.md
猫哥教你写爬虫 035--爬虫-BeautifulSoup实践-作业-电影top250.md
猫哥教你写爬虫 036--爬虫-BeautifulSoup实践-作业-电影top250-作业解析.md
猫哥教你写爬虫 037--爬虫-宝宝要听歌.md
猫哥教你写爬虫 038--带参数请求.md
猫哥教你写爬虫 039--存储数据.md
猫哥教你写爬虫 040--存储数据-作业.md
猫哥教你写爬虫 041--模拟登录-cookie.md
猫哥教你写爬虫 042--session的用法.md
猫哥教你写爬虫 043--模拟浏览器.md
猫哥教你写爬虫 044--模拟浏览器-作业.md
猫哥教你写爬虫 045--协程.md
猫哥教你写爬虫 046--协程-实践-吃什么不会胖.md
猫哥教你写爬虫 047--scrapy框架.md
猫哥教你写爬虫 048--爬虫和反爬虫.md
猫哥教你写爬虫 049--完结撒花.md

转载于:https://juejin.im/post/5cfc4adde51d45773d4685e4

猜你喜欢

转载自blog.csdn.net/weixin_34353714/article/details/91428165