在成功完成基金淨值爬蟲的爬蟲後,簡單瞭解爬蟲的一些原理之後,心中難免產生一點困惑——爲何咱們不能直接經過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
一、Chrome及其developer tools瀏覽器
二、python3.7服務器
三、PyCharmcookie
一、Selenium
二、pandas
三、random
四、 time
五、os
Mac OS 10.13.2
pip install selenium
首先,咱們先來了解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文件中,實現代碼以下:
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來抓取。
至此,基金淨值爬蟲的分析正式完結,撒花~