转载过程中,图片丢失,代码显示错乱。
为了更好的学习内容,请访问原创版本:
http://www.missshi.cn/api/view/blog/59a6b2e8e519f50d040000fe
Ps:初次访问由于js文件较大,请耐心等候(8s左右)
众所周知,Python是非常擅长网络爬虫的。
而对于一个大规模的网络爬虫而言,使用常规的Python编程会使得效率极其低效。
本文主要讲解如何在Python中利用Gevent来大幅提高程序运行的效率。
让我们从一个标准的Python顺序执行代码开始吧!
一段正常运行的Python代码如下:
123456789101112131415161718192021222324252627importrequestsimporttimedefrequest_url(url):"""#访问一个url并得到响应结果:paramurl::return:"""response=requests.get(url)returnresponse.contentif__name__=="__main__":url_list=['https://github.com/','http://www.missshi.cn:8888/','https://www.python.org/','https://www.yahoo.com/']*10response_list=[]begin_time=time.time()forurlinurl_list:printurlresponse_content=request_url(url)response_list.append(response_content)end_time=time.time()used_time=end_time-begin_timeprintused_time
运行该程序时,我们可以发现它会依次遍历url_list中的每一个url。
在上一个请求得到响应后,再去发送下一个请求。
在我本地调试时,访问40个url的总耗时为33.207s。
而这其中,绝大部分的时间都是在等待接口的响应结果。
Gevent又称之为协程,它是一个有助于大幅度提高网络传输应用型服务的性能。
Gevent的主要实现原理是在运行一个任务时,如果遇到IO或网络相关的操作,会在发送请求后切换到别的任务去处理,而不是在原任务中等待接收响应。这样一来,对于一个大量网络请求的任务而言,它可能起到很好的并发效果。
首先,我们通过一个简单的示例程序了解一下Gevent库的使用。
1234567891011121314importgeventdeffunction1(str1,str2):print"************"printstr1printstr2if__name__=="__main__":task_list=[]foriinrange(5):str1="string1_"+str(i)str2="string2_"+str(i)task_list.append(gevent.spawn(function1,str1,str2))result=gevent.joinall(task_list)
对于gevent而言,我们需要准备一个任务列表。
任务列表中每一个元素都是一个gevent.spawn()对象。
其中,gevent.spawn()函数可以接收一至多个参数。
第一个参数为任务需要执行的函数,后续的参数为函数对应的输入参数。
最终,当我们得到完成的任务列表后,可以调用gevent.joinall()来执行该列表中的任务。
运行该程序,我们可以得到如下结果:
123456789101112131415************string1_0string2_0************string1_1string2_1************string1_2string2_2************string1_3string2_3************string1_4string2_4
通过观察结果,我们不难发现,该程序仍然是顺序执行的所有的任务,而没有在不同的任务中进行切换。
那么是什么原因呢?
1. 没有包含IO或网络操作,因此没有自动触发切换任务。
2. 也没有手工触发切换进程。
首先,我们先来学习如何手工触发切换任务:
只需要添加一行代码gevent.sleep()
后,得到的代码如下:
123456789101112131415importgeventdeffunction1(str1,str2):print"************"printstr1gevent.sleep()printstr2if__name__=="__main__":task_list=[]foriinrange(5):str1="string1_"+str(i)str2="string2_"+str(i)task_list.append(gevent.spawn(function1,str1,str2))result=gevent.joinall(task_list)
重新运行程序后观察结果:
123456789101112131415************string1_0************string1_1************string1_2************string1_3************string1_4string2_0string2_1string2_2string2_3string2_4
观察结果后,我们可以发现程序在运行到gevent.sleep()
后,不会继续在当前任务中执行,而是切换至别的任务中运行。
当然,在应用代码中,我们很少会手工触发gevent.sleep()
来切换任务。
而是通过引入一些相关的函数,可以自动起到在面临网络请求的任务时,自动切换任务。
具体的实现方法我们根据如下代码来进行讲解:
12345678910111213141516171819202122232425262728293031importgeventimportrequestsimporttimedefrequest_url(url):"""#访问一个url并得到响应结果:paramurl::return:"""fromgeventimportmonkeymonkey.patch_socket()printurlresponse=requests.get(url)print"responseofurl:",urlreturnresponse.contentif__name__=="__main__":url_list=['https://github.com/','http://www.missshi.cn:8888/','https://www.python.org/','https://www.yahoo.com/']*10task_list=[]response_list=[]begin_time=time.time()forurlinurl_list:task_list.append(gevent.spawn(request_url,url))result=gevent.joinall(task_list)end_time=time.time()used_time=end_time-begin_timeprintused_time
需要注意的是,当我们引入猴子补丁后,会对已经以后的方法进行改写。
因此,不建议在全局范围内引入猴子补丁,最好是在哪部分为并发执行函数,则在哪部分引入猴子补丁。
在引入猴子补丁后,当运行到网络请求时,则会切换至其他任务继续执行,而不是在当前任务中继续等待。
最后,我们来讲解一下针对任务列表中的任务,在执行完成后如何获取并发任务的返回值。
123456789101112131415if__name__=="__main__":url_list=['https://github.com/','http://www.missshi.cn:8888/','https://www.python.org/','https://www.yahoo.com/']*10task_list=[]response_list=[]begin_time=time.time()forurlinurl_list:task_list.append(gevent.spawn(request_url,url))result=gevent.joinall(task_list)response_list=[element.valueforelementinresult]end_time=time.time()used_time=end_time-begin_timeprintused_time
对于gevent.joinall()
函数而言,得到的结果是一个迭代器。
其中,迭代器中每一个元素都包含一个属性value,其对应值为每个任务函数的返回值。
更多更详细的内容,请访问原创网站:
http://www.missshi.cn/api/view/blog/59a6b2e8e519f50d040000fe
Ps:初次访问由于js文件较大,请耐心等候(8s左右)