掃二維碼與項目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
[[378975]]

創(chuàng)新互聯(lián)建站-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價比市中網(wǎng)站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫,直接使用。一站式市中網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋市中地區(qū)。費用合理售后完善,十年實體公司更值得信賴。
一、前言
今天我要給大家分享的是如何爬取中農(nóng)網(wǎng)產(chǎn)品報價數(shù)據(jù),并分別用普通的單線程、多線程和協(xié)程來爬取,從而對比單線程、多線程和協(xié)程在網(wǎng)絡(luò)爬蟲中的性能。
目標URL:https://www.zhongnongwang.com/quote/product-htm-page-1.html
爬取產(chǎn)品品名、最新報價、單位、報價數(shù)、報價時間等信息,保存到本地Excel。
二、爬取測試
翻頁查看 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ù)。
思路:每一條產(chǎn)品報價信息在 class 為 tb 的 table 標簽下的 tbody 下的 tr 標簽里,獲取到所有 tr 標簽的內(nèi)容,然后遍歷,從中提取出每一個產(chǎn)品品名、最新報價、單位、報價數(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')
- # 隨機產(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()')) # 最新報價
- unit = ''.join(item.xpath('.//td[4]/text()')) # 單位
- nums = ''.join(item.xpath('.//td[5]/text()')) # 報價數(shù)
- time_ = ''.join(item.xpath('.//td[6]/text()')) # 報價時間
- logging.info([name, price, unit, nums, time_])
運行結(jié)果如下:
可以成功爬取到數(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')
- # 隨機產(chǎn)生請求頭
- ua = UserAgent(verify_ssl=False, path='fake_useragent.json')
- wb = openpyxl.Workbook()
- sheet = wb.active
- sheet.append(['品名', '最新報價', '單位', '報價數(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()')) # 最新報價
- unit = ''.join(item.xpath('.//td[4]/text()')) # 單位
- nums = ''.join(item.xpath('.//td[5]/text()')) # 報價數(shù)
- time_ = ''.join(item.xpath('.//td[6]/text()')) # 報價時間
- 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'用時:{delta}s')
運行結(jié)果如下:
單線程爬蟲必須上一個頁面爬取完成才能繼續(xù)爬取,還可能受當時網(wǎng)絡(luò)狀態(tài)影響,用時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')
- # 隨機產(chǎn)生請求頭
- ua = UserAgent(verify_ssl=False, path='fake_useragent.json')
- wb = openpyxl.Workbook()
- sheet = wb.active
- sheet.append(['品名', '最新報價', '單位', '報價數(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()')) # 最新報價
- unit = ''.join(item.xpath('.//td[4]/text()')) # 單位
- nums = ''.join(item.xpath('.//td[5]/text()')) # 報價數(shù)
- time_ = ''.join(item.xpath('.//td[6]/text()')) # 報價時間
- 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'用時:{delta}s')
- run()
運行結(jié)果如下:
多線程爬蟲爬取效率提升非常可觀,用時 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')
- # 隨機產(chǎn)生請求頭
- ua = UserAgent(verify_ssl=False, path='fake_useragent.json')
- wb = openpyxl.Workbook()
- sheet = wb.active
- sheet.append(['品名', '最新報價', '單位', '報價數(shù)', '報價時間'])
- start = datetime.now()
- class Spider(object):
- def __init__(self):
- # self.semaphore = asyncio.Semaphore(6) # 信號量,有時候需要控制協(xié)程數(shù),防止爬的過快被反爬
- self.header = {
- "Accept-Encoding": "gzip", # 使用gzip壓縮傳輸數(shù)據(jù)讓訪問更快
- "User-Agent": ua.random
- }
- async def scrape(self, url):
- # async with self.semaphore: # 設(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()')) # 最新報價
- unit = ''.join(item.xpath('.//td[4]/text()')) # 單位
- nums = ''.join(item.xpath('.//td[5]/text()')) # 報價數(shù)
- time_ = ''.join(item.xpath('.//td[6]/text()')) # 報價時間
- 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("用時:{:.3f}s".format(delta))
運行結(jié)果如下:
而到了協(xié)程異步爬蟲,爬取速度更快,嗖的一下,用時 0.930s 就爬取完 50 頁數(shù)據(jù),aiohttp + asyncio 異步爬蟲竟恐怖如斯。異步爬蟲在服務(wù)器能承受高并發(fā)的前提下增加并發(fā)數(shù)量,爬取效率提升是非??捎^的,比多線程還要快一些。
三種爬蟲都將 50 頁的數(shù)據(jù)爬取下來保存到了本地,結(jié)果如下:
六、總結(jié)回顧
今天我演示了簡單的單線程爬蟲、多線程爬蟲和協(xié)程異步爬蟲??梢钥吹揭话闱闆r下異步爬蟲速度最快,多線程爬蟲略慢一點,單線程爬蟲速度較慢,必須上一個頁面爬取完成才能繼續(xù)爬取。
但協(xié)程異步爬蟲相對來說并不是那么好編寫,數(shù)據(jù)抓取無法使用 request 庫,只能使用aiohttp,而且爬取數(shù)據(jù)量大時,異步爬蟲需要設(shè)置最大信號量來控制協(xié)程數(shù),防止爬的過快被反爬。所以在實際編寫 Python 爬蟲時,我們一般都會使用多線程爬蟲來提速,但必須注意的是網(wǎng)站都有 ip 訪問頻率限制,爬的過快可能會被封ip,所以一般我們在多線程提速的同時可以使用代理 ip 來并發(fā)地爬取數(shù)據(jù)。

我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流