1月 242018
 

转自:http://www.missshi.cn/api/view/blog/59a6b2e8e519f50d040000fe

众所周知,Python是非常擅长网络爬虫的。

而对于一个大规模的网络爬虫而言,使用常规的Python编程会使得效率极其低效。

本文主要讲解如何在Python中利用Gevent来大幅提高程序运行的效率。

让我们从一个标准的Python顺序执行代码开始吧!

一段正常运行的Python代码如下:

  1. import requests
  2. import time
  3.  
  4. def request_url(url):
  5.     """
  6.     # 访问一个url并得到响应结果
  7.     :param url:
  8.     :return:
  9.     """
  10.     response = requests.get(url)
  11.     return response.content
  12.  
  13. if __name__ == "__main__":
  14.     # 准备一个待访问的url列表,该列表共包含4 * 10 = 40个url
  15.     url_list = ['https://github.com/',
  16.                 'http://www.missshi.cn:8888/',
  17.                 'https://www.python.org/',
  18.                 'https://www.yahoo.com/'] * 10
  19.     response_list = []
  20.     begin_time = time.time()  #记录当前时间
  21.     for url in url_list:
  22.         print url
  23.         response_content = request_url(url)
  24.         response_list.append(response_content)
  25.     end_time = time.time()  #记录结束时间
  26.     used_time = end_time - begin_time  #计算消耗时间
  27.     print used_time

运行该程序时,我们可以发现它会依次遍历url_list中的每一个url。

在上一个请求得到响应后,再去发送下一个请求。

在我本地调试时,访问40个url的总耗时为33.207s。

而这其中,绝大部分的时间都是在等待接口的响应结果。

Gevent又称之为协程,它是一个有助于大幅度提高网络传输应用型服务的性能。

Gevent的主要实现原理是在运行一个任务时,如果遇到IO或网络相关的操作,会在发送请求后切换到别的任务去处理,而不是在原任务中等待接收响应。这样一来,对于一个大量网络请求的任务而言,它可能起到很好的并发效果。

首先,我们通过一个简单的示例程序了解一下Gevent库的使用。

  1. import gevent
  2.  
  3. def function1(str1, str2):
  4.     print "************"
  5.     print str1
  6.     print str2
  7.  
  8. if __name__ == "__main__":
  9.     task_list = [] #准备一个任务列表
  10.     for i in range(5):
  11.         str1 = "string1_" + str(i)
  12.         str2 = "string2_" + str(i)
  13.         task_list.append(gevent.spawn(function1, str1, str2))  #向任务列表中添加任务
  14.     result = gevent.joinall(task_list)

对于gevent而言,我们需要准备一个任务列表。

任务列表中每一个元素都是一个gevent.spawn()对象。

其中,gevent.spawn()函数可以接收一至多个参数。

第一个参数为任务需要执行的函数,后续的参数为函数对应的输入参数。

最终,当我们得到完成的任务列表后,可以调用gevent.joinall()来执行该列表中的任务。

运行该程序,我们可以得到如下结果:

  1. ************
  2. string1_0
  3. string2_0
  4. ************
  5. string1_1
  6. string2_1
  7. ************
  8. string1_2
  9. string2_2
  10. ************
  11. string1_3
  12. string2_3
  13. ************
  14. string1_4
  15. string2_4

通过观察结果,我们不难发现,该程序仍然是顺序执行的所有的任务,而没有在不同的任务中进行切换。

那么是什么原因呢?

1. 没有包含IO或网络操作,因此没有自动触发切换任务。

2. 也没有手工触发切换进程。

首先,我们先来学习如何手工触发切换任务:

只需要添加一行代码gevent.sleep()后,得到的代码如下:

  1. import gevent
  2.  
  3. def function1(str1, str2):
  4.     print "************"
  5.     print str1
  6.     gevent.sleep()  #添加一行代码
  7.     print str2
  8.  
  9. if __name__ == "__main__":
  10.     task_list = []
  11.     for i in range(5):
  12.         str1 = "string1_" + str(i)
  13.         str2 = "string2_" + str(i)
  14.         task_list.append(gevent.spawn(function1, str1, str2))
  15.     result = gevent.joinall(task_list)

重新运行程序后观察结果:

  1. ************
  2. string1_0
  3. ************
  4. string1_1
  5. ************
  6. string1_2
  7. ************
  8. string1_3
  9. ************
  10. string1_4
  11. string2_0
  12. string2_1
  13. string2_2
  14. string2_3
  15. string2_4

观察结果后,我们可以发现程序在运行到gevent.sleep()后,不会继续在当前任务中执行,而是切换至别的任务中运行。

当然,在应用代码中,我们很少会手工触发gevent.sleep()来切换任务。

而是通过引入一些相关的函数,可以自动起到在面临网络请求的任务时,自动切换任务。

具体的实现方法我们根据如下代码来进行讲解:

  1. import gevent
  2. import requests
  3. import time
  4.  
  5. def request_url(url):
  6.     """
  7.     # 访问一个url并得到响应结果
  8.     :param url:
  9.     :return:
  10.     """
  11.     from gevent import monkey
  12.     monkey.patch_socket()  #引入猴子补丁
  13.     print url
  14.     response = requests.get(url)
  15.     print "response of url:", url
  16.     return response.content
  17.  
  18. if __name__ == "__main__":
  19.     url_list = ['https://github.com/',
  20.                 'http://www.missshi.cn:8888/',
  21.                 'https://www.python.org/',
  22.                 'https://www.yahoo.com/'] * 10
  23.     task_list = []
  24.     response_list = []
  25.     begin_time = time.time()
  26.     for url in url_list:
  27.         task_list.append(gevent.spawn(request_url, url))
  28.     result = gevent.joinall(task_list)
  29.     end_time = time.time()
  30.     used_time = end_time - begin_time
  31.     print used_time

需要注意的是,当我们引入猴子补丁后,会对已经以后的方法进行改写。

因此,不建议在全局范围内引入猴子补丁,最好是在哪部分为并发执行函数,则在哪部分引入猴子补丁。

在引入猴子补丁后,当运行到网络请求时,则会切换至其他任务继续执行,而不是在当前任务中继续等待。

最后,我们来讲解一下针对任务列表中的任务,在执行完成后如何获取并发任务的返回值。

  1. if __name__ == "__main__":
  2.     url_list = ['https://github.com/',
  3.                 'http://www.missshi.cn:8888/',
  4.                 'https://www.python.org/',
  5.                 'https://www.yahoo.com/'] * 10
  6.     task_list = []
  7.     response_list = []
  8.     begin_time = time.time()
  9.     for url in url_list:
  10.         task_list.append(gevent.spawn(request_url, url))
  11.     result = gevent.joinall(task_list)
  12.     response_list = [element.value for element in result]  #从result中获取每个请求的Response
  13.     end_time = time.time()
  14.     used_time = end_time - begin_time
  15.     print used_time

对于gevent.joinall()函数而言,得到的结果是一个迭代器。

其中,迭代器中每一个元素都包含一个属性value,其对应值为每个任务函数的返回值。

Sorry, the comment form is closed at this time.