Python爬蟲週記之案例篇——基金淨值Selenium動態爬蟲

在成功完成基金淨值爬蟲的爬蟲後,簡單瞭解爬蟲的一些原理之後,心中難免產生一點困惑——爲何咱們不能直接經過Request獲取網頁的源代碼,而是經過查找相關的js文件來爬取數據呢?css

 

有時候咱們在用requests抓取頁面的時候,獲得的結果可能和瀏覽器中看到的不同:瀏覽器中能夠看到正常顯示的頁面數據,可是使用requests獲得的結果並無。html

這是由於requests獲取的都是原始的HTML文檔,而瀏覽器中的頁面則是通過JavaScript處理數據後生成的結果,這些數據來源多種,多是經過Ajax加載的,多是包含在HTML文檔中的,也多是通過JavaScript和特定算法計算後生成的。而依照目前Web發展的趨勢,網頁的原始HTML文檔不會包含任何數據,都是經過Ajax等方法統一加載後再呈現出來,這樣在Web開發上能夠作到先後端分離,並且下降服務器直接渲染頁面帶來的。一般,咱們把這種網頁稱爲動態渲染頁面。python

以前的基金淨值數據爬蟲就是經過直接向服務器獲取數據接口,即找到包含數據的js文件,向服務器發送相關的請求,才獲取文件。web

 

那麼,有沒有什麼辦法能夠直接獲取網頁的動態渲染數據呢?答案是有的。算法

咱們還能夠直接使用模擬瀏覽器運行的方式來實現動態網頁的抓取,這樣就能夠作到在瀏覽器中看卻是什麼樣,抓取的源碼就是什麼樣,即實現——可見便可爬。後端

Python提供了許多模擬瀏覽器運行的庫,如:Selenium、Splash、PyV八、Ghost等。本文將繼續以基金淨值爬蟲爲例,Selenium對其進行動態頁面爬蟲。api

 

環境

tools

一、Chrome及其developer tools瀏覽器

二、python3.7服務器

三、PyCharmcookie

 

python3.7中使用的庫

一、Selenium

二、pandas

三、random

四、 time

五、os

 

系統

Mac OS 10.13.2

 

Selenium基本功能及使用

 

 準備工做 

 

  • Chrome瀏覽器
  • Selenium庫
    • 可直接經過pip安裝,執行以下命令便可:
    • pip install selenium
  • ChromDriver配置
    • Selenium庫是一個自動化測試工具,須要瀏覽器來配合使用,咱們主要介紹Chrome瀏覽器及ChromeDriver驅動的配置,只有安裝了ChromeDriver並配置好對應環境,才能驅動Chrome瀏覽器完成相應的操做。Windows和Mac下的安裝配置方法略有不一樣,具體可經過網上查閱資料得知,在此暫時不作贅述。

 

 

 基本使用 

 

首先,咱們先來了解Selenium的一些功能,以及它能作些什麼:

Selenium是一個自動化測試工具,利用它能夠驅動遊覽器執行特定的動做,如點擊、下拉等操做,同時還能夠獲取瀏覽器當前呈現的頁面的源代碼,作到可見便可爬。對於一些動態渲染的頁面來講,此種抓取方式很是有效。它的基本功能實現也十分的方便,下面咱們來看一些簡單的代碼:

 

 1 from selenium import webdriver  2 from selenium.webdriver.common.by import By  3 from selenium.webdriver.common.keys import Keys  4 from selenium.webdriver.support import expected_conditions as EC  5 from selenium.webdriver.support.wait import WebDriverWait  6 
 7 
 8 browser = webdriver.Chrome()  # 聲明瀏覽器對象
 9 try: 10     browser.get('https://www.baidu.com')  # 傳入連接URL請求網頁
11     query = browser.find_element_by_id('kw')   # 查找節點
12     query.send_keys('Python')  # 輸入文字
13     query.send_keys(Keys.ENTER)  # 回車跳轉頁面
14     wait = WebDriverWait(browser, 10)  # 設置最長加載等待時間
15     print(browser.current_url)  # 獲取當前URL
16     print(browser.get_cookies())  # 獲取當前Cookies
17     print(browser.page_source)  # 獲取源代碼
18 finally: 19     browser.close()  # 關閉瀏覽器

 

運行代碼後,會自動彈出一個Chrome瀏覽器。瀏覽器會跳轉到百度,而後在搜索框中輸入Python→回車→跳轉到搜索結果頁,在獲取結果後關閉瀏覽器。這至關於模擬了一個咱們上百度搜索Python的全套動做,有木有以爲很神奇!!

在過程當中,當網頁結果加載出來後,控制檯會分別輸出當前的URL、當前的Cookies和網頁源代碼:

 

能夠看到,咱們獲得的內容都是瀏覽器中真實的內容,由此能夠看出,用Selenium來驅動瀏覽器加載網頁能夠直接拿到JavaScript渲染的結果。接下來,咱們也將主要利用Selenium來進行基金淨值的爬取~

 

注:Selenium更多詳細用法和功能能夠經過官網查閱(https://selenium-python.readthedocs.io/api.html

 

基金淨值數據爬蟲

經過以前的爬蟲,咱們會發現數據接口的分析相對來講較爲繁瑣,須要分析相關的參數,而若是直接用Selenium來模擬瀏覽器的話,能夠再也不關注這些接口參數,只要能直接在瀏覽器頁面裏看到的內容,均可以爬取,下面咱們就來試一試該如何完成咱們的目標——基金淨值數據爬蟲。

 頁面分析 

本次爬蟲的目標是單個基金的淨值數據,抓取的URL爲:http://fundf10.eastmoney.com/jjjz_519961.html(以單個基金519961爲例),URL的構造規律很明顯,當咱們在瀏覽器中輸入訪問連接後,呈現的就是最新的基金淨值數據的第一頁結果:

在數據的下方,有一個分頁導航,其中既包括前五頁的鏈接,也包括最後一頁和下一頁的鏈接,同時還有一個輸入任意頁碼跳轉的連接:

若是咱們想獲取第二頁及之後的數據,則須要跳轉到對應頁數。所以,若是咱們須要獲取全部的歷史淨值數據,只須要將全部頁碼遍歷便可,能夠直接在頁面跳轉文本框中輸入要跳轉的頁碼,而後點擊「肯定」按鈕便可跳轉到頁碼對應的頁面。

此處不直接點擊「下一頁」的緣由是:一旦爬蟲過程當中出現異常退出,好比到50頁退出了,此時點擊「下一頁」時,沒法快速切換到對應的後續頁面。此外,在爬蟲過程當中也須要記錄當前的爬蟲進度,可以及時作異常檢測,檢測問題是出在第幾頁。整個過程相對較爲複雜,用直接跳轉的方式來爬取網頁較爲合理。

當咱們成功加載出某一頁的淨值數據時,利用Selenium便可獲取頁面源代碼,定位到特定的節點後進行操做便可獲取目標的HTML內容,再對其進行相應的解析便可獲取咱們的目標數據。下面,咱們用代碼來實現整個抓取過程。

 

 獲取基金淨值列表 

首先,須要構造目標URL,這裏的URL構成規律十分明顯,爲http://fundf10.eastmoney.com/jjjz_基金代碼.html,咱們能夠經過規律來構造本身想要爬取的基金對象。這裏,咱們將以基金519961爲例進行抓取。

 1 browser = webdriver.Chrome()  2 wait = WebDriverWait(browser, 10)  3 fundcode='519961'
 4 
 5 def index_page(page):  6     '''
 7  抓取基金索引頁  8  :param page: 頁碼  9  :param fundcode: 基金代碼 10     '''
11     print('正在爬取基金%s第%d頁' % (fundcode, page)) 12     try: 13         url = 'http://fundf10.eastmoney.com/jjjz_%s.html' % fundcode 14  browser.get(url) 15         if page>1: 16             input_page = wait.until( 17                 EC.presence_of_element_located((By.CSS_SELECTOR, '#pagebar input.pnum'))) 18             submit = wait.until( 19                 EC.element_to_be_clickable((By.CSS_SELECTOR, '#pagebar input.pgo'))) 20  input_page.clear() 21  input_page.send_keys(str(page)) 22  submit.click() 23  wait.until( 24         EC.text_to_be_present_in_element((By.CSS_SELECTOR, '#pagebar label.cur'), 25  str(page))) 26         wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '#jztable'))) 27  get_jjjz() 28     except TimeoutException: 29         index_page(page)

這裏,首相構造一個WebDriver對象,即聲明瀏覽器對象,使用的瀏覽器爲Chrome,而後指定一個基金代碼(519961),接着定義了index_page()方法,用於抓取基金淨值數據列表。

在該方法裏,咱們首先訪問了搜索基金的連接,而後判斷了當前的頁碼,若是大於1,就進行跳頁操做,不然等頁面加載完成。

在等待加載時,咱們使用了WebDriverWait對象,它能夠指定等待條件,同時制定一個最長等待時間,這裏指定爲最長10秒。若是在這個時間內匹配了等待條件,也就是說頁面元素成功加載出來,就當即返回相應結果並繼續向下執行,不然到了最大等待時間尚未加載出來時,就直接拋出超市異常。

好比,咱們最終須要等待歷史淨值信息加載出來就指定presence_of_element_located這個條件,而後傳入了CSS選擇器的對應條件#jztable,而這個選擇器對應的頁面內容就是每一頁基金淨值數據的信息快,能夠到網頁裏面查看一下:

注:這裏講一個小技巧,若是同窗們對CSS選擇器的語法不是很瞭解的話,能夠直接在選定的節點點擊右鍵→拷貝→拷貝選擇器,能夠直接獲取對應的選擇器:

關於CSS選擇器的語法能夠參考CSS選擇器參考手冊(http://www.w3school.com.cn/cssref/css_selectors.asp)。

 

當加載成功後,機會秩序後續的get_jjjz()方法,提取歷史淨值信息。

關於翻頁操做,這裏首先獲取頁碼輸入框,賦值爲input_page,而後獲取「肯定」按鈕,賦值爲submit:

首先,咱們狀況輸入框(不管輸入框是否有頁碼數據),此時調用clear()方法便可。隨後,調用send_keys()方法將頁碼填充到輸入框中,而後點擊「肯定」按鈕便可,聽起來彷佛和咱們常規操做的方法同樣。

那麼,怎樣知道有沒有跳轉到對應的頁碼呢?咱們能夠注意到,跳轉到當前頁的時候,頁碼都會高亮顯示:

咱們只須要判斷當前高亮的頁碼數是當前的頁碼數便可,左移這裏使用了另外一個等待條件text_to_be_present_in_element,它會等待指定的文本出如今某一個節點裏時即返回成功,這裏咱們將高亮的頁碼對應的CSS選擇器和當前要跳轉到 頁碼經過參數傳遞給這個等待條件,這樣它就會檢測當前高亮的頁碼節點是否是咱們傳過來的頁碼數,若是是,就證實頁面成功跳轉到了這一頁,頁面跳轉成功。

這樣,剛從實現的index_page()方法就能夠傳入對應的頁碼,待加載出對應頁碼的商品列表後,再去調用get_jjjz()方法進行頁面解析。

 

 解析歷史淨值數據列表 

接下來,咱們就能夠實現get_jjjz()方法來解析歷史淨值數據列表了。這裏,咱們經過查找全部歷史淨值數據節點來獲取對應的HTML內容

並進行對應解析,實現以下:

 1 def get_jjjz():  2     '''
 3  提取基金淨值數據  4     '''
 5     lsjz = pd.DataFrame()  6     html_list = browser.find_elements_by_css_selector('#jztable tbody tr')  7     for html in html_list:  8         data = html.text.split(' ')  9         datas = { 10             '淨值日期': data[0], 11             '單位淨值': data[1], 12             '累計淨值': data[2], 13             '日增加率': data[3], 14             '申購狀態': data[4], 15             '贖回狀態': data[5], 16  } 17         lsjz = lsjz.append(datas, ignore_index=True) 18     save_to_csv(lsjz)

首先,調用find_elements_by_css_selector來獲取全部存儲歷史淨值數據的節點,此時使用的CSS選擇器是#jztable tbody tr,它會匹配全部基金淨值節點,輸出的是一個封裝爲list的HTML。利用for循環對list進行遍歷,用text方法提取每一個html裏面的文本內容,得到的輸出是用空格隔開的字符串數據,爲了方便後續處理,咱們能夠用split方法將數據切割,以一個新的list形式存儲,再將其轉化爲dict形式。

最後,爲了方便處理,咱們將遍歷的數據存儲爲一個DataFrame再用save_to_csv()方法進行存儲爲csv文件。

 

 保存爲本地csv文件 

接下來,咱們將獲取的基金歷史淨值數據保存爲本地的csv文件中,實現代碼以下:

 1 def save_to_csv(lsjz):  2     '''
 3  保存爲csv文件  4  : param result: 歷史淨值  5     '''
 6     file_path = 'lsjz_%s.csv' % fundcode  7     try:  8         if not os.path.isfile(file_path):  # 判斷當前目錄下是否已存在該csv文件,若不存在,則直接存儲
 9             lsjz.to_csv(file_path, index=False) 10         else:  # 若已存在,則追加存儲,並設置header參數爲False,防止列名重複存儲
11             lsjz.to_csv(file_path, mode='a', index=False, header=False) 12         print('存儲成功') 13     except Exception as e: 14         print('存儲失敗')

此處,result變量就是get_jjjz()方法裏傳來的歷史淨值數據。

 

 遍歷每一頁 

咱們以前定義的get_index()方法須要接受參數page,page表明頁碼。這裏,因爲不一樣基金的數據頁數並不相同,而爲了遍歷全部頁咱們須要獲取最大頁數,固然,咱們也能夠用一些巧辦法來解決這個問題,頁碼遍歷代碼以下:

 1 def main():  2     '''
 3  遍歷每一頁  4     '''
 5     flag = True  6     page = 1
 7     while flag:  8         try:  9  index_page(page) 10             time.sleep(random.randint(1, 5)) 11             page += 1
12         except: 13             flag = False 14             print('彷佛是最後一頁了呢')

其實現方法結合了try...except和while方法,逐個遍歷下一頁內容,當頁碼超過,即不存在時,index_page()的運行就會出現報錯,此時能夠將flag變爲False,則下一次while循環不會繼續,這樣,咱們即可遍歷全部的頁碼了。

 

由此,咱們的基金淨值數據爬蟲已經基本完成,最後直接調用main()方法便可運行。

 

 

總結

在本文中,咱們用Selenium演示了基金淨值頁面的抓取,有興趣的同窗能夠嘗試利用其它的條件來爬取基金數據,如設置數據的起始和結束日期:

利用日期來爬取內容能夠方便往後的數據更新,此外,若是以爲瀏覽器的彈出較爲惱人,能夠嘗試Chrome Headless模式或者利用PhantomJS來抓取。

至此,基金淨值爬蟲的分析正式完結,撒花~

相關文章
相關標籤/搜索