转载:[Python] 通过采集两万条数据,对《无名之辈》影评分析
一 、说明
本文主要讲述采集猫眼电影用户评论进行分析,相关爬虫采集程序可以爬取多个电影评论。
运行环境:Win10/Python3.5。
分析工具:jieba、wordcloud、pyecharts、matplotlib。
基本流程:下载内容 —> 分析获取关键数据 —> 保存本地文件 —> 分析本地文件制作图表
注意:本文所有图文和源码 仅供学习 ,请勿他用,转发请注明出处!
本文主要参考:https://mp.weixin.qq.com/s/mTxxkwRZPgBiKC3Sv-jo3g
二、开始采集
2.1、分析数据接口:
为了健全数据样本,数据直接从移动端接口进行采集,连接如下,其中橙色部分为猫眼电影ID,修改即可爬取其他电影。
链接地址:http://m.maoyan.com/mmdb/comments/movie/ 1208282 .json?v=yes&offset=15&startTime=
接口返回的数据如下,主要采集(昵称、城市、评论、评分和时间),用户评论在 json[‘cmts’]中:
2.2、爬虫程序核心内容(详细可以看后面源代码):
>启动脚本需要的参数如下(脚本名+猫眼电影ID+上映日期+数据保存的文件名):.\myMovieComment.py 1208282 2016-11-16 myCmts2.txt
>下载html内容:download(self, url),通过python的requests模块进行下载,将下载的数据转成json格式
1 def download(self, url):
2 """下载html内容"""
3
4 print("正在下载URL: "+url)
5 # 下载html内容
6 response = requests.get(url, headers=self.headers)
7
8 # 转成json格式数据
9 if response.status_code == 200:
10 return response.json()
11 else:
12 # print(html.status_code)
13 print('下载数据为空!')
14 return ""
>然后就是对已下载的内容进行分析,就是取出我们需要的数据:
1 def parse(self, content):
2 """分析数据"""
3
4 comments = []
5 try:
6 for item in content['cmts']:
7 comment = {
8 'nickName': item['nickName'], # 昵称
9 'cityName': item['cityName'], # 城市
10 'content': item['content'], # 评论内容
11 'score': item['score'], # 评分
12 'startTime': item['startTime'], # 时间
13 }
14 comments.append(comment)
15
16 except Exception as e:
17 print(e)
18
19 finally:
20 return comments
>将分析出来的数据,进行本地保存,方便后续的分析工作:
1 def save(self, data):
2 """写入文件"""
3
4 print("保存数据,写入文件中...")
5 self.save_file.write(data)
> 爬虫的核心控制也即爬虫的程序启动入口,管理上面几个方法的有序执行:
1 def start(self):
2 """启动控制方法"""
3
4 print("爬虫开始...\r\n")
5
6 start_time = self.start_time
7 end_time = self.end_time
8
9 num = 1
10 while start_time > end_time:
11 print("执行次数:", num)
12 # 1、下载html
13 content = self.download(self.target_url + str(start_time))
14
15 # 2、分析获取关键数据
16 comments = ''
17 if content != "":
18 comments = self.parse(content)
19
20 if len(comments) <= 0:
21 print("本次数据量为:0,退出爬取!\r\n")
22 break
23
24 # 3、写入文件
25 res = ''
26 for cmt in comments:
27 res += "%s###%s###%s###%s###%s\n" % (cmt['nickName'], cmt['cityName'], cmt['content'], cmt['score'], cmt['startTime'])
28 self.save(res)
29
30 print("本次数据量:%s\r\n" % len(comments))
31
32 # 获取最后一条数据的时间 ,然后减去一秒
33 start_time = datetime.strptime(comments[len(comments) - 1]['startTime'], "%Y-%m-%d %H:%M:%S") + timedelta(seconds=-1)
34 # start_time = datetime.strptime(start_time, "%Y-%m-%d %H:%M:%S")
35
36 # 休眠3s
37 num += 1
38 time.sleep(3)
39
40 self.save_file.close()
41 print("爬虫结束...")
2.3 数据样本,最终爬取将近2万条数据,每条记录的每个数据使用 ### 进行分割:
三、图形化分析数据
3.1、制作观众城市分布热点图,( pyecharts-geo ):
从图表可以轻松看出,用户主要分布地区,主要以沿海一些发达城市群为主:
1 def createCharts(self):
2 """生成图表"""
3
4 # 读取数据,格式:[{"北京", 10}, {"上海",10}]
5 data = self.readCityNum()
6
7 # 1 热点图
8 geo1 = Geo("《无名之辈》观众位置分布热点图", "数据来源:猫眼,Fly采集", title_color="#FFF", title_pos="center", width="100%", height=600, background_color="#404A59")
9
10 attr1, value1 = geo1.cast(data)
11
12 geo1.add("", attr1, value1, type="heatmap", visual_range=[0, 1000], visual_text_color="#FFF", symbol_size=15, is_visualmap=True, is_piecewise=False, visual_split_number=10)
13 geo1.render("files/无名之辈-观众位置热点图.html")
14
15 # 2 位置图
16 geo2 = Geo("《无名之辈》观众位置分布", "数据来源:猫眼,Fly采集", title_color="#FFF", title_pos="center", width="100%", height=600,
17 background_color="#404A59")
18
19 attr2, value2 = geo1.cast(data)
20 geo2.add("", attr2, value2, visual_range=[0, 1000], visual_text_color="#FFF", symbol_size=15,
21 is_visualmap=True, is_piecewise=False, visual_split_number=10)
22 geo2.render("files/无名之辈-观众位置图.html")
23
24 # 3、top20 柱状图
25 data_top20 = data[:20]
26 bar = Bar("《无名之辈》观众来源排行 TOP20", "数据来源:猫眼,Fly采集", title_pos="center", width="100%", height=600)
27 attr, value = bar.cast(data_top20)
28 bar.add('', attr, value, is_visualmap=True, visual_range=[0, 3500], visual_text_color="#FFF", is_more_utils=True, is_label_show=True)
29 bar.render("files/无名之辈-观众来源top20.html")
30
31 print("图表生成完成")
3.2、制作观众人数TOP20的柱形图,( pyecharts-bar ):
3.3、制作评论词云,( jieba、wordcloud ):
生成词云核心代码:
1 def createWordCloud(self):
2 """生成评论词云"""
3 comments = self.readAllComments() # 19185
4
5 # 使用 jieba 分词
6 commens_split = jieba.cut(str(comments), cut_all=False)
7 words = ''.join(commens_split)
8
9 # 给词库添加停止词
10 stopwords = STOPWORDS.copy()
11 stopwords.add("电影")
12 stopwords.add("一部")
13 stopwords.add("无名之辈")
14 stopwords.add("一部")
15 stopwords.add("一个")
16 stopwords.add("有点")
17 stopwords.add("觉得")
18
19 # 加载背景图片
20 bg_image = plt.imread("files/2048_bg.png")
21
22 # 初始化 WordCloud
23 wc = WordCloud(width=1200, height=600, background_color='#FFF', mask=bg_image, font_path='C:/Windows/Fonts/STFANGSO.ttf', stopwords=stopwords, max_font_size=400, random_state=50)
24
25 # 生成,显示图片
26 wc.generate_from_text(words)
27 plt.imshow(wc)
28 plt.axis('off')
29 plt.show()
四、修改pyecharts源码
4.1、样本数据的城市简称与数据集完整城市名匹配不上:
使用位置热点图时候,由于采集数据城市是一些简称,与pyecharts的已存在数据的城市名对不上, 所以对源码进行一些修改,方便匹配一些简称。
黔南 => 黔南布依族苗族自治州
模块自带的全国主要市县经纬度在:[python安装路径]\Lib\site-packages\pyecharts\datasets\city_coordinates.json
由于默认情况下,一旦城市名不能完全匹配就会报异常,程序会停止,所以对源码修改如下(报错方法为 Geo.add() ),其中添加注析为个人修改部分:
1 def get_coordinate(self, name, region="中国", raise_exception=False):
2 """
3 Return coordinate for the city name.
4
5 :param name: City name or any custom name string.
6 :param raise_exception: Whether to raise exception if not exist.
7 :return: A list like [longitude, latitude] or None
8 """
9 if name in self._coordinates:
10 return self._coordinates[name]
11
12
13 coordinate = get_coordinate(name, region=region)
14
15 # [ 20181204 添加
16 # print(name, coordinate)
17 if coordinate is None:
18 # 如果字典key匹配不上,尝试进行模糊查询
19 search_res = search_coordinates_by_region_and_keyword(region, name)
20 # print("###",search_res)
21 if search_res:
22 coordinate = sorted(search_res.values())[0]
23 # 20181204 添加 ]
24
25 if coordinate is None and raise_exception:
26 raise ValueError("No coordinate is specified for {}".format(name))
27
28 return coordinate
相应的需要对 __add()方法进行如下修改:
五、附录-源码
*说明:源码为本人所写,数据来源为猫眼,全部内容仅供学习,拒绝其他用途!转发请注明出处!
5.1 采集源码
1 # -*- coding:utf-8 -*-
2
3 import requests
4 from datetime import datetime, timedelta
5 import os
6 import time
7 import sys
8
9
10 class MaoyanFilmReviewSpider:
11 """猫眼影评爬虫"""
12
13 def __init__(self, url, end_time, filename):
14 # 头部
15 self.headers = {
16 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1'
17 }
18
19 # 目标URL
20 self.target_url = url
21
22 # 数据获取时间段,start_time:截止日期,end_time:上映时间
23 now = datetime.now()
24
25 # 获取当天的 零点
26 self.start_time = now + timedelta(hours=-now.hour, minutes=-now.minute, seconds=-now.second)
27 self.start_time = self.start_time.replace(microsecond=0)
28 self.end_time = datetime.strptime(end_time, "%Y-%m-%d %H:%M:%S")
29
30 # 打开写入文件, 创建目录
31 self.save_path = "files/"
32 if not os.path.exists(self.save_path):
33 os.makedirs(self.save_path)
34 self.save_file = open(self.save_path + filename, "a", encoding="utf-8")
35
36 def download(self, url):
37 """下载html内容"""
38
39 print("正在下载URL: "+url)
40 # 下载html内容
41 response = requests.get(url, headers=self.headers)
42
43 # 转成json格式数据
44 if response.status_code == 200:
45 return response.json()
46 else:
47 # print(html.status_code)
48 print('下载数据为空!')
49 return ""
50
51 def parse(self, content):
52 """分析数据"""
53
54 comments = []
55 try:
56 for item in content['cmts']:
57 comment = {
58 'nickName': item['nickName'], # 昵称
59 'cityName': item['cityName'], # 城市
60 'content': item['content'], # 评论内容
61 'score': item['score'], # 评分
62 'startTime': item['startTime'], # 时间
63 }
64 comments.append(comment)
65
66 except Exception as e:
67 print(e)
68
69 finally:
70 return comments
71
72 def save(self, data):
73 """写入文件"""
74
75 print("保存数据,写入文件中...")
76 self.save_file.write(data)
77
78 def start(self):
79 """启动控制方法"""
80
81 print("爬虫开始...\r\n")
82
83 start_time = self.start_time
84 end_time = self.end_time
85
86 num = 1
87 while start_time > end_time:
88 print("执行次数:", num)
89 # 1、下载html
90 content = self.download(self.target_url + str(start_time))
91
92 # 2、分析获取关键数据
93 comments = ''
94 if content != "":
95 comments = self.parse(content)
96
97 if len(comments) <= 0:
98 print("本次数据量为:0,退出爬取!\r\n")
99 break
100
101 # 3、写入文件
102 res = ''
103 for cmt in comments:
104 res += "%s###%s###%s###%s###%s\n" % (cmt['nickName'], cmt['cityName'], cmt['content'], cmt['score'], cmt['startTime'])
105 self.save(res)
106
107 print("本次数据量:%s\r\n" % len(comments))
108
109 # 获取最后一条数据的时间 ,然后减去一秒
110 start_time = datetime.strptime(comments[len(comments) - 1]['startTime'], "%Y-%m-%d %H:%M:%S") + timedelta(seconds=-1)
111 # start_time = datetime.strptime(start_time, "%Y-%m-%d %H:%M:%S")
112
113 # 休眠3s
114 num += 1
115 time.sleep(3)
116
117 self.save_file.close()
118 print("爬虫结束...")
119
120
121 if __name__ == "__main__":
122 # 确保输入参数
123 if len(sys.argv) != 4:
124 print("请输入相关参数:[moveid]、[上映日期]和[保存文件名],如:xxx.py 42962 2018-11-09 text.txt")
125 exit()
126
127 # 猫眼电影ID
128 mid = sys.argv[1] # "1208282" # "42964"
129 # 电影上映日期
130 end_time = sys.argv[2] # "2018-11-16" # "2018-11-09"
131 # 每次爬取条数
132 offset = 15
133 # 保存文件名
134 filename = sys.argv[3]
135
136 spider = MaoyanFilmReviewSpider(url="http://m.maoyan.com/mmdb/comments/movie/%s.json?v=yes&offset=%d&startTime=" % (mid, offset), end_time="%s 00:00:00" % end_time, filename=filename)
137 # spider.start()
138
139 spider.start()
140 # t1 = "2018-11-09 23:56:23"
141 # t2 = "2018-11-25"
142 #
143 # res = datetime.strptime(t1, "%Y-%m-%d %H:%M:%S") + timedelta(days=-1)
144 # print(type(res))
MaoyanFilmReviewSpider.py
5.2 分析制图源码
1 # -*- coding:utf-8 -*-
2 from pyecharts import Geo, Bar, Bar3D
3 import jieba
4 from wordcloud import STOPWORDS, WordCloud
5 import matplotlib.pyplot as plt
6
7
8 class ACoolFishAnalysis:
9 """无名之辈 --- 数据分析"""
10 def __init__(self):
11 pass
12
13 def readCityNum(self):
14 """读取观众城市分布数量"""
15 d = {}
16
17 with open("files/myCmts2.txt", "r", encoding="utf-8") as f:
18 row = f.readline()
19
20 while row != "":
21 arr = row.split('###')
22
23 # 确保每条记录长度为 5
24 while len(arr) < 5:
25 row += f.readline()
26 arr = row.split('###')
27
28 # 记录每个城市的人数
29 if arr[1] in d:
30 d[arr[1]] += 1
31 else:
32 d[arr[1]] = 1 # 首次加入字典,为 1
33
34 row = f.readline()
35
36
37 # print(len(comments))
38 # print(d)
39
40 # 字典 转 元组数组
41 res = []
42 for ks in d.keys():
43 if ks == "":
44 continue
45 tmp = (ks, d[ks])
46 res.append(tmp)
47
48 # 按地点人数降序
49 res = sorted(res, key=lambda x: (x[1]),reverse=True)
50 return res
51
52 def readAllComments(self):
53 """读取所有评论"""
54 comments = []
55
56 # 打开文件读取数据
57 with open("files/myCmts2.txt", "r", encoding="utf-8") as f:
58 row = f.readline()
59
60 while row != "":
61 arr = row.split('###')
62
63 # 每天记录长度为 5
64 while len(arr) < 5:
65 row += f.readline()
66 arr = row.split('###')
67
68 if len(arr) == 5:
69 comments.append(arr[2])
70
71 # if len(comments) > 20:
72 # break
73 row = f.readline()
74
75 return comments
76
77 def createCharts(self):
78 """生成图表"""
79
80 # 读取数据,格式:[{"北京", 10}, {"上海",10}]
81 data = self.readCityNum()
82
83 # 1 热点图
84 geo1 = Geo("《无名之辈》观众位置分布热点图", "数据来源:猫眼,Fly采集", title_color="#FFF", title_pos="center", width="100%", height=600, background_color="#404A59")
85
86 attr1, value1 = geo1.cast(data)
87
88 geo1.add("", attr1, value1, type="heatmap", visual_range=[0, 1000], visual_text_color="#FFF", symbol_size=15, is_visualmap=True, is_piecewise=False, visual_split_number=10)
89 geo1.render("files/无名之辈-观众位置热点图.html")
90
91 # 2 位置图
92 geo2 = Geo("《无名之辈》观众位置分布", "数据来源:猫眼,Fly采集", title_color="#FFF", title_pos="center", width="100%", height=600,
93 background_color="#404A59")
94
95 attr2, value2 = geo1.cast(data)
96 geo2.add("", attr2, value2, visual_range=[0, 1000], visual_text_color="#FFF", symbol_size=15,
97 is_visualmap=True, is_piecewise=False, visual_split_number=10)
98 geo2.render("files/无名之辈-观众位置图.html")
99
100 # 3、top20 柱状图
101 data_top20 = data[:20]
102 bar = Bar("《无名之辈》观众来源排行 TOP20", "数据来源:猫眼,Fly采集", title_pos="center", width="100%", height=600)
103 attr, value = bar.cast(data_top20)
104 bar.add('', attr, value, is_visualmap=True, visual_range=[0, 3500], visual_text_color="#FFF", is_more_utils=True, is_label_show=True)
105 bar.render("files/无名之辈-观众来源top20.html")
106
107 print("图表生成完成")
108
109 def createWordCloud(self):
110 """生成评论词云"""
111 comments = self.readAllComments() # 19185
112
113 # 使用 jieba 分词
114 commens_split = jieba.cut(str(comments), cut_all=False)
115 words = ''.join(commens_split)
116
117 # 给词库添加停止词
118 stopwords = STOPWORDS.copy()
119 stopwords.add("电影")
120 stopwords.add("一部")
121 stopwords.add("无名之辈")
122 stopwords.add("一部")
123 stopwords.add("一个")
124 stopwords.add("有点")
125 stopwords.add("觉得")
126
127 # 加载背景图片
128 bg_image = plt.imread("files/2048_bg.png")
129
130 # 初始化 WordCloud
131 wc = WordCloud(width=1200, height=600, background_color='#FFF', mask=bg_image, font_path='C:/Windows/Fonts/STFANGSO.ttf', stopwords=stopwords, max_font_size=400, random_state=50)
132
133 # 生成,显示图片
134 wc.generate_from_text(words)
135 plt.imshow(wc)
136 plt.axis('off')
137 plt.show()
138
139
140
141 if __name__ == "__main__":
142 demo = ACoolFishAnalysis()
143 demo.createWordCloud()