本篇讲介绍一个简单的Python爬虫案例–爬取豆瓣 TOP250 电影排行榜。

很多朋友在看一部电影前都喜欢先找一下网友们对该片的评价。

说到电影评分的网站,除了国外的 IMDB 和烂番茄,国内要数豆瓣最为出名。

主要原因是豆瓣有一套完整的评分和防水军机制 。

在这套机制下,豆瓣评分高的电影不一定是所有人都喜欢的,但是豆瓣评分低的电影,一定是实打实的烂片!

虽然每个人的喜好偏爱不同,但通常豆瓣评分 8 分以上的电影,都是值得一看的。

豆瓣还专门提供了一个 TOP250 的电影链接 -> https://movie.douban.com/top250

爬取思路

爬取的过程很好理解,这里只需要两个过程:
① 从服务器上下载所需页面
② 解析这个页面,得到自己需要有用的内容

①抓取页面

有的人可能会利用 urllib 模块实现网络抓取功能。但在 Python 中,有一个更好地替代者——Requests。Requests 简化了 urllib 的诸多冗杂且无意义的操作,并提供了更强大的功能。
所以在这里我们使用 Requests 模块的 get() 方法从服务器上来下载这个页面。

1
2
3
4
5
6
7
8
import requests

url = "https://movie.douban.com/top250"
headers = {
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3314.0 Safari/537.36 SE 2.X MetaSr 1.0"
}

res = requests.get(url,headers=headers)

res就是我们需要的这个页面的资源,我们不妨打开来看看是不是

1
2
with open("豆瓣电影.txt",'w',encoding='utf-8') as f:
f.write(res.text)

打开文本如下图

我们可以看出这确实是当前网页的资源,所以我们就抓取成功了。

②解析页面

解析网页内容推荐使用 BeautifulSoup 模块,它可以化腐朽为神奇,将一个复杂的网页结构转化为书籍目录的形式供你浏览。
例如,我们现在需要解析提取出当前页面的电影名字

1
2
3
4
5
import bs4
soup = bs4.BeautifulSoup(res.text,"html.parser")
targets = soup.find_all("div",class_="hd")
for each in targets:
print(each.a.span.text)

可以得到如下结果

肖申克的救赎
霸王别姬
阿甘正传
这个杀手不太冷
美丽人生
泰坦尼克号
千与千寻
辛德勒的名单
盗梦空间
忠犬八公的故事
海上钢琴师
机器人总动员
三傻大闹宝莱坞
楚门的世界
放牛班的春天
星际穿越
大话西游之大圣娶亲
熔炉
疯狂动物城
无间道
龙猫
教父
当幸福来敲门
怦然心动
触不可及

这里你可能就会有疑问,这些数据是怎么得来的呢?
我们先来看下 HTML 源代码:

发现每个电影的标题都是位于 <div class="hd">...</div> 标签中的,它的从属关系是:div -> a -> span

所以我们先调用 find_all() 方法,找到所有 class=”hd” 的 div 标签,然后按照从属关系即可直接取出电影名。
同理,我们借用此发方法来解析提取出电影的评分、介绍等需要的信息。

附加问题

我们刚才解析提取的仅仅是第一页的页面,那么还有第二、第三、第四页……呢?

其实,解决起来也很简单,我们可以使用for循环来对每一页进行上述的两个过程。

但,我们此时又有新的问题,我们不可能每抓取一次,就重新输入下一网页的链接地址,这样很麻烦,效率也不高。

我们可以分析每一页的链接:

第一页:https://movie.douban.com/top250
第二页:https://movie.douban.com/top250?start=25
第三页:https://movie.douban.com/top250?start=50
第四页:https://movie.douban.com/top250?start=75
第五页:https://movie.douban.com/top250?start=100
… …
…. …

我们可以发现这样的规律:
每一次的更新的 url = https://movie.douban.com/top250 + '/?start=' + str(25*i)其中i可以表示为页数-1

咦,这个时候,你可能会有疑问,我们怎么知道一共有多少页呢,不能一直for循环无穷吧。

那当然不可能的了,我们可以按第二步解析网页方式来获取页数

1
depth = soup.find('span',class_='next').previous_sibling.previous_sibling.text

注意,这个返回的depth是给字符串形式,需要int()

这样结合刚才的过程,就可以迭代每一页了

代码清单

感兴趣的话,可以试一试哦

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
import requests
import bs4

#抓取网页
def open_url(url):
headers = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3314.0 Safari/537.36 SE 2.X MetaSr 1.0"}
res = requests.get(url,headers=headers)

return res

#得到总页数
def find_depth(res):
soup = bs4.BeautifulSoup(res.text,'html.parser')
depth = soup.find('span',class_='next').previous_sibling.previous_sibling.text

return int(depth)

#解析网页,提取内容
def find_movies(res):
soup = bs4.BeautifulSoup(res.text,'html.parser')

names = []
target = soup.find_all('div',class_='hd')
for i in target:
names.append(i.a.span.text)

ranks = []
target = soup.find_all('span',class_='rating_num')
for i in target:
ranks.append(i.text)

messages = []
target = soup.find_all('div',class_='bd')
for i in target:
try:
messages.append(i.p.text.split('\n')[1].strip() + i.p.text.split('\n')[2].strip())
except:
continue

result = []
length = len(names)
for i in range(length):
result.append(names[i] + ',' +ranks[i] + ',' + messages[i] + '\n')

return result

def main():
host = "https://movie.douban.com/top250"
res = open_url(host)
depth = find_depth(res)

result = []
for i in range(depth):
url = host + '/?start=' + str(25*i)
res = open_url(url)
result.extend(find_movies(res))

with open("豆瓣TOP250电影.txt",'w',encoding='utf-8') as f:
for each in result:
f.write(each)

if __name__=="__main__":
main()