- 背景與挖掘目標
- 獲取豆瓣評論數據
- 分析好評與差評的關鍵信息
- 分析評論數量及評分與時間的關係
- 分析評論者的城市分佈情況
1. 背景與挖掘目標
豆瓣(douban)是一個社區網站。網站由楊勃(網名“阿北”) 創立於2005年3月6日。該網站以書影音起家,提供關於書籍、電影、音樂等作品的信息,無論描述還是評論都由用戶提供(User-generated content,UGC),是Web 2.0網站中具有特色的一個網站。
網站還提供書影音推薦、線下同城活動、小組話題交流等多種服務功能,它更像一個集品味系統(讀書、電影、音樂)、表達系統(我讀、我看、我聽)和交流系統(同城、小組、友鄰)於一體的創新網絡服務,一直致力於幫助都市人羣發現生活中有用的事物。
2019年2月5日電影《流浪地球》正式在中國內地上映。根據劉慈欣同名小說改編,影片故事設定在2075年,講述了太陽即將毀滅,已經不適合人類生存,而面對絕境,人類將開啓“流浪地球”計劃,試圖帶着地球一起逃離太陽系,尋找人類新家園的故事。
《流浪地球》舉行首映的時候,口碑好得出奇,所有去看片的業界大咖都發出了同樣讚歎。文化學者戴錦華說:“中國科幻電影元年開啓了。”導演徐崢則說,“里程碑式的電影,絕對是世界級別的。
可是公映之後,《流浪地球》的豆瓣評分卻從8.4一路跌到了7.9。影片頁面排在第一位的,是一篇一星影評《流浪地球,不及格》。文末有2.8萬人點了“有用”,3.6萬人點了“沒用”。
關於《流浪地球》的觀影評價,已經變成了一場逐漸失控的輿論混戰,如“槍稿”作者灰狼所說,“關於它的輿論,已經演化成‘政治正確、水軍橫行、自來水滅差評、道德綁架、戰狼精神。’”
挖掘目標:
- 獲取豆瓣評論數據
- 分析好評與差評的關鍵信息
- 分析評論數量及評分與時間的關係
- 分析評論者的城市分佈情況
2. 獲取豆瓣評論數據
目標網址:https://movie.douban.com/subject/26266893/comments?status=P
獲取首頁數據
通過selenium和chromedriver獲取數據
from selenium import webdriver
driver = webdriver.Chrome()
url = 'https://movie.douban.com/subject/26266893/comments?status=P'
driver.get(url)
獲取用戶名
通過driver.page_source獲取網頁源碼
我們使用Xpath解析網頁
通過下列代碼獲取names並命名
names = dom.xpath('//div[@class="comment-item"]//span[@class="comment-info"]/a/text()')
獲取評價打分
通過谷歌開發者工具可以看到,class的屬性決定評星。allstar10是一星,allstar50是五星。所以我們想要獲取評價就要獲取class的屬性
同理:
dom.xpath('//div[@class="comment-item"]//span[@class="comment-info"]/span[2]/@class')
然而發現結果是:
顯然,這其中有未打分的(comment-time),要對這種進行篩選。
獲取評分時間
通過谷歌開發者工具發現,時間是在span的title屬性裏面
times = dom.xpath('//div[@class="comment-item"]//span[@class="comment-info"]/span[3]/@title')
獲取短評內容
messages = dom.xpath('//div[@class="comment-item"]//div[@class="comment"]/p/span[@class="short"]/text()')
獲取贊同量
votes = dom.xpath('//div[@class="comment-item"]//div[@class="comment"]/h3/span[@class="comment-vote"]/span[@class="votes"]/text()') # 贊同量
獲取用戶主頁網
user_url = names = dom.xpath('//div[@class="comment-item"]//span[@class="comment-info"]/a/@href') # 用戶主頁網址
一直到上面這,都爬的好好的,然後當我想用request進入某個主頁網址的時候,遇到了418反爬
這裏我選擇的是用cookie。 登錄獲取一下cookies ,然後轉換成字典(因爲個人隱私,我刪了幾個數字):
cookies_str = 'bid=qKOdCLIajIs; _pk_ses.100001.4cf6=*; ap_v=0,6.0; __utma=30149280.688453215.1585988569.1585988569.1585988569.1; __utmc=30149280; __utmz=30149280.1585988569.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __utma=223695111.1438858643.1585988569.1585988569.1585988569.1; __utmb=223695111.0.10.185988569; __utmc=223695111; __utmz=223695111.1585988569.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __utmb=30149280.1.10.1585988569; ps=y; dbcl2="214558448:XIwFTrlzX1s"; ck=I1oq; _pk_id.100001.4cf6=279739e4a763236.1585988569.1.1585989402.1585988569.; push_noty_num=0; push_doumail_num=0'
cookies = {}
# 拆分
for i in cookies_str.split(';'):
key,value = i.split('=',1)
cookies[key] = value
cookies
好的,加了cookie之後發現還是418,去翻了翻博客,決定加一下頭文件:
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'}
然後再爬,終於成功了
獲取用戶居住地和入會時間信息
address = dom_url.xpath('//div[@class="user-info"]/a/text()') # 用戶居住地
load_time = dom_url.xpath('//div[@class="basic-info"]//div[@class="pl"]/text()') # 用戶加入時間
因爲一個頁面所以的評價都有一個主頁,所以用for做個遍歷。
同時,導入時間庫,爬一次睡兩秒防止反爬,部分代碼:
import time
cities = [] #用戶居住地址列表
load_times = [] # 用戶加入時間列表
for i in user_url:
web_data = requests.get(i,cookies=cookies,headers=headers)
dom_url = etree.HTML(web_data.text,etree.HTMLParser(encoding='utf-8'))
address = dom_url.xpath('//div[@class="basic-info"]//div[@class="user-info"]/a/text()') # 用戶居住地
load_time = dom_url.xpath('//div[@class="basic-info"]//div[@class="pl"]/text()') # 用戶加入時間
cities.append(address)
load_times.append(load_time)
time.sleep(2) # 2秒訪問一次反爬
結果:
算是初步爬取成功
單頁數據整理
獲取到的數據要進行整理,去掉一些沒用的數據,去掉缺失值,然後dataframe表格.
1. 評分數據ratings
如果沒有評分,就保留一個空字符串;有評分打印評分。
先導入re模塊。我們需要的是一個int型數據,使用==int()轉換類型,加個[0]==來獲取引號裏面的值,,就有:
import re
["" if 'rating' not in i else int(re.findall('\d{2}', i)[0]) for i in ratings] # 評分數據整理
2. load_times
我們需要的是後面那個元素的日期.
可以使用strip將前後的元素剔除,如果沒有加入時間使用”“代替,不需要最後兩個字,所以加一個==[:-2]==
load_times = ["" if i==[] else i[1].strip()[:-2] for i in load_times] # 入會時間整理
3. cities
我們需要放在一個列表中,而不是嵌套列表
cities = ["" if i==[] else i[0] for i in cities] # 居住地的數據整理
最後導入庫準備建表:
import pandas as pd
data = pd.DataFrame({
'用戶名':names,
'居住城市':cities,
'加入時間':load_times,
'評分':ratings,
'發表時間':times,
'短評正文':messages,
'贊同數量':votes
})
但是報錯:arrays must all be same length
查找了一番,發現times只有19個數據,其他的都20個數據.
然後改了一下times的查找方式:
times = dom.xpath('//div[@class="comment-item"]//span[@class="comment-info"]/span[@class="comment-time "]/@title')# 評論發佈時間
然後數據就保存好了,但是這個數據是一個頁面的數據,
所以要把這個data的構建定義成一個函數:
def get_web_data(dom=None,cookies=None):
names = dom.xpath('//div[@class="comment-item"]//span[@class="comment-info"]/a/text()') # 用戶名
ratings= dom.xpath('//div[@class="comment-item"]//span[@class="comment-info"]/span[2]/@class') # 評分
times = dom.xpath('//div[@class="comment-item"]//span[@class="comment-info"]/span[@class="comment-time "]/@title')# 評論發佈時間
messages = dom.xpath('//div[@class="comment-item"]//div[@class="comment"]/p/span[@class="short"]/text()') # 短評正文
user_url = dom.xpath('//div[@class="comment-item"]//span[@class="comment-info"]/a/@href') # 用戶主頁網址
votes = dom.xpath('//div[@class="comment-item"]//div[@class="comment"]/h3/span[@class="comment-vote"]/span[@class="votes"]/text()') # 贊同量
cities = [] #用戶居住地址列表
load_times = [] # 用戶加入時間列表
for i in user_url:
web_data = requests.get(i,cookies=cookies,headers=headers)
dom_url = etree.HTML(web_data.text,etree.HTMLParser(encoding='utf-8'))
address = dom_url.xpath('//div[@class="basic-info"]//div[@class="user-info"]/a/text()') # 用戶居住地
load_time = dom_url.xpath('//div[@class="basic-info"]//div[@class="pl"]/text()') # 用戶加入時間
cities.append(address)
load_times.append(load_time)
time.sleep(2) # 2秒訪問一次反爬
ratings = ["" if 'rating' not in i else int(re.findall('\d{2}', i)[0]) for i in ratings] # 評分數據整理
load_times = ["" if i==[] else i[1].strip()[:-2] for i in load_times] # 入會時間整理
cities = ["" if i==[] else i[0] for i in cities] # 居住地的數據整理
data = pd.DataFrame({
'用戶名':names,
'居住城市':cities,
'加入時間':load_times,
'評分':ratings,
'發表時間':times,
'短評正文':messages,
'贊同數量':votes
})
return data
接下來就是把這個函數應用於每一頁,所以我們需要判斷網頁是否已經加載。
設置一個死循環whlie ture,除非滿足到最後一頁,否轉一直進行單頁讀取,還要設置一個10秒沒有加載好就報錯的警示。
噹噹前頁面最後一個評論的主頁可以被點擊,就默認這個頁面被加載進來,否轉就終止程序.
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
wait.until(
EC.element_to_be_clickable(
(By.CSS_SELECTOR,'#comments > div:nth-child(20) > div.comment > h3 > span.comment-info > a')
)
)
最後一頁,可以通過selector來判斷,如果沒有 #paginator > a.next 則是尾頁,或者有 #paginator > span 是尾頁,但是首頁也有,所以選擇第一種。
代碼:
all_data = pd.DataFrame()
wait = WebDriverWait(driver, 10) # 10秒內能加載就操作,不能就報錯
while True:
wait.until(
EC.element_to_be_clickable(
(By.CSS_SELECTOR,'#comments > div:nth-child(20) > div.comment > h3 > span.comment-info > a')
)
)
dom = etree.HTML(driver.page_source,etree.HTMLParser(encoding='utf-8'))
data = get_web_data(dom=dom,cookies=cookies)
all_data = pd.concat([all_data,data],axis=0) # 按列拼接
# 當後頁無法點擊,終止循環
#paginator > span
#paginator > a.next
if driver.find_element_by_css_selector('#paginator > a.next')=[]: # 判斷是否還有“後頁”按鈕
break
confirm_bnt = wait.until(
EC.element_to_be_clickable(
(By.CSS_SELECTOR,"#paginator > a.next")
)
)
confirm_bnt.click() # 翻頁
大概十幾二十分鐘,就爬取完了之後就可以保存了
all_data.to_csv('doubanliulangdiqiu.csv', index=None.encoding='GB18030')
編碼方式可以自己根據需要決定。
爲了爬取所有數據,還是自己登錄一下比較好