简介

刚接触python不久,做一个小项目来练练手。前几天看了《战狼2》,发现它在最新上映的电影里面是排行第一的,如下图所示。准备把豆瓣上对它的影评做一个分析。

目标总览

主要做了三件事:

  • 抓取网页数据
  • 清理数据
  • 用词云进行展示
    使用的python版本是3.5.

一、抓取网页数据

第一步要对网页进行访问,python中使用的是urllib库。代码如下:

from urllib import request
resp = request.urlopen('https://movie.douban.com/nowplaying/hangzhou/')
html_data = resp.read().decode('utf-8')

其中https://movie.douban.com/nowp...是豆瓣最新上映的电影页面,可以在浏览器中输入该网址进行查看。
html_data是字符串类型的变量,里面存放了网页的html代码。
输入print(html_data)可以查看,如下图所示:

第二步,需要对得到的html代码进行解析,得到里面提取我们需要的数据。
在python中使用BeautifulSoup库进行html代码的解析。
(注:如果没有安装此库,则使用pip install BeautifulSoup进行安装即可!)
BeautifulSoup使用的格式如下:

BeautifulSoup(html,"html.parser")

第一个参数为需要提取数据的html,第二个参数是指定解析器,然后使用find_all()读取html标签中的内容。

但是html中有这么多的标签,该读取哪些标签呢?其实,最简单的办法是我们可以打开我们爬取网页的html代码,然后查看我们需要的数据在哪个html标签里面,再进行读取就可以了。如下图所示:

从上图中可以看出在div id="nowplaying"标签开始是我们想要的数据,里面有电影的名称、评分、主演等信息。所以相应的代码编写如下:

from bs4 import BeautifulSoup as bs
soup = bs(html_data, 'html.parser')    
nowplaying_movie = soup.find_all('div', id='nowplaying')
nowplaying_movie_list = nowplaying_movie[0].find_all('li', class_='list-item') 

其中nowplaying_movie_list 是一个列表,可以用print(nowplaying_movie_list[0])查看里面的内容,如下图所示:

在上图中可以看到data-subject属性里面放了电影的id号码,而在img标签的alt属性里面放了电影的名字,因此我们就通过这两个属性来得到电影的id和名称。(注:打开电影短评的网页时需要用到电影的id,所以需要对它进行解析),编写代码如下:

nowplaying_list = [] 
for item in nowplaying_movie_list:        
        nowplaying_dict = {}        
        nowplaying_dict['id'] = item['data-subject']       
        for tag_img_item in item.find_all('img'):            
            nowplaying_dict['name'] = tag_img_item['alt']            
            nowplaying_list.append(nowplaying_dict)  

其中列表nowplaying_list中就存放了最新电影的id和名称,可以使用print(nowplaying_list)进行查看,如下图所示:

可以看到和豆瓣网址上面是匹配的。这样就得到了最新电影的信息了。接下来就要进行对最新电影短评进行分析了。例如《战狼2》的短评网址为:https://movie.douban.com/subject/26363254/comments?start=0&limit=20
其中26363254就是电影的id,start=0表示评论的第0条评论。

接下来接对该网址进行解析了。打开上图中的短评页面的html代码,我们发现关于评论的数据是在div标签的comment属性下面,如下图所示:

因此对此标签进行解析,代码如下:

requrl = 'https://movie.douban.com/subject/' + nowplaying_list[0]['id'] + '/comments' +'?' +'start=0' + '&limit=20' 
resp = request.urlopen(requrl) 
html_data = resp.read().decode('utf-8') 
soup = bs(html_data, 'html.parser') 
comment_div_lits = soup.find_all('div', class_='comment') 

此时在comment_div_lits 列表中存放的就是div标签和comment属性下面的html代码了。在上图中还可以发现在p标签下面存放了网友对电影的评论,如下图所示:

因此对comment_div_lits 代码中的html代码继续进行解析,代码如下:

eachCommentList = []; 
for item in comment_div_lits: 
        if item.find_all('p')[0].string is not None:     
            eachCommentList.append(item.find_all('p')[0].string)

使用print(eachCommentList)查看eachCommentList列表中的内容,可以看到里面存里我们想要的影评。如下图所示:

好的,至此我们已经爬取了豆瓣最近播放电影的评论数据,接下来就要对数据进行清洗和词云显示了。

二、数据清洗

为了方便进行数据进行清洗,我们将列表中的数据放在一个字符串数组中,代码如下:

comments = ''
for k in range(len(eachCommentList)):
    comments = comments + (str(eachCommentList[k])).strip()

使用print(comments)进行查看,如下图所示:

可以看到所有的评论已经变成一个字符串了,但是我们发现评论中还有不少的标点符号等。这些符号对我们进行词频统计时根本没有用,因此要将它们清除。所用的方法是正则表达式。python中正则表达式是通过re模块来实现的。代码如下:

import re

pattern = re.compile(r'[\u4e00-\u9fa5]+')
filterdata = re.findall(pattern, comments)
cleaned_comments = ''.join(filterdata)

继续使用print(cleaned_comments)语句进行查看,如下图所示:


我们可以看到此时评论数据中已经没有那些标点符号了,数据变得“干净”了很多。

因此要进行词频统计,所以先要进行中文分词操作。在这里我使用的是结巴分词。如果没有安装结巴分词,可以在控制台使用pip install jieba进行安装。(注:可以使用pip list查看是否安装了这些库)。代码如下所示:

import jieba    #分词包
import pandas as pd  

segment = jieba.lcut(cleaned_comments)
words_df=pd.DataFrame({'segment':segment})

因为结巴分词要用到pandas,所以我们这里加载了pandas包。可以使用words_df.head()查看分词之后的结果,如下图所示:

从上图可以看到我们的数据中有“看”、“太”、“的”等虚词(停用词),而这些词在任何场景中都是高频时,并且没有实际的含义,所以我们要他们进行清除。

我把停用词放在一个stopwords.txt文件中,将我们的数据与停用词进行比对即可(注:只要在百度中输入stopwords.txt,就可以下载到该文件)。去停用词代码如下代码如下:

stopwords=pd.read_csv("stopwords.txt",index_col=False,quoting=3,sep="\t",names=['stopword'], encoding='utf-8')#quoting=3全不引用
words_df=words_df[~words_df.segment.isin(stopwords.stopword)]

继续使用words_df.head()语句来查看结果,如下图所示,停用词已经被出去了。

接下来就要进行词频统计了,代码如下:

import numpy    #numpy计算包
words_stat=words_df.groupby(by=['segment'])['segment'].agg({"计数":numpy.size})
words_stat=words_stat.reset_index().sort_values(by=["计数"],ascending=False)

words_stat.head()进行查看,结果如下:

由于我们前面只是爬取了第一页的评论,所以数据有点少,在最后给出的完整代码中,我爬取了10页的评论,所数据还是有参考价值。

三、用词云进行显示

代码如下:

import matplotlib.pyplot as plt
%matplotlib inline
import matplotlib
matplotlib.rcParams['figure.figsize'] = (10.0, 5.0)
from wordcloud import WordCloud#词云包

wordcloud=WordCloud(font_path="simhei.ttf",background_color="white",max_font_size=80) #指定字体类型、字体大小和字体颜色
word_frequence = {x[0]:x[1] for x in words_stat.head(1000).values}
word_frequence_list = []
for key in word_frequence:
    temp = (key,word_frequence[key])
    word_frequence_list.append(temp)

wordcloud=wordcloud.fit_words(word_frequence_list)
plt.imshow(wordcloud)

其中simhei.ttf使用来指定字体的,可以在百度上输入simhei.ttf进行下载后,放入程序的根目录即可。显示的图像如下:

到此为止,整个项目的介绍就结束了。由于自己也还是个初学者,接触python不久,代码写的并不好。而且第一次写技术博客,表达的有些冗余,请大家多多包涵,有不对的地方,请大家批评指正。以后我也会将自己做的小项目以这种形式写在博客上和大家一起交流!最后贴上完整的代码。

完整代码

#coding:utf-8
__author__ = 'hang'

import warnings
warnings.filterwarnings("ignore")
import jieba    #分词包
import numpy    #numpy计算包
import codecs   #codecs提供的open方法来指定打开的文件的语言编码,它会在读取的时候自动转换为内部unicode 
import re
import pandas as pd  
import matplotlib.pyplot as plt
from urllib import request
from bs4 import BeautifulSoup as bs
%matplotlib inline
import matplotlib
matplotlib.rcParams['figure.figsize'] = (10.0, 5.0)
from wordcloud import WordCloud#词云包

#分析网页函数
def getNowPlayingMovie_list():   
    resp = request.urlopen('https://movie.douban.com/nowplaying/hangzhou/')        
    html_data = resp.read().decode('utf-8')    
    soup = bs(html_data, 'html.parser')    
    nowplaying_movie = soup.find_all('div', id='nowplaying')        
    nowplaying_movie_list = nowplaying_movie[0].find_all('li', class_='list-item')    
    nowplaying_list = []    
    for item in nowplaying_movie_list:        
        nowplaying_dict = {}        
        nowplaying_dict['id'] = item['data-subject']       
        for tag_img_item in item.find_all('img'):            
            nowplaying_dict['name'] = tag_img_item['alt']            
            nowplaying_list.append(nowplaying_dict)    
    return nowplaying_list

#爬取评论函数
def getCommentsById(movieId, pageNum): 
    eachCommentList = []; 
    if pageNum>0: 
         start = (pageNum-1) * 20 
    else: 
        return False 
    requrl = 'https://movie.douban.com/subject/' + movieId + '/comments' +'?' +'start=' + str(start) + '&limit=20' 
    print(requrl)
    resp = request.urlopen(requrl) 
    html_data = resp.read().decode('utf-8') 
    soup = bs(html_data, 'html.parser') 
    comment_div_lits = soup.find_all('div', class_='comment') 
    for item in comment_div_lits: 
        if item.find_all('p')[0].string is not None:     
            eachCommentList.append(item.find_all('p')[0].string)
    return eachCommentList

def main():
    #循环获取第一个电影的前10页评论
    commentList = []
    NowPlayingMovie_list = getNowPlayingMovie_list()
    for i in range(10):    
        num = i + 1 
        commentList_temp = getCommentsById(NowPlayingMovie_list[0]['id'], num)
        commentList.append(commentList_temp)

    #将列表中的数据转换为字符串
    comments = ''
    for k in range(len(commentList)):
        comments = comments + (str(commentList[k])).strip()

    #使用正则表达式去除标点符号
    pattern = re.compile(r'[\u4e00-\u9fa5]+')
    filterdata = re.findall(pattern, comments)
    cleaned_comments = ''.join(filterdata)

    #使用结巴分词进行中文分词
    segment = jieba.lcut(cleaned_comments)
    words_df=pd.DataFrame({'segment':segment})

    #去掉停用词
    stopwords=pd.read_csv("stopwords.txt",index_col=False,quoting=3,sep="\t",names=['stopword'], encoding='utf-8')#quoting=3全不引用
    words_df=words_df[~words_df.segment.isin(stopwords.stopword)]

    #统计词频
    words_stat=words_df.groupby(by=['segment'])['segment'].agg({"计数":numpy.size})
    words_stat=words_stat.reset_index().sort_values(by=["计数"],ascending=False)

    #用词云进行显示
    wordcloud=WordCloud(font_path="simhei.ttf",background_color="white",max_font_size=80)
    word_frequence = {x[0]:x[1] for x in words_stat.head(1000).values}

    word_frequence_list = []
    for key in word_frequence:
        temp = (key,word_frequence[key])
        word_frequence_list.append(temp)

    wordcloud=wordcloud.fit_words(word_frequence_list)
    plt.imshow(wordcloud)

#主函数
main()

结果显示如下:

上图基本反映了《战狼2》这部电影的情况。PS:我本人并不喜欢这部电影,内容太空洞、太假,为了爱国而爱国,没意思。哎,这两年真是国产电影的低谷啊,没有一部拿得出手的国产电影,看看人家印度拍的《摔跤吧,爸爸》那才是拍的有深度,同样是表现爱国,国产电影还是需要向别的国家好好学学。

你可能感兴趣的文章

26 条评论
lart · 8月17日

%matplotlib inline是jupyer notebook 的命令

有个小问题,想提下。

word_frequence 为字典类型,可以直接传入wordcloud.fit_words()

word_frequence = {x[0]:x[1] for x in words_stat.head(1000).values}
wordcloud = wordcloud.fit_words(word_frequence)

这里,这样就可以了吧

+1 回复

k先生 · 8月8日

问博主,这个程序在jupyer notebook 还是其他什么环境下运行,在pycharm中无法运行%matplotlib inline,请指教,谢谢。

回复

白菜18650 · 8月13日

%matplotlib inline 无效语法?

回复

0

这个语法只在jupyter notebook或ipython notebook下才有效

robotc · 8月20日
东风造极 · 8月17日

顶楼上的,提供一份停词,https://github.com/wendy1990/...

回复

柯米 · 8月17日

请问画出来的图很模糊怎么办??

回复

0

可以给plt.savefig中的dpi赋一个高一点的值,或者在一开始就定义一个高一点的dpi值:matplotlib.rcParams['plt.dpi'] = 900
虽然增益并不是特别明显,但至少要稍微好一些

robotc · 8月20日
Ykkkk · 8月17日

有个错误的地方,爬取评论的函数,getCommentsById,里面start值错了,设想的是20一页,所以start=(n-1)*20,可能是某些评论过激被封了,一页评论要满20条,就要第20+条来补位,所以start不能简单的这么来计算。具体的值应该查看页面最下面的‘下一页’中的值,或者干脆就以这个下一页中a标签的链接来找下一页。

回复

Ykkkk · 8月17日

仔细看了下,文章说要爬影评https://movie.douban.com/subj...,结果爬的是短评https://movie.douban.com/subj...。影评的话就start值没有问题,每页20评论。
顺便还发现了豆瓣一个bug,在没有登录的情况下,在短评页面https://movie.douban.com/subj...,在不是第一页的页面,点前页,会提示没有权限要登陆。
此刻时间是:2017年8月17日23:40:24。(手动捂脸~)

回复

0

2017.8.30豆瓣这个Bug依旧存在。什么鬼?

QK1HPYS · 8月30日
yuming9230 · 8月19日

我想爬30页的评论,但每次到第十一页的时候就出现403,我用了代理也用了不同的header,请问这是为什么?

回复

0

在博主代码基础上,我按自己的需求做了修改,运行过程中也遇到了 403 的问题。一个原因是有些电影没有短影评,如《建军大业》和个别新出的电影,后来用状态码判断来解决的(我喜欢用 requestsurllib 的话你可以自己修改)

...
 r = requests.get(requrl)
 if r.status_code == 200:
...
schema · 8月23日
0

@schema
我爬的是战狼2的短评,是有30页以上的评论的,是不是豆瓣用了别的反爬方法?

yuming9230 · 8月23日
0

@yuming9230 我刚试了几次爬20页,也是到第11页就停了😅

schema · 8月23日
schema · 8月21日

教程写得很详细,代码注释写的也好,对新手非常友好,赞一个👍

在你代码的基础上,我用 Flask + Ajax 随机输出最近电影的一条短影评。

回复

豆芽太羊 · 8月22日

楼主你好,首先感谢分享教程。python小白按教程一步步做到最后,用的python自带的IDLE运行的,最后没能出来词云图,报错信息如下:
Traceback (most recent call last):
File "Z:Python学习爬虫_samplepa_chong_sample.py", line 155, in <module>

main()

File "Z:Python学习爬虫_samplepa_chong_sample.py", line 152, in main

wordcloud=wordcloud.fit_words(word_frequence_list)

File "X:Pythonlibsite-packageswordcloudwordcloud.py", line 331, in fit_words

return self.generate_from_frequencies(frequencies)

File "X:Pythonlibsite-packageswordcloudwordcloud.py", line 350, in generate_from_frequencies

frequencies = sorted(frequencies.items(), key=item1, reverse=True)

AttributeError: 'list' object has no attribute 'items'
因为%matplotlib inline这句提示无效语法,所以把它注释掉了。不知道词云的图会以什么方式呈现,是不是我编译器的问题?

回复

0

问题解决啦 按照1楼的说法改了一下,运行不出错了,但是还是显示不出图来。查了一下是因为我把那行代码注释掉了,如果不用LZ说的编译器用pycharm的话,解决方法是在最后加上一行plt.show()就可以了。
刚接触python,对编译器什么的也不太了解,提的问题有点傻,让lz见笑啦。

豆芽太羊 · 8月22日
0

我在pycharm运行有问题,请问plt.show()是加在主函数里面么?源代码主函数怎么就一个头子?

asia008 · 8月29日
0

@asia008 plt.show()加在主函数的最后一句 也就是plt.imshow(wordcloud)后面就可以了 主函数有内容啊 你仔细看看 上面不就是 def main():吗 你说的头子是最下面那行运行的语句吧?

豆芽太羊 · 8月29日
yanice · 8月23日
楼主你好,首先感谢分享,我按教程一步步做到最后,是用jupyter notebook 运行的,最后一步出错了,错误如下

AttributeError Traceback (most recent call last)
<ipython-input-50-ac0897241006> in <module>()

 12     word_frequence_list.append(temp)
 13 

---> 14 wordcloud=wordcloud.fit_words(word_frequence_list)

 15 plt.imshow(wordcloud)

C:Usersasussetuplibsite-packageswordcloudwordcloud.py in fit_words(self, frequencies)

329         self
330         """

--> 331 return self.generate_from_frequencies(frequencies)

332 
333     def generate_from_frequencies(self, frequencies, max_font_size=None):

C:Usersasussetuplibsite-packageswordcloudwordcloud.py in generate_from_frequencies(self, frequencies, max_font_size)

348         """
349         # make sure frequencies are sorted and normalized

--> 350 frequencies = sorted(frequencies.items(), key=item1, reverse=True)

351         if len(frequencies) <= 0:
352             raise ValueError("We need at least 1 word to plot a word cloud, "

AttributeError: 'list' object has no attribute 'items'

请问我该怎么解决呢?

回复

asia008 · 8月29日

我在pycharm运行有问题,源代码主函数怎么就一个头子,里面没内容?

回复

传奇 · 9月22日

报错了,说是找不到stopwords.txt,但是我在python根目录下,pandas目录下,.py文件所在的目录下都放了,还是不行,具体运行结果及报错如下:
https://movie.douban.com/subj...
https://movie.douban.com/subj...
https://movie.douban.com/subj...
https://movie.douban.com/subj...
https://movie.douban.com/subj...
https://movie.douban.com/subj...
https://movie.douban.com/subj...
https://movie.douban.com/subj...
https://movie.douban.com/subj...
https://movie.douban.com/subj...
Building prefix dict from the default dictionary ...
Loading model from cache C:UsersXUDONG~1AppDataLocalTempjieba.cache
Loading model cost 0.980 seconds.
Prefix dict has been built succesfully.
Traceback (most recent call last):
File "E:学习资源&工具pythonnew5.py", line 98, in <module>

main()

File "E:学习资源&工具pythonnew5.py", line 77, in main

stopwords=pd.read_csv("stopwords.txt",index_col=False,quoting=3,sep="\t",nam

es=['stopword'], encoding='utf-8')#quoting=3全不引用
File "C:UsersxudongshengAppDataLocalProgramsPythonPython35libsite-pac
kagespandasioparsers.py", line 655, in parser_f

return _read(filepath_or_buffer, kwds)

File "C:UsersxudongshengAppDataLocalProgramsPythonPython35libsite-pac
kagespandasioparsers.py", line 405, in _read

parser = TextFileReader(filepath_or_buffer, **kwds)

File "C:UsersxudongshengAppDataLocalProgramsPythonPython35libsite-pac
kagespandasioparsers.py", line 764, in init

self._make_engine(self.engine)

File "C:UsersxudongshengAppDataLocalProgramsPythonPython35libsite-pac
kagespandasioparsers.py", line 985, in _make_engine

self._engine = CParserWrapper(self.f, **self.options)

File "C:UsersxudongshengAppDataLocalProgramsPythonPython35libsite-pac
kagespandasioparsers.py", line 1605, in init

self._reader = parsers.TextReader(src, **kwds)

File "pandas_libsparsers.pyx", line 394, in pandas._libs.parsers.TextReader.
cinit (pandas_libsparsers.c:4209)
File "pandas_libsparsers.pyx", line 710, in pandas._libs.parsers.TextReader.
_setup_parser_source (pandas_libsparsers.c:8873)
FileNotFoundError: File b'stopwords.txt' does not exist
请按任意键继续. . .

回复

sophie983 · 10月10日

我看了你发的利用python分析豆瓣影评的帖子,对这个技术很感兴趣,想用在我们的一个课题里,能谈谈吗?

回复

0

@sophie983 好的

hang 作者 · 10月29日
载入中...
hang hang

62 声望