利用urllib2和bs4爬取豆瓣北京热播电影

在正式爬虫之前还有一些入门需要了解的知识点,以下是我入门时看的一点东西:

  1. Python爬虫入门一之综述
  2. Python爬虫入门二之爬虫基础了解
  3. Python爬虫入门三之Urllib库的基本使用
  4. Python爬虫入门四之Urllib库的高级用法
  5. Python爬虫入门五之URLError异常处理
  6. Python爬虫入门六之Cookie的使用
  7. Python爬虫入门七之正则表达式
  8. Python爬虫入门八之Beautiful Soup的用法

(转载自:http://cuiqingcai.com/1052.html)

首先来张图:

这是最简单的爬虫了,就是给一个网址,然后从他的内容中利用正则或者bs4找出它的规律,最终将需要的东西提取出来。找需要的内容和内容所在位置的规律这就是自己需要根据网页源码来总结的了。

虽然在一个页面中就有自己需要的所有内容了,但是这样也有差别:

  • 一种是向服务器直接提交一个网址后,服务器将所有要显示的内容一次性都给发过来;
  • 另一种就是利用ajax技术,并不是一次将所有内容都给用户返回而是在页面中有一个加载更多的按钮,如果用户想要看到更多的内容的话,通过点击按钮然后浏览器在发送新的内容的url请求。利用这种技术:可以在不重新加载整个页面的情况下,为页面的某部分更新,更多ajax内容请自行搜索,我们这个网址也有这么一个按钮,如下

但是此爬虫不是利用了ajax技术,只是利用了JavaScript技术,将一部分内容隐藏了起来,你点击显示全部影片的按钮后,会将隐藏起来的内容在给你显示出来而已,上边这张图片最后一个电影是《三城记》,而从下面可以看“显示全部影片”按钮的上面还有很多影片,所以这就证实了我上面所说的没有利用ajax技术,只是隐藏起来了而已。

下面开始正式爬虫:
这个爬虫总共分三个步骤

  • 根据url取页面内容
  • Bs4根据自己发现的规则来提取具体内容
  • 根据电影的星级排序

首先获取网页内容

1
2
3
4
5
6
7
8
9
def get_films_sorted(url):
#urllib2.urlopen()函数不支持验证、cookie或者其它HTTP高级功能。要支持这些功能,必须使用build_opener()函数创建自定义Opener对象
headers = ('User-Agent', 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.154 Safari/537.36')
opener = urllib2.build_opener() # create an OpenerDirector object
opener.addheaders = [headers] # 设置http头
content = opener.open(url).read() #请求url然后获取response回来的内容
#以上几步和urlopen()函数的功能相同
encoding = chardet.detect(content)['encoding'] #取得网页编码
content = content.decode(encoding, 'ignore') #python内部编码为unicode,此处需要用decode将字符串转换为unicode,ignore为忽略非法字符,还有replace会用?代替非法字符

接下来用bs4来提取内容

1
2
3
soup = bs4.BeautifulSoup(content)
div_now_playing=soup.find('div', id='nowplaying') # 返回第一个指定属性的div标签的所有内容
list_li_films = div_now_playing.findAll('li', class_="list-item") # 返回一个包含所有li标签,class属性为"list-item"的列表

这样所有class为list-item的li标签就都存储到list_li_films中了,因为这样的li标签不止一个,所以需要用一个for循环进去依次提取需要的信息,下面是一个li标签

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
<li
id="24719063"
class="list-item"
data-title="烈日灼心"
data-score="7.9"
data-star="40"
data-release="2015"
data-duration="139分钟"
data-region="中国大陆"
data-director="曹保平"
data-actors="邓超 / 段奕宏 / 郭涛"
data-category="nowplaying"
data-enough="True"
data-showed="True"
data-votecount="63345"
data-subject="24719063"
>
<ul class="">
<li class="poster">
<a href="http://movie.douban.com/subject/24719063/?from=playing_poster" class=ticket-btn target="_blank" data-psource="poster">
<img src="http://img4.douban.com/view/movie_poster_cover/mpst/public/p2262236348.jpg" alt="烈日灼心" rel="nofollow" class="" />
</a>
</li>
<li class="stitle">
<a href="http://movie.douban.com/subject/24719063/?from=playing_poster"
class="ticket-btn"
target="_blank"
title="烈日灼心"
data-psource="title">
烈日灼心
</a>
</li>
<li class="srating">
<span class="rating-star allstar40"></span>
<span class="subject-rate">7.9</span>
</li>
<li class="sbtn">
<a href="http://movie.douban.com/subject/24719063/cinema/beijing/?from=playing_btn"
target="_self"
data-subject="24719063"
data-psource="btn"
data-title="烈日灼心"
class="ticket-btn">
选座购票
</a>
</li>
</ul>
</li>

其实我们需要获取的信息在li的属性里就都能拿到但是为了多介绍点bs4的用法,其中的两项:评分和星级就不从li中获取了,而是从下面包含其他部分中获取出来,具体代码如下:

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
# 遍历每个电影
for li_film in list_li_films:
# 字典
film_dic = {}
if (li_film.ul.li['class'] == ['poster']):
# 提取相应信息
film_dic['film_name'] = li_film.ul.li.img['alt']
film_dic['film_release'] = li_film['data-release']
film_dic['film_actors'] = li_film['data-actors']
film_dic['film_director'] = li_film['data-director']
# 属性也可以用字典定义
if li_film.find('span', attrs={'class': 'subject-rate'}):
# 如果有评分
film_dic['film_points'] = li_film.find('span', {'class','subject-rate'}).string.strip()
film_dic['points'] = float(film_dic['film_points'])
else:
# 如果没评分
film_dic['film_points'] = u'暂无评分'
film_dic['points'] = 0
# stars为class属性的内容
stars = li_film.find('li', attrs={'class': 'srating'}).span['class']
if stars[0] != 'rating-star':
# 没有评星
film_dic['film_stars'] = u'评价人数不足'
else:
# 有评星
str_stars=stars[1] #stars[1] = allstar25
#将allstar后面的数字转换出来
match = re.match(r'^(\D+)(\d{2})$', str_stars) #\D非数字 ^以什么开头 \d为数字 {2}为匹配前边两次 $以什么结尾
#match.groups()为全部上面括号里的内容,一个是allstar,另一个是25,序数从1开始,match.groups(2)就是25
film_dic['film_stars'] = str(int(match.group(2)) / 10).decode('utf-8') + u'颗星'
films_list.append(film_dic)

我们定义了一个空的字典film_dic来追加存储获得的信息。可以看出获取评分和星级不是从li标签的属性中获取的,但也是利用bs4的find函数,而方式不同了。

正则和bs4是写python爬虫的必备知识。

通过字典的append,已经把页面中的所有信息都获取完了,现在还缺少最后一步就是按照评分排序一下,代码如下:

1
films_list_sorted = sorted(films_list, key=lambda x:x['points'], reverse = True)

这些功能都是些在一个函数中,现在只需在main中调用即可:

1
2
3
4
if __name__=="__main__":
url="http://movie.douban.com/nowplaying/beijing/"
for film_dic in get_films_sorted(url):
print "%-20s"%(film_dic['film_name'])+"%-10s"%str(film_dic['points'])+"%-10s"%(film_dic['film_stars'])

pycharm的输出结果如下:

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
西游记之大圣归来 8.4 4颗星
猛龙过江 8.2 4颗星
烈日灼心 7.9 4颗星
滚蛋吧!肿瘤君 7.7 4颗星
亚马逊萌猴奇遇记 7.6 4颗星
这里的黎明静悄悄 7.6 4颗星
刺客聂隐娘 7.5 4颗星
捉妖记 7.2 3颗星
终结者:创世纪 6.9 3颗星
煎饼侠 6.8 3颗星
华丽上班族 6.4 3颗星
落跑吧爱情 6.2 3颗星
三城记 6.1 3颗星
情敌蜜月 5.4 3颗星
黑猫警长之翡翠之星 5.3 3颗星
桂宝之爆笑闯宇宙 5.0 2颗星
洛克王国4:出发!巨人谷 4.5 2颗星
天际浩劫 4.4 2颗星
新娘大作战 4.0 2颗星
百团大战 3.9 2颗星
双生灵 3.5 2颗星
男神时代 3.4 2颗星
赛尔号大电影5:雷神崛起 3.4 2颗星
诡劫 3.2 1颗星
魔镜奇缘 2.8 1颗星
枕边诡影 2.8 1颗星
大变局之梦回甲午 2.8 1颗星
开罗宣言 2.6 1颗星
少年杨靖宇 2.5 1颗星
穷途 0 评价人数不足

这个可以说应该是最基本的爬虫了,代码已上传github。

地址为:https://github.com/lowkeynic4/crawl/blob/master/doubandianying.py