爬蟲實戰(二):Selenium 模擬登入並爬取資訊

ACool發表於2018-07-15

前敘

系列文章:

爬蟲實戰(一):爬取微博使用者資訊

爬蟲實戰(二):Selenium 模擬登入並爬取資訊

爬蟲實戰(三):微博使用者資訊分析

該系列文章介紹了什麼?

1.爬蟲分析和處理方法

2.Python中的資料庫操作方法

3.Selenium瀏覽器自動化以及無頭瀏覽器使用方法

4.對資料進行詞雲分析的方法

5.對資料進行視覺化的方法

6.LDA隱含狄利克雷分佈模型的建模和使用方法

前言

前一篇文章 爬蟲實戰(一):爬取微博使用者資訊 中爬取的是 weibo.cn 這個網頁,但是由於該網頁缺少維護,微博官方可能加了一些限制,導致稍微頻繁一點的訪問都會報 403 錯誤,加上每次手動獲取 cookies 也比較麻煩,不友好,所以針對這些情況,我使用了一種新的抓取方式,也是一種更為高階的爬蟲手段。

我之前在文章裡面提到“ 爬取微博主頁 weibo.com/ 或者 m.weibo.cn/ 較為困難 ”,為什麼會這麼說呢?因為這兩種頁面較新,所以採用的技術比較新穎,反爬措施做得要好一些。特別是它們採用了滾動式頁面,每次向下滾動到底後會載入出新的內容,這種動態載入模式使得傳統的改變網頁地址中的頁碼獲得相應內容的方法失效了,含有使用者資訊內容的原始碼需要抓包獲取,或者直接操作瀏覽器獲取。後者一般都是Selenium+PhantomJS來實現。

由於 Phantom.js 的維護者 Slobodin 在Google論壇上發帖表示,鑑於Chrome 59推出了無頭瀏覽特性,他認為“Chrome比PhantomJS更快,更穩定”,沒有理由再繼續維護Phantom.js(開發者很有自知之明:P,不過 Phantom.js 確實是一個很好用的東西),所以本文采用 Selenium+Chrome/Firefox 無頭瀏覽器的方式進行模擬登入和抓取使用者動態資訊的操作。

Selenium

Selenium 是一個瀏覽器自動化測試框架,起初是為了自動化測試開發的,在爬蟲流行起來以後,也成為了一種爬蟲的工具。它的功能簡單來說就是可以控制瀏覽器,用程式碼模擬人對瀏覽器的操作,實現自動化。

安裝

和大多數 python 包一樣,selenium 可以使用 pip 進行安裝:

# python 2
pip install selenium

# python 3
pip3 install selenium
複製程式碼

因為 selenium 是對瀏覽器進行控制,所以首先要裝對應的驅動(driver),Selenium 針對幾個主流的瀏覽器都有相應的官方 driver。讀者可以根據自己的情況下載並安裝。比如筆者是使用的 Linux 系統上的 Chrome 瀏覽器最新版本,那麼便下載相應版本的 driver ,下載完成以後,執行命令:

#/usr/bin 或者 /usr/local/bin
sudo cp 下載的driver位置 /usr/bin
sudo chmod +x /usr/bin/chromedriver
複製程式碼

安裝完成以後測試一下是否成功。

測試

首先來測試一下是否安裝成功:

from selenium import webdriver
 
browser = webdriver.Chrome()
browser.get('http://www.baidu.com/')
複製程式碼

執行這段程式碼,會自動開啟瀏覽器訪問百度。

如果程式執行錯誤,瀏覽器沒有開啟,那麼可能是沒有裝 Chrome 瀏覽器或者 Chrome 驅動沒有配置在環境變數裡或者驅動和瀏覽器版本不匹配。

模擬登入

登入微博需要使用驗證碼,自動識別驗證碼這一塊我研究了一下,使用影象識別,也不難,但是因為我們可以將cookies 持久化儲存下來,使用手動輸入驗證碼並不麻煩,所以自動識別驗證碼可以暫時先放一放,後面慢慢來研究。

使用 selenium 控制瀏覽器,通過對頁面的元素進行定位來模擬人的操作,API 詳細介紹請見 參考文件 。模擬登入程式碼如下:

def get():
    conf, engine = Connect('conf.yaml')  # 獲取配置檔案的內容
    loginname = conf.get('loginname')
    password = conf.get('password')

    loginname = list(loginname.values())
    password = list(password.values())
    with open('cookies.pkl', 'wb') as f:
        for i in range(len(password)):  # 將每個賬號的cookies儲存下來.
            try:
                driver = webdriver.Chrome()
                driver.set_window_size(1124, 850)  # 防止得到的WebElement的狀態is_displayed為False,即不可見
                driver.get("http://www.weibo.com/login.php")
                time.sleep(5)
                #自動點選並輸入使用者名稱
                driver.find_element_by_xpath('//*[@id="loginname"]').clear()
                driver.find_element_by_xpath('//*[@id="loginname"]').send_keys(loginname[i])
                driver.find_element_by_xpath('//*[@id="pl_login_form"]/div/div[3]/div[2]/div/input').clear()

                time.sleep(2)
                #自動點選並輸入登入的密碼
                driver.find_element_by_xpath('//*[@id="pl_login_form"]/div/div[3]/div[2]/div/input').send_keys(
                    password[i])
                driver.find_element_by_xpath('//*[@id="pl_login_form"]/div/div[3]/div[6]/a').click()
				
                #輸入驗證碼
                driver.find_element_by_xpath('//*[@id="pl_login_form"]/div/div[3]/div[3]/div/input').send_keys(
                    input("輸入驗證碼: "))

                time.sleep(1)
                driver.find_element_by_xpath('//*[@id="pl_login_form"]/div/div[3]/div[6]/a').click()
            except Exception as e:
                print("驗證碼輸入錯誤,請重新輸入!")
                driver.find_element_by_xpath('//*[@id="pl_login_form"]/div/div[3]/div[3]/div/input').send_keys(
                    input("輸入驗證碼: "))
                time.sleep(1)
                driver.find_element_by_xpath('//*[@id="pl_login_form"]/div/div[3]/div[6]/a').click()
            cookies = driver.get_cookies()
            pickle.dump(cookies, f)#序列化cookies物件
複製程式碼

程式碼註釋應該寫得比較清楚,其中有一個細節就是我們需要將獲取的 cookies 序列化。什麼是序列化?

我們把變數從記憶體中變成可儲存或傳輸的過程稱之為序列化,即把資料寫入臨時或永續性儲存區,而把變數內容從序列化的物件重新讀到記憶體裡稱之為反序列化。

意思是在這裡將 cookies 以二進位制形式儲存下來,這樣可以方便後續爬蟲使用。

使用 selenium 爬取使用者資訊

爬取使用者資訊的大致思路和上一篇文章 爬蟲實戰(一):爬取微博使用者資訊 差不多 ,但仍然有以下區別:

  • 爬取 https://m.weibo.cn/ 而不是 https://weibo.cn/
  • 使用 seenium 代替 requests 獲取原始碼
  • 使用 selenium 載入滾動頁面直到所有動態資訊載入完成
  • 先使用正常的Chrome除錯,除錯完成以後再改成無頭瀏覽器

首先我們來看微博 html5 移動端的頁面長什麼樣:

weibo3-1.png

為什麼選這個網址而不是PC端的頁面呢?因為PC端的頁面每向下滑動三次需要跳頁,操作要繁瑣一些,而且 selenium 容易因為失去焦點導致跳轉失敗,我也沒找到很好的解決方法,而 html5 移動端的頁面多次滑動到底便可以獲得所有動態資訊,不需要跳頁,所以要簡單很多。

再來看看使用 selenium 如何操作瀏覽器滑動到底,下面是相關的處理函式,這個函式將 web 頁面滑動多次直到無法再滑動(即滑動到底了)並使用正規表示式提取出動態和動態釋出時間:

#獲取使用者所有動態資訊和動態釋出時間並返回
def execute_times(driver):
    dynamic = []
    T = []
    d = re.compile(r'og"><div class="weibo-text">(.*?)<', re.S)  # 匹配動態
    t = re.compile(r'<span class="time">(.*?)<', re.S)  # 匹配動態釋出時間
	
	#返回滾動高度
    last_height = driver.execute_script("return document.body.scrollHeight")

    while True:
        # 滑動一次
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

        # 等待載入
        time.sleep(random.random())

        # 計算新的滾動高度並與上一個滾動高度進行比較
        new_height = driver.execute_script("return document.body.scrollHeight")
        if new_height == last_height:
            break
        last_height = new_height

    html = driver.page_source

    dynamic += re.findall(d, html)
    T += re.findall(t, html)
    return dynamic, T #返回使用者所有動態資訊和動態釋出時間列表
複製程式碼

得到使用者所有動態資訊和動態釋出時間列表以後,其他處理和前一篇文章類似,在此不再累述,詳情請見原始碼 weibo_spider.py

因為每次執行程式都需要彈出瀏覽器視窗,而且速度較慢,所以可以將瀏覽器設定成無頭模式:

#Chrome
opt = webdriver.ChromeOptions()  # 建立chrome引數物件
opt.set_headless()  # 把chrome設定成無頭模式,不論windows還是linux都可以,自動適配對應引數
driver = webdriver.Chrome(options=opt)#不制定options選項則是普通有頭瀏覽器

#Firefox
opt = webdriver.FirefoxOptions()  
opt.set_headless() 
driver = webdriver.Firefox(options=opt)
複製程式碼

至此模擬登入並爬取資訊方法介紹完畢。

原始碼地址:https://github.com/starFalll/Spider

相關文章