Python爬取《流浪地球》豆瓣影评与数据分析

Posted Facktoo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python爬取《流浪地球》豆瓣影评与数据分析相关的知识,希望对你有一定的参考价值。

一、选题背景:

  2019年年初,《流浪地球》全国上榜。在豆瓣上,首日开分站稳8分以上,评分了之后点映的高热。微博上跟着出现吴京客串31天与6000万的热度搜。知乎上关于“评价刘慈欣如何评价刘慈欣小说改编的同名电影《流浪地球片》”的热门话题,包括导演郭帆的最高赞回答。

二、数据说明:

  本篇文章爬取了豆瓣网上《流浪地球》的部分影评,并进行数据分析和可视化处理。

三、实施过程及代码:

 1 # 爬取电影《流浪地球》的影评
 2 import requests
 3 from lxml import etree
 4 from tqdm import tqdm
 5 import time
 6 import random
 7 import pandas as pd
 8 import re
 9 
10 name_list, content_list, date_list, score_list, city_list = [], [], [], [], []
11 movie_name = ""
12 
13 def get_city(url, i):
14     time.sleep(round(random.uniform(2, 3), 2))
15     headers = {
16         \'User-Agent\': \'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/67.0.3396.99 Safari/537.36\'}
17     cookies = {\'cookie\': \'bid=Ge7txCUP3v4; ll="108303"; _vwo_uuid_v2=DB48689393ACB497681C7C540C832B546|f3d53bcb0314c9a34c861e9c724fcdec; ap_v=0,6.0; dbcl2="159607750:sijMjNWV7ek"; ck=kgmP; push_doumail_num=0; push_noty_num=0; _pk_ref.100001.8cb4=%5B%22%22%2C%22%22%2C1549433417%2C%22https%3A%2F%2Fmovie.douban.com%2Fsubject%2F26266893%2Fcomments%3Fsort%3Dnew_score%26status%3DP%22%5D; _pk_ses.100001.8cb4=*; __lnkrntdmcvrd=-1; __yadk_uid=KqejvPo3L0HIkc2Zx7UXOJF6Vt9PpoJU; _pk_id.100001.8cb4=91514e1ada30bfa5.1549433417.1.1549433694.1549433417\'}  # 2018.7.25修改,
18     res = requests.get(url, cookies=cookies, headers=headers)
19     if (res.status_code == 200):
20         print("\\n成功获取第{}个用户城市信息!".format(i))
21     else:
22         print("\\n第{}个用户城市信息获取失败".format(i))
23     pattern = re.compile(\'<div class="user-info">.*?<a href=".*?">(.*?)</a>\', re.S)
24     item = re.findall(pattern, res.text)  # list类型
25     return (item[0])  # 只有一个元素,所以直接返回

(1)网页分析

获取对象:

  • 评论用户

  • 评论内容

  • 评分

  • 评论日期

  • 用户所在城市

 

(2)数据获取与存储

1、获取饼干

本人用的是Chrome浏览器,Ctrl+F12进入开发者工具页面。F5刷新一下出现数据,找到cookies、headers。

2、加载headers、cookies,并用requests库获取信息

 1 def get_content(id, page):
 2     headers = {
 3         \'User-Agent\': \'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36\'}
 4     cookies = {\'cookie\': \'                  此处填入自己的cookies,否则不能正常爬取                  \'}
 5     url = "https://movie.douban.com/subject/" + str(id) + "/comments?start=" + str(page * 10) + "&limit=20&sort=new_score&status=P"
 6     res = requests.get(url, headers=headers, cookies=cookies)
 7 
 8     pattern = re.compile(\'<div id="wrapper">.*?<div id="content">.*?<h1>(.*?) 短评</h1>\', re.S)
 9     global movie_name
10     movie_name = re.findall(pattern, res.text)[0]  # list类型
11 
12     res.encoding = "utf-8"
13     if (res.status_code == 200):
14         print("\\n第{}页短评爬取成功!".format(page + 1))
15         print(url)
16     else:
17         print("\\n第{}页爬取失败!".format(page + 1))
18 
19     with open(\'html.html\', \'w\', encoding=\'utf-8\') as f:
20         f.write(res.text)
21         f.close()
22     x = etree.HTML(res.text)

3、解析需求数据

  此处我用xpath解析。发现有的用户虽然有评论,但没有给评分,所以分数和日期这两个的xpath位置是会变动的。因此需要加判断,如果发现分数里面解析日期,证明该条评论没有给出评分。

 1     for i in range(1, 21):   # 每页20个评论用户
 2         name = x.xpath(\'//*[@id="comments"]/div[{}]/div[2]/h3/span[2]/a/text()\'.format(i))
 3         # 下面是个大bug,如果有的人没有评分,但是评论了,那么score解析出来是日期,而日期所在位置spen[3]为空
 4         score = x.xpath(\'//*[@id="comments"]/div[{}]/div[2]/h3/span[2]/span[2]/@title\'.format(i))
 5         date = x.xpath(\'//*[@id="comments"]/div[{}]/div[2]/h3/span[2]/span[3]/@title\'.format(i))
 6         m = \'\\d{4}-\\d{2}-\\d{2}\'
 7         try:
 8             match = re.compile(m).match(score[0])
 9         except IndexError:
10             break
11         if match is not None:
12             date = score
13             score = ["null"]
14         else:
15             pass
16         content = x.xpath(\'//*[@id="comments"]/div[{}]/div[2]/p/span/text()\'.format(i))
17         id = x.xpath(\'//*[@id="comments"]/div[{}]/div[2]/h3/span[2]/a/@href\'.format(i))
18         try:
19             city = get_city(id[0], i)  # 调用评论用户的ID城市信息获取
20         except IndexError:
21             city = " "
22         name_list.append(str(name[0]))
23         score_list.append(str(score[0]).strip(\'[]\\\'\'))  # bug 有些人评论了文字,但是没有给出评分
24         date_list.append(str(date[0]).strip(\'[\\\'\').split(\' \')[0])
25         content_list.append(str(content[0]).strip())
26         city_list.append(city)

4、获取电影名称

1 pattern = re.compile(\'<div id="wrapper">.*?<div id="content">.*?<h1>(.*?) 短评</h1>\', re.S)
2 global movie_name
3 movie_name = re.findall(pattern, res.text)[0]  # list类型

5、数据存储

1 def main(ID, pages):
2     global movie_name
3     for i in tqdm(range(0, pages)):  # 豆瓣只开放500条评论
4         get_content(ID, i)  # 第一个参数是豆瓣电影对应的id序号,第二个参数是想爬取的评论页数
5         time.sleep(round(random.uniform(3, 5), 2))
6     infos = {\'name\': name_list, \'city\': city_list, \'content\': content_list, \'score\': score_list, \'date\': date_list}
7     data = pd.DataFrame(infos, columns=[\'name\', \'city\', \'content\', \'score\', \'date\'])
8     data.to_csv(movie_name + ".csv")  # 存储名为  电影名.csv

(3)、数据分析与可视化

1、获取饼干

 1 # 数据分析可视化
 2 import os
 3 import pandas as pd
 4 from pandas import DataFrame
 5 import re
 6 from pyecharts import Line, Geo, Bar, Pie, Page, ThemeRiver
 7 from snownlp import SnowNLP
 8 import jieba
 9 import matplotlib.pyplot as plt
10 from wordcloud import WordCloud, STOPWORDS, ImageColorGenerator
11 
12 fth = open(\'pyecharts_citys_supported.txt\', \'r\', encoding=\'utf-8\').read() # pyecharts支持城市列表

  城市信息过滤器中文字

1 # 过滤字符串只保留中文
2 def translate(str):
3     line = str.strip()
4     p2 = re.compile(\'[^\\u4e00-\\u9fa5]\')   # 中文的编码范围是:\\u4e00到\\u9fa5
5     zh = " ".join(p2.split(line)).strip()
6     zh = ",".join(zh.split())
7     str = re.sub("[A-Za-z0-9!!,%\\[\\],。]", "", zh)
8     return str

  匹配pyecharts支持的城市列表

 1 # 下面是按照列属性读取的
 2 def count_sentiment(csv_file):
 3     path = os.path.abspath(os.curdir)
 4     csv_file = path+ "\\\\" + csv_file + ".csv"
 5     csv_file = csv_file.replace(\'\\\\\', \'\\\\\\\\\')
 6     d = pd.read_csv(csv_file, engine=\'python\', encoding=\'utf-8\')
 7     motion_list = []
 8     for i in d[\'content\']:
 9         try:
10             s = round(SnowNLP(i).sentiments, 2)
11             motion_list.append(s)
12         except TypeError:
13             continue
14     result = {}
15     for i in set(motion_list):
16         result[i] = motion_list.count(i)
17     return result

2、基于nownlp的情感分析

  snownlp主要进行中文分词(算法是Character-Base Generative Model)、词性可以官网的原理是TnT、3-gram 隐马)、情感分析(有介绍原理,但指定购物类的评论的准确率,其实是因为它的语料库主要是再生方面的,可以自己构建相关领域语料库,替换原来的,准确率也相当不错的)、文本分类(原理是朴素贝叶斯)、转换拼音、繁体转简体、提取文本关键词(原理是TextRank)、提取摘要(原理是TextRank)、分割句子、相似文本(原理是BM25)【摘自CSDN】。在此之前,可以先看一下官网,里面有最基础的一些命令的介绍。官网链接:https://pypi.org/project/snownlp/

  因为已知p全部是unicode编码,所以要注意数据是否为unicode编码。是unicode编码,所以不要为了去除中文文本里面含有的英文,所以不要把中文文本里面含有的英文,都转码成统一的编码格式只是调用snownlp原生语料对文本进行分析,snlp重点针对购物领域,所以是为了提高情绪分析评价的适当程度,可以采取训练语料库的方法。

 1 def draw_sentiment_pic(csv_file):
 2     attr, val = [], []
 3     info = count_sentiment(csv_file)
 4     info = sorted(info.items(), key=lambda x: x[0], reverse=False)  # dict的排序方法
 5     for each in info[:-1]:
 6         attr.append(each[0])
 7         val.append(each[1])
 8     line = Line(csv_file+":影评情感分析")
 9     line.add("", attr, val, is_smooth=True, is_more_utils=True)
10     line.render(csv_file+"_情感分析曲线图.html")

3、评论来源城市分析

  调用pyecharts的页面函数,可以在一个图像对象中创建chart,只需要对应的添加组件。

 1 def draw_citys_pic(csv_file):
 2     page = Page(csv_file+":评论城市分析")
 3     info = count_city(csv_file)
 4     geo = Geo("","小本聪原创",title_pos="center", width=1200,height=600, background_color=\'#404a59\', title_color="#fff")
 5     while True:   # 二次筛选,和pyecharts支持的城市库进行匹配,如果报错则删除该城市对应的统计
 6         try:
 7             attr, val = geo.cast(info)
 8             geo.add("", attr, val, visual_range=[0, 300], visual_text_color="#fff", is_geo_effect_show=False,
 9                     is_piecewise=True, visual_split_number=6, symbol_size=15, is_visualmap=True)
10         except ValueError as e:
11             e = str(e)
12             e = e.split("No coordinate is specified for ")[1]  # 获取不支持的城市名称
13             info.pop(e)
14         else:
15             break
16     info = sorted(info.items(), key=lambda x: x[1], reverse=False)  # list排序
17     print(info)
18     info = dict(info)   # list转dict
19     print(info)
20     attr, val = [], []
21     for key in info:
22         attr.append(key)
23         val.append(info[key])
24 
25 
26     geo1 = Geo("", "评论城市分布", title_pos="center", width=1200, height=600,
27               background_color=\'#404a59\', title_color="#fff")
28     geo1.add("", attr, val, visual_range=[0, 300], visual_text_color="#fff", is_geo_effect_show=False,
29             is_piecewise=True, visual_split_number=10, symbol_size=15, is_visualmap=True, is_more_utils=True)
30     # geo1.render(csv_file + "_城市dotmap.html")
31     page.add_chart(geo1)
32     geo2 = Geo("", "评论来源热力图",title_pos="center", width=1200,height=600, background_color=\'#404a59\', title_color="#fff",)
33     geo2.add("", attr, val, type="heatmap", is_visualmap=True, visual_range=[0, 50],visual_text_color=\'#fff\', is_more_utils=True)
34     # geo2.render(csv_file+"_城市heatmap.html")  # 取CSV文件名的前8位数
35     page.add_chart(geo2)
36     bar = Bar("", "评论来源排行", title_pos="center", width=1200, height=600 )
37     bar.add("", attr, val, is_visualmap=True, visual_range=[0, 100], visual_text_color=\'#fff\',mark_point=["average"],mark_line=["average"],
38             is_more_utils=True, is_label_show=True, is_datazoom_show=True, xaxis_rotate=45)
39     bar.render(csv_file+"_城市评论bar.html")  # 取CSV文件名的前8位数
40     page.add_chart(bar)
41     pie = Pie("", "评论来源饼图", title_pos="right", width=1200, height=600)
42     pie.add("", attr, val, radius=[20, 50], label_text_color=None, is_label_show=True, legend_orient=\'vertical\', is_more_utils=True, legend_pos=\'left\')
43     pie.render(csv_file + "_城市评论Pie.html")  # 取CSV文件名的前8位数
44     page.add_chart(pie)
45     page.render(csv_file + "_城市评论分析汇总.html")

4、电影推荐走势分析

  • 读取csv文件,以dataframe(df)形式保存

  • 遍历df行,保存到list

  • 统计相同 日期相同 评分的个数

  • 转换为df格式,设置列名

  • 按日期排序

  • 去新的每一个日期的推荐种,因此需要增加到最少的5种。

 1 def score_draw(csv_file):
 2     page = Page(csv_file+":评论等级分析")
 3     score, date, val, score_list = [], [], [], []
 4     result = {}
 5     path = os.path.abspath(os.curdir)
 6     csv_file = path + "\\\\" + csv_file + ".csv"
 7     csv_file = csv_file.replace(\'\\\\\', \'\\\\\\\\\')
 8     d = pd.read_csv(csv_file, engine=\'python\', encoding=\'utf-8\')[[\'score\', \'date\']].dropna()  # 读取CSV转为dataframe格式,并丢弃评论为空的记录
 9     for indexs in d.index:  # 一种遍历df行的方法(下面还有第二种,iterrows)
10         score_list.append(tuple(d.loc[indexs].values[:])) # 目前只找到转换为tuple然后统计相同元素个数的方法
11     print("有效评分总数量为:",len(score_list), "")
12     for i in set(list(score_list)):
13         result[i] = score_list.count(i)  # dict类型
14     info = []
15     for key in result:
16         score= key[0]
17         date = key[1]
18         val = result[key]
19         info.append([score, date, val])
20     info_new = DataFrame(info)  # 将字典转换成为数据框
21     info_new.columns = [\'score\', \'date\', \'votes\']
22     info_new.sort_values(\'date\', inplace=True)    # 按日期升序排列df,便于找最早date和最晚data,方便后面插值
23     print("first df", info_new)
24     # 以下代码用于插入空缺的数据,每个日期的评分类型应该有5中,依次遍历判断是否存在,若不存在则往新的df中插入新数值
25     mark = 0
26     creat_df = pd.DataFrame(columns = [\'score\', \'date\', \'votes\']) # 创建空的dataframe
27     for i in list(info_new[\'date\']):
28         location = info_new[(info_new.date==i)&(info_new.score=="力荐")].index.tolist()
29         if location == []:
30             creat_df.loc[mark] = ["力荐", i, 0]
31             mark += 1
32         location = info_new[(info_new.date==i)&(info_new.score=="推荐")].index.tolist()
33         if location == []:
34             creat_df.loc[mark] = ["推荐", i, 0]
35             mark += 1
36         location = info_new[(info_new.date==i)&(info_new.score=="还行")].index.tolist()
37         if location == []:
38             creat_df.loc[mark] = ["还行", i, 0]
39             mark += 1
40         location = info_new[(info_new.date==i)&(info_new.score=="较差")].index.tolist()
41         if location == []:
42             creat_df.loc[mark] = ["较差", i, 0]
43             mark += 1
44         location = info_new[(info_new.date==i)&(info_new.score=="很差")].index.tolist()
45         if location == []:
46             creat_df.loc[mark] = ["很差", i, 0]
47             mark += 1
48     info_new = info_new.append(creat_df.drop_duplicates(), ignore_index=True)
49     score_list = []
50     info_new.sort_values(\'date\', inplace=True)    # 按日期升序排列df,便于找最早date和最晚data,方便后面插值
51     print(info_new)
52     for index, row in info_new.iterrows():   # 第二种遍历df的方法
53         score_list.append([row[\'date\'], row[\'votes\'], row[\'score\']])
54     tr = ThemeRiver()
55     tr.add([\'力荐\', \'推荐\', \'还行\', \'较差\', \'很差\'], score_list, is_label_show=True, is_more_utils=True)
56     page.add_chart(tr)
57 
58     attr, v1, v2, v3, v4, v5 = [], [], [], [], [], []
59     attr = list(sorted(set(info_new[\'date\'])))
60     bar = Bar()
61     for i in attr:
62         v1.append(int(info_new[(info_new[\'date\']==i)&(info_new[\'score\']=="力荐爬取豆瓣网影评数据并进行简单分析与展示

【python爬虫实战】爬取豆瓣影评数据

python爬取豆瓣影评,根据关键词生成词云图

Python登录豆瓣并爬取影评

python爬虫爬取豆瓣影评返回403怎么办,代理IP和cookie都设置了

Python豆瓣电影《肖申克的救赎》评论爬取