原文地址:https://blogof33.com/post/12/
前言
前一篇文章 爬蟲實戰(zhàn)(一):爬取微博用戶信息 中爬取的是 https://weibo.cn 這個網(wǎng)頁,但是由于該網(wǎng)頁缺少維護(hù),微博官方可能加了一些限制,導(dǎo)致稍微頻繁一點(diǎn)的訪問都會報 403 錯誤,加上每次手動獲取 cookies 也比較麻煩,不友好,所以針對這些情況,我使用了一種新的抓取方式,也是一種更為高級的爬蟲手段。
我之前在文章里面提到“ 爬取微博主頁 https://weibo.com/ 或者 https://m.weibo.cn/ 較為困難 ”,為什么會這么說呢?因為這兩種頁面較新,所以采用的技術(shù)比較新穎,反爬措施做得要好一些。特別是它們采用了滾動式頁面,每次向下滾動到底后會加載出新的內(nèi)容,這種動態(tài)加載模式使得傳統(tǒng)的改變網(wǎng)頁地址中的頁碼獲得相應(yīng)內(nèi)容的方法失效了,含有用戶信息內(nèi)容的源碼需要抓包獲取,或者直接操作瀏覽器獲取。后者一般都是Selenium+PhantomJS來實現(xiàn)。
由于 Phantom.js 的維護(hù)者 Slobodin 在Google論壇上發(fā)帖表示,鑒于Chrome 59推出了無頭瀏覽特性,他認(rèn)為“Chrome比PhantomJS更快,更穩(wěn)定”,沒有理由再繼續(xù)維護(hù)Phantom.js(開發(fā)者很有自知之明:P,不過 Phantom.js 確實是一個很好用的東西),所以本文采用 Selenium+Chrome/Firefox 無頭瀏覽器的方式進(jìn)行模擬登錄和抓取用戶動態(tài)信息的操作。
Selenium
Selenium 是一個瀏覽器自動化測試框架,起初是為了自動化測試開發(fā)的,在爬蟲流行起來以后,也成為了一種爬蟲的工具。它的功能簡單來說就是可以控制瀏覽器,用代碼模擬人對瀏覽器的操作,實現(xiàn)自動化。
安裝
和大多數(shù) python 包一樣,selenium 可以使用 pip 進(jìn)行安裝:
# python 2
pip install selenium
# python 3
pip3 install selenium
因為 selenium 是對瀏覽器進(jìn)行控制,所以首先要裝對應(yīng)的驅(qū)動(driver),Selenium 針對幾個主流的瀏覽器都有相應(yīng)的官方 driver。讀者可以根據(jù)自己的情況下載并安裝。比如筆者是使用的 Linux 系統(tǒng)上的 Chrome 瀏覽器最新版本,那么便下載相應(yīng)版本的 driver ,下載完成以后,執(zhí)行命令:
#/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/')
運(yùn)行這段代碼,會自動打開瀏覽器訪問百度。
如果程序執(zhí)行錯誤,瀏覽器沒有打開,那么可能是沒有裝 Chrome 瀏覽器或者 Chrome 驅(qū)動沒有配置在環(huán)境變量里或者驅(qū)動和瀏覽器版本不匹配。
模擬登錄
登錄微博需要使用驗證碼,自動識別驗證碼這一塊我研究了一下,使用圖像識別,也不難,但是因為我們可以將cookies 持久化保存下來,使用手動輸入驗證碼并不麻煩,所以自動識別驗證碼可以暫時先放一放,后面慢慢來研究。
使用 selenium 控制瀏覽器,通過對頁面的元素進(jìn)行定位來模擬人的操作,API 詳細(xì)介紹請見 參考文檔 。模擬登錄代碼如下:
def get():
conf, engine = Connect('conf.yaml') # 獲取配置文件的內(nèi)容
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的狀態(tài)is_displayed為False,即不可見
driver.get("http://www.weibo.com/login.php")
time.sleep(5)
#自動點(diǎn)擊并輸入用戶名
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)
#自動點(diǎn)擊并輸入登錄的密碼
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對象
代碼注釋應(yīng)該寫得比較清楚,其中有一個細(xì)節(jié)就是我們需要將獲取的 cookies 序列化。什么是序列化?
我們把變量從內(nèi)存中變成可存儲或傳輸?shù)倪^程稱之為序列化,即把數(shù)據(jù)寫入臨時或持久性存儲區(qū),而把變量內(nèi)容從序列化的對象重新讀到內(nèi)存里稱之為反序列化。
意思是在這里將 cookies 以二進(jìn)制形式保存下來,這樣可以方便后續(xù)爬蟲使用。
使用 selenium 爬取用戶信息
爬取用戶信息的大致思路和上一篇文章 爬蟲實戰(zhàn)(一):爬取微博用戶信息 差不多 ,但仍然有以下區(qū)別:
- 爬取 https://m.weibo.cn/ 而不是 https://weibo.cn/
- 使用 seenium 代替 requests 獲取源碼
- 使用 selenium 加載滾動頁面直到所有動態(tài)信息加載完成
- 先使用正常的Chrome調(diào)試,調(diào)試完成以后再改成無頭瀏覽器
首先我們來看微博 html5 移動端的頁面長什么樣:

為什么選這個網(wǎng)址而不是PC端的頁面呢?因為PC端的頁面每向下滑動三次需要跳頁,操作要繁瑣一些,而且 selenium 容易因為失去焦點(diǎn)導(dǎo)致跳轉(zhuǎn)失敗,我也沒找到很好的解決方法,而 html5 移動端的頁面多次滑動到底便可以獲得所有動態(tài)信息,不需要跳頁,所以要簡單很多。
再來看看使用 selenium 如何操作瀏覽器滑動到底,下面是相關(guān)的處理函數(shù),這個函數(shù)將 web 頁面滑動多次直到無法再滑動(即滑動到底了)并使用正則表達(dá)式提取出動態(tài)和動態(tài)發(fā)布時間:
#獲取用戶所有動態(tài)信息和動態(tài)發(fā)布時間并返回
def execute_times(driver):
dynamic = []
T = []
d = re.compile(r'og"><div class="weibo-text">(.*?)<', re.S) # 匹配動態(tài)
t = re.compile(r'<span class="time">(.*?)<', re.S) # 匹配動態(tài)發(fā)布時間
#返回滾動高度
last_height = driver.execute_script("return document.body.scrollHeight")
while True:
# 滑動一次
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
# 等待加載
time.sleep(random.random())
# 計算新的滾動高度并與上一個滾動高度進(jìn)行比較
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 #返回用戶所有動態(tài)信息和動態(tài)發(fā)布時間列表
得到用戶所有動態(tài)信息和動態(tài)發(fā)布時間列表以后,其他處理和前一篇文章類似,在此不再累述,詳情請見源碼 weibo_spider.py。
因為每次運(yùn)行程序都需要彈出瀏覽器窗口,而且速度較慢,所以可以將瀏覽器設(shè)置成無頭模式:
#Chrome
opt = webdriver.ChromeOptions() # 創(chuàng)建chrome參數(shù)對象
opt.set_headless() # 把chrome設(shè)置成無頭模式,不論windows還是linux都可以,自動適配對應(yīng)參數(shù)
driver = webdriver.Chrome(options=opt)#不制定options選項則是普通有頭瀏覽器
#Firefox
opt = webdriver.FirefoxOptions()
opt.set_headless()
driver = webdriver.Firefox(options=opt)
至此模擬登錄并爬取信息方法介紹完畢。
源碼地址:https://github.com/starFalll/Spider# 爬蟲實戰(zhàn)(二):Selenium 模擬登錄并爬取信息