基于Python的豆瓣影评数据爬取及分析

基于Python的豆瓣影评数据爬取及分析

一、前言

豆瓣用户每天都在对“看过”的电影进行“很差”到“力荐”的评价,豆瓣根据每部影片看过的人数以及该影片所得的评价等综合数据,通过算法分析产生豆瓣电影 Top 250。对于这个榜单,我产生了一些疑问。比如,我喜欢的某个导演有多少部电影进入榜单?榜单中哪一类型的电影最多?电影的排名与评分有什么样的关系?为了回答这些问题,我将构建爬虫程序抓取豆瓣电影Top250影评信息,并进行简单的数据分析。

二、数据爬取

1. 数据抓取

观察豆瓣电影Top250任意一页URL地址的格式。可以看到第一页URL地址为“https://movie.douban.com/top250?start=0&filter= ”,第二页的URL地址为“https://movie.douban.com/top250?start=25&filter= ”,并且每一页都展示25条豆瓣影评数据。

分析这些URL地址:

http://代表资源传输使用https协议
movie.douban.com 是豆瓣的二级域名,指向豆瓣的服务器。
/top250 是服务器的某个资源,即豆瓣电影Top250的地址定位符。
start=25&filter=  是URL的两个参数,分别代表从多少条记录开始展示和过滤条件。

将URL划分为两部分:

基础部分:http://movie.douban.com/top250
参数部分:?start=  &filter

接下来使用urlib模块抓取页面内容。

定义一个类MovieTop,在类中定义一个初始化方法和一个获取页面方法。

class MovieTop(object):
    def main(self):
        print('开始从豆瓣电影抓取数据.......')
        self.get_movie_info()

将一些基本信息的参数初始化放在类的初始化中,即init方法。

def __init__(self):
    self.start = 0
    self.param = '&filter'
    self.headers = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) "
                        }
    self.movieList = []

使用re模块中的compile函数解析爬取到的HTML文本,从而得到所需要的数据。

pattern = re.compile(u'<div.*?class="item">.*?'
            + u'<div.*?class="pic">.*?'
            + u'<em.*?class="">(.*?)</em>.*?'
            + u'<div.*?class="info">.*?'
            + u'<span.*?class="title">(.*?)</span>.*?'
            + u'<span.*?class="other">(.*?)</span>.*?'
            + u'<div.*?class="bd">.*?'
            + u'<p.*?class="">.*?'
            + u'导演:\s(.*?)\s.*?<br>'
            + u'(.*?)&nbsp;/&nbsp;'
            + u'(.*?)&nbsp;/&nbsp;(.*?)</p>.*?'
            + u'<div.*?class="star">.*?'
            + u'<span.*?class="rating_num".*?property="v:average">'
            + u'(.*?)</span>.*?'
            + u'<span>(.*?)人评价</span>.*?'
            + u'<span.*?class="inq">(.*?)</span>', re.S)

另外,获取页面信息时,需要知道从第几条记录开始查找和每次查找多少条记录。在这个方法中需要一个循环,通过循环抓取需要的记录。构建基础代码如下:

def get_page(self):
    try:
        url = 'https://movie.douban.com/top250?start=' + str(self.start) + '&filter='
        req = request.Request(url, headers=self.headers)
        response = request.urlopen(req)
        page = response.read().decode('utf-8')
        page_num = (self.start + 25) // 25
        print('正在抓取第' + str(page_num) + '页数据...')
        self.start += 25
        return page
    except request.URLError as e:
        if hasattr(e, 'reason'):
           print('获取失败,失败原因:', e.reason)
def get_movie_info(self):
    pattern = re.compile(u'<div.*?class="item">.*?'
                  + u'<div.*?class="pic">.*?'
                  + u'<em.*?class="">(.*?)</em>.*?'
                  + u'<div.*?class="info">.*?'
                  + u'<span.*?class="title">(.*?)</span>.*?'
                  + u'<span.*?class="other">(.*?)</span>.*?'
                  + u'<div.*?class="bd">.*?'
                  + u'<p.*?class="">.*?'
                  + u'导演:\s(.*?)\s.*?<br>'
                  + u'(.*?)&nbsp;/&nbsp;'
                  + u'(.*?)&nbsp;/&nbsp;(.*?)</p>.*?'
                  + u'<div.*?class="star">.*?'
                  + u'<span.*?class="rating_num".*?property="v:average">'
                  + u'(.*?)</span>.*?'
                  + u'<span>(.*?)人评价</span>.*?'
                  + u'<span.*?class="inq">(.*?)</span>', re.S)
    while self.start <= 225:
        page = self.get_page()
        movies = re.findall(pattern, page)
        for movie in movies:
            self.movieList.append([movie[0],
                                    movie[1],
                                    movie[2].lstrip('&nbsp;/&nbsp;'),
                                    movie[3],
                                    movie[4].lstrip(),
                                    movie[5],
                                    movie[6].rstrip(),
                                    movie[7],
                                    movie[8],
                                    movie[9]])

2. 数据存储

2.1 写入csv文件

MovieTop类中,定义write_into_csv方法,使用csv模块,将爬取到的数据写入到文件douban_top250.csv中。

def write_into_csv(self):
        print('开始写入.csv文件...')
        write_to_csv = open('douban_top250.csv', 'w', newline='', encoding='utf-8-sig')
        csv_top=csv.writer(write_to_csv)
        try:
            csv_top.writerow(['电影排名','电影名称','电影别名','导演','上映年份','制作国家/地区','电影类别','评分','参评人数','简短影评'])
        except Exception as e:
            print('e')
        write_csv = csv.writer(write_to_csv)
        try:
            for movie in self.movieList:
                write_csv.writerow([
                    movie[0],
                    movie[1],
                    movie[2],
                    movie[3],
                    movie[4],
                    movie[5],
                    movie[6],
                    movie[7],
                    movie[8],
                    movie[9]
                ])
            print('成功写入.csv文件')
        except Exception as e:
            print(e)
        finally:
            write_into_csv.close()

2.2 插入数据库

连接MySQL数据库,并运行此SQL脚本,即可生成表douban_top250

CREATE TABLE douban_top250(
    ID int PRIMARY KEY AUTO_INCREMENT,
    rankey int,
    name varchar(50),
    alias varchar(100),
    director varchar(50),
    showYear varchar(50),
    makeCountry varchar(50),
    movieType varchar(50),
    movieScore float,
    scoreNum int,
    shortFilm varchar(255)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

定义insert_into_mysql方法,使用pymysql模块,将爬取到的数据上传到MySQL数据库中,实现数据的持久化。

def insert_into_mysql(self):
        print('连接数据库...')
        db = pymysql.connect(host='', port=, user='', passwd='', db='douban_top250',
                             charset='utf8')
        cursor = db.cursor()
        print('数据库连接成功...\n开始上传数据...')
        insertStr = "INSERT INTO douban_top250(rankey, name, alias, director," \
                    "showYear, makeCountry, movieType, movieScore, scoreNum, shortFilm)" \
                    "VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', %f, %d, '%s')"
        try:
            for movie in self.movieList:
                insertSQL = insertStr % (int(movie[0]), str(movie[1]), str(movie[2]), str(movie[3]),str(movie[4]), str(movie[5]), str(movie[6]), float(movie[7]),                                     int(movie[8]), str(movie[9]))
                cursor.execute(insertSQL)
            db.commit()
            print('上传成功...')
        except Exception as e:
            print(e)
            db.rollback()
        finally:
            db.close()

完善此爬虫程序,执行即可抓取豆瓣电影Top250影评数据并实现数据持久化。

三、数据分析

1.数据清洗

1.1 预览数据
# 查看数据基本信息
df = pd.read_csv('douban_top250.csv', encoding='utf-8-sig')
df.head()
df.info()
1.2 重复值检查
count=df.duplicated().value_counts()
print(count)

2.数据可视化

2.1 电影制作国家/地区分析

有些电影由多个国家或地区参与制作。对于这种情况,可以采用split方法对每一项国家或地区数据进行切割,并将空值NaN替换为”0”,先按列计数,再按行汇总,由此统计数量。使用matplotlib.pyplot工具绘制排名统计直方图,使用wordcloud模块绘制词云图。

# 国家地区排名
area_split = df['制作国家/地区'].str.split(' ').apply(pd.Series)
all_country = area_split.apply(pd.value_counts).fillna('0')
all_country.columns = ['area1', 'area2', 'area3', 'area4', 'area5', 'area6']
all_country['area1'] = all_country['area1'].astype(int)
all_country['area2'] = all_country['area2'].astype(int)
all_country['area3'] = all_country['area3'].astype(int)
all_country['area4'] = all_country['area4'].astype(int)
all_country['area5'] = all_country['area5'].astype(int)
all_country['area6'] = all_country['area6'].astype(int)
all_country['all_counts'] = all_country['area1'] + all_country['area2'] \
                            + all_country['area3'] + all_country['area4'] \
                            + all_country['area5'] + all_country['area5']
all_country.sort_values(['all_counts'], ascending=False)  # 降序
country = pd.DataFrame({'制作国家/地区': all_country['all_counts']})
country.sort_values(by='制作国家/地区', ascending=False).plot(kind='bar', figsize=(10, 7))
plt.show()
# 绘制wordcloud
stopwords = set(STOPWORDS)
stopwords.add("NaN")
area_text = area_split.to_string(header=False, index=False)
WC_area = wordcloud.WordCloud(background_color='white',
                              scale=1.5,
                              stopwords=stopwords,
                              prefer_horizontal=1
                              ).generate(area_text)
plt.imshow(WC_area)
plt.axis("off")
plt.show()

绘制结果如下:

https://pic-1252881216.cos.ap-beijing.myqcloud.com/douban_top_250/movie_area.png

https://pic-1252881216.cos.ap-beijing.myqcloud.com/douban_top_250/wc_movie_area.png

由数据可以看出,上榜数最多的国家是美国,中国大陆排名第六。

2.2 电影类型分析

有些电影属于多种类型。比如排在第一位的《肖申克的救赎》既属于犯罪片也属于剧情片,而排在第二位的《霸王别姬》既属于剧情片又属于爱情片与同性片。对于这种情况,采用split方法对每一项国家或地区数据进行切割,并将空值NaN替换为”0”,先按列计数,再按行汇总,由此统计数量。使用matplotlib.pyplot模块绘制电影类型统计直方图,使用wordcloud模块绘制词云图。

# 电影类型统计
all_type = df['电影类别'].str.split(' ').apply(pd.Series)
type_text = all_type.to_string(header=False, index=False)
all_type = all_type.apply(pd.value_counts).fillna('0')
all_type.columns = ['type1', 'type2', 'type3', 'type4', 'type5']
all_type['type1'] = all_type['type1'].astype(int)
all_type['type2'] = all_type['type2'].astype(int)
all_type['type3'] = all_type['type3'].astype(int)
all_type['type4'] = all_type['type4'].astype(int)
all_type['type5'] = all_type['type5'].astype(int)
all_type['all_counts'] = all_type['type1'] + all_type['type2'] \
                         + all_type['type3'] + all_type['type4'] + all_type['type5']
 
all_type = all_type.sort_values(['all_counts'], ascending=False)
movie_type = pd.DataFrame({'数量': all_type['all_counts']})
print(movie_type)
movie_type.sort_values(by='数量', ascending=False).plot(kind='bar', figsize=(13, 6))
plt.show()
# 绘制wordcloud
stopwords = set(STOPWORDS)
stopwords.add("NaN")
WC_type = wordcloud.WordCloud(background_color='white',
                              scale=1.5,
                              stopwords=stopwords,
                              collocations=False,
                              prefer_horizontal=1
                              ).generate(type_text)
plt.imshow(WC_type)
plt.axis("off")
plt.show()

绘制结果如下:

https://pic-1252881216.cos.ap-beijing.myqcloud.com/douban_top_250/movie_type.png

https://pic-1252881216.cos.ap-beijing.myqcloud.com/douban_top_250/wc_movie_type.png

由数据图可以明显看出,剧情片的上榜比例远远超过其他类型,而情色片、运动片等类型上榜数较少。

2.3 导演上榜次数分析

使用pandas模块提取数据,使用wordcloud模块绘制词云图。

# 导演上榜次数
director = df['导演'].value_counts()
myDirector = pd.DataFrame({'name':director.index,'counts':director.values})
print(myDirector)
# 绘制wordcloud
Director=df['导演'].str.split(' ').apply(pd.Series)
stopwords = set(STOPWORDS)
stopwords.add("NaN")
director_text = Director.to_string(header=False, index=False)
WC_director= wordcloud.WordCloud(background_color='white',
                              scale=1.5,
                              stopwords=stopwords,
                              prefer_horizontal=1
                              ).generate(director_text)
plt.imshow(WC_director)
plt.axis("off")
plt.show()

绘制结果如下:


https://pic-1252881216.cos.ap-beijing.myqcloud.com/douban_top_250/movie_director.png

https://pic-1252881216.cos.ap-beijing.myqcloud.com/douban_top_250/wc_director.png

排在前几名的都是耳熟能详的导演,如斯皮尔伯格、克里斯托弗诺兰与宫崎骏,上榜数量都为7部,而华人导演王家卫上榜数量为5部。

2.4 评分与排名关系分析

使用pandas模块提取数据,使用matplotlib.pyplot模块绘制评分和排名关系散点图以及电影评分的分布图。

# 评分和排名的关系散点图
plt.figure(figsize=(14, 6))
plt.subplot(1, 2, 1)
plt.scatter(df['评分'], df['电影排名'])
plt.xlabel('评分')
plt.ylabel('电影排名')
plt.gca().invert_yaxis()
# 评分数量直方图
plt.subplot(1, 2, 2)
plt.hist(df['评分'], bins=14)
plt.xlabel('评分')
plt.ylabel('出现次数')
plt.show()

绘制结果如下:

https://pic-1252881216.cos.ap-beijing.myqcloud.com/douban_top_250/movie_score_number.png

由上图可以看出,电影的评分大多是集中在8.5-9.4之间。

2.5 评分与参评人数关系分析

使用pandas模块提取数据,使用matplotlib.pyplot模块绘制参评人数与电影排名关系散点图以及参评人数频次图。

# 评分与评价人数的关系散点图
plt.figure(figsize=(14, 6))
plt.subplot(1, 2, 1)
plt.scatter(df['参评人数'], df['电影排名'])
plt.xlabel('参评人数')
plt.ylabel('电影排名')
plt.gca().invert_yaxis()
# 评价人数直方图
plt.subplot(1, 2, 2)
plt.hist(df['参评人数'], bins=14)
plt.xlabel('参评人数')
plt.ylabel('出现次数')
plt.show()

绘制结果如下:


https://pic-1252881216.cos.ap-beijing.myqcloud.com/douban_top_250/movie_review_number.png

由统计图可以看出,电影排名与其参评人数基本呈正相关趋势,且参评人数主要分布在350000-900000人之间。

四、总结

综上分析,可以看出,中国观众对于电影的类型更认可剧情片和爱情片;对于电影的生产国家,更认可具有强大电影工业体系的美国;而对于该榜单中的电影而言,好电影会吸引更多的人来评价,而评价人数多的电影,质量也确实不错。或许榜单的意义就在于:让真正的好电影能通过排名,吸引更多人观看评价。

五、附录

源码地址:

https://www.jianshu.com/p/b4de7764999d

Python量化投资网携手4326手游为资深游戏玩家推荐:《称霸《天下》手游90级战场?90级全职业各流派攻略(上)拿稳!

「点点赞赏,手留余香」

    还没有人赞赏,快来当第一个赞赏的人吧!
PyTorch
NumPy
0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论