selenium 知網爬蟲之根據【關鍵詞】獲取文獻資訊

鹹魚Linux運維發表於2023-10-28

哈嘍大家好,我是鹹魚

之前鹹魚寫過幾篇關於知網爬蟲的文章,後臺反響都很不錯。雖然但是,鹹魚還是忍不住想訴苦一下

有些小夥伴文章甚至程式碼看都沒看完,就問我 ”為什麼只能爬這麼多條文獻資訊?“(看過程式碼的會發現我程式碼裡面定義了 papers_need 變數來設定爬取篇數),”為什麼爬其他文獻不行?我想爬 XXX 文獻“(因為程式碼裡面寫的是透過【知網高階搜尋中的文獻來源】來搜尋文章),或者是有些小夥伴直接把程式碼報錯貼給我,問我咋回事

我覺得在網上看到別人的程式碼,不要一昧地拿來主義,複製貼上就行了,你要結合你自己的本地環境對程式碼做適當地修改。比如定位 Xpath 元素路徑,不通電腦或者說不同瀏覽器同一元素的 Xpath 路徑有可能不是一樣的,這個路徑在我本地執行沒問題,到了你那裡就報錯

當看別人的程式碼時,最好先搞清楚:

  1. 別人是怎麼想的
  2. 別人為什麼要這麼寫
  3. 這麼寫的邏輯是什麼?

以我這幾篇知網爬蟲文章舉例:

  1. 為什麼要用 selenium 來爬取?
  2. 如何分析網頁?如何定位元素?(Xpath、CSS 選擇器等等)
  3. 如何透過 selenium 來模擬人為操作瀏覽器(滑鼠移動、點選、滑動視窗等等)

言歸正傳,鹹魚昨天收到一位粉絲私信說能不能根據【關鍵詞】來搜尋文獻
image
今天這篇文章著重講如何分析網頁結構然後使用 selenium 根據知網的關鍵詞來搜尋文獻。至於對搜尋到的文獻的爬取,本文不過多介紹,因為以前的文章已經寫過了

需求分析

我們先來看下如果要透過關鍵詞搜尋文獻,該怎麼操作?

知網:中國知網 (cnki.net)

首先我們登入網站,點選【高階搜尋】(也可以直接點選搜尋框中的【主題】下拉選擇)
image
然後我們點選【主題】——>選擇【關鍵詞】
image
image
輸入要搜尋的關鍵詞(例如:數字普惠金融)然後點選【檢索】
image

網頁分析&元素定位

結合前面的需求分析,我們就可以對網頁進行分析並定位出對應的元素

首先是【高階搜尋】,高階搜尋有一個連結:高階檢索-中國知網 (cnki.net),這樣就能省掉一個步驟了

然後我們需要點選 【主題】,才會出現下拉框。在分析網頁的時候我發現當出現下拉框時,標籤 <div class="sort-list" style="display: none;">" 中的 style 屬性由 "display: none;" 變成 "display: block;"
image
下拉框出現之後,我們需要定位到 【關鍵詞】 這個標籤

# 關鍵詞 Xpath 路徑或 CSS 選擇器
//*[@id="gradetxt"]/dd[1]/div[2]/div[1]/div[2]/ul/li[3]

li[data-val="KY"]

image
接著找到【搜尋框】的 Xpath 路徑。這裡是一個 input 元素,用於接收來自使用者的資料

# 輸入框
//*[@id="gradetxt"]/dd[1]/div[2]/input

image

往輸入框傳入資料之後,我們需要點選下面的【檢索】按鈕

# 檢索
/html/body/div[2]/div/div[2]/div/div[1]/div[1]/div[2]/div[2]/input

image
點選搜尋之後我們把【文獻條數】爬取下來

# 文獻條數
/html/body/div[3]/div[2]/div[2]/div[2]/form/div/div[1]/div[1]/span[1]/em

image

程式碼實現

selenium 是一個自動化測試工具,可以用來進行 web 自動化測試。其本質是透過驅動瀏覽器,完全模擬瀏覽器的操作(比如跳轉、輸入、點選、下拉等)來實現網頁渲染之後的結果,可支援多種瀏覽器

爬蟲中用到 selenium 主要是為了解決 requests 無法直接執行 JavaScript 程式碼等問題

匯入相關庫

import time
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.common.action_chains import ActionChains

建立瀏覽器物件

這裡我用的是 Edge 瀏覽器

def webserver():
    # get直接返回,不再等待介面載入完成
    desired_capabilities = DesiredCapabilities.EDGE
    desired_capabilities["pageLoadStrategy"] = "none"

    # 設定微軟碟機動器的環境
    options = webdriver.EdgeOptions()
    
    # 設定瀏覽器不載入圖片,提高載入速度
    options.add_experimental_option("prefs", {"profile.managed_default_content_settings.images": 2})

    # 建立一個微軟碟機動器
    driver = webdriver.Edge(options=options)
    return driver

爬取網頁

其實邏輯並不難,就是先定位到各個元素然後用 selenium 來模擬我們人為點選瀏覽器的操作就行了

首先開啟頁面,等待個一兩秒讓網頁完全載入

    driver.get("https://kns.cnki.net/kns8/AdvSearch")
    time.sleep(2)

然後然下拉框顯示出來,前面我們提到:標籤 <div class="sort-list" style="display: none;">" 中的 style 屬性由 "display: none;" 變成 "display: block;" 時,就會出現下拉框

這裡我們透過執行 js 指令碼來修改裡面的 style 屬性

    # 修改屬性,使下拉框顯示
    opt = driver.find_element(By.CSS_SELECTOR, 'div.sort-list')  # 定位下拉框
    # 執行 js 指令碼進行屬性的修改; arguments[0]代表第一個屬性
    driver.execute_script("arguments[0].setAttribute('style', 'display: block;')", opt)  

下拉框顯示出來之後我們需要點選【關鍵詞】,這樣才會切換到關鍵詞搜尋

這裡需要注意的是,當我在測試的時候發現下拉框載入是有問題的,這時候程式碼會報錯說Element <li data-val="KY">...</li> is not clickable at point (189, 249)

就會使得程式點選不了【關鍵詞】
image
而且我還發現如果載入不完全的話,需要滑鼠移動到下拉框那裡,讓下拉框完全載入。所以這裡我使用了 selenium 中的 ActionChains 來模擬滑鼠的操作

用 selenium 做自動化,有時候會遇到需要模擬滑鼠操作才能進行的情況,比如單擊、雙擊、點選滑鼠右鍵、拖拽等等

selenium 給我們提供了一個類來處理這類事件——ActionChains

還有一點需要注意的是:如果滑鼠只是移到【關鍵詞】,下拉框其實還是不能正確載入出來,最好是移動到下拉框的最底部或者關鍵詞後面的元素,這裡我移動到【通訊作者】

# 【通訊作者】定位
/html/body/div[2]/div/div[2]/div/div[2]/div[1]/div[1]/div[2]/ul/li[8]

li[data-val="RP"]

image

下拉框載入完成之後,定位到【關鍵詞】再點選

    # 滑鼠移動到下拉框
    ActionChains(driver).move_to_element(driver.find_element(By.CSS_SELECTOR, 'li[data-val="RP"]')).perform()

    # 找到[關鍵詞]選項並點選
    WebDriverWait(driver, 100).until(
        EC.visibility_of_element_located((By.CSS_SELECTOR, 'li[data-val="KY"]'))).click()

定位出搜尋框,傳入我們要搜尋的關鍵詞

    # 傳入關鍵字
    WebDriverWait(driver, 100).until(
        EC.presence_of_element_located((By.XPATH, '''//*[@id="gradetxt"]/dd[1]/div[2]/input'''))
    ).send_keys(keyword)

    # 點選搜尋
    WebDriverWait(driver, 100).until(
        EC.presence_of_element_located((By.XPATH, "/html/body/div[2]/div/div[2]/div/div[1]/div[1]/div[2]/div[2]/input"))
    ).click()

搜尋結果出來之後定位【文獻條數】,獲取對應的條數(text 標籤)

    # 獲取總文獻數和頁數
    res_unm = WebDriverWait(driver, 100).until(EC.presence_of_element_located(
        (By.XPATH, "/html/body/div[3]/div[2]/div[2]/div[2]/form/div/div[1]/div[1]/span[1]/em"))
    ).text

完整程式碼如下:

import time
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.common.action_chains import ActionChains


def webserver():
    # get直接返回,不再等待介面載入完成
    desired_capabilities = DesiredCapabilities.EDGE
    desired_capabilities["pageLoadStrategy"] = "none"

    # 設定微軟碟機動器的環境
    options = webdriver.EdgeOptions()
    # 設定瀏覽器不載入圖片,提高速度
    options.add_experimental_option("prefs", {"profile.managed_default_content_settings.images": 2})

    # 建立一個微軟碟機動器
    driver = webdriver.Edge(options=options)

    return driver


def open_page(driver, keyword):
    # 開啟頁面,等待兩秒
    driver.get("https://kns.cnki.net/kns8/AdvSearch")
    time.sleep(2)

    # 修改屬性,使下拉框顯示
    opt = driver.find_element(By.CSS_SELECTOR, 'div.sort-list')  # 定位元素
    driver.execute_script("arguments[0].setAttribute('style', 'display: block;')", opt)  # 執行 js 指令碼進行屬性的修改;arguments[0]代表第一個屬性

    # 滑鼠移動到下拉框中的[通訊作者]
    ActionChains(driver).move_to_element(driver.find_element(By.CSS_SELECTOR, 'li[data-val="RP"]')).perform()

    # 找到[關鍵詞]選項並點選
    WebDriverWait(driver, 100).until(
        EC.visibility_of_element_located((By.CSS_SELECTOR, 'li[data-val="KY"]'))).click()

    # 傳入關鍵字
    WebDriverWait(driver, 100).until(
        EC.presence_of_element_located((By.XPATH, '''//*[@id="gradetxt"]/dd[1]/div[2]/input'''))
    ).send_keys(keyword)

    # 點選搜尋
    WebDriverWait(driver, 100).until(
        EC.presence_of_element_located((By.XPATH, "/html/body/div[2]/div/div[2]/div/div[1]/div[1]/div[2]/div[2]/input"))
    ).click()

    # 點選切換中文文獻
    WebDriverWait(driver, 100).until(
        EC.presence_of_element_located((By.XPATH, "/html/body/div[3]/div[1]/div/div/div/a[1]"))
    ).click()

    # 獲取總文獻數和頁數
    res_unm = WebDriverWait(driver, 100).until(EC.presence_of_element_located(
        (By.XPATH, "/html/body/div[3]/div[2]/div[2]/div[2]/form/div/div[1]/div[1]/span[1]/em"))
    ).text

    # 去除千分位裡的逗號
    res_unm = int(res_unm.replace(",", ''))
    page_unm = int(res_unm / 20) + 1
    print(f"共找到 {res_unm} 條結果, {page_unm} 頁。")


if __name__ == '__main__':
    keyword = "數字普惠金融"
    driver = webserver()
    open_page(driver, keyword)

結果如下:
image

相關文章