Python 基於 selenium 實現不同商城的商品價格差異分析系統

一枚大果殼發表於2022-03-19

1. 前言

selenium 原本是一款自動化測試工具,因其出色的頁面資料解析使用者行為模擬能力而常用於爬蟲程式中,致使爬蟲程式的爬取過程更簡單、快捷。

爬蟲程式與其它型別程式相比較,本質一樣,為資料提供處理邏輯,只是爬蟲程式的資料來源於 HTML 程式碼片段中。

怎樣準確查詢到頁面中資料所在的標籤(或叫節點、元素、元件)就成了爬蟲程式的關鍵,只有這一步成立,後續的資料提取、清洗、彙總才有可能。

相比較於 Beaufulsoup 模組, selenium 底層依靠的是強大的瀏覽器引擎,在頁面解析能力上頗有王者的從容和決絕。

本文將使用 selenium 自動摸擬使用者的搜尋行為,獲取不同商城上同型別商品的價格資訊,最終生成商品在不同商城上的價格差對比表。

本文通過實現程式流程講解 selenium,只會講解程式中涉及到的 selenium 功能。不會深究其它 selenium API 的細節。所以你在閱讀本文時,請確定你對 selenium 有所一點點的瞭解。

2、程式設計流程

2.1 需求分析:

本程式實現了使用者不開啟瀏覽器、只需要輸入一個商品關鍵字,便能全自動化的實現在不同商城中查詢商品價格,並彙總出價格一些差異資訊。

  1. 程式執行時,提示使用者輸入需要搜尋的商品關鍵字。

    本程式僅為探研 selenium 的奇妙之處,感受其王者風範,沒有在程式結構和介面上費心力。

  2. 使用 selenium 摸擬使用者開啟京東蘇寧易購首頁。

    為什麼選擇京東和蘇寧易,而不選擇淘寶?

    因為這 2 個網站使用搜尋功能時沒有登入驗證需要,可簡化本程式程式碼。

  3. 使用 selenium 在首頁的文字搜尋框中自動輸入商品關鍵字,然後自動觸發搜尋按鈕的點選事件,進入商品列表頁面。

  4. 使用 selenium 分析、爬取不同商城中商品列表頁面中的商品名稱和價格資料。

  5. 對商品的價格資料做簡單分析後,使用 CSV 模組以檔案方式儲存。

    主要分析商品在不同商城上的平均價格、最低價格、最高體系的差異。

    當然,如果有需要,可以藉助其它的模組或分析邏輯,得到更多的資料分析結論。

2.2 認識 selenium

雖然本文不深究 selenium API 的細節,但是,既然要用它,其使用流程還是要面面俱到。

  1. 安裝:

    seleniumpython 第三庫,使用前要安裝,安裝細節就沒必要在此多費筆墨。

pip3 install selenium

除了安裝 selenium 模組,還需要為它下載一個瀏覽器驅動程式,否則它無法工作。

什麼是瀏覽器驅動程式?為什麼需要它?

解釋這個問題,需要從 selenium 的工作原理說起。

  1. 淺淡 selenium 的工作原理:

Beautiful soup 使用特定的解析器程式解析 HTML 頁面。selenium 更乾脆、直接藉助瀏覽器的解析能力。通過呼叫瀏覽器的底層 API 完成頁面資料查詢,也是跪服了,不僅爬取,還可以向瀏覽器模擬使用者行為傳送操作指令。

有沒有感覺瀏覽器就是 selenium 手中的牽線木偶(玩弄瀏覽器於股掌之中)。 selenium 的工作就是驅動瀏覽器,向瀏覽器傳送指令或接收瀏覽的反饋,此過程中,瀏覽器驅動程式(webdriver)就起到了上傳下達的作用。

典型的元件開發模式。

很顯然,因不同瀏覽器的核心存在差異性,驅動程式必然也不相同,所以,下載驅動程式之前,請確定你使用的瀏覽器型別和版本。

本文使用谷歌瀏覽器,需要下載與谷歌瀏覽器對應的 webdriver 驅動程式。

  1. 進入 https://www.selenium.dev/downloads/ 網站,選擇 python 語言,選擇最新穩定版本。

  1. 請選擇與正使用的瀏覽版本一致的驅動程式。

  1. 下載完畢後,指定一個驅動程式的存放目錄,本文存放在 D:\chromedriver\chromedriver.exe 。也可存放在瀏覽器的安裝目錄。

2.3 功能函式設計

準備工作就緒後,開始編碼:

  1. 匯入程式所需要的模組,定義程式所需要的變數。
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
import csv
import time
import math
# 瀏覽器物件
chrome_browser = None
# 商品關鍵字
search_keyword = None
# 儲存在京東商城搜尋到的商品資料,格式{商品名:價格}
jd_data = {}
# 儲存在蘇寧商城搜尋到的商品資料,格式{商品名:價格}
sn_data = {}
  • webdriver: 用來構建瀏覽器物件,從底層設計角度講,是 selenium 和瀏覽器之間的介面層。selenium 向上為使用者提供高階應用介面,向下通過 webdriver 和瀏覽器無障礙溝通。

  • Service: webdriver 構建瀏覽器物件時的引數型別。

  • By: ** 封裝了查詢頁面元件的各種方式。selenium** 向開者提供了很多高階方法用來查詢 HTML 頁面元件,如通過元素 ID、樣式、樣式選擇器、XPATH……By 封裝了這些方案。

    諸如:find_element_by_class_name( )、 find_element_by_id()、find_element_by_()、find_element_by_tag_name()、find_element_by_class_name()、find_element_by_xpath()、find_element_by_css_selector()

    以上方法已經被標註為過時,請使用 find_element( ) 方法,配合 By 物件切換方式。

  • csv: 用來把獲取到的資料以 csv 格式儲存。

  • time: 時間模組,用來模擬網路延遲。

  • math: 數學模組,輔助資料分析。

  1. 初始化函式:初始化瀏覽器物件和使用者輸入資料。
'''
初始瀏覽器物件
'''
def init_data():
    # 驅動程式存放路徑
    webdriver_path = r"D:\chromedriver\chromedriver.exe"
    service = Service(webdriver_path)
    # 構建瀏覽器物件
    browser = webdriver.Chrome(service=service)
    # 等待瀏覽器就緒
    browser.implicitly_wait(10)
    return browser

'''
初始使用者輸入的商品名稱關鍵字
'''
def input_search_key():
    info = input("請輸入商品關鍵字:")
    return info
  1. 查詢京東商品資訊。在京東商城查詢商品,分兩個步驟,在首頁輸入商品關鍵字,點選搜尋後,在結果頁面查詢價格資訊。完整程式碼如下:
'''
進入京東商城查詢商品資訊
'''
def search_jd():
    global jd_data
    products_names = []
    products_prices = []
    # 京東首頁
    jd_index_url = r"https://www.jd.com/"
    # 開啟京東首面
    try:
        if chrome_browser is None:
            raise Exception()
        else:
            # 開啟京東首頁
            chrome_browser.get(jd_index_url)
            # 模擬網路延遲
            chrome_browser.implicitly_wait(10)
            # 找到文字輸入元件
            search_input = chrome_browser.find_element(By.ID, "key")
            # 在文字框中輸入商品關鍵字
            search_input.send_keys(search_keyword)
            chrome_browser.implicitly_wait(5)
            # 找到搜尋按鈕 這裡使用 CSS 選擇器方案
            search_button = chrome_browser.find_element(By.CSS_SELECTOR, "#search > div > div.form > button")
            # 觸發按鈕事件
            search_button.click()
            chrome_browser.implicitly_wait(5)
            # 獲取所有開啟的視窗(當點選按鈕後應該有 2 個)
            windows = chrome_browser.window_handles
            # 切換新開啟的視窗,使用負索引找到最後開啟的視窗
            chrome_browser.switch_to.window(windows[-1])
            chrome_browser.implicitly_wait(5)
            # 獲取商品價格
            product_price_divs = chrome_browser.find_elements(By.CLASS_NAME, "p-price")
            for i in range(5):
                div = product_price_divs[i]
                if len(div.text) != 0:
                    # 刪除價格前面的美元符號
                    products_prices.append(float(div.text[1:]))
            # 獲取商品名稱
            product_name_divs = chrome_browser.find_elements(By.CLASS_NAME, "p-name")
            chrome_browser.implicitly_wait(10)
            for i in range(5):
                div = product_name_divs[i]
                if len(div.text) != 0:
                    products_names.append(div.text)

            jd_data = dict(zip(products_names, products_prices))

            jd_data["平均價格"] = sum(products_prices) / len(products_prices)
            jd_data["最低價格"] = min(products_prices)
            jd_data["最高價格"] = max(products_prices)
            # 使用 CSV 模組寫入文件
            csv_save("京東商城", jd_data)

    except Exception as e:
        print(e)

chrome_browser:webdriver 構建出來的對瀏覽器對映的物件,selenium 通過此物件控制對瀏覽器的所有操作。

此物件有一個 find_element( ) 核心方法,用來查詢(定位)HTML 頁面元素。查詢時,可以通過 By 物件指定查詢的方式(這裡使用了工廠設計模式), By 的取值可以是 ID、CSS_SELECTOR、XPATH、CLASS_NAME、CSS_SELECTOR、TAG_NAME、LINK_TEX、PARTIAL_LINK_TEXT。

開啟京東首頁後,先定位定位文字搜尋框搜尋按鈕

使用瀏覽器的開發者工具,檢查到文字框的原始碼是一段 input html 片段,為了精確地定位到此元件,一般先試著分析此元件有沒有獨有的屬性或特徵值,id 是一個不錯的選擇。html 語法規範 id 值應該是一個唯一值。

search_input = chrome_browser.find_element(By.ID, "key")

找到元件後,可以對此元件進行一系列操作,常用的操作:

  • text 屬性: 獲取元件的文字內容。

  • send_keys( ) 方法:為此元件賦值。

  • get_attribute( ) 方法:獲取元件的屬性值。

這裡使用 send_keys 給文字元件賦予使用者輸入商品關鍵字。

search_input.send_keys(search_keyword)

再查詢搜尋按鈕元件:

按鈕元件是一段 button html 程式碼,沒有過於顯著的特性屬性值,為了找到這個唯一元件,可以使用 XPATHCSS 選擇器方式。右擊此程式碼片段,在彈出的快捷選單中找到“複製”命令,再找到此元件的 CSS選擇器值。

search_button = chrome_browser.find_element(By.CSS_SELECTOR, "#search > div > div.form > button")

呼叫按鈕元件的 click() 方法,模擬使用者點選操作,此操作會開啟新視窗,並以列表方式顯示搜尋出來的商品資料。

search_button.click()

selenium 接收到瀏覽器開啟新窗後的反饋後,可以使用 window_handles 屬性獲取瀏覽器中已經開啟的所有視窗,並以列表的方式儲存每一個視窗的操作引用。

windows = chrome_browser.window_handles

對頁面元素進行定位查詢時,有一個當前視窗(當前可以、正在操作的視窗)的概念。剛開始是在首頁視窗操作,現在要在搜尋結果視窗中進行操作,所以要切換到剛開啟的新視窗。使用負索引得到剛開啟的視窗(剛開啟的視窗一定是最後一個視窗)。

chrome_browser.switch_to.window(windows[-1])

注意,這時切換到了搜尋結果視窗,便可以在這個視窗中搜尋所需要元件。

在這個頁面中,只需要獲取前 5 名的商品具體資訊,包括商品名、商品價格。至於具體要獲取什麼資料,可以根據自己的需要定奪。本程式只需要商品的價格和名稱,則檢查頁面,找到對應的 html 片段。

商品名資訊存放在一個 div 片段中,此 div 有一個值為 p-name 的 class 屬性。可以使用 CSS-NAME 方式獲取,因為所有的商品採用相同片段模板,這裡使用 find_elements( ) 方法即可。

product_name_divs = chrome_browser.find_elements(By.CLASS_NAME, "p-name")

find_elements 方法返回具有相同 CSS-NAME 的元件列表,編寫程式碼迭代出每一個元件,並獲取資料,然後儲存在商品名稱列表中。

for i in range(5):
    div = product_name_divs[i]
    if len(div.text) != 0:
       products_names.append(div.text)

以同樣的方式,獲取到價格資料。再把商品名稱和價格資料製成字典,並對價格資料做簡單分析。

 jd_data = dict(zip(products_names, products_prices))
jd_data["平均價格"] = sum(products_prices) / len(products_prices)
jd_data["最低價格"] = min(products_prices)
jd_data["最高價格"] = max(products_prices)
csv_save("京東商城", jd_data)
  1. 儲存資料:資料被壓制到字典後,可把字典中的資料以 CSV 格式儲存在文件中,以便使用者查閱、決策。
def csv_save(sc_name, dic):
    with open("d:/" + sc_name + ".csv", "w", newline='') as f:
        csv_writer = csv.writer(f)
        csv_writer.writerow([sc_name, search_keyword + "價格分析表"])
        for key, val in dic.items():
            csv_writer.writerow([key, val])  

以 CSV 格式儲存從京東商城上爬取下來的資料。

  1. 獲取蘇寧易購上的商品資料。與從京東上獲取資料的邏輯一樣(兩段程式碼可以整合到一個函式中,為了便於理解,本文分開編寫)。兩者的區別在於頁面結構、承載資料的頁面元件不一樣或元件的屬性設定不一樣。
def search_sn():
    global sn_data
    # 儲存商品名稱
    products_names = []
    # 儲存商品價格
    products_prices = []
    # 蘇寧首頁
    sn_index_url = r"https://www.suning.com/"
    try:
        if chrome_browser is None:
            raise Exception()
        else:
            # 開啟首頁
            chrome_browser.get(sn_index_url)
            # 摸擬網路延遲
            chrome_browser.implicitly_wait(10)
            # 查詢文字輸入元件
            search_input = chrome_browser.find_element(By.ID, "searchKeywords")
            # 在文字框中輸入商品關鍵字
            search_input.send_keys(search_keyword)
            time.sleep(2)
            # 找到搜尋按鈕 這裡使用 CSS 選擇器方案
            search_button = chrome_browser.find_element(By.ID, "searchSubmit")
            # 觸發按鈕事件
            search_button.click()
            time.sleep(3)
            # 獲取所有開啟的視窗(當點選按鈕後應該有 2 個)
            windows = chrome_browser.window_handles
            # 切換新開啟的視窗,使用負索引找到最後開啟的視窗
            chrome_browser.switch_to.window(windows[-1])
            chrome_browser.implicitly_wait(20)
            # 獲取商品價格所在標籤
            product_price_divs = chrome_browser.find_elements(By.CLASS_NAME, "def-price")
            # 僅檢視前 5 個商品資訊
            for i in range(5):
                div = product_price_divs[i]
                # 刪除價格前面的美元符號
                if len(div.text) != 0:
                    products_prices.append(float(div.text[1:]))
            chrome_browser.implicitly_wait(10)
            # 獲取商品名稱
            product_name_divs = chrome_browser.find_elements(By.CLASS_NAME, "title-selling-point")
            for i in range(5):
                products_names.append(product_name_divs[i].text)
            #
            sn_data = dict(zip(products_names, products_prices))
            sn_data["平均價格"] = sum(products_prices) / len(products_prices)
            sn_data["最低價格"] = min(products_prices)
            sn_data["最高價格"] = max(products_prices)
            # 使用 CSV 模組寫入文件
            csv_save("蘇寧商城", sn_data)

    except Exception as e:
        print(e)

獲取到蘇寧易購上的商品資料後,同樣以 CSV 格式儲存。

  1. 儲存最終的分析結果。這裡僅分析了兩個商城上同型別商品的平均價格、最低價、最高價的差異性。
def price_result():
    if len(jd_data) != 0 and len(sn_data) != 0:
        with open("d:/商品比較表.csv", "w", newline='') as f:
            csv_writer = csv.writer(f)
            jd_name = list(jd_data.keys())
            jd_price = list(jd_data.values())
            sn_price = list(sn_data.values())
            csv_writer.writerow(["比較項", "京東價格", "蘇寧價格", "價格差"])
            for i in range(5, len(jd_price)):
                csv_writer.writerow([jd_name[i], jd_price[i], sn_price[i], math.fabs(jd_price[i] - sn_price[i])])

儲存了兩個商城上商品價格的平均值、最小值、最大值以及絕對差。

  1. 最終測試程式碼
if __name__ == '__main__':
    search_keyword = input_search_key()
    chrome_browser = init_data()
    search_jd()
    time.sleep(2)
    search_sn()
    price_result()
請輸入商品關鍵字:華為meta 40

3. 總結

本文主要是應用 selenium 。通過應用過程對 selenium 做一個講解,瞭解 selenium 的基本使用流程。資料分析並不是本文的重點。

如果要得到更全面的分析結果,則需要提供更多維度的資料分析邏輯。

相關文章