一规揪、前言
最近臨近畢業(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.by的By, 聯(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. 知網頁面元素分析
知網首頁中颠区,我們僅需要先在輸入框中鍵入主題詞,然后點擊搜索圖標通铲,即可跳轉到結果頁面毕莱。
我們通過瀏覽器的檢查頁面,得到輸入框和搜索圖標的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個條目拗慨,每個條目包含題目廓八、作者奉芦、來源等信息。
通過對當前頁面分析剧蹂,發(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 定位到下載鏈接,其他元素同理乐埠。
完成以上知網頁面的分析后抗斤,我們就可以根據需求開始寫代碼了!
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.py(https://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 這類軟件能夠直接模擬瀏覽器操作髓霞,進而繞過一些反爬機制卦睹,但并不是不能夠被網站識別。在真實的情況中方库,我們往往需要結合多種手段爬取網頁结序。
在這場爬蟲和反爬蟲的軍備競賽中;在人與計算機的競賽中纵潦,我們只有不斷地學習新的東西徐鹤,才能在這場進化中不被淘汰垃环。