1. 程式人生 > >我的第一個豆瓣短評爬蟲

我的第一個豆瓣短評爬蟲

豆瓣上有著大量的影視劇的評論,所以說,要是想要實現對廣大人民群眾的觀點的分析,對一部片子的理解,綜合來看大家的評論是很有必要的。而短評作為短小精幹的快速評論入口,是值得一談的。

所以先要實現對其的資料的爬取。

目前來看,基本內容是可以爬取的。最大的問題在於速度。後續考慮準備運用多執行緒的方式處理下。以及可以嘗試其他提速的方法。

下面是這個程式的構思編寫過程。

構思準備

爬取的思路,及反省與思考

盲目狀態

最初,並不知道豆瓣對於未登陸使用者的限制,盲目的爬取,看著評論檔案,發現行數太少,也就是說評論內容太少,感覺不對勁。

我利用了即時列印寫入內容的方式,發現,到了第十頁左右的時候,出現無法獲得頁面內評論內容,思考了下後,試著將頁面原始碼打印出來,發現到了後面,就出現提示許可權不足

。我一下子知道了,是因為沒有登入的原因。

登入

之前看過內容,明白這時候應該藉助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條評論,不管是否有所遺漏,基數已經基本無差。

後續處理

資料已經到手,怎樣玩耍就看自己的想法了,近期看到了一篇文章,講了利用詞頻製作詞雲,生成圖片,有點意思,決定模仿試試。

Python 爬蟲實踐:《戰狼2》豆瓣影評分析

github:amueller/word_cloud