Scrapy是一个基于Python爬虫框架,可以方便的抓取WEB网页,并从中提取结构化的数据。
1.Scrapy框架简介
1.1 Scrapy框架组成
Scrapy框架主要由五大组件组成,分别是调度器(Scheduler)、下载器(Downloader)、爬虫(Spider)和实体管道(Item Pipeline)、Scrapy引擎(Scrapy Engine)。
(1) 调度器(Scheduler)
负责接受引擎发送过来的Request请求,并按照一定的方式进行整理排列,入队,当引擎需要时,交还给引擎。用户可根据自己的需求定制调度器。
(2) 下载器(Downloader)
负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spider来处理。Scrapy下载器是建立在高效的异步模型twisted上的,效率非常高。
(3) 爬虫(Spider)
负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器)。爬虫需要用户根据自己的需求进行编写。
(4) 实体管道(Item Pipeline)
负责处理Spider中获取到的Item,并进行数据分析、过滤、验证、持久化等处理。
(5) Scrapy引擎(Scrapy Engine)
Scrapy引擎是整个框架的核心,负责Spider、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等,相当于计算机的CPU,它控制着整个流程。
1.2 Scrapy爬虫编写步骤
编写Scrapy爬虫可以分为四个步骤:
(1) 新建项目 :新建一个新的爬虫项目
(2) 明确目标 :明确你想要抓取的目标,编写items.py
(3) 制作爬虫 :编写爬取网页的爬虫,编写spiders/xxspider.py
(4) 存储内容 :设计管道存储爬取内容,编写pipelines.py
此外,还需要根据所爬取的网站特点,修改scrapy.cfg配置文件。
2.安装Scrapy
2.1 安装pip3
Scrapy是基于Python的,通过pip工具安装。首先需要安装pip工具,教程中采用系统自带的Python 3.6,因此要安装pip3。
sudo apt install python3-pip
2.2 更换pip3源
安装过程中,如果pip3安装下载缓慢,可以更换pip3的安装源到阿里的源。
mkdir ~/.pip
vim ~/.pip/pip.conf
[global]
index-url = https://mirrors.aliyun.com/pypi/simple
2.3 安装Scrapy所需的类库
为了避免安装过程中出错,把Scrapy所需的类库预先安装好。
sudo apt-get install python-dev python3-dev libxml2-dev libxslt1-dev zlib1g-dev libffi-dev libssl-dev
2.4 安装scrapy
sudo pip3 install scrapy
2.5 安装pymongo
本教程中,把爬虫爬取的数据存入《MongoDB Replicaset集群搭建教程》中搭建的MongoDB Replicaset副本集,还需要安装pymongo。
pymongo是在Python中用于连接 MongoDB服务器的驱动库。我们在Python3环境中访问MongoDB数据库时,需要pymongo连接并操作数据库。
在python3环境中的安装命令如下:
sudo pip3 install pymongo==3.2.0
MongoDB Replicaset副本集的配置,请参考MongoDB数据库相关的教程。
3.编写scrapy爬虫
豆瓣网是一个非常知名的社区网站。我们就以豆瓣电影网站movie.douban.com为例,结合scrapy爬虫抓取电影信息,来实践MongoDB数据库的数据库创建、collection的创建和document的存储。
3.1 新建爬虫
3.1.1创建工程
scrapy startproject scrapy_douban
3.1.2创建爬虫
scrapy genspider douban movie.douban.com
3.1.3 去除setting.py中把ITEM-PIPELINES开关注释
3.1.4 网站抓取数据太快的话,会出现服务器访问错误,因此我们设置0.5秒发送一个请求,在setting.py中修改DOWNLOAD-DELAY = 0.5
3.2 分析网站
打开豆瓣电影主页,点击进入"选电影"页面,我们可以按照不同的标签来选电影,如"热门"、"最新"、"经典"等。
我们以"热门"标签为例,来分析网页数据。选择"热门"标签后,具体电影的列表显示会以热度、时间、评价三种排序方式可供选择。
我们选择默认的"按热度排序"方式,页面会显示20个电影信息,点击电影下方的"加载更多"后,会更新20个电影信息。
点击具体电影后,会进入电影信息页面,我们可以在这个页面上抓取具体的影片信息,并存入MongoDB数据库。
这样,整体的思路为:从"选电影"页面上抓取标签信息,然后轮询标签,抓取各个标签下的电影列表的URL,再从这些URL的页面上抓取具体的影片信息,存入MongDB数据库。
3.3 抓取标签信息
我们来分析一下豆瓣电影的HTML代码,在页面上显示标签的位置,并没有直接的标签信息,而是一个form表单。
......
<div class="filter">
<form action="get" class="gaia_frm" autocomplete="off">
<input type="hidden" name="type" value="movie">
<div class="tags">
<div class="tag-list"></div>
<div class="custom-frm" data-type="tag">
<input type="text" />
<button>确定</button>
</div>
</div>
......
</form>
......
</div>
这样就必须是由JS启动获取具体的标签请求,来填充这个form。我们分析chrome浏览器的开发者工具中的"Network"交互数据,找到了获取具体标签的URL请求:
https://movie.douban.com/j/search_tags?type=movie&source=
我觉得这样做是有好处的,一方面可以动态更新网页上的标签信息,另一方面也可以根据登录用户的历史数据,展示用户更喜欢的影片信息。
我们简单处理标签问题,手动确定几个标签。
tags = ['热门','经典','华语']
3.4 抓取电影列表
跟标签类似,电影的列表信息也是由另外的请求获取的。我们同样从"Network"交互数据中找到了请求的URL:
https://movie.douban.com/j/search_subjects?type=movie&tag=%E7%83%AD%E9%97%A8&sort=recommend&page_limit=20&page_start=0
URL中有两个重要参数:tag为标签信息,page-start为获取影片的起始index,以步长20累加,每次点击"加载更多",都会page-start都会增加20,取获取新的影片列表。
我们定义parse-movielist函数来处理循环获取的电影列表:
url_tpl = "https://movie.douban.com/j/search_subjects?type=movie&tag={tag}&sort=recommend&page_limit=20&page_start={page}"
tags = ['热门','经典','华语']
for tag in tags:
# 可以自行调整抓取的页数 页数 = max/20
max = 40
start = 0
while start <= max:
url = url_tpl.format(tag=tag, page=start)
yield scrapy.Request(url, callback=self.parse_movielist)
start += 20
运行上述代码时,会出现以下错误:
2019-11-15 19:58:38 [scrapy.downloadermiddlewares.robotstxt] DEBUG: Forbidden by robots.txt: <GET https://movie.douban.com/j/search_subjects?type=movie&tag=%E7%83%AD%E9%97%A8&sort=recommend&page_limit=20&page_start=0>
2019-11-15 19:58:39 [scrapy.core.engine] INFO: Closing spider (finished)
这是因为scrapy在爬取设定的URL之前,会先向服务器根目录请求一个robots.txt文件,这个文件规定了爬取范围,scrapy会查看自己是否符合权限,出错说明不符合,所以我们设定
不遵守这个协议。
在settings.py中找到ROBOTSSTXT的设置项目,设为FALSE。
ROBOTSTXT_OBEY = False
3.5 抓取影片信息
请求返回的数据是标准的json数据,存放了20个影片的概要信息,我们可以从中获取影片详情的URL,并存放在数组中,然后循环获取影片详情。
def parse_movielist(self, respones):
#respones是json字符串
resjson = respones.body.decode()
movielist = json.loads(resjson)
urls = []
for movie in movielist.get("subjects"):
urls.append(movie["url"].replace('\/','/'))
for url in urls:
yield scrapy.Request(url, callback=self.parse_movie)
返回的影片列表信息中包含16进制的UTF-8中文编码,需要先decode,才能转换成json结构。
3.6 存储影片信息
进入影片详情页面,从页面的HTML代码中可以找到影片的详情都在id="content"的div中,直接解析获取即可。
我们在items.py中给影片信息定义5个字段,分别为:电影名字、详情、发布年份、评星、电影简介。
class ScrapyDoubanItem(scrapy.Item):
moviename = scrapy.Field()
info = scrapy.Field()
year = scrapy.Field()
stars = scrapy.Field()
synopsis = scrapy.Field()
获取到影片详情后,存入MongoDB数据库。
MongoDB中的数据库名字为douban,collection名字为movies。
class ScrapyDoubanPipeline(object):
def __init__(self):
host = '127.0.0.1'
port = 27017
dbname = 'douban'
sheetname = 'movies'
self.client = pymongo.MongoClient('mongodb://admin:admin@127.0.0.1:27017/')
db = self.client[dbname]
self.moviedb = db[sheetname]
def process_item(self, item, spider):
data = dict(item)
self.moviedb.insert(data)
4.运行爬虫
在命令行中,运行scrapy crawl douban, 爬虫就开始工作啦!
我们来看一下爬虫的成果。
mongo命令,连接MongoDB Replicaset的主(Primary)节点。
用"show dbs"命令查看数据库,douban数据库已经建立。
jishu@Jishu:~$ sudo mongo 127.0.0.1:27017/admin -u admin -p admin
MongoDB shell version v4.2.6
......
test:PRIMARY> show dbs;
admin 0.000GB
config 0.000GB
douban 0.000GB
local 0.001GB
进入douban数据库,用"show collections"命令查看collections,collection douban成功建立。
test:PRIMARY> use douban
switched to db douban
test:PRIMARY> show tables;
movies
查看collection douban中的数据。
> db.movies.find()
{ "_id" : ObjectId("5dd3c5b374fece22577d3949"), "moviename" : "比悲伤更悲伤的故事 比悲傷更悲傷的故事", "year" : "2018", "info" : "\n 导演: 林孝谦\n 编剧: 吕安弦\n 主演: 陈意涵 / 刘以豪 / 张书豪 / 陈庭妮 / 吴映洁 / 禾浩辰 / 游大庆 / 石知田 / 黄丽玲 / 姚爱宁\n 类型: 爱情\n \n 制片国家/地区: 中国台湾\n 语言: 汉语普通话\n 上映日期: 2018-11-30(中国台湾) / 2019-03-14(中国大陆)\n 片长: 105分钟\n 又名: More Than Blue\n IMDb链接: tt9081562\n\n", "stars" : "4.8", "synopsis" : "唱片制作人张哲凯(刘以豪)和王牌作词人宋媛媛(陈意涵)相依为命,两人自幼身世坎坷只有彼此为伴,他们是亲人、是朋友,也彷佛是命中注定的另一半。父亲罹患遗传重症而被母亲抛弃的哲凯,深怕自己随时会发病不久人世,始终没有跨出友谊的界线对媛媛展露爱意。眼见哲凯的病情加重,他暗自决定用剩余的生命完成他们之间的终曲,再为媛媛找个可以托付一生的好男人。这时,事业有 成温柔体贴的医生(张书豪)适时的出现让他成为照顾媛媛的最佳人选,二人按部就班发展着关系。一切看似都在哲凯的计划下进行。然而,故事远比这里所写更要悲伤......" }
{ "_id" : ObjectId("5dd3c5b574fece22577d394a"), "moviename" : "一条狗的使命2 A Dog's Journey", "year" : "2019", "info" : "\n 导演: 盖尔·曼库索\n 编剧: W·布鲁斯·卡梅伦 / 玛雅·福布斯 / 凯瑟琳·迈克 / 华莱士·沃洛达斯基\n 主演: 丹尼斯·奎德 / 凯瑟琳·普雷斯科特 / 刘宪华 / 玛格·海根柏格 / 贝蒂·吉尔平 / 乔什·加德 / 艾比·莱德·弗特森 / 杰克·曼利 / 达妮埃拉·巴博萨 / 陈琦烨 / 杰夫·罗普 / 吉姆·科比\n 类型: 剧情 / 喜剧 / 家庭\n \n 制片国家/地区: 中国大陆 / 印度 / 中国香港 / 美国\n 语言: 英语\n 上映日期: 2019-05-17(美国/中国大陆)\n 片长: 108分钟\n 又名: 再见亦是狗朋友2(港) / 狗狗的旅程(台) / 一条狗的旅程 / 为了与你相遇2 / A Dog's Purpose 2\n IMDb链接: tt8385474\n\n", "stars" : "6.9", "synopsis" : "小狗贝利延续使命,在主人伊森的嘱托下,通过不断的生命轮回, 执着守护伊森的孙女CJ,将伊森对孙女的爱与陪伴,当做最重要的 使命和意义,最终帮助CJ收获幸福,再次回到主人伊森身边。" }
......
数据都正常存储。
mongo命令,连接备份(Secondary)节点,也可以查询到douban数据库的数据。