飞道的博客

python异步爬虫实现与总结

267人阅读  评论(0)

一 背景

默认情况下,用get请求时,会出现阻塞,需要很多时间来等待,对于有很多请求url时,速度就很慢。因为需要一个url请求的完成,才能让下一个url继续访问。一种很自然的想法就是用异步机制来提高爬虫速度。通过构建线程池或者进程池完成异步爬虫,即使用多线程或者多进程来处理多个请求(在别的进程或者线程阻塞时)。


  
  1. import time
  2. #串形
  3. def getPage(url):
  4. print( "开始爬取网站",url)
  5. time.sleep( 2) #阻塞
  6. print( "爬取完成!!!",url)
  7. urls = [ 'url1', 'url2', 'url3', 'url4', 'url5']
  8. beginTime = time.time() #开始计时
  9. for url in urls:
  10. getPage(url)
  11. endTime= time.time() #结束计时
  12. print( "完成时间%d"%(endTime - beginTime))

下面通过模拟爬取网站来完成对多线程,多进程,协程的理解。

二 多线程实现


  
  1. import time
  2. #使用线程池对象
  3. from multiprocessing.dummy import Pool
  4. def getPage(url):
  5. print( "开始爬取网站",url)
  6. time.sleep( 2) #阻塞
  7. print( "爬取完成!!!",url)
  8. urls = [ 'url1', 'url2', 'url3', 'url4', 'url5']
  9. beginTime = time.time() #开始计时
  10. #准备开启5个线程,并示例化对象
  11. pool = Pool( 5)
  12. pool.map(getPage, urls) #urls是可迭代对象,里面每个参数都会给getPage方法处理
  13. endTime= time.time() #结束计时
  14. print( "完成时间%d"%(endTime - beginTime))

完成时间只需要2s!!!!!!!!

线程池使用原则:适合处理耗时并且阻塞的操作

三 多进程实现

 

四 协程实现

单线程+异步协程!!!!!!!!!!强烈推荐,目前流行的方式。

相关概念:

 


  
  1. #%%
  2. import time
  3. #使用协程
  4. import asyncio
  5. async def getPage(url): #定义了一个协程对象,python中函数也是对象
  6. print( "开始爬取网站",url)
  7. time.sleep( 2) #阻塞
  8. print( "爬取完成!!!",url)
  9. #async修饰的函数返回的对象
  10. c = getPage( 11)
  11. #创建事件对象
  12. loop_event = asyncio.get_event_loop()
  13. #注册并启动looP
  14. loop_event.run_until_complete(c)
  15. #task对象使用,封装协程对象c
  16. '''
  17. loop_event = asyncio.get_event_loop()
  18. task = loop_event.create_task(c)
  19. loop_event.run_until_complete(task)
  20. '''
  21. #Future对象使用,封装协程对象c 用法和task差不多
  22. '''
  23. loop_event = asyncio.get_event_loop()
  24. task = asyncio.ensure_future(c)
  25. loop_event.run_until_complete(task)
  26. '''
  27. #绑定回调使用
  28. async def getPage2(url): #定义了一个协程对象,python中函数也是对象
  29. print( "开始爬取网站",url)
  30. time.sleep( 2) #阻塞
  31. print( "爬取完成!!!",url)
  32. return url
  33. #async修饰的函数返回的对象
  34. c2 = getPage2( 2)
  35. def callback_func(task):
  36. print(task.result()) #task.result()返回任务对象中封装的协程对象对应函数的返回值
  37. #绑定回调
  38. loop_event = asyncio.get_event_loop()
  39. task = asyncio.ensure_future(c2)
  40. task.add_done_callback(callback_func) #真正绑定,
  41. loop_event.run_until_complete(task)

输出:

多任务协程实现:

 


  
  1. import time
  2. #使用多任务协程
  3. import asyncio
  4. urls = [ 'url1', 'url2', 'url3', 'url4', 'url5']
  5. async def getPage(url): #定义了一个协程对象,python中函数也是对象
  6. print( "开始爬取网站",url)
  7. #在异步协程中如果出现同步模块相关的代码,那么无法实现异步
  8. #time.sleep(2)#阻塞
  9. await asyncio.sleep( 2) #遇到阻塞操作必须手动挂起
  10. print( "爬取完成!!!",url)
  11. return url
  12. beginTime = time.time()
  13. #任务列表,有多个任务
  14. tasks = []
  15. for url in urls:
  16. c = getPage(url)
  17. task = asyncio.ensure_future(c) #创建任务对象
  18. tasks.append(task)
  19. loop = asyncio.get_event_loop()
  20. loop.run_until_complete(asyncio.wait(tasks)) #不能直接放task,需要封装进入asyncio,wait()方法中
  21. endTime = time.time()
  22. print( "完成时间%d"%(endTime - beginTime))

此时不能用time.sleep(2),用了还是10秒

对于真正爬取过程中,如在getPage()方法中真正爬取数据时,即requests.get(url) ,它是基于同步方式实现。应该使用异步网络请求模块aiohttp

参考下面代码:


  
  1. async def getPage(url):   #定义了一个协程对象,python中函数也是对象
  2.     print( "开始爬取网站",url)
  3.     #在异步协程中如果出现同步模块相关的代码,那么无法实现异步
  4.     #requests.get(url)#阻塞
  5.     async with aiohttp.ClintSession() as session:
  6.                       async with await  session.get(url) as response: #手动挂起
  7.                                        page_text =  await response.text() #.text()返回字符串,read()返回二进制数据,注意不是content
  8.     print( "爬取完成!!!",url)
  9.     return page_text


转载:https://blog.csdn.net/qq_39463175/article/details/116606573
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场