猫眼电影Top100

前言

本来是爬豆瓣电影 Top250 的,数据都爬下来了,发现后面的数据分析不太好做,然后就去爬猫眼了。

爬虫目标

  • 爬取猫眼电影 Top100 的电影名称、人物、时间、评分等,并保存为 csv 文件;
  • 对所爬取的数据进行一定的分析。

环境:Win10 + Python3.7 (以及一些第三方库) + SublimeText3

基本步骤

URL 分析

先进入 ( https://maoyan.com/board/4?offset=0 ) ,点击第二页,发现链接变为 ( https://maoyan.com/board/4?offset=10 ),就只有 offset=XXX 参数变了;进一步发现每页只有 10 部电影,所以第一页 offset=0, 第二页 offset=10 ,依次类推,只需要 10 次循环就能爬完 100 部电影了。

获取页面

直接用 requests.get() 来获取页面:

官网:Requests中文文档

1
2
# 获得网页的 html 文本
html = requests.get(url, headers = headers).text

这个页面几乎没有反爬,所以成功获取页面的图片就不上了;接下来就可以开始解析页面了。

页面解析

右键-检查- Elements 选项 (或按 F12) 进入调试页面,

可以看到要抓取的电影信息都包含在一个 <dl class="board-wrapper"> 标签下的 <dd> 标签里,更具体的可以看到:

电影名称在一个 <p class="name"> 的标签里;

主演在一个 <p class="star"> 的标签里;

上映时间和电影出自哪个地区 (有的电影可能没有写地区) 在一个 <p class="releasetime"> 的标签里;

电影评分就在 <p class="score"> 的标签里,只是整数和小数部分是分开的,一会儿合起来就行了。

image

这里采用较为简单的 BeautifulSoup 来解析页面 ;

提一下 find()find_all() 的区别:

  • find() 选取到的是第一个满足选取条件的标签
  • find_all() 选取到的是所有满足选取条件的标签

强大的 BeautifulSoup 可以越过父节点直接选取;有时间学学其他的,比如什么 xpath、正则啥的。

官网:BeautifulSoup;良心官网啊,有中文文档。

具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 把上映时间和上映地区分开
def getRelease(info) :
'''
:param info: 上映信息的字符串 "YY-MM-DD(Area)"
'''

l = info.split('(')
try :
time = l[0][5:]
except : time = 'Unknow'
try :
area = l[1][:-1]
except : area = 'Unknow'
return time, area

# 解析当前页面
def parseThisPage(url) :
'''
:param url: 爬取的网页
'''

# 获得网页的 html 文本
html = requests.get(url, headers = headers).text
# print (html)
# 构造解析器
soup = BeautifulSoup(html, 'lxml')

# 找到 <dl class='board-wrapper'> 中的所有 <dd> 标签
# 是一个 list
lists = soup.find('dl', {'class': 'board-wrapper'}).find_all('dd')

for item in lists :

# 找 class='name' 的 <p> 标签, 其 <a> 子标签的文本就是电影名称
name = item.find('p', {'class': 'name'}).a.text
# class='star' 的文本就是主演 (并把前面 "主演:" 去掉)
star = item.find('p', {'class': 'star'}).text.strip()[3:]

# 电影评分 整数和小数分开的, 爬下来之后合起来就行
score_info = item.find('p', {'class': 'score'}).find_all('i')
score = score_info[0].text + score_info[1].text

# 先爬取上映信息
release_info = item.find('p', {'class': 'releasetime'}).text
# 获得对应的时间和地区
time = getRelease(release_info)[0]
area = getRelease(release_info)[1]

# 合成一个字典
info = {'name': name, 'stars': star, 'score': score, 'time': time, 'area': area}

information.append(info)

保存数据

利用 csv 包的 DictWriter() 函数将字典格式的数据写入 csv 文件;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def WriteDictToCSV(csv_file, csv_cols, dict_data) :
'''
:param csv_file: csv 文件的路径
:param csv_cols: csv 文件的列名
:param dict_data: 元素为字典的 list 数据
'''

# 指定编码方式为 utf-8 (newline 参数不这样指定的话, 会有多的空行)
with open(csv_file, 'w', newline = '', encoding = 'utf-8') as csvfile :

# 传递列名给相应的参数
writer = csv.DictWriter(csvfile, fieldnames = csv_cols)
writer.writeheader() # 写入列名

for data in dict_data :
writer.writerow(data)

def save_data() :
# 获取当前路径 并指定文件名
csv_file = os.getcwd() + '\\猫眼电影Top100.csv'
# 指定列名
csv_cols = ['name', 'stars', 'score', 'time', 'area']
WriteDictToCSV(csv_file, csv_cols, information)

分页爬取

最后就只需要改变 offset 参数的值,代码如下:

1
2
3
4
5
6
if __name__ == '__main__':
for i in range(10) :
url = URL + str(i * 10)
parseThisPage(url)

save_data()

这样就完成了猫眼电影 Top100 的爬取,结果如下:

image

数据分析

接下来对抓取下来的数据进行简单的数据分析,顺便学一下基础的图表。

评分最高的 10 部电影

这里用柱状图来呈现,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 分析评分最高的 10 部电影
def Analysis_film() :
# 先按评分排序
top_films = df.sort_values('score', ascending = False)

# 取出 Top10 电影名称及对应的评分
names = list(top_films.name[:10])
scores = list(top_films.score[:10])

# 画图的时候不支持字符串, 所以转化为 float
for i, v in enumerate(scores) :
scores[i] = float(v)

x_pos = [i for i, _ in enumerate(scores)] # 下标
width = 0.75 # 柱条的宽度
# 柱状图
plt.bar(x_pos, scores, width, color = '#FF5A00')

plt.xlabel("电影名称") # x 轴的信息
plt.ylabel("评分") # y 轴的信息
plt.title("猫眼评分最高的10部电影") # 图表的标题

plt.ylim( (9.0, 9.7) ) # 设置 y 轴的范围

# 给柱条上写上对应的评分
for i, v in enumerate(scores) :
# plt.text(x, y, text, ...) x,y 坐标, text 文本, 后面还可以有其他参数
plt.text(i, v + 0.02, str(v), ha = 'center', color = 'blue')

plt.xticks(x_pos, names, rotation = -45)
plt.tight_layout() #自动控制空白边缘, 以全部显示x轴名称
# plt.savefig('./imgs/top10_film.png', dpi = 700) # dpi 表示分辨率
plt.show()

结果如图:

image

呃,,,才看过一半,有机会再看看其他的吧。

参演电影数量最多的 10 位演员

这里也用柱状图来呈现,画图和上面差不多,说一下主要思路和遇到的问题;

思路很简单,对所有演员去重之后分别统计其参演电影数量就行;当然,参演数量为 1 的就不考虑了,不然太多了。

期间遇到一个问题:排序之后的结果不稳定。就是参演电影数量一样的演员,在每次排完序后,姓名的顺序都是不一样的!

搜了一下,用 lambda 函数(叫函数还是表达式我也不知道)解决;还需要学一学啊。

1
2
# 先按第 1 维的值排序,再按第 0 维的值排序
dict_star = sorted(dict_star.items(), key = lambda x: (x[1], x[0]), reverse = True)

结果如图:

image

各国家(或地区)电影数量

用水平柱状图来看一下这 100 部电影都是来自哪些国家或地区,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 分析各国家/地区的电影数量
def Analysis_area() :
# 按地区分组, 统计数量后排序
area_count = df.groupby('area').area.count().sort_values(ascending = True)

y_pos = [i for i, _ in enumerate(area_count.values)]
width = 0.70
# 水平的柱状图
plt.barh(y_pos, area_count.values, width, align = 'center', color = 'red')

for i, v in enumerate(area_count.values) :
plt.text(v + 0.7, i - 0.1, str(v), ha = 'center', color = 'green')

plt.yticks(y_pos, area_count.index)
plt.title(u'各国家/地区电影数量')
plt.ylabel(u'国家/地区')
plt.xlabel(u'数量(部)')

plt.tight_layout()
# plt.savefig('./imgs/area.png', dpi = 700)
plt.show()

结果如图:

image

美国电影这么多上榜的倒是一点也不意外,中国的这么少就稍微有点意外了。

各年份的电影数量

用折线图看一下各年份的电影数量,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 分析各年份的电影数量
def Analysis_year() :
# method one
# lambda 表达式, 不是很懂, 但是很简单
# df['year'] = df['time'].map(lambda x: x.split('-')[0])

# method two
df['year'] = df['time'].copy()
# loc[i, 'year'] 表示第 i 行 year 列的单元值
for i, v in enumerate( df['year'] ) :
df.loc[i, 'year'] = v.split('-')[0]

# 指定图形的宽度, 高度 和分辨率
plt.figure(figsize = (13.195, 5.841), dpi = 700)

year_count = df.groupby(by = 'year').year.count()

x_pos = [i for i, _ in enumerate(year_count.values)]

# 折线图
plt.plot(year_count.index, year_count.values, linewidth = 2, color = 'green')

# 在对应位置写上文本
for i, v in enumerate(year_count.values) :
plt.text(i, v + 0.2, str(v), ha = 'center', color = 'blue')

plt.title(u'各年份上映的电影数量')
plt.xlabel(u'年份(年)')
plt.ylabel(u'数量(部)')

plt.tight_layout()
# plt.savefig('./imgs/year.png', dpi = 700)
plt.show()

结果如图:

image

看来 2010、2011 两年出了不少好电影啊,不过那时候在读初中,没什么印象。

完整程序

爬虫部分

可视化部分

------ The happy endingThank you for reading ------
坚持原创技术分享,您的支持将鼓励我继续创作!