Python爬蟲,批量獲取知網文獻信息

一规揪、前言

最近臨近畢業(yè)犹撒,寫畢業(yè)論文需要從知網查找大量的文獻。但去知網一條一條進去看摘要又略顯麻煩和浪費時間粒褒。于是识颊,反手寫一個爬蟲,批量獲取基本信息奕坟,豈不美哉祥款?
在開始這個項目之前,我抱著不重復造輪子的心態(tài)月杉,尋思著去Github先找找刃跛。結果發(fā)現(xiàn)基本上都是幾年前的項目,現(xiàn)在早已不能使用苛萎。最后證實了桨昙,靠別人不如靠自己,擼起袖子就開干腌歉!

1. 爬蟲基礎

網絡爬蟲就是模擬瀏覽器發(fā)送網絡請求蛙酪,接收請求響應,一種按照一定的規(guī)則翘盖,自動地抓取互聯(lián)網信息的程序桂塞。
目前爬蟲主要分為以 requests 庫為代表的模擬請求類爬蟲和以 selenium 為代表的模擬瀏覽器用戶行為的爬蟲兩類。:

  • Requests 是用Python語言編寫馍驯,基于 urllib阁危,采用 Apache2 Licensed 開源協(xié)議的 HTTP 庫。它比 urllib 更加方便汰瘫,可以節(jié)約我們大量的工作狂打,完全滿足 HTTP 測試需求。Requests 的哲學是以 PEP 20 的習語為中心開發(fā)的混弥,所以它比 urllib 更加 Pythoner趴乡。
  • Selenium 是一個用于Web應用程序測試的工具。Selenium測試直接運行在瀏覽器中剑逃,就像真正的用戶在操作一樣浙宜。支持的瀏覽器包括IE官辽,Mozilla Firefox蛹磺,Safari,Google Chrome同仆,Opera等萤捆。

中國知網作為國內最知名的文獻數據庫之一,有著復雜的反爬蟲機制,包括:動態(tài)JS俗或、iframe市怎、驗證碼等等。直接模擬請求難度較大辛慰,且容易被封IP地址区匠,所以本文主要介紹如何使用Selenium來爬取知網。

2. Selenium基本用法

  • 聲明瀏覽器對象

Selenium支持非常多的瀏覽器帅腌,如Chrome驰弄、Firefox、Edge等速客,我們只要首先下載好相應瀏覽器的webdriver到python主目錄中戚篙,或者加入環(huán)境變量即可。

不同瀏覽器的初始化:

from selenium import webdriver

browser = webdriver.Chrome()
browser = webdriver.Firefox()
browser = webdriver.Edge()
browser = webdriver.Safari()
  • 訪問頁面

我們可以用get()方法來請求一個網頁溺职,傳入參數鏈接URL

browser.get('https://www.bing.com')
  • 查找元素

find_element_by_id()
find_element_by_name()
find_element_by_class_name()
find_element_by_tag_name()
find_element_by_link_text()
find_element_by_partial_link_text()
find_element_by_xpath()
find_element_by_css_selector()

在element變成elements就是找所有滿足的條件岔擂,返回數組。
另外浪耘,我經常使用的查找元素方法為selenium中selenium.webdriver.common.byBy, 聯(lián)合隱士等待EC
用法如下:

# 單個元素
WebDriverWait( driver, 10 ).until( EC.presence_of_element_located((By.XPATH ,"") ) )
# 多個元素
WebDriverWait( driver, 10 ).until( EC.presence_of_all_elements_located( (By.CLASS_NAME  ,"fz14") ) )

# 元素類型有:
CLASS_NAME = 'class name'
CSS_SELECTOR = 'css selector'
ID = 'id'
LINK_TEXT = 'link text'
NAME = 'name'
PARTIAL_LINK_TEXT = 'partial link text'
TAG_NAME = 'tag name'
XPATH = 'xpath'
  • 常用方法

在找到相應元素位置后乱灵,我們常用的交互動作包括:點擊、輸入七冲、清楚阔蛉、獲取屬性、獲取文本

element = find_element_by_id(''id)

element.send_keys('Hello')  # 傳入Hello
element.clear()  # 清除輸入框
element.click()  # 點擊元素
element.text  # 獲取元素文本信息
element.get_attribute('href')  # 獲取元素屬性

還有大量的方法這里沒有提及癞埠,不過有了以上基本知識状原,我們就可以開始項目了!

二苗踪、知網爬蟲實戰(zhàn)

1. 知網頁面元素分析

知網首頁中颠区,我們僅需要先在輸入框中鍵入主題詞,然后點擊搜索圖標通铲,即可跳轉到結果頁面毕莱。


image.png

我們通過瀏覽器的檢查頁面,得到輸入框和搜索圖標的XPATH分別為:

input_xpath = '/html[1]/body[1]/div[1]/div[2]/div[1]/div[1]/input[1]'
button_xpath =  '/html[1]/body[1]/div[1]/div[2]/div[1]/div[1]/input[2]'

我們只需要在輸入框鍵入我們要搜索的主題颅夺,然后操作搜索按鈕即可轉到結果頁朋截。以搜索Python為例,結果頁如下所示吧黄,共找到15,925條部服,300頁。每頁中包含20個條目拗慨,每個條目包含題目廓八、作者奉芦、來源等信息。

image.png

通過對當前頁面分析剧蹂,發(fā)現(xiàn)每個條目對應的的xpath的規(guī)律声功。

/html[1]/body[1]/div[5]/div[2]/div[2]/div[2]/form[1]/div[1]/table[1]/tbody[1]/tr[1]/td[2]

即倒數第二個標簽數字代表本頁的第幾個條目,最后一個標簽 2 - 6 分別代表題目宠叼、作者先巴、來源、發(fā)表時間和數據庫冒冬。
我們在當前頁面無法或者文獻的摘要信息筹裕,下載鏈接等等,需要進一步點擊進入相關文獻條目。
進入詳情頁面后窄驹,我們根據class name:abstract-text 能夠很容易定位到摘要的文本朝卒,class name: btn-dlcaj 定位到下載鏈接,其他元素同理乐埠。

image.png

完成以上知網頁面的分析后抗斤,我們就可以根據需求開始寫代碼了!

2. 代碼示例

引用所需要的包

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 urllib.parse import urljoin

創(chuàng)建瀏覽器對象

這里我們創(chuàng)建一個Chrome瀏覽器的窗口丈咐,并設置相關參數:

#get直接返回瑞眼,不再等待界面加載完成
desired_capabilities = DesiredCapabilities.CHROME
desired_capabilities["pageLoadStrategy"] = "none"

# 設置谷歌驅動器的環(huán)境
options = webdriver.ChromeOptions()
# 設置chrome不加載圖片,提高速度
options.add_experimental_option("prefs", {"profile.managed_default_content_settings.images": 2})
# 設置不顯示窗口
#options.add_argument('--headless')
# 創(chuàng)建一個谷歌驅動器
driver = webdriver.Chrome(options=options)
# 設置搜索主題
theme = "Python"
# 設置所需篇數
papers_need = 100

打開頁面并搜索關鍵詞

# 打開頁面
driver.get("https://www.cnki.net")
# 傳入關鍵字
WebDriverWait( driver, 100 ).until( EC.presence_of_element_located( (By.XPATH ,'''//*[@id="txt_SearchText"]''') ) ).send_keys(theme)
# 點擊搜索
WebDriverWait( driver, 100 ).until( EC.presence_of_element_located( (By.XPATH ,"/html/body/div[1]/div[2]/div/div[1]/input[2]") ) ).click()
time.sleep(3)

# 點擊切換中文文獻
WebDriverWait( driver, 100 ).until( EC.presence_of_element_located( (By.XPATH ,"/html/body/div[5]/div[1]/div/div/div/a[1]") ) ).click()
time.sleep(1)
# 獲取總文獻數和頁數
res_unm = WebDriverWait( driver, 100 ).until( EC.presence_of_element_located( (By.XPATH ,"/html/body/div[5]/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} 頁棵逊。")

解析結果頁

# 賦值序號, 控制爬取的文章數量
count = 1
# 當伤疙,爬取數量小于需求時,循環(huán)網頁頁碼
while count <= papers_need:
    # 等待加載完全辆影,休眠3S
    time.sleep(3)

    title_list = WebDriverWait( driver, 10 ).until( EC.presence_of_all_elements_located( (By.CLASS_NAME  ,"fz14") ) )

    # 循環(huán)網頁一頁中的條目   
    for i in range(len(title_list)):
        try:
            term = count%20   # 本頁的第幾個條目
            title_xpath = f"/html[1]/body[1]/div[5]/div[2]/div[2]/div[2]/form[1]/div[1]/table[1]/tbody[1]/tr[{term}]/td[2]"
            author_xpath = f"/html[1]/body[1]/div[5]/div[2]/div[2]/div[2]/form[1]/div[1]/table[1]/tbody[1]/tr[{term}]/td[3]"
            source_xpath = f"/html[1]/body[1]/div[5]/div[2]/div[2]/div[2]/form[1]/div[1]/table[1]/tbody[1]/tr[{term}]/td[4]"
            date_xpath = f"/html[1]/body[1]/div[5]/div[2]/div[2]/div[2]/form[1]/div[1]/table[1]/tbody[1]/tr[{term}]/td[5]"
            database_xpath = f"/html[1]/body[1]/div[5]/div[2]/div[2]/div[2]/form[1]/div[1]/table[1]/tbody[1]/tr[{term}]/td[6]"
            title = WebDriverWait( driver, 10 ).until( EC.presence_of_element_located((By.XPATH ,title_xpath) ) ).text
            authors = WebDriverWait( driver, 10 ).until( EC.presence_of_element_located((By.XPATH ,author_xpath) ) ).text
            source = WebDriverWait( driver, 10 ).until( EC.presence_of_element_located((By.XPATH ,source_xpath) ) ).text
            date = WebDriverWait( driver, 10 ).until( EC.presence_of_element_located((By.XPATH ,date_xpath) ) ).text
            database = WebDriverWait( driver, 10 ).until( EC.presence_of_element_located((By.XPATH ,database_xpath) ) ).text
                        
            # 點擊條目
            title_list[i].click()
            # 獲取driver的句柄
            n = driver.window_handles 
            # driver切換至最新生產的頁面
            driver.switch_to_window(n[-1])  
            # 開始獲取頁面信息
            # title = WebDriverWait( driver, 10 ).until( EC.presence_of_element_located((By.XPATH ,"/html/body/div[2]/div[1]/div[3]/div/div/div[3]/div/h1") ) ).text
            # authors = WebDriverWait( driver, 10 ).until( EC.presence_of_element_located((By.XPATH ,"/html/body/div[2]/div[1]/div[3]/div/div/div[3]/div/h3[1]") ) ).text
            institute = WebDriverWait( driver, 10 ).until( EC.presence_of_element_located((By.XPATH ,"/html[1]/body[1]/div[2]/div[1]/div[3]/div[1]/div[1]/div[3]/div[1]/h3[2]") ) ).text
            abstract = WebDriverWait( driver, 10 ).until( EC.presence_of_element_located((By.CLASS_NAME  ,"abstract-text") ) ).text
            try:
                keywords = WebDriverWait( driver, 10 ).until( EC.presence_of_element_located((By.CLASS_NAME  ,"keywords") ) ).text[:-1]
            except:
                keywords = '無'
            url = driver.current_url
            # 獲取下載鏈接 
            # link = WebDriverWait( driver, 10 ).until( EC.presence_of_all_elements_located((By.CLASS_NAME  ,"btn-dlcaj") ) )[0].get_attribute('href')
            # link = urljoin(driver.current_url, link)

            # 寫入文件
            res = f"{count}\t{title}\t{authors}\t{institute}\t{date}\t{source}\t{database}\t{keywords}\t{abstract}\t{url}".replace("\n","")+"\n"
            print(res)
            with open('CNKI_res.tsv', 'a', encoding='gbk') as f:
                f.write(res)

        except:
            print(f" 第{count} 條爬取失敗\n")
            # 跳過本條徒像,接著下一個
            continue
        finally:
            # 如果有多個窗口,關閉第二個窗口蛙讥, 切換回主頁
            n2 = driver.window_handles
            if len(n2) > 1:
                driver.close()
                driver.switch_to_window(n2[0])
            # 計數,判斷需求是否足夠
            count += 1
            if count == papers_need:break

    
    # 切換到下一頁
    WebDriverWait( driver, 10 ).until( EC.presence_of_element_located( (By.XPATH ,"http://a[@id='PageNext']") ) ).click()

# 關閉瀏覽器
driver.close()

至此锯蛀,所有功能都已實現(xiàn),代碼中寫了詳細的注釋次慢。需要獲得完整版的可以直達我的Github下載旁涤,CNKI_Spider.pyhttps://github.com/byemaxx/BioTools)。

結果展示

結果是一個以制表符分隔的表格文件迫像,其中包含了論文的基本信息劈愚,包括:題目、作者闻妓、來源菌羽、摘要、鏈接

三纷闺、遇到的一些坑

1. 網頁加載太慢導致元素查找出錯

有時候我們并不需要網頁完全算凿,我們想要的信息已經加載出來,于是加上以下設置:

#get直接返回犁功,不再等待界面加載完成
desired_capabilities = DesiredCapabilities.CHROME
desired_capabilities["pageLoadStrategy"] = "none"

另一方面氓轰,在適當的地方加上 time.sleep(3) 延時幾秒,既可以等待頁面加載浸卦,也可以防止爬取太快被封IP署鸡。

2. 編碼不同導致的文件寫入失敗

在寫入文件時,由于存在不同的編碼限嫌,常常導致文件寫入失敗靴庆,在最開始我轉換編碼為 utf-8 ,后來發(fā)現(xiàn)這個編碼在excel里面居然是亂碼怒医,于是改為 gbk 編碼炉抒。

with open('CNKI_res.tsv', 'a', encoding='gbk') as f:
  f.write(res)

3. xpath 地址經常發(fā)生改變

由于知網中包含著不同類型的文獻,如期刊稚叹、碩博焰薄、會議、專利 等扒袖,xpath的位置不是一成不變的塞茅,雖然xpath唯一定位的特性便于我們找到相應的標簽。但偶爾class name 或許是更好的選擇季率。

四野瘦、后記

在數據分析中,往往花費時間的事數據的獲取數據清洗飒泻,怎樣從互聯(lián)網海量的數據中高效獲取我們想要的部分鞭光?無疑網絡爬蟲是最佳的選擇之一。學習好爬蟲的相關庫的運用泞遗,不論是從或聯(lián)網上獲取相關的數據衰猛,還是設置某些東東提交任務,甚至是寫一些搶票軟件都不是什么困難的事情刹孔。
另一方面啡省,爬蟲與網站的反爬系統(tǒng)一直都是攻防的雙方,Selenium 這類軟件能夠直接模擬瀏覽器操作髓霞,進而繞過一些反爬機制卦睹,但并不是不能夠被網站識別。在真實的情況中方库,我們往往需要結合多種手段爬取網頁结序。
在這場爬蟲和反爬蟲的軍備競賽中;在人與計算機的競賽中纵潦,我們只有不斷地學習新的東西徐鹤,才能在這場進化中不被淘汰垃环。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市返敬,隨后出現(xiàn)的幾起案子遂庄,更是在濱河造成了極大的恐慌,老刑警劉巖劲赠,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涛目,死亡現(xiàn)場離奇詭異,居然都是意外死亡凛澎,警方通過查閱死者的電腦和手機霹肝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來塑煎,“玉大人沫换,你說我怎么就攤上這事∽钐” “怎么了苗沧?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長炭晒。 經常有香客問我待逞,道長,這世上最難降的妖魔是什么网严? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任识樱,我火速辦了婚禮,結果婚禮上震束,老公的妹妹穿的比我還像新娘怜庸。我一直安慰自己,他們只是感情好垢村,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布割疾。 她就那樣靜靜地躺著,像睡著了一般嘉栓。 火紅的嫁衣襯著肌膚如雪宏榕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天侵佃,我揣著相機與錄音麻昼,去河邊找鬼。 笑死馋辈,一個胖子當著我的面吹牛抚芦,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤斗塘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡鹤盒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年蚕脏,在試婚紗的時候發(fā)現(xiàn)自己被綠了侦副。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡驼鞭,死狀恐怖秦驯,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情挣棕,我是刑警寧澤译隘,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站洛心,受9級特大地震影響固耘,放射性物質發(fā)生泄漏。R本人自食惡果不足惜词身,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一厅目、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧法严,春花似錦损敷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至溯街,卻和暖如春诱桂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背呈昔。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工访诱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人韩肝。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓触菜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親哀峻。 傳聞我的和親對象是個殘疾皇子涡相,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內容