python爬蟲:豆瓣《神秘巨星》2921篇影評

背景音樂:雨還是不停地落下 - 孫燕姿

繼續(xù)水一篇文章……


前段時間看了電影《神秘巨星》,路轉(zhuǎn)粉啊。
我個人是很喜歡啦醉途,盡管劇情方面簡單了點捕犬,但是音樂很贊呀跷坝。
那其他人是怎么看待《神秘巨星》的呢酵镜?讓我們?nèi)?a target="_blank" rel="nofollow">豆瓣上的影評上了解一下。

可以看到有2861條影評(截止2018年2月10日)柴钻,每條影評可以收集的數(shù)據(jù)包括:作者淮韭、評分、日期贴届、影評內(nèi)容靠粪、點贊數(shù)、反對數(shù)以及評論數(shù)毫蚓。

其中占键,影評部分是被折疊的,想要看到全部內(nèi)容元潘,要么點擊“展開”畔乙,要么點擊題目跳轉(zhuǎn)到指定頁。從操作上來看翩概,前者更容易牲距,因為后者跳轉(zhuǎn)后還涉及到一個返回的過程,會造成更多的不確定性氮帐。


遇到的問題

1 豆瓣的反爬蟲機制

在寫python腳本進行爬蟲的時候嗅虏,最開始是直接用requests模塊發(fā)起get請求,結(jié)果爬了幾頁后上沐,服務(wù)器返回的是臟數(shù)據(jù)皮服。

因為最開始爬蟲是成功的,說明豆瓣的服務(wù)器是通過分析我的行為才識別出爬蟲参咙,那么在這種情況下通常有三種比較簡單的做法:
1)【改用戶】設(shè)置代理服務(wù)器龄广,不斷修改IP;
2)【改行為】延長請求間隔時間蕴侧,減少被檢測出來的概率择同;
3)【改行為】模擬人類行為,用selenium開啟瀏覽器爬染幌敲才;

由于此時我用腳本的方式已經(jīng)無法正常發(fā)起請求了(盡管我盡可能構(gòu)造了合理的頭信息和用有效的cookie還是無能為力),但是瀏覽器可以正常訪問择葡,為了趕時間紧武,我決定采用方法3,同時降低我的爬蟲速度敏储,畢竟阻星,你好我好大家好嘛。

愉快地決定了已添!

2 selenium的等待問題

在用selenium的過程中妥箕,我發(fā)現(xiàn)常常出現(xiàn)點擊<展開>失敗的情況滥酥,后來查了一下,發(fā)現(xiàn)這其實是因為該元素沒有被及時加載畦幢。

現(xiàn)在大部分的網(wǎng)絡(luò)應(yīng)用都使用AJAX技術(shù)坎吻。當(dāng)瀏覽器加載頁面時,該頁面內(nèi)的元素可能會以不同的時間間隔加載呛讲,并且加載速度同時還取決于你的網(wǎng)絡(luò)狀況禾怠。因此,這使得定位元素變得困難:如果DOM中還沒有元素贝搁,則定位函數(shù)將引發(fā)ElementNotVisibleException異常。所以芽偏,有必要對selenium引入等待雷逆,即在執(zhí)行的操作之間保持一些間隔。

Selenium Webdriver提供了兩種類型的等待:隱式和顯式污尉。顯式的等待是指WebDriver在繼續(xù)執(zhí)行之前會等待特定的條件發(fā)生膀哲。隱式的等待是指WebDriver在嘗試查找元素時會輪詢DOM一段時間。

2.1 顯式等待

以百度首頁為例被碗,我希望進入后能點擊右上角的“地圖”button某宪。
其實并不需要等待,這里只是作為示意锐朴。
通過查看網(wǎng)頁代碼兴喂,可以發(fā)現(xiàn)該button的name屬性為tj_trmap。

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# 初始化瀏覽器
driver = webdriver.Chrome() 

# 打開網(wǎng)頁
driver.get('https://www.baidu.com')

# 等待并點擊
wait = WebDriverWait(driver, 10)
element = wait.until(EC.element_to_be_clickable((By.NAME, 'tj_trmap')))
element.click()

上面的腳本的意思是焚志,瀏覽器會最多等待10秒直到指定的元素是clickable的衣迷,然后再點擊。
所謂clickable的酱酬,就是該元素可見(不管在不在視窗之外)并且已啟用壶谒。

2.2 隱式等待

繼續(xù)用上面的case,

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# 初始化瀏覽器
driver = webdriver.Chrome() 
driver.implicitly_wait(10)

# 打開網(wǎng)頁
driver.get('https://www.baidu.com')

# 點擊“地圖”
element = driver.find_element_by_name('tj_trmap')
element.click()

3. selenium的定位問題

我發(fā)現(xiàn)膳沽,對于已經(jīng)加載好的元素汗菜,如果它是在可視范圍之外,即不滾動則看不到挑社,那么對它的點擊操作將會失斣山纭!

豆瓣的招聘頁為例滔灶,我的目標(biāo)是讓瀏覽器自動從頂部滾動到底部并點擊“聯(lián)系我們”普碎。

from selenium import webdriver

# 初始化瀏覽器
driver = webdriver.Chrome() 
driver.implicitly_wait(10)

# 打開網(wǎng)頁
driver.get('https://jobs.douban.com/')

# 滾動到指定元素并點擊
element = driver.find_element_by_xpath("http://a[@)
driver.execute_script("arguments[0].scrollIntoView();", element) 
element.click()

上面的腳本里,我用execute_script來執(zhí)行一個js操作來實現(xiàn)滾動的效果录平,參考了python中selenium操作下拉滾動條方法匯總


爬影評

環(huán)境:python 2.7
系統(tǒng):macOS 10.13.1
模塊:BeautifulSoup麻车、selenium缀皱、pandas、numpy动猬、os啤斗、sys、time

解決了上面三個比較關(guān)鍵的問題后赁咙,開始完善腳本钮莲,爬起來!

from __future__ import print_function
import os
import sys
import time
import pandas as pd
import numpy as np
from selenium import webdriver  
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from BeautifulSoup import BeautifulSoup


# 初始化瀏覽器
def reset_driver(current_url):
    # 設(shè)置Chrome瀏覽器參數(shù)為不加載圖片
    chrome_options = webdriver.ChromeOptions()
    prefs = {"profile.managed_default_content_settings.images":2}
    chrome_options.add_experimental_option("prefs",prefs)
    driver = webdriver.Chrome('./chromedriver', chrome_options=chrome_options) 
    driver.implicitly_wait(10)  #設(shè)置智能等待10秒

    # 登陸(后來發(fā)現(xiàn)也可以不登錄啦)
    # driver.get("https://www.douban.com/accounts/login")  
    # elem_user = driver.find_element_by_id("email")  
    # elem_user.send_keys("用戶名")  
    # elem_pwd = driver.find_element_by_id("password")  
    # elem_pwd.send_keys("密碼")  
    # elem_pwd.send_keys(Keys.RETURN)  

    # 跳轉(zhuǎn)到指定頁
    driver.get(current_url)

    return driver


# 瀏覽器滾動到指定元素的位置并點擊
def scroll_and_click(element):
    wait = WebDriverWait(driver, 10)
    element = wait.until(EC.visibility_of(element))
    driver.execute_script("arguments[0].scrollIntoView();", element)
    element.click()


# 合并結(jié)果
def merge_results():
    df_list = []
    # 遍歷臨時文件夾下的所有文件
    for f in os.listdir(path_tmp):
        if not f.startswith('.') and not f.startswith('all') and f.endswith('.csv'):
            index = int(f.split('.')[0])
            df = pd.read_csv(path_tmp + f)
            df_list.append([index, df])

    # 按照文件名的數(shù)字排序
    df_list = sorted(df_list)
    df_list = list(zip(*df_list))[1]

    # 合并
    df_all = pd.concat(df_list)

    # 保存到指定文件
    file_target = path_tmp + 'all.csv'
    df_all.to_csv(file_target, index=False)

    print('{} files -> {}'.format(len(df_list), file_target))


# 爬當(dāng)前頁的影評數(shù)據(jù)
def crawl():
    # 評分字典彼水,中文→數(shù)字
    rating_dict = {u'力薦':5, u'推薦':4, u'還行':3, u'較差':2, u'很差':1}
    comments = []

    # 若發(fā)現(xiàn)折疊崔拥,則點擊展開
    unfolders = driver.find_elements_by_xpath("http://a[@class='btn-unfold']")
    if len(unfolders):
        scroll_and_click(unfolders[0])

    # 用BeautifulSoup對網(wǎng)頁進行處理
    page = BeautifulSoup(driver.page_source)
    comment_grids = page.findAll('div', {'typeof':'v:Review'})
    total_num = len(comment_grids)  # 影評總數(shù)
    page_index = int(page.find('span', {'class':'thispage'}).text) # 當(dāng)前頁碼

    for N, comment in enumerate(comment_grids):
        # 收集本頁的基本信息:姓名、評分凤覆、日期
        name = comment.find('a', {'class':'name'}).text
        rating = comment.find('span', {'property':'v:rating'})
        rating = rating_dict[rating.get('title')] if rating is not None else np.nan  # 沒有評分時用缺失值代替
        date = comment.find('span', {'property':'v:dtreviewed'}).text

        # 點擊展開獲得完整影評
        element = driver.find_elements_by_xpath("http://div[@class='short-content']")[N]
        scroll_and_click(element)
    
        # 根據(jù)data_cid來定位完整影評链瓦,等待完全加載
        data_cid = comment.get('data-cid').encode('utf-8')
        xpath = "http://div[@property='v:description'][@data-url='https://movie.douban.com/review/{}/']".format(data_cid)
        wait = WebDriverWait(driver, 10)
        element = wait.until(EC.visibility_of(driver.find_element_by_xpath(xpath)))
        text = element.text

        # 添加到列表
        comment = {
            'name': name,
            'rating': rating,
            'time': date,
            'text': text
        }
        comments.append(comment)

        # 打印進度
        print('items: {0:4.0f}:{1:4.0f} / pages: {2:4.0f}:{3:4.0f}'.format(N + 1, total_num, page_index, total_page), end='\r')
        sys.stdout.flush()

        # 設(shè)置點擊的時間間隔在2~4秒
        time.sleep(2 + np.random.uniform() * 2)

    # 保存到臨時文件
    file_tmp_result = path_tmp + str(page_index) + '.csv'
    df = pd.DataFrame(comments)
    df.to_csv(file_tmp_result, encoding='utf-8', index=False)


# 在當(dāng)前頁創(chuàng)建一個臨時文件用于存儲臨時數(shù)據(jù)
path_tmp = './tmp/'
if not os.path.exists(path_tmp):
    os.mkdir(path_tmp)

# 初始化瀏覽器
current_url = 'https://movie.douban.com/subject/26942674/reviews?start=0'
driver = reset_driver(current_url)

# 設(shè)置總頁數(shù)
page = BeautifulSoup(driver.page_source)
total_page = int(page.find('span', {'class':'next'}).findPrevious('a').text)

# 開始爬
for i in range(total_page):
    crawl()  # 爬取當(dāng)前頁
    if i < total_page - 1:
        scroll_and_click("http://a[text()='后頁>']")  # 翻頁

# 合并結(jié)果
merge_results()

運行起來吧!估算完成時間為2.5個小時盯桦。

中途可以很方便地看到目前的進展慈俯,爬到了第86頁的第9個影評

items:    9:  20 / pages:   86: 147

最后可以看到147個文件合并成all.csv:

147 files -> ./tmp/all.csv

打開看結(jié)果,還不錯:


后記

  1. 在爬的過程中拥峦,發(fā)現(xiàn)極少數(shù)影評居然沒有評分贴膘,導(dǎo)致報錯,這個也是蠻神奇的略号,后來不得不在腳本里額外加了個判斷刑峡。

  2. 這次先把爬蟲的腳本寫好,之后有時間了再對這些文本進行數(shù)據(jù)分析璃哟。

  3. 有部分影評是被折疊的氛琢,打開看了以后并沒有覺得不合適,所以這次都爬了随闪。

  4. 2~4秒的點擊間隔是多次嘗試的結(jié)果阳似,對于我的網(wǎng)絡(luò)狀況而言,這樣剛好不被豆瓣服務(wù)器判定為爬蟲铐伴。如果網(wǎng)絡(luò)狀況不太好的話撮奏,得把點擊間隔設(shè)得更大一些,同時等待時間也要設(shè)得長一些当宴。

  5. 爬好的數(shù)據(jù)在如下鏈接畜吊,僅供學(xué)習(xí):https://pan.baidu.com/s/1hsTQLPa 密碼:1vki

往期相關(guān)文章

python爬蟲:豆瓣電影TOP100

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市户矢,隨后出現(xiàn)的幾起案子玲献,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捌年,死亡現(xiàn)場離奇詭異瓢娜,居然都是意外死亡,警方通過查閱死者的電腦和手機礼预,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門眠砾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人托酸,你說我怎么就攤上這事褒颈。” “怎么了励堡?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵谷丸,是天一觀的道長。 經(jīng)常有香客問我应结,道長淤井,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任摊趾,我火速辦了婚禮,結(jié)果婚禮上游两,老公的妹妹穿的比我還像新娘砾层。我一直安慰自己,他們只是感情好贱案,可當(dāng)我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布肛炮。 她就那樣靜靜地躺著,像睡著了一般宝踪。 火紅的嫁衣襯著肌膚如雪侨糟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天瘩燥,我揣著相機與錄音秕重,去河邊找鬼。 笑死厉膀,一個胖子當(dāng)著我的面吹牛溶耘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播服鹅,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼凳兵,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了企软?” 一聲冷哼從身側(cè)響起庐扫,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后形庭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铅辞,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年碘勉,在試婚紗的時候發(fā)現(xiàn)自己被綠了巷挥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡验靡,死狀恐怖倍宾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情胜嗓,我是刑警寧澤高职,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站辞州,受9級特大地震影響怔锌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜变过,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一埃元、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧媚狰,春花似錦岛杀、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至辨宠,卻和暖如春遗锣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嗤形。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工精偿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人派殷。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓还最,卻偏偏與公主長得像,于是被迫代替她去往敵國和親毡惜。 傳聞我的和親對象是個殘疾皇子拓轻,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容