爬取猫眼电影Top100

For what it’s worth, it’s never too late. ——《返老还童》

有意义的事,什么时候做都不迟。

  最近因为新型冠状病毒,在家宅着实在无聊,想着找些电影看。于是乎…

站点分析

  我们需要抓取的目标站点为 https://maoyan.com/board/4 ,打开之后便可以查看榜单。如下图,排名第一的电影是霸王别姬。


  将网页滑到最下方,发现有分页,点击切换到第2页,观察URL的变化。发现页面的URL变成了 https://maoyan.com/board/4?offset=10 ,如下图:


  比之前的URL多了一个offset参数,而目前显示的结果是排名11 ~ 20的电影,初步推断这是一个偏移量的参数。再点击下一页,offset参数变成了20,显示的结果是排名21 ~ 30的电影。多次切换页码offset都有改变,由此得出规律,offset代表偏移量值,如果偏移量为n,则显示的是排名n+1 ~ n+10的电影。也就是说Top100我们只需要分开请求10次即可,而10次的参数分别设置为0、10、20…90即可。

抓取单页源码

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
import requests
from requests.exceptions import RequestException


def get_one_page():
try:
url = "http://maoyan.com/board/4?offset={0}".format(0)
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/80.0.3987.87 Safari/537.36'
}
response = requests.get(url, headers=headers)
# 判定响应是否成功,成功则打印响应内容,否则返回None
if response.status_code == 200:
print(response.text)
return None
except RequestException:
return None


def main():
get_one_page()


if __name__ == "__main__":
main()

  运行即可得到网页源码。获取源码之后,就需要解析页面,提提取出我们想要的信息。

解析单页源码

  我们使用Chrome浏览器,按下F12在开发者模式下的Network监听组件中查看源代码。查看其中一个条目的源代码。一部电影的信息对的是一个dd节点,我们可以使用正则表达式来提取里面电影的信息。首先排名信息是在class为board-index的i节点内;第二个img节点的data-src属性是图片的链接;再往后,电影名称在后面的p节点内,class为name;主演、发布时间、评分等内容以此类推。如图所示:

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
import re


def parse_one_page(html):
pattern = re.compile(
'<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?star.*?>(.*?)</p>.*?'
'releasetime.*?>(.*?)</p>.*?integer.*?>(.*?)</i>.*?fraction.*?>(.*?)</i>.*?</dd>',
re.S
)
items = re.findall(pattern, html)
# print(items)
for item in items:
yield {
'index': item[0],
'image': item[1],
'title': item[2].strip(),
'actor': item[3].strip()[3:] if len(item[3]) > 3 else '',
'time': item[4].strip()[5:] if len(item[4]) > 5 else '',
'score': item[5].strip() + item[6].strip()
}


def main():
html = get_one_page()
for item in parse_one_page(html):
print(item)


if __name__ == "__main__":
main()

保存到文件中

有两种方式:一种是保存到txt文件中,另一种是保存到csv文件中,可根据需求选择其中一中。

保存到txt文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import json


def write_to_txtFile(content):
with open('result.txt', 'a', encoding='utf-8') as f:
# 利用json.dumps()方法将字典序列化,并将ensure_ascii参数设置为False,保证结果是中文而不是Unicode码
f.write(json.dumps(content, ensure_ascii=False) + '\n')


def main():
html = get_one_page(url)
for item in parse_one_page(html):
write_to_txtfile(item)


if __name__ == "__main__":
main()

保存到csv文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import csv


def write_to_csvFile(content):
with open("MovieResult.csv", 'a', encoding='utf-8', newline='') as f:
fieldnames = ["index", "image", "title", "actor", "time", "score"]
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(content)


def main(offset):
html = get_one_page(url)
rows = []
for item in parse_one_page(html):
rows.append(item)
write_to_csvfile(rows)


if __name__ == "__main__":
main()

抓取多个页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def main(offset):
url = 'http://maoyan.com/board/4?offset=' + str(offset)
html = get_one_page(url)
rows = []
for item in parse_one_page(html):
# write_to_txtfile(item)
rows.append(item)
write_to_csvfile(rows)


if __name__ == "__main__":
for i in range(10):
main(offset=i * 10)
time.sleep(2)

整合完整代码

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import re
import csv
import time
import json
import requests
from requests.exceptions import RequestException


# 抓取单页
def get_one_page(url):
try:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/80.0.3987.87 Safari/537.36'
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
return response.text
return None
except RequestException:
return None


# 正则提取
def parse_one_page(html):
pattern = re.compile(
'<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?star.*?>(.*?)</p>.*?'
'releasetime.*?>(.*?)</p>.*?integer.*?>(.*?)</i>.*?fraction.*?>(.*?)</i>.*?</dd>',
re.S
)
items = re.findall(pattern, html)
# print(items)
for item in items:
yield {
'index': item[0],
'image': item[1],
'title': item[2].strip(),
'actor': item[3].strip()[3:] if len(item[3]) > 3 else '',
'time': item[4].strip()[5:] if len(item[4]) > 5 else '',
'score': item[5].strip() + item[6].strip()
}


# 写入txt文件
def write_to_txtFile(content):
with open('MovieTop100.txt', 'a', encoding='utf-8') as f:
# print(type(json.dumps(content)))
f.write(json.dumps(content, ensure_ascii=False) + '\n')


# 写入CSV文件表头
def write_to_csvField(filename):
with open("MovieTop100.csv", 'a', encoding='utf-8', newline='') as f:
writer = csv.DictWriter(f, filename)
writer.writeheader()


# 写入CSV文件内容
def write_to_csvRows(content, filename):
with open("MovieTop100.csv", 'a', encoding='utf-8', newline='') as f:
writer = csv.DictWriter(f, filename)
# writer.writeheader() # 在抓取多页面时会造成表头重复
writer.writerows(content)


def main(offset, fieldnames):
url = 'http://maoyan.com/board/4?offset={0}'.format(offset)
html = get_one_page(url)
rows = []
for item in parse_one_page(html):
# print(item)
# write_to_txtFile(item)
rows.append(item)
write_to_csvRows(rows, fieldnames)


if __name__ == "__main__":
fieldnames = ["index", "image", "title", "actor", "time", "score"]
write_to_csvField(fieldnames)
for i in range(10):
main(offset=i * 10, fieldnames=fieldnames)
time.sleep(1)

最终效果展示

PS: 本文参考资料《Python3网络开发实战》–崔庆才。