豆瓣爬虫实战——Angelababy到底是什么风评
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/7059b1fa9725d351c19360bca6bc1181.jpeg)
研究目的
作为一个不经常观看娱乐圈的直男,我对Angelababy这个人的印象飘忽不定:在各个平台非常唯美的一个人,一转战到b站知乎就变成了一个差评如潮的演员。说实在话,我除了看过《奔跑吧》和《寻龙诀》,剩下的对这个明星一无所知。所以惊天我就来满足一下我的好奇心,从演员角度看看在群众眼里他到底是一个什么样的人。
大体思路
作为演员,我们最直观的就是看她参演过的电影或者电视剧反响,所以在这里选择了豆瓣网——信息多且易爬取。在豆瓣上我们搜索“杨颖”就能得到她参演的各种项目。(豆瓣查询链接)
截至目前,搜索页一共有9页,我们需要先拿出这9页的所有节目信息,然后把上映的电影电视剧筛出来,对于每一个节目查询他们的短评,这样就算是拿到了所有的评价信息。
如果我们仔细看页面链接,发现每一个影片都有ID,我们只要按照格式就能进入这个页面的总短评(不管是电视剧还是电影)。
短评格式:https://movie.douban.com/subject/节目ID/comments?start=0&limit=20&sort=new_score&status=P
例:https://movie.douban.com/subject/26980826/comments?start=0&limit=20&sort=new_score&status=P
拿到评价信息之后,我们可以先从评论里看看对杨颖参演的总体评价是如何的。
操作
1.数据获取
(1)首先是提取所有的电影信息,做成一个表。
我们对上边这个页面进行翻页,发现网页的规律如下:
第一页:https://movie.douban.com/celebrity/1033011/movies?start=0&format=pic&sortby=time&
第二页:https://movie.douban.com/celebrity/1033011/movies?start=10&format=pic&sortby=time&
我们发现每十个节目是一页,下一页就在这个位置+10就可以了,所以我们编写code如下
import requests
import pandas as pd
from fake_useragent import UserAgent
from bs4 import BeautifulSoup
from tqdm import trange
from tqdm import tqdm_notebook as tqdm
# 建立随机agent 防止页面拒绝访问
ua = UserAgent()
headers = {'User-Agent':ua.random}
#提取总页面内容,做一个索引
tbl_baby_movies = pd.DataFrame()
for main_page_num in trange(9):#对于杨颖的9页节目单
#设置可随着loop变的url
baby_movie_url = f'https://movie.douban.com/celebrity/1033011/movies?start={main_page_num*10}&format=pic&sortby=time&'
#获取
response = requests.get(baby_movie_url, headers=headers)
soup = BeautifulSoup(response.text, "html.parser")
for movie in range(len(soup.find_all('h6'))):#对于每一个节目
baby_movies_dict = {}
#节目名称
baby_movies_dict['movie_name'] = soup.find_all('h6')[movie].a.text
#节目链接(以防万一)
baby_movies_dict['movie_link'] = soup.find_all('h6')[movie].a['href']
#是否上映
if soup.find_all('h6')[movie].find_all('span')[1].text=='(未上映)':
baby_movies_dict['coming_soon']='Y'
else:
baby_movies_dict['coming_soon']='N'
tbl_baby_movies = tbl_baby_movies.append(baby_movies_dict,ignore_index=True)
之后我们就得到了一个这样的表
(2)再获取一些比较细节的节目内容
拿到这些link之后,我们要挨个进去看看它们是什么类型的节目,这样有助于帮我们选择出电视剧和电影。
这些从细节页面里都有体现。除此之外,我们还可以拿一拿其他的电影信息。
#把已经上架的选出来
onboard_general_links = tbl_baby_movies[tbl_baby_movies['coming_soon']=='N']['movie_link'].tolist()
#获取页面
general_links = pd.DataFrame()
for link in tqdm(onboard_general_links):
general = {}
response = requests.get(link, headers=headers)
soup = BeautifulSoup(response.text, "html.parser")
#左边的节目详细信息
general['name'] = soup.find('span',{'property':'v:itemreviewed'}).text
try:
general['link'] = link
#影片类型
general['type1'] = soup.find_all('span',{'property':'v:genre'})[0].text
general['type2'] = soup.find_all('span',{'property':'v:genre'})[1].text
general['type3'] = soup.find_all('span',{'property':'v:genre'})[2].text
except:
pass
#影片市场
try:
general['program_length'] = int(soup.find_all('span',{'property':'v:runtime'})[0].text.replace('分钟',''))
except:
pass
#右边的豆瓣评分板块
#每个星评分的百分比
try:
general['overall_star']= float(soup.find_all('strong',{'class':'ll rating_num'})[0].text)
for star in range(5):
general['overall_star_'+str(star+1)]= float(soup.find_all('span',{'class':'rating_per'})[star].text.replace('%',''))
except:
pass
#好于百分之几的什么片
try:
good_than=soup.find_all('div',{'class':'rating_betterthan'})[0].find_all('a')
general['good_than_1'] = good_than[0].text
general['good_than_2'] = good_than[1].text
except:
pass
general_links = general_links.append(general,ignore_index=True)
general_links[[col for col in general_links.columns.tolist() if 'overall_star' in col]] = general_links[[col for col in
#评分补空值
general_links.columns.tolist() if 'overall_star' in col]].fillna(0)
#保存表
general_links.to_csv('angelababy_programs.csv',index= None,encoding ='utf-8-sig')
做出来之后就是这样的一个索引:
顺便看一下杨颖参演的总体评分
general_links.loc[general_links['overall_star']!=-1,'overall_star'].mean()
#5.6551724137931005
5.7,一个不高也不低的数字。
我们再把歌舞晚会真人秀这样的节目从表里去掉,然后再拿到每个节目的的ID,一会要用这些得到的ID去搜索它们的短评。
tbl_baby_movie_links = general_links[(general_links['type1']!='真人秀')\
& (general_links['type1']!='脱口秀') \
& (general_links['type1']!='歌舞')\
& (general_links['type1']!='音乐')\
]
tbl_baby_movie_links['link_id'] = tbl_baby_movie_links['link'].str.replace('https://movie.douban.com/subject/','').str.replace('/','')
拿到的ID就像是这样
再看看总体评分的变化
貌似有一点下降。。。。。。(别着急)
(3)用筛选出来的电影/电视剧ID抓取短评
用这些ID我们做一个循环抓取
data= pd.DataFrame()
for baby_item in tqdm(tbl_baby_movie_links['link_id'].tolist()):
extract_page = 10
ID = baby_item
status='P'#or F:想看 P: 看过
SORT = 'new_score'#or time:按最新排序 new_score: 按热门排序
for page in tqdm(range(extract_page)):
url = f'https://movie.douban.com/subject/{baby_item}/comments?start={page*20}&limit=20&sort={SORT}&status={status}'
headers = {'User-Agent':ua.random}
try:#尝试着对我们合成的页面进行获取
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.text, "html.parser")
#拿到一些电影统一的信息
movie_name = soup.find_all('div',{'id':'content'})[0].select('h1')[0].text.replace(' 短评','')
movie_staff = soup.find_all('span',{'class':'attrs'})[0].find_all('a')
actor = ' '.join([name.text for name in movie_staff[1:-1]])
movie_type = soup.find_all('span',{'class':'attrs'})[0].find_all('p')[2].text.replace(' ','').replace('\n','').replace('类型:','')
movie_region = soup.find_all('span',{'class':'attrs'})[0].find_all('p')[3].text.replace(' ','').replace('\n','').replace('地区:','')
movie_time = soup.find_all('span',{'class':'attrs'})[0].find_all('p')[4].text.replace(' ','').replace('\n','').replace('片长:','').replace('分钟','')
move_onboard_time = soup.find_all('span',{'class':'attrs'})[0].find_all('p')[-1].text.replace(' ','').replace('\n','').replace('上映:','')
comments = soup.find_all('div',{'class':'comment'})
for comment in range(len(comments)):
temp_comment_info={}
#影片固定信息
temp_comment_info['movie'] = movie_name
temp_comment_info['director'] = movie_staff[0].text
temp_comment_info['actor'] = actor
#评论信息
temp_comment_info['user'] = comments[comment].find_all('a',{'class':''})[0].text
temp_comment_info['user_url'] = comments[comment].find_all('a',{'class':''})[0]['href']
temp_comment_info['useful'] = int(comments[comment].find('span',{'class':'votes'}).text)
temp_comment_info['date'] = comments[comment].find('span',{'class':'comment-time'}).text.replace('\n','').strip(' ')
temp_comment_info['text'] = comments[comment].find('span',{'class':'short'}).text
if status == 'P':
temp_comment_info['status'] = 'P'
try:
temp_comment_info['rating'] = int(comments[comment].find('span',{'class':'comment-info'}).find_all('span')[1]['class'][0].replace('allstar',''))
except:
print(f'no rating in page {page}, comment {comment+1}')
else:
temp_comment_info['status'] = 'F'
data = data.append(temp_comment_info,ignore_index=True)
except:#如果没有的话就说明评论太少导致页面不足,进入下一个电影/电视剧
print(f'no page {page+1} for id {baby_item}')
break
data.to_csv('baby_comment.csv',index= None,encoding ='utf-8-sig')
做出来之后就像这样:
(4)拿到了这些短评之后,做一些数据分析
(a)从评分看
先改一下日期格式
data_1 = data.copy()
#更改日期格式
data_1['year_mth'] = data_1['date'].str[:7].str.replace('-','').astype(str)
data_1['date'] = pd.to_datetime(data_1['date']).dt.strftime('%Y/%m/%d')
#选出有评分的
data_2 = data_1[data_1['rating'].notnull()]
康康我们群众的眼睛,再次看看短评对杨颖参演电视剧/电影的总体评分
WOOOOOOO…这跟影评里写的差池很大啊,别着急。之前的豆瓣电影的评分就是基于用户打分:把豆瓣用户的打分(一到五星换算为零到十分)加起来,再除以用户数。所以民众评分换算过来就是4.8分。
看看平均评分最高的和最低的
import plotly.express as px
fig = px.bar(data_2.groupby('movie')['rating'].mean().sort_values(ascending=False).reset_index(), x='movie', y='rating',color='rating')
fig.show()
何以笙箫默?那没事了…
看看有没有冲分的(我们选的是精华帖,也就意味着代表民意;评分是总体的加权)。
#我们连接上最开始的总体评分
data_2 = data_2.merge(tbl_baby_movie_links,how='left',left_on='movie',right_on='name')
#标准化一下评分
data_2['rating']=data_2['rating']/10
data_2['overall_star']=data_2['overall_star']/2
#制作表格
pingfen_compare = data_2.groupby('movie')[['rating','overall_star']].mean().sort_values(by='rating',ascending=False).reset_index()
#只选有影评分的(有两个没有)
pingfen_compare = pingfen_compare[pingfen_compare['overall_star']!=-0.5]
import plotly.graph_objects as go
fig = go.Figure(data=[
go.Bar(name='热评评分', x=pingfen_compare['movie'], y=pingfen_compare['rating'],marker_color='lightgreen'),
go.Bar(name='豆瓣影评分', x=pingfen_compare['movie'], y=pingfen_compare['overall_star'],marker_color='lightsalmon')
])
# Change the bar mode
fig.update_layout(barmode='group')
fig.show()
要是这个不明显的话,看看两者之差
pingfen_compare['diff'] = pingfen_compare['rating']-pingfen_compare['overall_star']
fig = go.Figure(data=[go.Bar(x=pingfen_compare.sort_values('diff')['movie'], y=pingfen_compare.sort_values('diff')['diff'])])
# Customize aspect
fig.update_traces(marker_color='rgb(158,202,225)', marker_line_color='rgb(8,48,107)',
marker_line_width=1, opacity=0.7)
fig.update_layout(title_text='热评与总评分之差(越大越没有嫌疑)')
fig.show()
咳咳,又是《何以笙箫默》
再看看热门评分分数的占比吧
colors = ['gold', 'mediumturquoise', 'darkorange', 'lightgreen']
fig = go.Figure(data=[go.Pie(labels=data_2['rating'].value_counts().reset_index()['index'],
values=data_2['rating'].value_counts().reset_index()['rating'], hole=.5)])
fig.update_traces(hoverinfo='label+percent', textinfo='label+percent', textfont_size=20,
marker=dict(colors=colors, line=dict(color='#000000', width=2)))
fig.show()
秒懂,1分之王
最后让我们按照时间看看我们大baby的火爆时间
rating_by_ymh = pd.DataFrame(data_2.groupby(['year_mth','movie'])['rating'].mean()).reset_index(drop=False)
fig = px.bar(rating_by_ymh, x="year_mth", y="rating",color='movie',
height=400)
fig.show()
能看出来,2017年是她的最忙的时候,各种电影应接不暇。
、
(b)从评论看
我们先做个词云玩玩,看看评论最多的到底是啥。
我们用清华大学做的NLP库来分词(因为准确率要比其他的库高)
import thulac
thulac_model = thulac.thulac()
test_text = data_2['text']#找出所有的评分有效评论
select_word=[]
for roll_text in trange(data_2.shape[0]):
wordseg = thulac_model.cut(test_text[roll_text])
#拿到除标点符号、副词、量词、助词、代词和方位词的词
for word in wordseg:
if word[1]!='w' and word[1]!='d' and word[1]!='q' and word[1]!='u' and word[1]!='r' and word[1]!='f':
select_word.append(word[0])
#去掉单字
fnl_words = [word for word in select_word if len(word)>1]
#检查一下, 再去掉一些没分化出来的词
word_freq_chk = pd.DataFrame({'word':list(cnt_list.keys()),'cnt':list(cnt_list.values())}).sort_values(by='cnt', ascending=False)
word_freq_chk[:30]
fnl_words_1 = fnl_words
fnl_words_1 = [ele for ele in fnl_words_1 if ele not in ['一个','觉得','感觉']]
import wordcloud
wc = wordcloud.WordCloud(width=1920*2, font_path='simfang.ttf',height=1080*2)#设定词云画的大小字体,一定要设定字体,否则中文显示不出来
wc.generate(' '.join(fnl_words_1))
from matplotlib import pyplot as plt
plt.imshow(wc)
这还不够!因为这些电影有些杨颖只是刷了个脸,我们要挑选出来她是主演的电影
#分化一下演员(演员列表是按照顺序记录的)
tbl_actor_list = data_2["actor"].str.split(" ", n = 30, expand = True)
tbl_actor_list.columns = ['actor_'+str(i+1) for i in range(31)]
#join回去
data_2 = pd.concat([data_2, tbl_actor_list], axis=1, sort=False)
把她是主演的电影选出来(演员排名前五),再按照同样的方式做一遍
baby_test_text = data_2.loc[(data_2['actor_1']=='杨颖') |
(data_2['actor_2']=='杨颖') |
(data_2['actor_3']=='杨颖') |
(data_2['actor_4']=='杨颖') |
(data_2['actor_5']=='杨颖') , 'text'].reset_index(drop = True)
#找出所有的评分有效评论
select_word_2=[]
for roll_text in tqdm(baby_test_text):
wordseg = thulac_model.cut(roll_text)
#拿到除标点符号、副词、量词、助词、代词和方位词的词
for word in wordseg:
if word[1]!='w' and word[1]!='d' and word[1]!='q' and word[1]!='u' and word[1]!='r' and word[1]!='f':
select_word_2.append(word[0])
fnl_words_2 = [word for word in select_word_2 if len(word)>1]
fnl_words_3 = fnl_words_2
fnl_words_3 = [ele for ele in fnl_words_2 if ele not in ['一个','觉得','感觉','第一','看到']]
wc = wordcloud.WordCloud(width=1920*2, font_path='simfang.ttf',height=1080*2)#设定词云画的大小字体,一定要设定字体,否则中文显示不出来
wc.generate(' '.join(fnl_words_3))
plt.imshow(wc)
哦哦哦,有演技!!!我们再带回去确认一下!
for comment in data_2[data_2['text'].str.contains('演技', regex=False)]['text'].sample(frac=0.5).reset_index(drop=True)[:10]:
print(comment)
print('---------------------------------------------------------------------')
…咳咳,这不重要
(c)从演职人员来看
先做以下处理
actor_list = list(set(' '.join(data_2.drop_duplicates(subset= 'movie')['actor'].tolist()).split(' ')))[1:]
for actor in tqdm(actor_list):
data_2['actor_include_'+actor] = data_2['actor'].str.contains(actor, regex=False).astype(int)
看看哪位演员参与了低分(3分一下)演出的评论最多
bad_actor = data_2.loc[data_2['rating']<=3,[col for col in data_2.columns if 'actor_include_' in col]].sum().reset_index().sort_values(by=0,ascending=False)[1:20].reset_index(drop=True)
bad_actor.columns=['actor_include','comment_count']
bad_actor['actor_include']=bad_actor['actor_include'].str.replace('actor_include_','')
#画图
colors = ['lightslategray',] * 19
colors[0] = 'crimson'
fig = go.Figure(data=[go.Bar(
x=bad_actor['actor_include'],
y=bad_actor['comment_count'],
marker_color=colors
)])
fig.update_layout(title_text='哪位演员跟随差评最多')
fig.show()
看看哪位导演也受了危害
bad_dir = data_2.loc[data_2['rating']<=3,'director'].value_counts().sort_values(ascending=False).reset_index(drop=False)[:10]
bad_dir.columns =['director_include','comment_count']
colors = ['lightgray',] * 10
colors[0] = 'lightgreen'
fig = go.Figure(data=[go.Bar(
x=bad_dir['director_include'],
y=bad_dir['comment_count'],
marker_color=colors
)])
fig.update_layout(title_text='哪位导演跟随差评最多')
fig.show()
总结
以上就是部分杨颖的爬虫数据分析了。
不难看出我们的大BABY虽然非常热门,但是参演的大多数电影/电视剧风评都不太好,且这些差评作品也都是同期偶像演员凑成的班底,显然是以量取胜。
细心的同志会发现,我并没有完全开发完这个数据,只是对豆瓣网上的数据现状进行了剖析。后续我会用这个数据建模来分析怎样使你的评论更让大家觉得有用。
做完之后我会把链接加进这个博客。如果你有什更好的想法或者搞怪的主意,务必留言给我,让我们一起探寻!!