13518219792

建站动态

根据您的个性需求进行定制 先人一步 抢占小程序红利时代

Python爬虫实战:单线程、多线程和协程性能对比

[[378975]]

创新互联建站-专业网站定制、快速模板网站建设、高性价比市中网站开发、企业建站全套包干低至880元,成熟完善的模板库,直接使用。一站式市中网站制作公司更省心,省钱,快速模板网站建设找我们,业务覆盖市中地区。费用合理售后完善,十年实体公司更值得信赖。

 一、前言

今天我要给大家分享的是如何爬取中农网产品报价数据,并分别用普通的单线程、多线程和协程来爬取,从而对比单线程、多线程和协程在网络爬虫中的性能。

目标URL:https://www.zhongnongwang.com/quote/product-htm-page-1.html

爬取产品品名、最新报价、单位、报价数、报价时间等信息,保存到本地Excel。

二、爬取测试

翻页查看 URL 变化规律: 

 
 
 
 
  1. https://www.zhongnongwang.com/quote/product-htm-page-1.html  
  2. https://www.zhongnongwang.com/quote/product-htm-page-2.html  
  3. https://www.zhongnongwang.com/quote/product-htm-page-3.html  
  4. https://www.zhongnongwang.com/quote/product-htm-page-4.html  
  5. https://www.zhongnongwang.com/quote/product-htm-page-5.html  
  6. https://www.zhongnongwang.com/quote/product-htm-page-6.html 

检查网页,可以发现网页结构简单,容易解析和提取数据。

思路:每一条产品报价信息在 class 为 tb 的 table 标签下的 tbody 下的 tr 标签里,获取到所有 tr 标签的内容,然后遍历,从中提取出每一个产品品名、最新报价、单位、报价数、报价时间等信息。 

 
 
 
 
  1. # -*- coding: UTF-8 -*-  
  2. """  
  3. @File    :demo.py  
  4. @Author  :叶庭云  
  5. @CSDN    :https://yetingyun.blog.csdn.net/  
  6. """  
  7. import requests  
  8. import logging  
  9. from fake_useragent import UserAgent  
  10. from lxml import etree  
  11. # 日志输出的基本配置  
  12. logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s')  
  13. # 随机产生请求头  
  14. ua = UserAgent(verify_ssl=False, path='fake_useragent.json')  
  15. url = 'https://www.zhongnongwang.com/quote/product-htm-page-1.html'  
  16. # 伪装请求头  
  17. headers = {  
  18.     "Accept-Encoding": "gzip",  # 使用gzip压缩传输数据让访问更快  
  19.     "User-Agent": ua.random  
  20. }  
  21. # 发送请求  获取响应  
  22. rep = requests.get(url, headersheaders=headers)  
  23. print(rep.status_code)    # 200  
  24. # Xpath定位提取数据  
  25. html = etree.HTML(rep.text)  
  26. items = html.xpath('/html/body/div[10]/table/tr[@align="center"]')  
  27. logging.info(f'该页有多少条信息:{len(items)}')  # 一页有20条信息  
  28. # 遍历提取出数据  
  29. for item in items:  
  30.     name = ''.join(item.xpath('.//td[1]/a/text()'))  # 品名  
  31.     price = ''.join(item.xpath('.//td[3]/text()'))   # 最新报价  
  32.     unit = ''.join(item.xpath('.//td[4]/text()'))    # 单位  
  33.     nums = ''.join(item.xpath('.//td[5]/text()'))    # 报价数  
  34.     time_ = ''.join(item.xpath('.//td[6]/text()'))   # 报价时间  
  35.     logging.info([name, price, unit, nums, time_]) 

运行结果如下:

可以成功爬取到数据,接下来分别用普通的单线程、多线程和协程来爬取 50 页的数据、保存到Excel。

三、单线程爬虫 

 
 
 
 
  1. # -*- coding: UTF-8 -*-  
  2. """  
  3. @File    :单线程.py  
  4. @Author  :叶庭云  
  5. @CSDN    :https://yetingyun.blog.csdn.net/  
  6. """  
  7. import requests  
  8. import logging  
  9. from fake_useragent import UserAgent  
  10. from lxml import etree  
  11. import openpyxl  
  12. from datetime import datetime  
  13. # 日志输出的基本配置  
  14. logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s')  
  15. # 随机产生请求头  
  16. ua = UserAgent(verify_ssl=False, path='fake_useragent.json')  
  17. wb = openpyxl.Workbook() 
  18. sheet = wb.active 
  19. sheet.append(['品名', '最新报价', '单位', '报价数', '报价时间'])  
  20. start = datetime.now()  
  21. for page in range(1, 51):  
  22.     # 构造URL  
  23.     url = f'https://www.zhongnongwang.com/quote/product-htm-page-{page}.html'  
  24.     # 伪装请求头 
  25.     headers = {  
  26.         "Accept-Encoding": "gzip",  # 使用gzip压缩传输数据让访问更快  
  27.         "User-Agent": ua.random  
  28.     }  
  29.     # 发送请求  获取响应  
  30.     rep = requests.get(url, headersheaders=headers)  
  31.     # print(rep.status_code)  
  32.     # Xpath定位提取数据  
  33.     html = etree.HTML(rep.text)  
  34.     items = html.xpath('/html/body/div[10]/table/tr[@align="center"]')  
  35.     logging.info(f'该页有多少条信息:{len(items)}')  # 一页有20条信息  
  36.     # 遍历提取出数据  
  37.     for item in items:  
  38.         name = ''.join(item.xpath('.//td[1]/a/text()'))  # 品名  
  39.         price = ''.join(item.xpath('.//td[3]/text()'))   # 最新报价  
  40.         unit = ''.join(item.xpath('.//td[4]/text()'))    # 单位  
  41.         nums = ''.join(item.xpath('.//td[5]/text()'))    # 报价数  
  42.         time_ = ''.join(item.xpath('.//td[6]/text()'))   # 报价时间  
  43.         sheet.append([name, price, unit, nums, time_])  
  44.         logging.info([name, price, unit, nums, time_])  
  45. wb.save(filename='data1.xlsx')  
  46. delta = (datetime.now() - start).total_seconds()  
  47. logging.info(f'用时:{delta}s') 

运行结果如下:

单线程爬虫必须上一个页面爬取完成才能继续爬取,还可能受当时网络状态影响,用时48.528703s,才将数据爬取完,速度比较慢。

四、多线程爬虫 

 
 
 
 
  1. # -*- coding: UTF-8 -*-  
  2. """  
  3. @File    :多线程.py  
  4. @Author  :叶庭云  
  5. @CSDN    :https://yetingyun.blog.csdn.net/  
  6. """  
  7. import requests  
  8. import logging  
  9. from fake_useragent import UserAgent  
  10. from lxml import etree  
  11. import openpyxl  
  12. from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED  
  13. from datetime import datetime  
  14. # 日志输出的基本配置  
  15. logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s')  
  16. # 随机产生请求头  
  17. ua = UserAgent(verify_ssl=False, path='fake_useragent.json')  
  18. wb = openpyxl.Workbook()  
  19. sheet = wb.active  
  20. sheet.append(['品名', '最新报价', '单位', '报价数', '报价时间'])  
  21. start = datetime.now() 
  22. def get_data(page):  
  23.     # 构造URL  
  24.     url = f'https://www.zhongnongwang.com/quote/product-htm-page-{page}.html'  
  25.     # 伪装请求头  
  26.     headers = {  
  27.         "Accept-Encoding": "gzip",    # 使用gzip压缩传输数据让访问更快  
  28.         "User-Agent": ua.random  
  29.     }  
  30.     # 发送请求  获取响应  
  31.     rep = requests.get(url, headersheaders=headers)  
  32.     # print(rep.status_code)  
  33.     # Xpath定位提取数据  
  34.     html = etree.HTML(rep.text)  
  35.     items = html.xpath('/html/body/div[10]/table/tr[@align="center"]')  
  36.     logging.info(f'该页有多少条信息:{len(items)}')  # 一页有20条信息  
  37.     # 遍历提取出数据  
  38.     for item in items:  
  39.         name = ''.join(item.xpath('.//td[1]/a/text()'))   # 品名  
  40.         price = ''.join(item.xpath('.//td[3]/text()'))    # 最新报价  
  41.         unit = ''.join(item.xpath('.//td[4]/text()'))     # 单位  
  42.         nums = ''.join(item.xpath('.//td[5]/text()'))     # 报价数  
  43.         time_ = ''.join(item.xpath('.//td[6]/text()'))    # 报价时间  
  44.         sheet.append([name, price, unit, nums, time_])  
  45.         logging.info([name, price, unit, nums, time_]) 
  46. def run():  
  47.     # 爬取1-50页  
  48.     with ThreadPoolExecutor(max_workers=6) as executor:  
  49.         future_tasks = [executor.submit(get_data, i) for i in range(1, 51)]  
  50.         wait(future_tasks, return_when=ALL_COMPLETED)  
  51.     wb.save(filename='data2.xlsx')  
  52.     delta = (datetime.now() - start).total_seconds()  
  53.     print(f'用时:{delta}s')  
  54. run() 

运行结果如下:

多线程爬虫爬取效率提升非常可观,用时 2.648128s,爬取速度很快。

五、异步协程爬虫 

 
 
 
 
  1. # -*- coding: UTF-8 -*-  
  2. """  
  3. @File    :demo1.py  
  4. @Author  :叶庭云  
  5. @CSDN    :https://yetingyun.blog.csdn.net/  
  6. """  
  7. import aiohttp  
  8. import asyncio  
  9. import logging  
  10. from fake_useragent import UserAgent  
  11. from lxml import etree  
  12. import openpyxl  
  13. from datetime import datetime  
  14. # 日志输出的基本配置  
  15. logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s')  
  16. # 随机产生请求头  
  17. ua = UserAgent(verify_ssl=False, path='fake_useragent.json')  
  18. wb = openpyxl.Workbook()  
  19. sheet = wb.active 
  20. sheet.append(['品名', '最新报价', '单位', '报价数', '报价时间'])  
  21. start = datetime.now()  
  22. class Spider(object):  
  23.     def __init__(self):  
  24.         # self.semaphore = asyncio.Semaphore(6)  # 信号量,有时候需要控制协程数,防止爬的过快被反爬  
  25.         self.header = {  
  26.                 "Accept-Encoding": "gzip",    # 使用gzip压缩传输数据让访问更快  
  27.                 "User-Agent": ua.random  
  28.             } 
  29.     async def scrape(self, url):  
  30.         # async with self.semaphore:  # 设置最大信号量,有时候需要控制协程数,防止爬的过快被反爬  
  31.         session = aiohttp.ClientSession(headers=self.header, connector=aiohttp.TCPConnector(ssl=False))  
  32.         response = await session.get(url)  
  33.         result = await response.text()  
  34.         await session.close()  
  35.         return result  
  36.     async def scrape_index(self, page):  
  37.         url = f'https://www.zhongnongwang.com/quote/product-htm-page-{page}.html'  
  38.         text = await self.scrape(url)  
  39.         await self.parse(text)  
  40.     async def parse(self, text):  
  41.         # Xpath定位提取数据  
  42.         html = etree.HTML(text)  
  43.         items = html.xpath('/html/body/div[10]/table/tr[@align="center"]')  
  44.         logging.info(f'该页有多少条信息:{len(items)}')  # 一页有20条信息  
  45.         # 遍历提取出数据  
  46.         for item in items:  
  47.             name = ''.join(item.xpath('.//td[1]/a/text()'))  # 品名  
  48.             price = ''.join(item.xpath('.//td[3]/text()'))  # 最新报价  
  49.             unit = ''.join(item.xpath('.//td[4]/text()'))  # 单位  
  50.             nums = ''.join(item.xpath('.//td[5]/text()'))  # 报价数  
  51.             time_ = ''.join(item.xpath('.//td[6]/text()'))  # 报价时间  
  52.             sheet.append([name, price, unit, nums, time_])  
  53.             logging.info([name, price, unit, nums, time_])  
  54.     def main(self):  
  55.         # 50页的数据  
  56.         scrape_index_tasks = [asyncio.ensure_future(self.scrape_index(page)) for page in range(1, 51)]  
  57.         loop = asyncio.get_event_loop()  
  58.         tasks = asyncio.gather(*scrape_index_tasks)  
  59.         loop.run_until_complete(tasks)  
  60. if __name__ == '__main__':  
  61.     spider = Spider()  
  62.     spider.main()  
  63.     wb.save('data3.xlsx')  
  64.     delta = (datetime.now() - start).total_seconds()  
  65.     print("用时:{:.3f}s".format(delta)) 

运行结果如下:

而到了协程异步爬虫,爬取速度更快,嗖的一下,用时 0.930s 就爬取完 50 页数据,aiohttp + asyncio 异步爬虫竟恐怖如斯。异步爬虫在服务器能承受高并发的前提下增加并发数量,爬取效率提升是非常可观的,比多线程还要快一些。

三种爬虫都将 50 页的数据爬取下来保存到了本地,结果如下:

六、总结回顾

今天我演示了简单的单线程爬虫、多线程爬虫和协程异步爬虫。可以看到一般情况下异步爬虫速度最快,多线程爬虫略慢一点,单线程爬虫速度较慢,必须上一个页面爬取完成才能继续爬取。

但协程异步爬虫相对来说并不是那么好编写,数据抓取无法使用 request 库,只能使用aiohttp,而且爬取数据量大时,异步爬虫需要设置最大信号量来控制协程数,防止爬的过快被反爬。所以在实际编写 Python 爬虫时,我们一般都会使用多线程爬虫来提速,但必须注意的是网站都有 ip 访问频率限制,爬的过快可能会被封ip,所以一般我们在多线程提速的同时可以使用代理 ip 来并发地爬取数据。

 


分享标题:Python爬虫实战:单线程、多线程和协程性能对比
分享链接:http://cdbrznjsb.com/article/cdissep.html

其他资讯

让你的专属顾问为你服务