溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Python爬蟲實(shí)戰(zhàn)之單線程、多線程和協(xié)程性能有哪些區(qū)別

發(fā)布時(shí)間:2021-10-19 16:29:36 來源:億速云 閱讀:126 作者:iii 欄目:編程語言

本篇內(nèi)容主要講解“Python爬蟲實(shí)戰(zhàn)之單線程、多線程和協(xié)程性能有哪些區(qū)別”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“Python爬蟲實(shí)戰(zhàn)之單線程、多線程和協(xié)程性能有哪些區(qū)別”吧!

 一、前言

今天我要給大家分享的是如何爬取中農(nóng)網(wǎng)產(chǎn)品報(bào)價(jià)數(shù)據(jù),并分別用普通的單線程、多線程和協(xié)程來爬取,從而對比單線程、多線程和協(xié)程在網(wǎng)絡(luò)爬蟲中的性能。

Python爬蟲實(shí)戰(zhàn)之單線程、多線程和協(xié)程性能有哪些區(qū)別

爬取產(chǎn)品品名、最新報(bào)價(jià)、單位、報(bào)價(jià)數(shù)、報(bào)價(jià)時(shí)間等信息,保存到本地Excel。

Python爬蟲實(shí)戰(zhàn)之單線程、多線程和協(xié)程性能有哪些區(qū)別

二、爬取測試

翻頁查看 URL 變化規(guī)律:

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

檢查網(wǎng)頁,可以發(fā)現(xiàn)網(wǎng)頁結(jié)構(gòu)簡單,容易解析和提取數(shù)據(jù)。

Python爬蟲實(shí)戰(zhàn)之單線程、多線程和協(xié)程性能有哪些區(qū)別

思路:每一條產(chǎn)品報(bào)價(jià)信息在 class 為 tb 的 table 標(biāo)簽下的 tbody 下的 tr 標(biāo)簽里,獲取到所有 tr 標(biāo)簽的內(nèi)容,然后遍歷,從中提取出每一個(gè)產(chǎn)品品名、最新報(bào)價(jià)、單位、報(bào)價(jià)數(shù)、報(bào)價(jià)時(shí)間等信息。

# -*- coding: UTF-8 -*-  """  @File    :demo.py  @Author  :葉庭云  @CSDN    :https://yetingyun.blog.csdn.net/  """  import requests  import logging  from fake_useragent import UserAgent  from lxml import etree  # 日志輸出的基本配置  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s')  # 隨機(jī)產(chǎn)生請求頭  ua = UserAgent(verify_ssl=False, path='fake_useragent.json')  url = 'https://www.zhongnongwang.com/quote/product-htm-page-1.html'  # 偽裝請求頭  headers = {      "Accept-Encoding": "gzip",  # 使用gzip壓縮傳輸數(shù)據(jù)讓訪問更快      "User-Agent": ua.random  }  # 發(fā)送請求  獲取響應(yīng)  rep = requests.get(url, headersheaders=headers)  print(rep.status_code)    # 200  # Xpath定位提取數(shù)據(jù)  html = etree.HTML(rep.text)  items = html.xpath('/html/body/div[10]/table/tr[@align="center"]')  logging.info(f'該頁有多少條信息:{len(items)}')  # 一頁有20條信息  # 遍歷提取出數(shù)據(jù)  for item in items:      name = ''.join(item.xpath('.//td[1]/a/text()'))  # 品名      price = ''.join(item.xpath('.//td[3]/text()'))   # 最新報(bào)價(jià)      unit = ''.join(item.xpath('.//td[4]/text()'))    # 單位      nums = ''.join(item.xpath('.//td[5]/text()'))    # 報(bào)價(jià)數(shù)      time_ = ''.join(item.xpath('.//td[6]/text()'))   # 報(bào)價(jià)時(shí)間      logging.info([name, price, unit, nums, time_])

運(yùn)行結(jié)果如下:

Python爬蟲實(shí)戰(zhàn)之單線程、多線程和協(xié)程性能有哪些區(qū)別

可以成功爬取到數(shù)據(jù),接下來分別用普通的單線程、多線程和協(xié)程來爬取 50 頁的數(shù)據(jù)、保存到Excel。

三、單線程爬蟲

# -*- coding: UTF-8 -*-  """  @File    :單線程.py  @Author  :葉庭云  @CSDN    :https://yetingyun.blog.csdn.net/  """  import requests  import logging  from fake_useragent import UserAgent  from lxml import etree  import openpyxl  from datetime import datetime  # 日志輸出的基本配置  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s')  # 隨機(jī)產(chǎn)生請求頭  ua = UserAgent(verify_ssl=False, path='fake_useragent.json')  wb = openpyxl.Workbook() sheet = wb.active sheet.append(['品名', '最新報(bào)價(jià)', '單位', '報(bào)價(jià)數(shù)', '報(bào)價(jià)時(shí)間'])  start = datetime.now()  for page in range(1, 51):      # 構(gòu)造URL      url = f'https://www.zhongnongwang.com/quote/product-htm-page-{page}.html'      # 偽裝請求頭     headers = {          "Accept-Encoding": "gzip",  # 使用gzip壓縮傳輸數(shù)據(jù)讓訪問更快          "User-Agent": ua.random      }      # 發(fā)送請求  獲取響應(yīng)      rep = requests.get(url, headersheaders=headers)      # print(rep.status_code)      # Xpath定位提取數(shù)據(jù)      html = etree.HTML(rep.text)      items = html.xpath('/html/body/div[10]/table/tr[@align="center"]')      logging.info(f'該頁有多少條信息:{len(items)}')  # 一頁有20條信息      # 遍歷提取出數(shù)據(jù)      for item in items:          name = ''.join(item.xpath('.//td[1]/a/text()'))  # 品名          price = ''.join(item.xpath('.//td[3]/text()'))   # 最新報(bào)價(jià)          unit = ''.join(item.xpath('.//td[4]/text()'))    # 單位          nums = ''.join(item.xpath('.//td[5]/text()'))    # 報(bào)價(jià)數(shù)          time_ = ''.join(item.xpath('.//td[6]/text()'))   # 報(bào)價(jià)時(shí)間          sheet.append([name, price, unit, nums, time_])          logging.info([name, price, unit, nums, time_])  wb.save(filename='data1.xlsx')  delta = (datetime.now() - start).total_seconds()  logging.info(f'用時(shí):{delta}s')

運(yùn)行結(jié)果如下:

Python爬蟲實(shí)戰(zhàn)之單線程、多線程和協(xié)程性能有哪些區(qū)別

單線程爬蟲必須上一個(gè)頁面爬取完成才能繼續(xù)爬取,還可能受當(dāng)時(shí)網(wǎng)絡(luò)狀態(tài)影響,用時(shí)48.528703s,才將數(shù)據(jù)爬取完,速度比較慢。

四、多線程爬蟲

# -*- coding: UTF-8 -*-  """  @File    :多線程.py  @Author  :葉庭云  @CSDN    :https://yetingyun.blog.csdn.net/  """  import requests  import logging  from fake_useragent import UserAgent  from lxml import etree  import openpyxl  from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED  from datetime import datetime  # 日志輸出的基本配置  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s')  # 隨機(jī)產(chǎn)生請求頭  ua = UserAgent(verify_ssl=False, path='fake_useragent.json')  wb = openpyxl.Workbook()  sheet = wb.active  sheet.append(['品名', '最新報(bào)價(jià)', '單位', '報(bào)價(jià)數(shù)', '報(bào)價(jià)時(shí)間'])  start = datetime.now() def get_data(page):      # 構(gòu)造URL      url = f'https://www.zhongnongwang.com/quote/product-htm-page-{page}.html'      # 偽裝請求頭      headers = {          "Accept-Encoding": "gzip",    # 使用gzip壓縮傳輸數(shù)據(jù)讓訪問更快          "User-Agent": ua.random      }      # 發(fā)送請求  獲取響應(yīng)      rep = requests.get(url, headersheaders=headers)      # print(rep.status_code)      # Xpath定位提取數(shù)據(jù)      html = etree.HTML(rep.text)      items = html.xpath('/html/body/div[10]/table/tr[@align="center"]')      logging.info(f'該頁有多少條信息:{len(items)}')  # 一頁有20條信息      # 遍歷提取出數(shù)據(jù)      for item in items:          name = ''.join(item.xpath('.//td[1]/a/text()'))   # 品名          price = ''.join(item.xpath('.//td[3]/text()'))    # 最新報(bào)價(jià)          unit = ''.join(item.xpath('.//td[4]/text()'))     # 單位          nums = ''.join(item.xpath('.//td[5]/text()'))     # 報(bào)價(jià)數(shù)          time_ = ''.join(item.xpath('.//td[6]/text()'))    # 報(bào)價(jià)時(shí)間          sheet.append([name, price, unit, nums, time_])          logging.info([name, price, unit, nums, time_]) def run():      # 爬取1-50頁      with ThreadPoolExecutor(max_workers=6) as executor:          future_tasks = [executor.submit(get_data, i) for i in range(1, 51)]          wait(future_tasks, return_when=ALL_COMPLETED)      wb.save(filename='data2.xlsx')      delta = (datetime.now() - start).total_seconds()      print(f'用時(shí):{delta}s')  run()

運(yùn)行結(jié)果如下:

Python爬蟲實(shí)戰(zhàn)之單線程、多線程和協(xié)程性能有哪些區(qū)別

多線程爬蟲爬取效率提升非??捎^,用時(shí) 2.648128s,爬取速度很快。

五、異步協(xié)程爬蟲

# -*- coding: UTF-8 -*-  """  @File    :demo1.py  @Author  :葉庭云  @CSDN    :https://yetingyun.blog.csdn.net/  """  import aiohttp  import asyncio  import logging  from fake_useragent import UserAgent  from lxml import etree  import openpyxl  from datetime import datetime  # 日志輸出的基本配置  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s')  # 隨機(jī)產(chǎn)生請求頭  ua = UserAgent(verify_ssl=False, path='fake_useragent.json')  wb = openpyxl.Workbook()  sheet = wb.active sheet.append(['品名', '最新報(bào)價(jià)', '單位', '報(bào)價(jià)數(shù)', '報(bào)價(jià)時(shí)間'])  start = datetime.now()  class Spider(object):      def __init__(self):          # self.semaphore = asyncio.Semaphore(6)  # 信號(hào)量,有時(shí)候需要控制協(xié)程數(shù),防止爬的過快被反爬          self.header = {                  "Accept-Encoding": "gzip",    # 使用gzip壓縮傳輸數(shù)據(jù)讓訪問更快                  "User-Agent": ua.random              }     async def scrape(self, url):          # async with self.semaphore:  # 設(shè)置最大信號(hào)量,有時(shí)候需要控制協(xié)程數(shù),防止爬的過快被反爬          session = aiohttp.ClientSession(headers=self.header, connector=aiohttp.TCPConnector(ssl=False))          response = await session.get(url)          result = await response.text()          await session.close()          return result      async def scrape_index(self, page):          url = f'https://www.zhongnongwang.com/quote/product-htm-page-{page}.html'          text = await self.scrape(url)          await self.parse(text)      async def parse(self, text):          # Xpath定位提取數(shù)據(jù)          html = etree.HTML(text)          items = html.xpath('/html/body/div[10]/table/tr[@align="center"]')          logging.info(f'該頁有多少條信息:{len(items)}')  # 一頁有20條信息          # 遍歷提取出數(shù)據(jù)          for item in items:              name = ''.join(item.xpath('.//td[1]/a/text()'))  # 品名              price = ''.join(item.xpath('.//td[3]/text()'))  # 最新報(bào)價(jià)              unit = ''.join(item.xpath('.//td[4]/text()'))  # 單位              nums = ''.join(item.xpath('.//td[5]/text()'))  # 報(bào)價(jià)數(shù)              time_ = ''.join(item.xpath('.//td[6]/text()'))  # 報(bào)價(jià)時(shí)間              sheet.append([name, price, unit, nums, time_])              logging.info([name, price, unit, nums, time_])      def main(self):          # 50頁的數(shù)據(jù)          scrape_index_tasks = [asyncio.ensure_future(self.scrape_index(page)) for page in range(1, 51)]          loop = asyncio.get_event_loop()          tasks = asyncio.gather(*scrape_index_tasks)          loop.run_until_complete(tasks)  if __name__ == '__main__':      spider = Spider()      spider.main()      wb.save('data3.xlsx')      delta = (datetime.now() - start).total_seconds()      print("用時(shí):{:.3f}s".format(delta))

運(yùn)行結(jié)果如下:

Python爬蟲實(shí)戰(zhàn)之單線程、多線程和協(xié)程性能有哪些區(qū)別

而到了協(xié)程異步爬蟲,爬取速度更快,嗖的一下,用時(shí) 0.930s 就爬取完 50 頁數(shù)據(jù),aiohttp + asyncio 異步爬蟲竟恐怖如斯。異步爬蟲在服務(wù)器能承受高并發(fā)的前提下增加并發(fā)數(shù)量,爬取效率提升是非??捎^的,比多線程還要快一些。

三種爬蟲都將 50 頁的數(shù)據(jù)爬取下來保存到了本地,結(jié)果如下:

Python爬蟲實(shí)戰(zhàn)之單線程、多線程和協(xié)程性能有哪些區(qū)別

六、總結(jié)回顧

今天我演示了簡單的單線程爬蟲、多線程爬蟲和協(xié)程異步爬蟲??梢钥吹揭话闱闆r下異步爬蟲速度最快,多線程爬蟲略慢一點(diǎn),單線程爬蟲速度較慢,必須上一個(gè)頁面爬取完成才能繼續(xù)爬取。

但協(xié)程異步爬蟲相對來說并不是那么好編寫,數(shù)據(jù)抓取無法使用 request 庫,只能使用aiohttp,而且爬取數(shù)據(jù)量大時(shí),異步爬蟲需要設(shè)置最大信號(hào)量來控制協(xié)程數(shù),防止爬的過快被反爬。所以在實(shí)際編寫 Python 爬蟲時(shí),我們一般都會(huì)使用多線程爬蟲來提速,但必須注意的是網(wǎng)站都有 ip 訪問頻率限制,爬的過快可能會(huì)被封ip,所以一般我們在多線程提速的同時(shí)可以使用代理 ip 來并發(fā)地爬取數(shù)據(jù)。

  •  多線程(multithreading):是指從軟件或者硬件上實(shí)現(xiàn)多個(gè)線程并發(fā)執(zhí)行的技術(shù)。具有多線程能力的計(jì)算機(jī)因有硬件支持而能夠在同一時(shí)間執(zhí)行多于一個(gè)線程,進(jìn)而提升整體處理性能。具有這種能力的系統(tǒng)包括對稱多處理機(jī)、多核心處理器以及芯片級多處理或同時(shí)多線程處理器。在一個(gè)程序中,這些獨(dú)立運(yùn)行的程序片段叫作 "線程" (Thread),利用它編程的概念就叫作 "多線程處理"。

  •  異步(asynchronous):為完成某個(gè)任務(wù),不同程序單元之間過程中無需通信協(xié)調(diào),也能完成任務(wù)的方式,不相關(guān)的程序單元之間可以是異步的。例如,爬蟲下載網(wǎng)頁。調(diào)度程序調(diào)用下載程序后,即可調(diào)度其他任務(wù),而無需與該下載任務(wù)保持通信以協(xié)調(diào)行為。不同網(wǎng)頁的下載、保存等操作都是無關(guān)的,也無需相互通知協(xié)調(diào)。這些異步操作的完成時(shí)刻并不確定。簡言之,異步意味著無序。

  •  協(xié)程(coroutine),又稱微線程、纖程,協(xié)程是一種用戶態(tài)的輕量級線程。協(xié)程擁有自己的寄存器上下文和棧。協(xié)程調(diào)度切換時(shí),將寄存器上下文和棧保存到其他地方,在切回來的時(shí)候,恢復(fù)先前保存的寄存器上下文和棧。因此協(xié)程能保留上一次調(diào)用時(shí)的狀態(tài),即所有局部狀態(tài)的一個(gè)特定組合,每次過程重入時(shí),就相當(dāng)于進(jìn)入上一次調(diào)用的狀態(tài)。協(xié)程本質(zhì)上是個(gè)單進(jìn)程,協(xié)程相對于多進(jìn)程來說,無需線程上下文切換的開銷,無需原子操作鎖定及同步的開銷,編程模型也非常簡單。我們可以使用協(xié)程來實(shí)現(xiàn)異步操作,比如在網(wǎng)絡(luò)爬蟲場景下,我們發(fā)出一個(gè)請求之后,需要等待一定的時(shí)間才能得到響應(yīng),但其實(shí)在這個(gè)等待過程中,程序可以干許多其他的事情,等到響應(yīng)得到之后才切換回來繼續(xù)處理,這樣可以充分利用 CPU 和其他資源,這就是協(xié)程的優(yōu)勢。 

到此,相信大家對“Python爬蟲實(shí)戰(zhàn)之單線程、多線程和協(xié)程性能有哪些區(qū)別”有了更深的了解,不妨來實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI