我的第一個豆瓣短評爬蟲
豆瓣上有著大量的影視劇的評論,所以說,要是想要實現對廣大人民群眾的觀點的分析,對一部片子的理解,綜合來看大家的評論是很有必要的。而短評作為短小精幹的快速評論入口,是值得一談的。
所以先要實現對其的資料的爬取。
目前來看,基本內容是可以爬取的。最大的問題在於速度。後續考慮準備運用多執行緒的方式處理下。以及可以嘗試其他提速的方法。
下面是這個程式的構思編寫過程。
構思準備
爬取的思路,及反省與思考
盲目狀態
最初,並不知道豆瓣對於未登陸使用者的限制,盲目的爬取,看著評論檔案,發現行數太少,也就是說評論內容太少,感覺不對勁。
我利用了即時列印寫入內容的方式,發現,到了第十頁左右的時候,出現無法獲得頁面內評論內容,思考了下後,試著將頁面原始碼打印出來,發現到了後面,就出現提示許可權不足
登入
之前看過內容,明白這時候應該藉助cookie
的方式了。
但是又要處理驗證碼。而且,似乎初次登陸的時候並不需要驗證碼。為了方便,下面直接使用了存在驗證碼的方式。
由於開始不瞭解,不知道應該提交哪些資訊,多方查詢後,終於明白,就是在登入頁面登陸後,開啟瀏覽器的開發者工具裡,檢視裡面的網路,注意關注裡面的方法一列中的post
所在行那項。在登陸點選後,隨著頁面的跳轉,會出現一個post
頁面,點選後檢視其引數,若是火狐的話有個專門的引數視窗,其中就有要提交的引數了。包括使用者資訊,還有登入跳轉頁面(redir)等等。
在最初,我直接將https://accounts.douban.com/login
redir
還可以,要是用opener.open()
再登入'https://movie.douban.com/subject/26934346/comments?start=' + str(start) + '&limit=20&sort=new_score&status=P'
這裡構造的頁面是登不上的。這裡我也是測試了好久才發現的問題。具體原因我不清楚。可能是有哪些知識我是遺漏了的。
後來覺察到這一點後,我嘗試使用現在的資訊登入,如下。
main_url = 'https://accounts.douban.com/login?source=movie'
formdata = {
"form_email":"你的郵箱",
"form_password":"你的密碼",
"source":"movie",
"redir":"https://movie.douban.com/subject/26934346/",
"login":"登入"
}
user_agent = r'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36'
headers = {'User-Agnet': user_agent, 'Connection': 'keep-alive'}
誒,竟然登上去了!
關於驗證碼的糾結思考
由於豆瓣的驗證碼是登一次變一次的。所以在發生上面的問題時,在沒有找到正確的處理辦法之時,我懷疑是因為驗證碼,在我獲取上一次驗證碼圖片,並將驗證資訊輸入到formdata
這個提交資訊的字典中後,再次使用request.Request(url=main_url, data=logingpostdata, headers=headers)
與opener.open(req_ligin).read().decode('utf-8')
的時候,會不會驗證碼發生了變化?而我提交的是剛才的驗證碼?
又開始查資料,後來終於明白,這裡提交的資訊中指定了驗證碼的圖片的captchaID
,這樣使得提交資訊時候,返回來的驗證碼圖片也就是這個,圖片id是唯一的,當你自己修改了提交內容,豆瓣也會使用你提供的這個id來獲取伺服器裡的驗證碼圖片,所以保證了圖片的一致。(這是我的理解,覺得有問題,或者更精確的理解的,歡迎留言)
使用庫的考慮
BeautifulSoup, re
準備嘗試下beautiful soup
這個庫,對於拆解html頁面很便利。但是在實踐中,還是可能會用到正則表示式re
模組。可見,正則表示式還是很重要的。掌握基本可以查表使用時必須的。
在程式碼中可以看出,我對於該庫的使用還是有些粗,不巧妙,還有待加強。
不過,今天嘗試了下,用CSS選擇器還是很方便的。select()
方法,很方便,可以參考從瀏覽器開發者工具裡選擇元素對應的CSS選擇器,很直接。
urllib.request,http.cookiejar
借鑑之前使用urllib.request
的經驗,嘗試直接使用urllib.request.Request(url, headers=headers)
與urllib.request.urlopen(request, data=None, timeout=3)
發現,豆瓣的短評,最一開始還好,但是在爬取將近十多頁的短評時,會報出Forbidden
的異常,查詢後得知,應該是豆瓣對於遊客使用者的限制,需要登入才可以。參考網上一些其他教程,可以使用設定cookie
的方法來處理。
使用了cookiejar.CookieJar()
宣告物件,來儲存cookie
到了變數中。利用的request.HTTPCookieProcessor()
來建立cookie處理器。利用request.build_opener()
構造了一個 opener
,後面利用opener.open()
來開啟直接網頁或者處理請求。
socket
類似上一個爬蟲裡的設定,這裡直接使用了全域性的超時設定。
import socket
timeout = 3
socket.setdefaulttimeout(timeout)
限制三秒,超出就丟擲socket.timeout
異常。捕獲後重新連線。
# 超時重連
state = False
while not state:
try:
html = opener.open(url).read().decode('utf-8')
state = True
except socket.timeout:
state = False
time
為了防止爬取過快,設定了迴圈的延時。
for ...:
...
time.sleep(3)
完整程式碼
# -*- coding: utf-8 -*-
"""
Created on Thu Aug 17 16:31:35 2017
@note: 為了便於閱讀,將模組的引用就近安置了
@author: lart
"""
# 用於生成短評頁面網址的函式
def MakeUrl(start):
"""make the next page's url"""
url = 'https://movie.douban.com/subject/26934346/comments?start=' + str(start) + '&limit=20&sort=new_score&status=P'
return url
# 登入頁面資訊
main_url = 'https://accounts.douban.com/login?source=movie'
formdata = {
"form_email":"你的郵箱",
"form_password":"你的密碼",
"source":"movie",
"redir":"https://movie.douban.com/subject/26934346/",
"login":"登入"
}
user_agent = r'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36'
headers = {'User-Agnet': user_agent, 'Connection': 'keep-alive'}
# 儲存cookies便於後續頁面的保持登陸
from urllib import request
from http import cookiejar
cookie = cookiejar.CookieJar()
cookie_support = request.HTTPCookieProcessor(cookie)
opener = request.build_opener(cookie_support)
# 編碼資訊,生成請求,開啟頁面獲取內容
from urllib import parse
logingpostdata = parse.urlencode(formdata).encode('utf-8')
req_ligin = request.Request(url=main_url, data=logingpostdata, headers=headers)
response_login = opener.open(req_ligin).read().decode('utf-8')
# 獲取驗證碼圖片地址
from bs4 import BeautifulSoup
try:
soup = BeautifulSoup(response_login, "html.parser")
captchaAddr = soup.find('img', id='captcha_image')['src']
# 匹配驗證碼id
import re
reCaptchaID = r'<input type="hidden" name="captcha-id" value="(.*?)"/'
captchaID = re.findall(reCaptchaID, response_login)
# 下載驗證碼圖片
request.urlretrieve(captchaAddr, "captcha.jpg")
# 輸入驗證碼並加入提交資訊中,重新編碼提交獲得頁面內容
captcha = input('please input the captcha:')
formdata['captcha-solution'] = captcha
formdata['captcha-id'] = captchaID[0]
logingpostdata = parse.urlencode(formdata).encode('utf-8')
req_ligin = request.Request(url=main_url, data=logingpostdata, headers=headers)
response_login = opener.open(req_ligin).read().decode('utf-8')
finally:
# 獲得頁面評論文字
soup = BeautifulSoup(response_login, "html.parser")
totalnum = soup.select("div.mod-hd h2 span a")[0].get_text()[3:-2]
# 計算出頁數和最後一頁的評論數
pagenum = int(totalnum) // 20
commentnum = int(totalnum) % 20
print(pagenum, commentnum)
# 設定等待時間,避免爬取太快
import time
# 用於在超時的時候丟擲異常,便於捕獲重連
import socket
timeout = 3
socket.setdefaulttimeout(timeout)
# 追加寫檔案的方式開啟檔案
with open('祕密森林的短評.txt', 'w+', encoding='utf-8') as file:
# 迴圈爬取內容
for item in range(pagenum):
print('第' + str(item) + '頁')
start = item * 20
url = MakeUrl(start)
# 超時重連
state = False
while not state:
try:
html = opener.open(url).read().decode('utf-8')
state = True
except socket.timeout:
state = False
# 獲得評論內容
soup = BeautifulSoup(html, "html.parser")
comments = soup.select("div.comment > p")
for text in comments:
file.write(text.get_text().split()[0] + '\n')
print(text.get_text())
limit_num = 0
if item == pagenum - 1:
limit_num =+ 1
if limit_num == commentnum:
break
time.sleep(3)
print('採集寫入完畢')
結果
可見至少也有9200條評論,不管是否有所遺漏,基數已經基本無差。
後續處理
資料已經到手,怎樣玩耍就看自己的想法了,近期看到了一篇文章,講了利用詞頻製作詞雲,生成圖片,有點意思,決定模仿試試。