利用Python爬取豆瓣电影
Posted fangtaoa的个人博客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了利用Python爬取豆瓣电影相关的知识,希望对你有一定的参考价值。
目标:使用Python爬取豆瓣电影并保存MongoDB数据库中
我们先来看一下通过浏览器的方式来筛选某些特定的电影:
我们把URL来复制出来分析分析:
https://movie.douban.com/tag/#/?sort=T&range=0,10&tags=%E7%94%B5%E5%BD%B1,%E7%88%B1%E6%83%85,%E7%BE%8E%E5%9B%BD,%E9%BB%91%E5%B8%AE
有3个字段是非常重要的:
1.sort=T
2.range=0,10
3.tags=%E7%94%B5%E5%BD%B1,%E7%88%B1%E6%83%85,%E7%BE%8E%E5%9B%BD,%E9%BB%91%E5%B8%AE
具体分析方法如下:
1.sort:表示排序方式,可以看到它有3中排序方式
根据上图可以直到每个字母表示的含义:
T:热度排序,
R:时间排序,
S:评价排序:
2.range=0,10;表示一个范围,具体是什么范围呢?
range参数我们也搞定了,它就是表示评分区间!
默认评分区间是:0-10
3.tags:同样的原理,这是一个标签
我们选中的标签有:电影,爱情,美国,黑帮4个标签,但是在tags里面我们看到的不是这写汉字,而是被编码过的形式!
%E7%94%B5%E5%BD%B1,%E7%88%B1%E6%83%85,%E7%BE%8E%E5%9B%BD,%E9%BB%91%E5%B8%AE)
那如何知道这些字符是表示什么呢?
我们可以到网上进行解码看看正不正确?
4.那么还有没有可以选择的参数呢?
我们还有2个参数可以选择!
playbale=1:表示可播放
unwatched=1:表示还没看过的
至此,我们就已经把URL中的查询参数全都弄明白了!
但是,又有一个问题了,当我们在浏览器中点击"加载更多"按钮时,这个地址栏中的URL并没有发生变化,但是电影信息可以加载出来了!这是为什么?
如果知道AJAX加载技术的读者可能知道这个原理,实际上就是异步加载,服务器不需要刷新整个网页,只需要刷新局部网页就可以把数据展示到网页中,这样不仅可以加快速度,也可以减少服务器的压力.
重点来了:
抓包结果:
看看浏览器地址栏的URL与Request URL有什么不一样的地方?
我们在浏览器地址栏中看到的URL是:
https://movie.douban.com/tag/#/?
sort=S&range=5,10&tags=%E7%94%B5%E5%BD%B1,%E7%88%B1%E6%83%85,%E7%BE%8E%E5%9B%BD&playable=1&unwatched=1
实际浏览器发送的Request URL是:
https://movie.douban.com/j/new_search_subjects?
sort=S&range=5,10&tags=%E7%94%B5%E5%BD%B1,%E7%88%B1%E6%83%85,%E7%BE%8E%E5%9B%BD&playable=1&unwatched=1&start=0
除了被红色标记的地方不同之外,其他地方都是一样的!那我们发送请求的时候应该是用哪一个URL呢?
在上面我就已提到了,在豆瓣电影中,是采用异步加载的方式来加载数据的,也就是说在加载数据的过程中,地址栏中的URL是一直保持不变的,那我们还能用这个URL来发送请求吗?当然不能了!
既然不能用地址栏中的URL来发送请求,那我们就来分析一下浏览器实际发送的Request URL:
我们把这个URL复制到浏览器中看看会发生什么情况!
我们可以看到这个URL的响应结果恰恰就是我们想要的数据,采用json格式.在Python中,我们可以利用一些工具把它转换成字典格式,来提取我们想要的数据.
距离我们成功还有一小步:
在这个URL中,我们看到还有一个参数:start,这个是干嘛的呢?
这个数值表示偏移量,来控制每一次加载的偏移位置是在哪里!比如我们把它设置成20,表示一次请求的电影数量.那么得到的结果如下:
到这里,该案例的思路,难点就已经全都捋清楚了,剩下的就是代码的事情了!
项目结构:
完整的代码如下:
settings.py
1 User_Agents =[
2 \'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50\',
3 \'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50\',
4 \'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0\',
5 \'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1\',
6 \'Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11\',
7 \'Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11\',
8 \'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11\',
9 ]
mongoHelper.py
1 import pymongo 2 3 4 class MongoDBHelper: 5 """数据库操作""" 6 7 def __init__(self, collection_name=None): 8 # 启动mongo 9 self._client = pymongo.MongoClient(\'localhost\', 27017) 10 # 使用test数据库 11 self._test = self._client[\'test\'] 12 # 创建指定的集合 13 self._name = self._test[collection_name] 14 15 def insert_item(self, item): 16 """插入数据""" 17 self._name.insert_one(item) 18 19 def find_item(self): 20 """查询数据""" 21 data = self._name.find() 22 return data 23 24 25 def main(): 26 mongo = MongoDBHelper(\'collection\') 27 mongo.insert_item({\'a\': 1}) 28 29 30 if __name__ == \'__main__\': 31 main()
douban.py
1 import logging 2 import random 3 import string 4 import requests 5 import time 6 from collections import deque 7 from urllib import parse 8 9 from settings import User_Agents 10 from MongDBHelper import MongoDBHelper 11 12 13 class DoubanSpider(object): 14 """豆瓣爬虫""" 15 def __init__(self): 16 # 基本的URL 17 self.base_url = \'https://movie.douban.com/j/new_search_subjects?\' 18 self.full_url = self.base_url + \'{query_params}\' 19 # 从User-Agents中选择一个User-Agent 20 self.headers = {\'User-Agent\': random.choice(User_Agents)} 21 # 影视形式(电影, 电视剧,综艺) 22 self.form_tag = None # 类型 23 self.type_tag = None # 地区 24 self.countries_tag = None # 特色 25 self.genres_tag = None 26 self.sort = \'T\' # 排序方式,默认是T,表示热度 27 self.range = 0, 10 # 评分范围 28 self.playable = \'\' 29 self.unwatched = \'\' 30 # 连接数据库,集合名为douban_movies 31 self.db = MongoDBHelper(\'douban_movies\') 32 33 def get_query_parameter(self): 34 """获取用户输入信息""" 35 # 获取tags参数 36 self.form_tag = input(\'请输入你想看的影视形式(电影|电视剧|综艺...):\') 37 self.type_tag = input(\'请输入你想看的影视类型(剧情|爱情|喜剧|科幻...):\') 38 self.countries_tag = input(\'请输入你想看的影视地区(大陆|美国|香港...):\') 39 self.genres_tag = input(\'请输入你想看的影视特色(经典|冷门佳片|黑帮...):\') 40 41 def get_default_query_parameter(self): 42 """获取默认的查询参数""" 43 # 获取 sort, range, playable, unwatched参数 44 self.range = input(\'请输入评分范围[0-10]:\') 45 self.sort = input(\'请输入排序顺序(热度:T, 时间:R, 评价:S),三选一:\').upper() 46 self.playable = input(\'请选择是否可播放(默认不可播放):\') 47 self.unwatched = input(\'请选择是否为我没看过(默认是没看过):\') 48 49 def encode_query_data(self): 50 """对输入信息进行编码处理""" 51 if not (self.form_tag and self.type_tag and self.countries_tag and self.genres_tag): 52 all_tags = \'\' 53 else: 54 all_tags = [self.form_tag, self.type_tag, self.countries_tag, self.genres_tag] 55 query_param = { 56 \'sort\': self.sort, 57 \'range\': self.range, 58 \'tags\': all_tags, 59 \'playable\': self.playable, 60 \'unwatched\': self.unwatched, 61 } 62 63 # string.printable:表示ASCII字符就不用编码了 64 query_params = parse.urlencode(query_param, safe=string.printable) 65 # 去除查询参数中无效的字符 66 invalid_chars = [\'(\', \')\', \'[\', \']\', \'+\', \'\\\'\'] 67 for char in invalid_chars: 68 if char in query_params: 69 query_params = query_params.replace(char, \'\') 70 # 把查询参数和base_url组合起来形成完整的url 71 self.full_url = self.full_url.format(query_params=query_params) + \'&start={start}\' 72 73 def download_movies(self, offset): 74 """下载电影信息 75 :param offset: 控制一次请求的影视数量 76 :return resp:请求得到的响应体""" 77 full_url = self.full_url.format(start=offset) 78 resp = None 79 try: 80 resp = requests.get(full_url, headers=self.headers) 81 except Exception as e: 82 # print(resp) 83 logging.error(e) 84 return resp 85 86 def get_movies(self, resp): 87 """获取电影信息 88 :param resp: 响应体 89 :return movies:爬取到的电影信息""" 90 if resp: 91 if resp.status_code == 200: 92 # 获取响应文件中的电影数据 93 movies = dict(resp.json()).get(\'data\') 94 if movies: 95 # 获取到电影了, 96 print(movies) 97 return movies 98 else: 99 # 响应结果中没有电影了! 100 # print(\'已超出范围!\') 101 return None 102 else: 103 # 没有获取到电影信息 104 return None 105 106 def save_movies(self, movies, id): 107 """把请求到的电影保存到数据库中 108 :param movies:提取到的电影信息 109 :param id: 记录每部电影 110 """ 111 if not movies: 112 print(\'save_movies() error: movies为None!!!\') 113 return 114 115 all_movies = self.find_movies() 116 if len(all_movies) == 0: 117 # 数据库中还没有数据, 118 for movie in movies: 119 id += 1 120 movie[\'_id\'] = id 121 self.db.insert_item(movie) 122 else: 123 # 保存已经存在数据库中的电影标题 124 titles = [] 125 for existed_movie in all_movies: 126 # 获取数据库中的电影标题 127 titles.append(existed_movie.get(\'title\')) 128 129 for movie in movies: 130 # 判断数据库中是否已经存在该电影了 131 if movie.get(\'title\') not in titles: 132 id += 1 133 movie[\'_id\'] = id 134 # 如果不存在,那就进行插入操作 135 self.db.insert_item(movie) 136 else: 137 print(\'save_movies():该电影"{}"已经在数据库了!!!\'.format(movie.get(\'title\'))) 138 139 def find_movies(self): 140 """查询数据库中所有的电影数目 141 :return: 返回数据库中所有的电影 142 """ 143 all_movies = deque() 144 data = self.db.find_item() 145 for item in data: 146 all_movies.append(item) 147 return all_movies 148 149 150 def main(): 151 """豆瓣电影爬虫程序入口""" 152 # 1. 初始化工作,设置请求头等 153 spider = DoubanSpider() 154 # 2. 与用户交互,获取用户输入的信息 155 spider.get_query_parameter() 156 ret = input(\'是否需要设置排序方式,评分范围...(Y/N):\') 157 if ret.lower() == \'y\': 158 spider.get_default_query_parameter() 159 # 3. 对信息进行编码处理,组合成有效的URL 160 spider.encode_query_data() 161 id = offset = 0 162 while True: 163 # 4. 下载影视信息 164 reps = spider.download_movies(offset) 165 # 5.提取下载的信息 166 movies = spider.get_movies(reps) 167 # 6. 保存数据到MongoDB数据库 168 # spider.save_movies(movies, id) 169 offset += 20 170 id = offset 171 # 控制访问速速 172 time.sleep(5) 173 174 175 if __name__ == \'__main__\': 176 main()
小结:在本次案例中,主要的难点有:查询参数的组合那部分和了解异步加载的原理从而找到真正的URL!查询参数的设置主要用到urlencode()方法,当我们不要把ASCII字符编码的时候,我们要设置safe参数为string.printable,这样只要把一些非ASCII字符编码就好了,同样quote()也是用来编码的,也有safe参数;那么本例中为什么要使用urlencode()方法呢?主要是通过观察URL是key=value的形式,所以才选用它!当我们把数据插入到数据库中时,如果是有相同的名字的电影,我们就不插入,这样也是处于对性能的考虑,合理利用资源!
以上是关于利用Python爬取豆瓣电影的主要内容,如果未能解决你的问题,请参考以下文章