python3 爬虫入门


这里爬取猫眼电影 TOP100 榜的信息,作为学习的第一个Demo

有目标才有驱动力

个人觉得python这门语言,入门最好的方式就是直接实战,在做项目的时候去学习,做到每个知识点有的放矢。


今天开始接触的python,从爬虫开始。 语言相对来说比较简单,环境配置到是花了不少时间。

有个要注意的点是在引入beautifurSoup库的时候会报错,因为3.x的库需要引入的是beautifurSoup4.


到这一步环境配置基本上OK了,可以开始进入正题了

这里我跟的一个教程是
Python3网络爬虫实战
知识点比较广,从一个爬虫事例开始.

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

###准备工作
添加Requests依赖库,注意不是Request

###抓取分析
本节我们需要抓取的目标站点为:http://maoyan.com/board/4,打开之后便可以查看到榜单的信息,如图 [盼盼]

网页下滑到最下方可以发现有分页的列表,我们点击一下第二页观察一下页面的URL和内容发生了怎样的变化,如图[盼盼]

可以发现页面的 URL 变成了:http://maoyan.com/board/4?offset=10,相比之前的URL多了一个参数,那就是 offset=10,而目前显示的结果是排行 11-20 名的电影,初步推断这是一个偏移量的参数,再点击下一页,发现页面的 URL 变成了:http://maoyan.com/board/4?offset=20,参数 offset 变成了 20,而显示的结果是排行 21-30 的电影。

由此可以总结出规律,offset 代表了一个偏移量值,如果偏移量为 n,则显示的电影序号就是 n+1 到 n+10,每页显示 10 个。所以如果想获取 TOP100 电影,只需要分开请求 10 次,而 10 次的 offset 参数设置为 0,10,20,…,90 即可,这样获取不同的页面结果之后再用正则表达式提取出相关信息就可以得到 TOP100 的所有电影信息了。

###抓取首页
接下来我们用代码实现这个过程,首先抓取第一页的内容,我们实现一个 get_one_page() 方法,传入 url 参数,然后将抓取的页面结果返回,然后再实现一个 main() 方法调用一下,初步代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests
def get_one_page(url):
response = requests.get(url)
if response.status_code == 200:
return response.text
return None
def main():
url = 'http://maoyan.com/board/4'
html = get_one_page(url)
print(html)
main()

###正则提取
没接触过正则的看这里

接下来网页看一下页面的真实源码[盼盼]

查看其中的一个条目的源代码如图 [盼盼]

可以看到一部电影信息对应的源代码是一个 dd 节点,用正则表达式来提取这里面的一些电影信息,首先需要提取它的排名信息,而它的排名信息是在 class 为 board-index 的 i 节点内,所以所以这里利用非贪婪匹配来提取 i 节点内的信息,正则表达式写为:

1
<dd>.*?board-index.*?>(.*?)</i>

随后需要提取电影的图片,可以看到在后面有个 a 节点,其内部有两个 img 节点,经过检查后发现第二个 img 节点的 data-src属性是图片的链接,在这里提取第二个 img 节点的 data-src属性,所以正则可以改写如下:

1
<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)"

再往后我们需要提取电影的名称,它在后面的 p 节点内,class 为 name,所以我们可以用 name 做一个标志位,然后进一步提取到其内 a 节点的正文内容,正则改写如下:

1
<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>

随后如果需要再提取主演、发布时间、评分等内容的话都是同样的原理,最后正则表达式写为:

1
<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?star.*?>(.*?)</p>.*?releasetime.*?>(.*?)</p>.*?integer.*?>(.*?)</i>.*?fraction.*?>(.*?)</i>.*?</dd>

这样我们一个正则表达式可以匹配一个电影的结果,里面匹配了 7 个信息,接下来我们通过调用 findall() 方法提取出所有的内容,实现一个 parse_one_page() 方法如下:

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)

这样我们就可以成功将一页的 10 个电影信息都提取出来,但这样还不够,数据比较杂乱,再将匹配结果处理一下,遍历提取结果并生成字典,方法改写如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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:] if len(item[3]) > 3 else '',
'time': item[4].strip()[5:] if len(item[4]) > 5 else '',
'score': item[5].strip() + item[6].strip()
}

这样就可以成功提取出电影的排名、图片、标题、演员、时间、评分内容了,并把它赋值为一个个的字典,形成结构化数据,运行结果如下:

1
2
3
4
5
6
7
8
9
10
{'image': 'http://p1.meituan.net/movie/20803f59291c47e1e116c11963ce019e68711.jpg@160w_220h_1e_1c', 'actor': '张国荣,张丰毅,巩俐', 'score': '9.6', 'index': '1', 'title': '霸王别姬', 'time': '1993-01-01(中国香港)'}
{'image': 'http://p0.meituan.net/movie/__40191813__4767047.jpg@160w_220h_1e_1c', 'actor': '蒂姆·罗宾斯,摩根·弗里曼,鲍勃·冈顿', 'score': '9.5', 'index': '2', 'title': '肖申克的救赎', 'time': '1994-10-14(美国)'}
{'image': 'http://p0.meituan.net/movie/fc9d78dd2ce84d20e53b6d1ae2eea4fb1515304.jpg@160w_220h_1e_1c', 'actor': '让·雷诺,加里·奥德曼,娜塔莉·波特曼', 'score': '9.5', 'index': '3', 'title': '这个杀手不太冷', 'time': '1994-09-14(法国)'}
{'image': 'http://p0.meituan.net/movie/23/6009725.jpg@160w_220h_1e_1c', 'actor': '格利高利·派克,奥黛丽·赫本,埃迪·艾伯特', 'score': '9.1', 'index': '4', 'title': '罗马假日', 'time': '1953-09-02(美国)'}
{'image': 'http://p0.meituan.net/movie/53/1541925.jpg@160w_220h_1e_1c', 'actor': '汤姆·汉克斯,罗宾·怀特,加里·西尼斯', 'score': '9.4', 'index': '5', 'title': '阿甘正传', 'time': '1994-07-06(美国)'}
{'image': 'http://p0.meituan.net/movie/11/324629.jpg@160w_220h_1e_1c', 'actor': '莱昂纳多·迪卡普里奥,凯特·温丝莱特,比利·赞恩', 'score': '9.5', 'index': '6', 'title': '泰坦尼克号', 'time': '1998-04-03'}
{'image': 'http://p0.meituan.net/movie/99/678407.jpg@160w_220h_1e_1c', 'actor': '日高法子,坂本千夏,糸井重里', 'score': '9.2', 'index': '7', 'title': '龙猫', 'time': '1988-04-16(日本)'}
{'image': 'http://p0.meituan.net/movie/92/8212889.jpg@160w_220h_1e_1c', 'actor': '马龙·白兰度,阿尔·帕西诺,詹姆斯·凯恩', 'score': '9.3', 'index': '8', 'title': '教父', 'time': '1972-03-24(美国)'}
{'image': 'http://p0.meituan.net/movie/62/109878.jpg@160w_220h_1e_1c', 'actor': '周星驰,巩俐,郑佩佩', 'score': '9.2', 'index': '9', 'title': '唐伯虎点秋香', 'time': '1993-07-01(中国香港)'}
{'image': 'http://p0.meituan.net/movie/9bf7d7b81001a9cf8adbac5a7cf7d766132425.jpg@160w_220h_1e_1c', 'actor': '柊瑠美,入野自由,夏木真理', 'score': '9.3', 'index': '10', 'title': '千与千寻', 'time': '2001-07-20(日本)'}

到此为止我们就成功提取了单页的电影信息

写入文件

随后将提取的结果写入文件,在这里直接写入到一个文本文件中,通过 json 库的 dumps() 方法实现字典的序列化,并指定 ensure_ascii 参数为 False,这样可以保证输出的结果是中文形式而不是 Unicode 编码,代码实现如下:

1
2
3
4
def write_to_json(content):
with open('result.txt', 'a') as f:
print(type(json.dumps(content)))
f.write(json.dumps(content, ensure_ascii=False,).encode('utf-8'))

通过调用 write_to_json() 方法即可实现将字典写入到文本文件的过程,此处的 content 参数就是一部电影的提取结果,是一个字典。

###整合代码

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_json(item)

###分页爬取
遍历一下给这个链接传入一个 offset 参数,实现其他 90 部电影的爬取,添加如下调用即可:

1
2
3
if __name__ == '__main__':
for i in range(10):
main(offset=i * 10)

将 main() 方法修改一下,接收一个 offset 值作为偏移量,然后构造 URL 进行爬取,实现如下

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

###完整代码

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
import json
import requests
from requests.exceptions import RequestException
import re
import time
def get_one_page(url):
try:
response = requests.get(url)
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.*?>(\d+)</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],
'actor': item[3].strip()[3:],
'time': item[4].strip()[5:],
'score': item[5] + item[6]
}
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)

最后 跑起来,能得到一个txt文件,差不多是这样


万事开头难,然后中间难,最后也难。

愿每一个程序员都能长命白岁,哈哈