最近几年猫眼电影愈来愈热门了,都差很少和豆瓣并驾齐驱了。今年的《流浪地球》这么火,经过爬取猫眼电影上网友对该片的评价如何。javascript
先打开猫眼官网找到《流浪地球》的介绍页面:https://maoyan.com/films/248906java
虽然显示有112.4万人评分,可是页面只有热门短评,其余评论都去哪里了,手机明明是有的。python
那么咱们用chrome
切换到手机页面:git
这时候咱们就看到了全部的评论。github
在点击打开“查看所有330613条讨论”后,发现评论分为最热和最新两部分,最热数量有限,而最新则是未通过处理的,也正是咱们须要的。经过search
来查看下对应的请求:web
发现,在chrome
的网络展现中发现只有一个类型为document
的请求包含了所需的信息。那么这部分的评论获取就须要解析网页了,咱们再把屏幕上的评论往下拉,发现会自动加载更多的评论,对应的chrome
网络请求多出来了两个comments.json
的请求:chrome
果真这才是咱们须要的!把初始页面的url
和这两个json
请求的url
复制到一块儿比较一下:shell
http://m.maoyan.com/review/v2/comments.json?movieId=248906&userId=-1&offset=0&limit=15&ts=0&type=3 http://m.maoyan.com/review/v2/comments.json?movieId=248906&userId=-1&offset=15&limit=15&ts=1549965527295&type=3 http://m.maoyan.com/review/v2/comments.json?movieId=248906&userId=-1&offset=30&limit=15&ts=1549965527295&type=3
咱们能够发现规律:json
ts
值为0,随后会有ts
值,且保持不变。这里的ts
是当前的时间戳,能够经过转换工具查看:再看返回的json
结果:api
data.comments
中是评论的具体内容paging
中经过hasMore
来告诉咱们是否还有更多(判断是否继续抓取)咱们再尝试下将offset
设置为0,也加上ts
参数:
http://m.maoyan.com/review/v2/comments.json?movieId=248906&userId=-1&offset=0&limit=15&ts=1549965527295&type=3
发现也是能够获取数据的:
那么经过offset
和limit
来控制每次请求获取的数量。
咱们还能够经过加大limit
参数来尝试,是否能够一次性获取更多的评论:
http://m.maoyan.com/review/v2/comments.json?movieId=248906&userId=-1&offset=0&limit=30&ts=1549965527295&type=3
效果以下:
再增长limit
的值,会发现评论数回到了15
条,可见猫眼系统仅支持每次最多获取30条。
根据上面的分析,咱们构造请求的url
就很明确了:
offset=0&limit=30
开始paging.hasMore
来判断是否继续抓取url
中offset+=limit
根据上述分析,在返回的json
数据中是能够看到总评论数的,可是实际抓取的时候,在offset
超过1000以后,返回的数据中hasMore
就变成了false
。
因而尝试经过浏览器一直下拉刷新,到达offset
超过1000的状况,发现页面会不停的发送请求,但也没法获取数据。
那应该就是网站作了控制,不容许offset
超过1000。
那么就要考虑其余构造url的方法来抓取了。先观察下每一个请求返回的信息:
发现每一个comment
里都包含有一个time
信息,把time
作一下处理:
2019-02-13 13:38:00##感受韩朵朵这我的设是多余的 2019-02-13 13:38:00##真的感动 很是棒 2019-02-13 13:38:00##这电影大陆的起航 2019-02-13 13:38:00##不怎么样,剧情挺感人,可是有点尴尬 2019-02-13 13:37:00##好看。。。。。。。。。。 2019-02-13 13:37:00##超级超级超级超级超级超级超级好看 2019-02-13 13:37:00##太牛逼了,中国科幻片可有一部能看的了。支持吴京 2019-02-13 13:36:00##不错!中国科幻的但愿 2019-02-13 13:36:00##中国里程碑式的科幻电影。 2019-02-13 13:36:00##什么垃圾座位没人管的么乱坐的
能够发现后台是按照时间顺序的,每分钟一个间隔,那么就能够考虑根据每次返回comment
中的时间来更新url
中的ts
便可。
因为不肯定每次请求返回的数据中包含了多长的时间段,且返回的第一个评论时间戳与第二个评论是不一样的,因此抓取思路以下:
ts
设置为第二个时间戳,从新构造url
offset
来继续抓取,直到遇到第三个时间戳根据上面思路,实现相对就比较简单了:
生成url
def get_url(): global offset url = 'http://m.maoyan.com/review/v2/comments.json?movieId=' + movieId + '&userId=-1&offset=' + str( offset) + '&limit=' + str(limit) + '&ts=' + str(ts) + '&type=3' return url
访问url
def open_url(url): global ua try: headers = {'User-Agent': ua.random} response = requests.get(url, headers=headers) if response.status_code == 200: return response.text else: return None except Exception as e: print(e) return None
数据处理:将评论保存并判断是否要继续抓取
def parse_json(data): global count global offset global limit global ts ts_duration = ts res = json.loads(data) comments = res['data']['comments'] for comment in comments: comment_time = comment['time'] if ts == 0: ts = comment_time ts_duration = comment_time if comment_time != ts and ts == ts_duration: ts_duration = comment_time if comment_time !=ts_duration: ts = ts_duration offset = 0 return get_url() else: content = comment['content'].strip().replace('\n', '。') print('get comment ' + str(count)) count += 1 write_txt(time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(comment_time/1000)) + '##' + content + '\n') if res['paging']['hasMore']: offset += limit return get_url() else: return None
最后一共抓取评论131106条,足够作各类分析了
2019-02-13 18:13:11,625 - get_comments.py[line:78] - INFO: get comment 131104 2019-02-13 18:13:11,729 - get_comments.py[line:78] - INFO: get comment 131105 2019-02-13 18:13:11,827 - get_comments.py[line:78] - INFO: get comment 131106 2019-02-13 18:13:15,416 - get_comments.py[line:98] - INFO: end
前面咱们再抓取时,将评论的时间和内容经过csv
的格式保存下来,并使用;
分割。读取csv
文件并统计处理就要用到大名鼎鼎的pandas
了。
读取数据
pandas
提供read_csv
方法来直接独处数据保存为DateFrame
格式。
df = pd.read_csv('comment.csv', sep=';', header=None)
设置数据列名
因为咱们知道数据有两列,先经过这只列名能够方便后续引用。
df.columns = ['date', 'comment']
时间日期处理
在date
列,咱们保存的数据格式是string
,须要把转换为日期格式才能进一步处理。
df['date'] = pd.to_datetime(df['date'])
咱们须要按时间来统计,因此把date
列设置为index
:
df = df.set_index('date')
日期筛选
因为咱们知道《流浪地球》是2月5日上映的,咱们能够对日期进行限定,以避免出现有些在上映前的评论,会占用大段的空白状况。
设置index
以后,能够参考list
类型操做,因为时间是倒序的,因此能够直接使用[:'2019-02-04']
来选取2月4日以后到今天的全部数据。pandas
在数据筛选方面至关智能,按照datetime
的格式直接筛选便可。
cacu_df = df[:'2019-02-04']
按日期进行数量统计
pandas
中,经过resample
方法进行从新采样,经过传入rule
参数就能够按须要的频率获取数据,得到一个resampler
对象。
DataFrame.resample(rule, how=None, axis=0, fill_method=None, closed=None, label=None, convention='start', kind=None, loffset=None, limit=None, base=0, on=None, level=None)
resampler
对象提供了不少的统计方法,好比汇总求和可以使用Resampler.count()
。
# 按日统计数量 cacu = cacu_df.resample('D').count()
这样就完成了按日期求和统计操做。
绘图
画图须要使用matplotlib
库,经过导入该库,可直接对DateFrame
对象进行画图处理。画图及图表格式化以下:
# 设置中文字体 font = FontProperties(fname='/System/Library/Fonts/PingFang.ttc') plt.plot(cacu) plt.title("流浪地球评论分析", fontproperties=font) plt.xlabel("日期", fontproperties=font) plt.ylabel("评论数", fontproperties=font) plt.axis("tight") # 显示网格 plt.grid(True) # 自动旋转横轴日期 plt.gcf().autofmt_xdate() # 显示数值 for a, b in zip(cacu.index, cacu.values): plt.text(a, b, str(b[0])) # 保存图片 plt.savefig('comment_analysis.png') # 查看图片 plt.show()
结果以下:
可见从上映以后,关注度直线飙升,到2月10日以后(上映5天),你们关注度逐渐降低。其中2月14日为情人节,你们的关注又有了小幅的上升。也许不少人在这天经过看《流浪地球》过节吧。
数据清洗
首先因为评论是用户发表的,可能什么字符都会有,要先把一些特殊符号去掉,这里就用到了正则替换:
msg = re.sub("[\s+\.\!\/_,$%^*()+\"\'\?]+|[+——!,。?、~@#¥%……&*()【】;:]+|\[.+\]|\[.+\]", "", line)
分词与标签
清洗后的数据,可使用jieba
分词包来进行分词,并把全部的分词保存在一个list
中,而后计算出每一个分词出现的次数。
# 分词 tags = jieba.analyse.extract_tags(msg) for t in tags: word_list.append(t) # 计算词频 for word in word_list: if word not in word_dict: word_dict[word] = 1 else: word_dict[word] += 1
生成词云
使用wordcloud
包,就能够很方便的生成词云图片了。
先新建一个WordCloud
对象,进行配置,而后利用前面的分词词频就能够生成对应的图片了。
# 计算图片颜色 alice_coloring = np.array(img) my_wordcloud = WordCloud(background_color="white", max_words=500, mask=alice_coloring, max_font_size=200, random_state=42, font_path=(os.path.join(d, "font/msyh.ttf"))) my_wordcloud = my_wordcloud.generate_from_frequencies(wordList)
这里须要注意的是:
mask=alice_coloring
:这里经过numpy
将图片矩阵化,来获取图片的颜色做为WordCloud
的mask
,是为了最后生成的图云不只外形与咱们输入的图片保持一致,并且总体颜色也保持一致。保存图片
最后使用matplotlib.pyplot
来保存图片,保存前要进行图片属性的一些设置。
width = img.width/80 height = img.height/80 plt.figure(figsize=(width, height)) plt.imshow(my_wordcloud.recolor(color_func=image_colors)) plt.imshow(my_wordcloud) plt.axis("off") # 经过设置subplots_adjust来控制画面外边框 plt.subplots_adjust(bottom=.01, top=.99, left=.01, right=.99) plt.savefig("jupiter_wordcloud_1.png") plt.show()
这里须要注意的是: 建议根据原图片的长宽比例进行必定的缩小,以避免生成的图片像素过大而产生报错。
ValueError: Image size of 98400x46500 pixels is too large. It must be less than 2^16 in each direction.
放一张原图,你能看的出来嘛,抠图技术有限O(∩_∩)O哈哈~
以上就是使用抓取的评论生成词云的大体思路,完成的实现代码请见:https://github.com/keejo125/w...
若是有更好的方法,欢迎一块儿探讨。