动力网站建设,网站开发系统需求说明书,洛阳生活网,优秀网页设计作品案例欣赏文章目录 前言协程是什么多协程的用法gevent库queue模块 拓展复习复习 前言
照旧来回顾上一关的知识点#xff01;上一关我们学习如何将爬虫的结果发送邮件#xff0c;和定时执行爬虫。
关于邮件#xff0c;它是这样一种流程#xff1a; 我们要用到的模块是smtplib和emai… 文章目录 前言协程是什么多协程的用法gevent库queue模块 拓展复习复习 前言
照旧来回顾上一关的知识点上一关我们学习如何将爬虫的结果发送邮件和定时执行爬虫。
关于邮件它是这样一种流程 我们要用到的模块是smtplib和email前者负责连接服务器、登录、发送和退出的流程。后者负责填输邮件的标题与正文。 最后一个示例代码是这个模样
import smtplib
from email.mime.text import MIMEText
from email.header import Header
#引入smtplib、MIMEText和Headermailhostsmtp.qq.com
#把qq邮箱的服务器地址赋值到变量mailhost上地址应为字符串格式
qqmail smtplib.SMTP()
#实例化一个smtplib模块里的SMTP类的对象这样就可以调用SMTP对象的方法和属性了
qqmail.connect(mailhost,25)
#连接服务器第一个参数是服务器地址第二个参数是SMTP端口号。
#以上皆为连接服务器。account input(请输入你的邮箱)
#获取邮箱账号为字符串格式
password input(请输入你的密码)
#获取邮箱密码为字符串格式
qqmail.login(account,password)
#登录邮箱第一个参数为邮箱账号第二个参数为邮箱密码
#以上皆为登录邮箱。receiverinput(请输入收件人的邮箱)
#获取收件人的邮箱。contentinput(请输入邮件正文)
#输入你的邮件正文为字符串格式
message MIMEText(content, plain, utf-8)
#实例化一个MIMEText邮件对象该对象需要写进三个参数分别是邮件正文文本格式和编码
subject input(请输入你的邮件主题)
#输入你的邮件主题为字符串格式
message[Subject] Header(subject, utf-8)
#在等号的右边是实例化了一个Header邮件头对象该对象需要写入两个参数分别是邮件主题和编码然后赋值给等号左边的变量message[Subject]。
#以上为填写主题和正文。try:qqmail.sendmail(account, receiver, message.as_string())print (邮件发送成功)
except:print (邮件发送失败)
qqmail.quit()
#以上为发送邮件和退出邮箱。对于定时我们选取了schedule模块它的用法非常简洁官方文档里是这样讲述 下面的代码是示例
import schedule
import time
#引入schedule和timedef job():print(Im working...)
#定义一个叫job的函数函数的功能是打印Im working...schedule.every(10).minutes.do(job) #部署每10分钟执行一次job()函数的任务
schedule.every().hour.do(job) #部署每×小时执行一次job()函数的任务
schedule.every().day.at(10:30).do(job) #部署在每天的10:30执行job()函数的任务
schedule.every().monday.do(job) #部署每个星期一执行job()函数的任务
schedule.every().wednesday.at(13:15).do(job)#部署每周三的1315执行函数的任务while True:schedule.run_pending()time.sleep(1)
#15-17都是检查部署的情况如果任务准备就绪就开始执行任务。 协程是什么
我们已经做过不少爬虫项目不过我们爬取的数据都不算太大如果我们想要爬取的是成千上万条的数据那么就会遇到一个问题因为程序是一行一行依次执行的缘故要等待很久我们才能拿到想要的数据。
既然一个爬虫爬取大量数据要爬很久那我们能不能让多个爬虫一起爬取
这样无疑能提高爬取的效率就像一个人干不完的活儿组个团队一起干活一下被干完了。
这是一个很好的思路——让多个爬虫帮我们干活。但具体怎么用Python实现这事呢
我们可以先别急着想怎么实现这件事后面我会跟你说。
现在你先跟我想象一个情景 相信你肯定会这么做把三部电影都点击下载。看哪一部先下载好了就先看哪一部让还没有下载完的电影持续保持下载状态。
如果用计算机里的概念来解释这件事的话在一个任务未完成时就可以执行其他多个任务彼此不受影响在看第一部下载好的电影时其他电影继续保持下载状态彼此之间不受影响叫异步。
有异步的概念那应该也有同步的概念吧是的同步就是一个任务结束才能启动下一个类比你看完一部电影才能去看下一部电影。
显然异步执行任务会比同步更加节省时间因为它能减少不必要的等待。如果你需要对时间做优化异步是一个很值得考虑的方案。 如果我们把同步与异步的概念迁移到网络爬虫的场景中那我们之前学的爬虫方式都是同步的。
爬虫每发起一个请求都要等服务器返回响应后才会执行下一步。而很多时候由于网络不稳定加上服务器自身也需要响应的时间导致爬虫会浪费大量时间在等待上。这也是爬取大量数据时爬虫的速度会比较慢的原因。 那我们是不是可以采取异步的爬虫方式让多个爬虫在执行任务时保持相对独立彼此不受干扰这样不就可以免去等待时间显然这样爬虫的效率和速度都会提高。
怎样才能实现异步的爬虫方式提高爬虫的效率呢要回答这个问题的话得了解一点点计算机的历史小知识。
我们知道每台计算机都靠着CPU中央处理器干活。在过去单核CPU的计算机在处理多任务时会出现一个问题每个任务都要抢占CPU执行完了一个任务才开启下一个任务。CPU毕竟只有一个这会让计算机处理的效率很低。 为了解决这样的问题一种非抢占式的异步技术被创造了出来这种方式叫多协程在此多是多个的意思。
它的原理是一个任务在执行过程中如果遇到等待就先去执行其他的任务当等待结束再回来继续之前的那个任务。在计算机的世界这种任务来回切换得非常快速看上去就像多个任务在被同时执行一样。
这就好比当你要做一桌饭菜你可以在等电饭煲蒸饭的时候去炒菜。而不是等饭做好再去炒菜。你还是那个你但工作时间就这样被缩短了。多协程能够缩短工作时间的原理也是如此。
所以要实现异步的爬虫方式的话需要用到多协程。在它的帮助下我们能实现前面提到的“让多个爬虫替我们干活”。
那么新的问题来了——怎么使用多协程
多协程的用法
gevent库 所以接下来我会带你了解gevent的用法和实操一个多协程案例爬取8个网站包括百度、新浪、搜狐、腾讯、网易、爱奇艺、天猫、凤凰。
我们先用之前同步的爬虫方式爬取这8个网站然后等下再和gevent异步爬取做一个对比。
请你先认真看一遍左边的代码再运行。
import requests,time
#导入requests和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/]
#把8个网站封装成列表for url in url_list:
#遍历url_listr requests.get(url)#用requests.get()函数爬取网站print(url,r.status_code)#打印网址和抓取请求的状态码end time.time()
#记录程序结束时间
print(end-start)
#end-start是结束时间减去开始时间就是最终所花时间。
#最后把时间打印出来。运行结果
https://www.baidu.com/ 200
https://www.sina.com.cn/ 200
http://www.sohu.com/ 200
https://www.qq.com/ 200
https://www.163.com/ 200
http://www.iqiyi.com/ 200
https://www.tmall.com/ 200
http://www.ifeng.com/ 200
1.7253923416137695程序运行后你会看到同步的爬虫方式是依次爬取网站并等待服务器响应状态码为200表示正常响应后才爬取下一个网站。比如第一个先爬取了百度的网址等服务器响应后再去爬取新浪的网址以此类推直至全部爬取完毕。
为了让你能更直观地看到爬虫完成任务所需的时间我导入了time模块记录了程序开始和结束的时间最后打印出来的就是爬虫爬取这8个网站所花费的时间。
如果我们用了多协程来爬取会有什么不同
你可以先运行下面的代码看看直接运行体验。
from gevent import monkey
monkey.patch_all()
import gevent,time,requestsstart 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)运行结果
https://www.baidu.com/ 0.13728904724121094 200
https://www.sina.com.cn/ 0.14090418815612793 200
https://www.163.com/ 0.1483287811279297 200
https://www.qq.com/ 0.1953425407409668 200
https://www.tmall.com/ 0.24243402481079102 200
http://www.ifeng.com/ 0.2494034767150879 200
http://www.sohu.com/ 0.3105161190032959 200
http://www.iqiyi.com/ 0.7928042411804199 200
0.7928953170776367程序运行后打印出了网址、每个请求运行的时间、状态码和爬取8个网站最终所用时间。
通过每个请求运行的时间我们能知道爬虫用了异步的方式抓取了8个网站因为每个请求完成的时间并不是按着顺序来的。比如在我测试运行这个代码的时候最先爬取到的网站是搜狐接着是凤凰并不是百度和新浪。
且每个请求完成时间之间的间隔都非常短你可以看作这些请求几乎是“同时”发起的。
通过对比同步和异步爬取最终所花的时间用多协程异步的爬取方式确实比同步的爬虫方式速度更快。
其实我们案例爬取的数据量还比较小不能直接体现出更大的速度差异。如果爬的是大量的数据运用多协程会有更显著的速度优势。
比如我做了一个测试把爬取8个网站变成爬取80个网站用同步的爬取方式大概需要花17.3秒但用多协程异步爬取只需大概4.5秒整个爬取效率提升了280%。 现在我们一行行来看刚刚用了gevent的代码。
提醒导入gevent库前得先安装它。如果你想要在自己本地电脑操作的话就需要在本地上安装。安装方法window电脑在终端输入命令pip install gevent按下enter键mac电脑在终端输入命令pip3 install gevent按下enter键
from gevent import monkey
#从gevent库里导入monkey模块。
monkey.patch_all()
#monkey.patch_all()能把程序变成协作式运行就是可以帮助程序实现异步。
import gevent,time,requests
#导入gevent、time、requests。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/]
#把8个网站封装成列表。def crawler(url):
#定义一个crawler()函数。r requests.get(url)#用requests.get()函数爬取网站。print(url,time.time()-start,r.status_code)#打印网址、请求运行时间、状态码。tasks_list [ ]
#创建空的任务列表。for url in url_list:
#遍历url_list。task gevent.spawn(crawler,url)#用gevent.spawn()函数创建任务。tasks_list.append(task)#往任务列表添加任务。
gevent.joinall(tasks_list)
#执行任务列表里的所有任务就是让爬虫开始爬取网站。
end time.time()
#记录程序结束时间。
print(end-start)
#打印程序最终所需时间。
上面代码涉及到gevent的语法有些可能你还看不懂不要慌我跟你一个个具体解释一遍。 第1、3行代码从gevent库里导入了monkey模块这个模块能将程序转换成可异步的程序。monkey.patch_all()它的作用其实就像你的电脑有时会弹出“是否要用补丁修补漏洞或更新”一样。它能给程序打上补丁让程序变成是异步模式而不是同步模式。它也叫“猴子补丁”。
我们要在导入其他库和模块前先把monkey模块导入进来并运行monkey.patch_all()。这样才能先给程序打上补丁。你也可以理解成这是一个规范的写法。
第5行代码我们导入了gevent库来帮我们实现多协程导入了time模块来帮我们记录爬取所需时间导入了requests模块帮我们实现爬取8个网站。 第21、23、25行代码我们定义了一个crawler函数只要调用这个函数它就会执行【用requests.get()爬取网站】和【打印网址、请求运行时间、状态码】这两个任务。 第33行代码因为gevent只能处理gevent的任务对象不能直接调用普通函数所以需要借助gevent.spawn()来创建任务对象。
这里需要注意一点gevent.spawn()的参数需为要调用的函数名及该函数的参数。比如gevent.spawn(crawler,url)就是创建一个执行crawler函数的任务参数为crawler函数名和它自身的参数url。 第35行代码用append函数把任务添加到tasks_list的任务列表里。
第37行代码调用gevent库里的joinall方法能启动执行所有的任务。gevent.joinall(tasks_list)就是执行tasks_list这个任务列表里的所有任务开始爬取。 总结一下用gevent实现多协程爬取的重点 到这里用gevent实操抓取8个网站我们已经完成gevent的基础语法我们也大致了解。
那如果我们要爬的不是8个网站而是1000个网站我们可以怎么做
用我们刚刚学的gevent语法我们可以用gevent.spawn()创建1000个爬取任务再用gevent.joinall()执行这1000个任务。
但这种方法会有问题执行1000个任务就是一下子发起1000次请求这样子的恶意请求会拖垮网站的服务器。 既然这种直接创建1000个任务的方式不可取那我们能不能只创建成5个任务但每个任务爬取200个网站
假设我们有1000个任务那创建5个任务每个任务爬取200个网站的代码可以写成如下的样子此代码仅做展示并不可运行
from gevent import monkey
monkey.patch_all()
import gevent,time,requestsstart 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/
……
#假设有1000个网址
]def crawler(url_list):
#定义一个crawler()函数。for url in url_list:r requests.get(url)print(url,time.time()-start,r.status_code)tasks_list [ ]
#创建空的任务列表。
for i in range(5):task gevent.spawn(crawler,url_list[i*200:(i1)*200])#用gevent.spawn()函数创建5个任务。tasks_list.append(task)#往任务列表添加任务。gevent.joinall(tasks_list)
end time.time()
print(end-start)遗憾地告诉你这么做也还是会有问题的。就算我们用gevent.spawn()创建了5个分别执行爬取200个网站的任务这5个任务之间是异步执行的但是每个任务爬取200个网站内部是同步的。
这意味着如果有一个任务在执行的过程中它要爬取的一个网站一直在等待响应哪怕其他任务都完成了200个网站的爬取它也还是不能完成200个网站的爬取。 这个方法也不行那还有什么方法呢
这时我们可以从实际生活的案例中得到启发。想想银行是怎么在一天内办理1000个客户的业务的。
银行会开设办理业务的多个窗口让客户取号排队由银行的叫号系统分配客户到不同的窗口去办理业务。
在gevent库中也有一个模块可以实现这种功能——queue模块。
queue模块
当我们用多协程来爬虫需要创建大量任务时我们可以借助queue模块。
queue翻译成中文是队列的意思。我们可以用queue模块来存储任务让任务都变成一条整齐的队列就像银行窗口的排号做法。因为queue其实是一种有序的数据结构可以用来存取数据。
这样协程就可以从队列里把任务提取出来执行直到队列空了任务也就处理完了。就像银行窗口的工作人员会根据排号系统里的排号处理客人的业务如果已经没有新的排号就意味着客户的业务都已办理完毕。 接下来我们来实操看看可以怎么用queue模块和协程配合依旧以抓取8个网站为例。
请先运行下面的代码。
from gevent import monkey
monkey.patch_all()
import gevent,time,requests
from gevent.queue import Queuestart 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 Queue()
for url in url_list:work.put_nowait(url)def crawler():while not work.empty():url work.get_nowait()r requests.get(url)print(url,work.qsize(),r.status_code)tasks_list [ ]for x in range(2):task gevent.spawn(crawler)tasks_list.append(task)
gevent.joinall(tasks_list)end time.time()
print(end-start)运行结果
https://www.sina.com.cn/ 6 200
https://www.baidu.com/ 5 200
https://www.qq.com/ 4 200
http://www.sohu.com/ 3 200
https://www.163.com/ 2 200
https://www.tmall.com/ 1 200
http://www.ifeng.com/ 0 200
http://www.iqiyi.com/ 0 200
0.9640278816223145网址后面的数字指的是队列里还剩的任务数比如第一个网址后面的数字6就是此时队列里还剩6个抓取其他网址的任务。
现在我们把刚刚运行的代码拆成4部分来讲解第1部分是导入模块。
from gevent import monkey
#从gevent库里导入monkey模块。
monkey.patch_all()
#monkey.patch_all()能把程序变成协作式运行就是可以帮助程序实现异步。
import gevent,time,requests
#导入gevent、time、requests
from gevent.queue import Queue
#从gevent库里导入queue模块因为gevent库里就带有queue所以我们用【from gevent.queue import Queue】就能把queue模块导入。其他模块和代码我们在讲解gevent时已经讲解过了相信你能懂。
第2部分是如何创建队列以及怎么把任务存储进队列里。
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 Queue()
#创建队列对象并赋值给work。
for url in url_list:
#遍历url_listwork.put_nowait(url)#用put_nowait()函数可以把网址都放进队列里。用Queue()能创建queue对象相当于创建了一个不限任何存储数量的空队列。如果我们往Queue()中传入参数比如Queue(10)则表示这个队列只能存储10个任务。
创建了queue对象后我们就能调用这个对象的put_nowait方法把我们的每个网址都存储进我们刚刚建立好的空队列里。
work.put_nowait(url)这行代码就是把遍历的8个网站都存储进队列里。
第3部分是定义爬取函数和如何从队列里提取出刚刚存储进去的网址。
def crawler():while not work.empty():#当队列不是空的时候就执行下面的程序。url work.get_nowait()#用get_nowait()函数可以把队列里的网址都取出。r requests.get(url)#用requests.get()函数抓取网址。print(url,work.qsize(),r.status_code)#打印网址、队列长度、抓取请求的状态码。这里定义的crawler函数多了三个你可能看不懂的代码 1.while not work.empty() 2.url work.get_nowait() 3.work.qsize()。
这三个代码涉及到queue对象的三个方法 empty方法是用来判断队列是不是空了的 get_nowait方法是用来从队列里提取数据的 qsize方法是用来判断队列里还剩多少数量的。
当然queue对象的方法还不止这几种比如有判断队列是否为空的empty方法对应也有判断队列是否为满的full方法。
你是不是觉得queue对象这么多方法一下子记不住其实这些不需要你死记硬背的附上一张queue对象的方法表你只需要在用到的时候查查表就好。 代码的前3部分我们讲解完了。如果你能明白队列怎么创建、数据怎么存储进队列以及怎么从队列里提取出的数据就说明queue模块的重点内容你都掌握了。 接在第3部分代码的后面就是让爬虫用多协程执行任务爬取队列里的8个网站的代码重点看有注释的代码。
def crawler():while not work.empty():url work.get_nowait()r requests.get(url)print(url,work.qsize(),r.status_code)tasks_list [ ]
#创建空的任务列表
for x in range(2):
#相当于创建了2个爬虫task gevent.spawn(crawler)#用gevent.spawn()函数创建执行crawler()函数的任务。tasks_list.append(task)#往任务列表添加任务。
gevent.joinall(tasks_list)
#用gevent.joinall方法执行任务列表里的所有任务就是让爬虫开始爬取网站。
end time.time()
print(end-start)用一张图可以来解释这个过程 我们创建了两只可以异步爬取的爬虫。它们会从队列里取走网址执行爬取任务。一旦一个网址被一只爬虫取走另一只爬虫就取不到了另一只爬虫就会取走下一个网址。直至所有网址都被取走队列为空时爬虫就停止工作。
用协程技术和队列爬取8个网站的完整代码如下
from gevent import monkey
#从gevent库里导入monkey模块。
monkey.patch_all()
#monkey.patch_all()能把程序变成协作式运行就是可以帮助程序实现异步。
import gevent,time,requests
#导入gevent、time、requests
from gevent.queue import Queue
#从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 Queue()
#创建队列对象并赋值给work。
for url in url_list:
#遍历url_listwork.put_nowait(url)#用put_nowait()函数可以把网址都放进队列里。def crawler():while not work.empty():#当队列不是空的时候就执行下面的程序。url work.get_nowait()#用get_nowait()函数可以把队列里的网址都取出。r requests.get(url)#用requests.get()函数抓取网址。print(url,work.qsize(),r.status_code)#打印网址、队列长度、抓取请求的状态码。tasks_list [ ]
#创建空的任务列表
for x in range(2):
#相当于创建了2个爬虫task gevent.spawn(crawler)#用gevent.spawn()函数创建执行crawler()函数的任务。tasks_list.append(task)#往任务列表添加任务。
gevent.joinall(tasks_list)
#用gevent.joinall方法执行任务列表里的所有任务就是让爬虫开始爬取网站。
end time.time()
print(end-start)请你动手把上面的代码敲一遍不动手实践的爬虫学习都是耍流氓。
动手总会有收获的。恭喜你这一关的核心知识实现多协程的gevent库和Queue模块你都学完了
拓展复习
不过我还想和你拓展一点新的知识。
同样是要做饭菜我们已经知道比先做饭再做菜更好的方式是等待做饭的过程中去做菜。但其实还有更快的方案让一个人负责做饭一个人负责做菜。
继续说我们的计算机历史小知识在后来我们的CPU从单核终于进化到了多核每个核都能够独立运作。计算机开始能够真正意义上同时执行多个任务术语叫并行执行而不是在多个任务之间来回切换术语叫并发执行。
比如你现在打开浏览器看着爬虫课程的同时可以打开音乐播放器听歌还可以打开Excel。对于多核CPU而言这些任务就都是同时运行的。
时至今日我们电脑一般都会是多核CPU。多协程其实只占用了CPU的一个核运行没有充分利用到其他核。利用CPU的多个核同时执行任务的技术我们把它叫做“多进程”。
所以真正大型的爬虫程序不会单单只靠多协程来提升爬取速度的。比如百度搜索引擎可以说是超大型的爬虫程序它除了靠多协程一定还会靠多进程甚至是分布式爬虫。
多进程爬虫和分布式爬虫相对来说就比较复杂一些了我这里不会多讲。需要进阶学习的同学可以自己研究去学。
最后是这一关的复习。
复习
同步与异步—— 多协程是一种非抢占式的异步方式。使用多协程的话就能让多个爬取任务用异步的方式交替执行。 就是这些知识点了 我们下一关见