本篇内容:
Scrapy核心架构和其组件的功能 Scrapy的工作流 Scrapy的中文输出储存 介绍CrawSpider 编写了一个爬虫实战来进行我们的mysql数据库操作
Scrapy的核心架构
如下图所示: 主要组件包括了Scrapy引擎,调度器,管道,下载中间件,下载器,spider蜘蛛,爬虫中间件,实体管道(Item Pipeline)等。
1、Scrapy引擎: scrapy引擎是整个scrapy架构的核心,负责控制整个数据处理流程,以及一些事物吃力。scrapy引擎与调度器、实体管道、中间件、下载器等组件都有关系,其出入整个框架的中心位置,对各项组件进行控制及协调。
2、调度器: 调度器主要实现储存待爬取的网址,并确定这些网址的优先级,决定下一次爬取哪个网址等。调度器会从引擎中接收request请求并存入优先队列中。
3、下载器: 下载器主要实现对网络上要爬取的网页资源进行高速下载,由于该组件需要通过网络进行大量数据的传输,所以该组件的压力负担也会比其他的多。下载器下载了对应的网页资源后,也会将这些数据传递给Scrapy引擎,再由Scrapy引擎传递给对应的爬虫进行处理。
4、下载中间件: 下载中间件是处于下载器和引擎之间的一个特定的组件。是用于全局修改Scrapy request和response的一个轻量、底层的系统。
5、蜘蛛spider: spider是定义如何抓取某个网站(或一组网站)的类,包括如何执行抓取(即关注链接)以及如何从其网页中提取结构化数据(即抓取项目)。换句话说,Spider是您定义用于为特定网站(或在某些情况下,一组网站)抓取和解析网页的自定义行为的位置。
6、爬虫中间件: 爬虫中间件是处于Scrapy引擎与爬虫组件之间的一个特定的组件,主要用于对爬虫组件和Scrapy引擎之间的通信进行处理。同时,在爬虫中间件中可以加入一些自定义代码,很轻松的实现Scrapy功能的扩展。 7、实体管道: 实体管道主要用于接收从蜘蛛组件中提取出来的项目。接收后,会对这些item进行对应的处理。常见的处理主要由:清洗、验证、储存到数据库中。
Scrapy工作流
我们已经知道了Scrapy框架中主要由哪些组件,以及各项组件的具体作用有什么呢,各项数据在组件中又是怎么进行的呢。
1、将网址传递给scrapy引擎。 2、scrapy引擎将网址传给下载中间件 3、下载中间键将网址给下载器 4、下载器像网址发送request请求进行下载 5、网址接收请求,将响应返回给下载器 6、下载器将收到的响应返回给下载中间件 7、下载中间件与scrapy引擎通信 8、scrapy将response响应信息传递给爬虫中间件 9、爬虫中间件将响应传递给对应的爬虫进行处理 10、爬虫处理之后,会提取出来的数据和新的请求信息,将处理的信息传递给爬虫中间件 11、爬虫中间件将处理后的信息传递给Scrapy引擎 12、scrapy接收到信息之后,会将项目实体传递给实体管道进行进一步处理,同时将新的信息传递给调度器。 13、随后再重复执行1-12步,一直到调度器中没有网址或异常退出为止。
以上就是Scrapy框架中各项组件的工作流程。此时相信我们队Scrapu框架数据处理的过程就又了比较详细的了解。
Scrapy中文输出与中文存储
使用Scrapy抓取中文时,输出一般是unicode,要输出中文也只需要稍作改动。
单纯交互输出 如代码:
代码语言:javascript复制title = sel.xpath('a/text()').extract()
print title
此时输出的是title对应中文的unicode格式,只需要指定“utf-8”编码即可输出中文,如下:
代码语言:javascript复制title = sel.xpath('a/text()').extract()
for t in title:
print t.encode('utf-8')
这里需要注意的是“encode()”函数是字符串专有的,而title是一个列表,因此需要对title中的每一个执行该操作。
存储 存储中文数据可以利用pipeline实现
1.定义pipeline
代码语言:javascript复制#coding: utf-8
import codecs
import json
class TutorialPipeline(object):
def __init__(self):
self.file = codecs.open('data_cn.json', 'wb', encoding='utf-8')
def process_item(self, item, spider):
line = json.dumps(dict(item)) 'n'
self.file.write(line.decode("unicode_escape"))
return item
上述方法将得到的item解码,以便正常显示中文,并保存到定义的json文件中。
2.注册自定义的pipeline 为了启动pipeline,必须将其加入到“ITEM_PIPLINES”的配置中,在settings.py中加入下面一句:
代码语言:javascript复制ITEM_PIPELINES = {
'tutorial.pipelines.TutorialPipeline':300
}
其中根目录是tutorial,pipelines是我的pipeline文件名,TutorialPipeline是类名
CrawlSpider详解:
在Scrapy基础——Spider中,我简要地说了一下Spider类。Spider基本上能做很多事情了,但是如果你想爬取知乎或者是简书全站的话,你可能需要一个更强大的武器。 CrawlSpider基于Spider,但是可以说是为全站爬取而生。 简要说明:
- CrawlSpider是爬取那些具有一定规则网站的常用的爬虫,它基于Spider并有一些独特属性rules: 是Rule对象的集合,用于匹配目标网站并排除干扰
- parse_start_url: 用于爬取起始响应,必须要返回Item,Request中的一个。
- 因为rules是Rule对象的集合,所以这里也要介绍一下Rule。它有几个参数:link_extractor、callback=None、cb_kwargs=None、follow=None、process_links=None、process_request=None
- 其中的link_extractor既可以自己定义,也可以使用已有LinkExtractor类,主要参数为: allow:满足括号中“正则表达式”的值会被提取,如果为空,则全部匹配。 deny:与这个正则表达式(或正则表达式列表)不匹配的URL一定不提取。 allow_domains:会被提取的链接的domains。 deny_domains:一定不会被提取链接的domains。 restrict_xpaths:使用xpath表达式,和allow共同作用过滤链接。还有一个类似的restrict_css
问题:CrawlSpider如何工作的?
因为CrawlSpider继承了Spider,所以具有Spider的所有函数。 首先由start_requests对start_urls中的每一个url发起请求,这个请求会被parse接收。 在Spider里面的parse需要我们定义,但CrawlSpider定义parse去解析响应。 根据有无callback,follow和self.follow_links执行不同的操作
代码语言:javascript复制def _parse_response(self, response, callback, cb_kwargs, follow=True): ##如果传入了callback,使用这个callback解析页面并获取解析得到的reques或item
if callback:
cb_res = callback(response, **cb_kwargs) or ()
cb_res = self.process_results(response, cb_res) for requests_or_item in iterate_spider_output(cb_res): yield requests_or_item ## 其次判断有无follow,用_requests_to_follow解析响应是否有符合要求的link。
if follow and self._follow_links:
for request_or_item in self._requests_to_follow(response): yield request_or_item
爬取豆瓣电影前250信息:
为了讲解后面的操作数据到数据库,这里插入scrapy框架爬取豆瓣网站信息。 首先创建项目,cmd输入命令
代码语言:javascript复制scrapy startproject doubanmovie
在spiders文件夹下创建文件MySpider.py。 在MySpider.py中创建类DoubanMovie继承自scrapy.Spider,同时定义以下属性和方法
name : 爬虫的唯一标识符 start_urls : 初始爬取的url列表 parse() : 每个初始url访问后生成的Response对象作为唯一参数传给该方法,该方法解析返回的Response,提取数据,生成item,同时生成进一步要处理的url的request对象
在settings文件中添加下面一行:
代码语言:javascript复制USER_AGENT = 'Mozilla/5.0 (Windows NT 6.3; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0'
在doubanmovie文件夹下创建文件MovieItems.py,在该文件下编写存放爬取到的数据的容器 创建类MovieItem继承自scrapy.Item,定义各种属性,语句类似以下
代码语言:javascript复制name = scrapy.Field()
整个parse()方法代码如下
代码语言:javascript复制 def parse(self, response):
selector = scrapy.Selector(response)
# 解析出各个电影
movies = selector.xpath('//div[@class="item"]')
# 存放电影信息
item = MovieItem()
for movie in movies:
# 电影各种语言名字的列表
titles = movie.xpath('.//span[@class="title"]/text()').extract()
# 将中文名与英文名合成一个字符串
name = ''
for title in titles:
name = title.strip()
item['name'] = name
# 电影信息列表
infos = movie.xpath('.//div[@class="bd"]/p/text()').extract()
# 电影信息合成一个字符串
fullInfo = ''
for info in infos:
fullInfo = info.strip()
item['info'] = fullInfo
# 提取评分信息
item['rating'] = movie.xpath('.//span[@class="rating_num"]/text()').extract()[0].strip()
# 提取评价人数
item['num'] = movie.xpath('.//div[@class="star"]/span[last()]/text()').extract()[0].strip()[:-3]
# 提取经典语句,quote可能为空
quote = movie.xpath('.//span[@class="inq"]/text()').extract()
if quote:
quote = quote[0].strip()
item['quote'] = quote
# 提取电影图片
item['img_url'] = movie.xpath('.//img/@src').extract()[0]
yield item
next_page = selector.xpath('//span[@class="next"]/a/@href').extract()[0]
url = 'https://movie.douban.com/top250' next_page
if next_page:
yield scrapy.Request(url, callback=self.parse)
数据存储: 目前选择将数据存放在json文件中,对数据库的处理在下面会讲解 在doubanmovie文件夹下创建文件MoviePipelines.py,编写类MoviePipeline,重写方法process_item(self, item, spider)用于处理数据。
代码语言:javascript复制import scrapy
from scrapy.contrib.pipeline.images import ImagesPipeline
from scrapy.exceptions import DropItem
class ImagePipeline(ImagesPipeline):
def get_media_requests(self, item, info):
yield scrapy.Request(item['image_url'])
def item_completed(self, results, item, info):
image_url = [x['path'] for ok, x in results if ok]
if not image_url:
raise DropItem("Item contains no images")
item['image_url'] = image_url
return item
同时在settings文件中注册并设置下载目录:
代码语言:javascript复制ITEM_PIPELINES = {
'doubanmovie.MoviePipelines.MoviePipeline': 1,
'doubanmovie.ImgPipelines.ImgPipeline': 100,
}
在settings文件中将ROBOTSTXT_OBEY改为False,让scrapy不遵守robot协议,即可正常下载图片
代码语言:javascript复制IMAGES_STORE = 'E:\img\'
scrapy数据存入mysql数据库:
将爬取的各种信息通过json存在文件中,不过对数据的进一步使用显然放在数据库中更加方便,这里将数据存入mysql数据库以便以后利用。
首先在项目settings文件中添加与数据库连接相关的变量
代码语言:javascript复制MYSQL_HOST = 'localhost'
MYSQL_DBNAME = 'zzz'
MYSQL_USER = 'root'
MYSQL_PASSWD = '111'
创建数据库和表
代码语言:javascript复制class MovieItem(scrapy.Item):
# 电影名字
name = scrapy.Field()
# 电影信息
info = scrapy.Field()
# 评分
rating = scrapy.Field()
# 评论人数
num = scrapy.Field()
# 经典语句
quote = scrapy.Field()
# 电影图片
img_url = scrapy.Field()
据此创建数据库表,创建数据库的时候加上DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci,以防出现乱码
代码语言:javascript复制create database douban DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
use douban;
CREATE TABLE doubanmovie (
name VARCHAR(100) NOT NULL, # 电影名字
info VARCHAR(150), # 电影信息
rating VARCHAR(10), # 评分
num VARCHAR(10), # 评论人数
quote VARCHAR(100), # 经典语句
img_url VARCHAR(100), # 电影图片
)
在MoviePipelines.py文件中创建类DBPipeline,在其中进行对数据库的操作。 首先连接数据库,获取cursor以便之后对数据就行增删查改
代码语言:javascript复制def __init__(self):
# 连接数据库
self.connect = pymysql.connect(
host=settings.MYSQL_HOST,
db=settings.MYSQL_DBNAME,
user=settings.MYSQL_USER,
passwd=settings.MYSQL_PASSWD,
charset='utf8',
use_unicode=True)
# 通过cursor执行增删查改
self.cursor = self.connect.cursor();
注意这里charset属性为 ‘utf8’,中间没有-,在调试过程中因为这个-搞了半天
之后重载方法process_item(self, item, spider),在其中执行数据的增删查改,通过cursor编写sql语句,然后使用self.connect.commit()提交sql语句
代码语言:javascript复制def process_item(self, item, spider):
try:
# 插入数据
self.cursor.execute(
"""insert into doubanmovie(name, info, rating, num ,quote, img_url)
value (%s, %s, %s, %s, %s, %s)""",
(item['name'],
item['info'],
item['rating'],
item['num'],
item['quote'],
item['img_url']))
# 提交sql语句
self.connect.commit()
except Exception as error:
# 出现错误时打印错误日志
log(error)
return item
最后在settings文件中注册DBPipeline
代码语言:javascript复制ITEM_PIPELINES = {
'doubanmovie.MoviePipelines.MoviePipeline': 1,
'doubanmovie.ImgPipelines.ImgPipeline': 100,
'doubanmovie.MoviePipelines.DBPipeline': 10,
}
可以尝试运行了。 然而爬取的数据是250条,在数据库存储中只有239条 查看MySpider.py文件
代码语言:javascript复制quote = movie.xpath('.//span[@class="inq"]/text()').extract()
if quote:
quote = quote[0].strip()
item['quote'] = quote
如果网页中quote属性不存在,那么将item插入数据库时就会出错,增加一条else语句
代码语言:javascript复制if quote:
quote = quote[0].strip()
else:
quote = ' '
item['quote'] = quote
本篇到此为止了。本篇内容讲解了Scrapy核心架构和其组件的功能,Scrapy的工作量。以及Scrapy的中文输出储存,介绍了CrawSpider。并编写了一个爬虫实战来进行我们的mysql数据库操作。 理论上差不多可以了,后面的内容将会讲解各种实战项目。