Python爬蟲周記之案例篇——基金凈值Selenium動態爬蟲


在成功完成基金凈值爬蟲的爬蟲后,簡單了解爬蟲的一些原理以后,心中不免產生一點困惑——為什么我們不能直接通過Request獲取網頁的源代碼,而是通過查找相關的js文件來爬取數據呢?

 

有時候我們在用requests抓取頁面的時候,得到的結果可能和瀏覽器中看到的不一樣:瀏覽器中可以看到正常顯示的頁面數據,但是使用requests得到的結果並沒有。

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

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

 

那么,有沒有什么辦法可以直接獲取網頁的動態渲染數據呢?答案是有的。

我們還可以直接使用模擬瀏覽器運行的方式來實現動態網頁的抓取,這樣就可以做到在瀏覽器中看倒是什么樣,抓取的源碼就是什么樣,即實現——可見即可爬。

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

 

環境

tools

1、Chrome及其developer tools

2、python3.7

3、PyCharm

 

python3.7中使用的庫

1、Selenium

2、pandas

3、random

4、 time

5、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來抓取。

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM