Python爬取猫眼TOP100电影

目标

提取出猫眼电影 TOP100 的电影名称、时间、评分、图片等信息,提取的站点为 http://maoyan.com/board/4,并将提取的结果以文件的形式保存下来。

抓取分析

打开抓取的站点,发现第一页显示排行1-10的电影,第二页显示排行11-20的电影,但是url发生明显改变变为https://maoyan.com/board/4?offset=10比之前的多了一个参数offset=10,再点击第三页发现offset=20,可以推断出规律,offset代表偏移量值,如果偏移量为n,那么电影序号就为(n+1) ~(n+10),所以当我们想获取top100的电影,就需要分开10次请求,这样获取不同的页面后,再通过正则表达式提取出相关的信息,就可以了获取TOP100的所有电影信息了。

抓取首页

抓取首页的源代码

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

def get_one_page(url):
headers = {
'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36'
}
response = requests.get(url, headers = headers)
if response.status_code == 200:
return response.text
return None

def run():
url = 'http://maoyan.com/board/4'
html = get_one_page(url)
print(html)

run()

正则提取

获取首页的源代码之后,通过正则提取我们需要的信息。回到浏览器,再开发者模式下的Network监听组件中,查看源代码,我们可以发现一部电影的信息对应的源代码就是一个dd节点,我们用正则表达式来提取这里的一些电影信息。image-20190217183708327

  • 提取它的排名信息,排名第一的电影为“霸王别姬”,它的排名信息在 class 为 board-index / board-index-1 的 i 节点内,这里采用非贪婪匹配提取 i 节点内的信息,正则表达式为:<dd>.*board-index.*?>(.*?)</i>
  • 提取电影的图片,在后边的 a 标签内发现有两个 img 节点,经检查后发现,第二个 img 节点是电影封面,故这里提取第二个节点的 data-src 属性,正则表达式为:<dd>.*board-index.*?>(.*?)</i>.*?data-src="(.*?)"
  • 提取电影的名称,可以发现在后边的 class 为 name 的 p 节点中,其实这里有好几个地方都可以进行提取,书上提取了 a 节点中的文本信息,正则表达式为:<dd>.*board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>
  • 再提取主演、发布时间、评分等内容时,都是同样的原理,最后的完整的正则表达式为:<dd>.*board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?star.*?>(.*?)</p>.*?releasetime.*?>(.*?)</p>.*?integer.*?>(.*?)</i>.*?fraction.*?>(.*?)</i>.*?</dd>

接下来,通过调用findall()方法提取出所有的内容:

1
2
3
4
5
6
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)

image-20190217185920478

可以看出当前提取出来的数据,还十分的乱,所以我们需要将匹配结果再处理一下,遍历提取结果并生成字典:

1
2
3
4
5
6
7
8
9
10
11
12
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)
for item in items:
yield {
'index': item[0],
'image': item[1],
'title': item[2].strip(),
'actor': item[3].strip()[3:],
'time': item[4].strip()[5:],
'score': item[5].strip() + item[6].strip()
}

写入文件

将提取的结果写入文件,通过 JSON库的 dumps() 方法实现字典的序列化,并指定 ensure_ascii 参数为 False,这样可以保证输出结果是中文形式而不是 Unicode编码。

1
2
3
4
5
6
import json

def write_to_file(content):
with open('result.txt', 'a', encoding = 'utf-8') as f:
print(type(json.dumps(content)))
f.write(json.dumps(content, ensure_ascii = False) + '\n')

run()

通过 run() 方法来调用前面实现的方法,将单页的电影结果写入文件。

1
2
3
4
5
def main():
url = 'http://maoyan.com/board/4'
html = get_one_page(url)
for item in parse_one_page(html):
write_to_file(item)

分页爬取

因为抓取的是 猫眼TOP100 的电影,所以还需要遍历一下,给这个链接传入offset的参数

1
2
3
4
5
6
7
8
9
10
def main(offset):
url = 'http://maoyan.com/board/4?offset=' + str(offset)
html = get_one_page(url)
for item in parse_one_page(html):
print(item)
write_to_file(item)

if __name__=='__main__':
for i in range(10):
main(offset=i * 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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import json
import requests
import re
from requests.exceptions import RequestException
import time

def get_one_page(url):
try:
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 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)
for item in items:
yield {
'index': item[0],
'image': item[1],
'title': item[2].strip(),
'actor': item[3].strip()[3:],
'time': item[4].strip()[5:],
'score': item[5].strip() + item[6].strip()
}

def write_to_file(content):
with open('result.txt', 'a', encoding='utf-8') as f:
f.write(json.dumps(content, ensure_ascii=False) + '\n')

def main(offset):
url = 'http://maoyan.com/board/4?offset=' + str(offset)
html = get_one_page(url)
for item in parse_one_page(html):
print(item)
write_to_file(item)

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