前段时间我写了一篇《scrapy快速入门》,简单介绍了一点scrapy的知识。最近个人搬瓦工让墙了,并且我又学了一点mongodb的知识,因此此次就来介绍一些scrapy的进阶知识,作一些真正有用的爬虫来。php
首先先来介绍一下scrapy的体系结构和组件。css
下面是scrapy官网的结构图,能够帮助咱们理解scrapy的体系。html
在之前的爬虫中咱们都是直接返回一个字典,其实这并非最佳实践。scrapy提供了一个Item基类,咱们能够经过继承这个类定义本身的结构化数据,比处处传递字典更好。下面是官方文档的例子。python
import scrapy class Product(scrapy.Item): name = scrapy.Field() price = scrapy.Field() stock = scrapy.Field() last_updated = scrapy.Field(serializer=str)
这些项目类通常都定义在scrapy项目的items.py
文件中。定义好以后,在爬虫中咱们就不该该在反掌字典了,而是初始化并返回咱们自定义的Item对象。git
scrapy经过请求和响应对象来处理网页请求,这部分的文档能够参考https://doc.scrapy.org/en/latest/topics/request-response.html。请求和响应还有一些子类,能够帮助咱们完成更具体的工做。例如Request
的子类FormRequest
就能够帮助咱们模拟用户登陆。github
有时候须要模拟用户登陆,这时候可使用FormRequest.from_response
方法。这时候爬虫功能稍有变化,parse
函数用来发送用户名和密码,抽取数据的操做放在回调函数中进行。web
import scrapy class LoginSpider(scrapy.Spider): name = 'example.com' start_urls = ['http://www.example.com/users/login.php'] def parse(self, response): return scrapy.FormRequest.from_response( response, formdata={'username': 'john', 'password': 'secret'}, callback=self.after_login ) def after_login(self, response): # 检查是否登陆成功 if "authentication failed" in response.body: self.logger.error("Login failed") return # 在这里继续爬取数据
管道用来处理爬虫抽取到的数据,咱们能够经过管道对数据进行验证和持久化等操做。管道其实就是带有process_item(self, item, spider)
函数的一个普通类。下面是scrapy官方文档的例子,这个例子验证获取到的数据是否存在价格字段,并丢弃没有价格字段的无效数据。这里还引用了scrapy预约义的DropItem
异常,这个异常必须在管道中抛出,表示管道应该丢弃这个数据。若是想了解scrapy异常,能够查看官方文档。算法
from scrapy.exceptions import DropItem class PricePipeline(object): vat_factor = 1.15 def process_item(self, item, spider): if item['price']: if item['price_excludes_vat']: item['price'] = item['price'] * self.vat_factor return item else: raise DropItem("Missing price in %s" % item)
管道不是必定义好就能用的,还须要在配置文件settings.py
中激活。mongodb
ITEM_PIPELINES = { 'myproject.pipelines.PricePipeline': 300, 'myproject.pipelines.JsonWriterPipeline': 800, }
管道除了验证数据,还能够将数据保存到数据库中。这时候仅仅一个process_item(self, item, spider)
函数就不够了。因此操做数据库的管道还应该包含几个函数用于创建和关闭数据库链接。数据库
下面的例子也是scrapy官方文档的例子,演示了持久化数据管道的用法。这个管道是从类方法from_crawler(cls, crawler)
中初始化出来的,该方法实际上读取了scrapy的配置文件。这和直接将数据库链接写在代码中相比,是更加通用的方式。初始化和关闭数据库链接的操做都在对应的方法中执行。
import pymongo class MongoPipeline(object): collection_name = 'scrapy_items' def __init__(self, mongo_uri, mongo_db): self.mongo_uri = mongo_uri self.mongo_db = mongo_db @classmethod def from_crawler(cls, crawler): return cls( mongo_uri=crawler.settings.get('MONGO_URI'), mongo_db=crawler.settings.get('MONGO_DATABASE', 'items') ) def open_spider(self, spider): self.client = pymongo.MongoClient(self.mongo_uri) self.db = self.client[self.mongo_db] def close_spider(self, spider): self.client.close() def process_item(self, item, spider): self.db[self.collection_name].insert_one(dict(item)) return item
除了本身编写管道以外,scrapy还预约义了几个管道,能够帮助咱们方便的保存文件和图片。这些管道有如下特色:
对于图片管道来讲还有额外功能:
使用文件管道的过程以下:
file_urls
和files
两个属性,而后在爬虫中将想爬取的文件地址放到file_urls
属性中,而后返回files
属性中,file_urls
和files
中是一一对应的使用图片管道的过程是类似的,不过要操做的属性是image_urls
和images
。
若是你不想使用这几个属性,其实属性名也是能够修改的,须要修改下面四个属性。
FILES_URLS_FIELD = 'field_name_for_your_files_urls' FILES_RESULT_FIELD = 'field_name_for_your_processed_files' IMAGES_URLS_FIELD = 'field_name_for_your_images_urls' IMAGES_RESULT_FIELD = 'field_name_for_your_processed_images'
要启用文件管道和图片管道,一样须要激活,固然若是同时激活这两个管道也是可行的。
ITEM_PIPELINES = {'scrapy.pipelines.images.ImagesPipeline': 1} # 或者 ITEM_PIPELINES = {'scrapy.pipelines.files.FilesPipeline': 1}
文件和图片保存位置须要分别指定。
FILES_STORE = '/path/to/valid/dir' IMAGES_STORE = '/path/to/valid/dir'
文件和图片管道能够避免下载最近的文件,对应的文件过时时间也能够配置,单位是天。
# 120 days of delay for files expiration FILES_EXPIRES = 120 # 30 days of delay for images expiration IMAGES_EXPIRES = 30
图片管道能够在保存图片的时候同时生成缩略图,缩略图配置是一个字典,键是缩略图的名字,值是缩略图长和宽。
IMAGES_THUMBS = { 'small': (50, 50), 'big': (270, 270), }
最后图片会保存成下面这样,图片的文件名是图片路径的SHA1哈希值。
/图片保存路径/full/完整图片.jpg /图片保存路径/thumbs/small/小图片.jpg /图片保存路径/thumbs/big/中图片.jpg
若是不想使用SHA1哈希值做为文件名,能够继承ImagesPipeline
基类并重写file_path
函数,这里是另一位简书做者的爬虫项目,他重写了这个函数。咱们能够做为参考。
若是要过滤小图片,启用下面的配置。默认状况下对图片尺寸没有约束,因此全部图片都会下载。
IMAGES_MIN_HEIGHT = 110 IMAGES_MIN_WIDTH = 110
默认状况下文件和图片管道不支持重定向,遇到须要重定向的连接意味着下载失败,不过咱们也能够启用重定向。
MEDIA_ALLOW_REDIRECTS = True
下载器中间件能够在scrapy引擎和爬虫之间操纵请求和响应对象。要启用下载器中间件,启用下面的配置。这是一个字典,字典的键是要启用的中间件,值会用来比较中间件之间的顺序。
DOWNLOADER_MIDDLEWARES = { 'myproject.middlewares.CustomDownloaderMiddleware': 543, }
若是但愿禁用某些内置的中间件,能够将值设置为None
。
DOWNLOADER_MIDDLEWARES = { 'myproject.middlewares.CustomDownloaderMiddleware': 543, 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None, }
自定义下载器中间件应该继承scrapy.downloadermiddlewares.DownloaderMiddleware
类,该类有以下几个方法,用于操纵请求和响应,咱们只要重写这几个方法便可。这几个方法的做用请参考官方文档,它们比较复杂,因此我就不说了。
scrapy内置了14个下载器中间件,我简单介绍一下其中的几个。详情参考文档。
用于在爬虫发起请求和获取响应的时候保持Cookie。
用于设置请求的默认请求头。
该配置位于DEFAULT_REQUEST_HEADERS
,默认值以下。
{ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en', }
设置使用的网络代理。
设置使用的用户代理。
与下载器中间件相似,启用爬虫中间件须要一个字典来配置。
SPIDER_MIDDLEWARES = { 'myproject.middlewares.CustomSpiderMiddleware': 543, }
想要关闭某个中间件的时候传递None
值。
SPIDER_MIDDLEWARES = { 'myproject.middlewares.CustomSpiderMiddleware': 543, 'scrapy.spidermiddlewares.offsite.OffsiteMiddleware': None, }
编写本身的爬虫中间件须要继承scrapy.spidermiddlewares.SpiderMiddleware
基类,并重写如下几个方法。
scrapy内置了5个爬虫中间件,这里我仅介绍一两个。
该中间件记录了爬虫爬取请求地址的深度。
咱们可使用DEPTH_LIMIT
来指定爬虫爬取的深度。
该中间件会过滤掉超出最大容许长度的URL,爬虫不会访问这些超长URL。
最大长度经过URLLENGTH_LIMIT
配置来指定,默认值是2083。
URLLENGTH_LIMIT = 2083
scrapy内置了几个服务,可让咱们使用scrapy更加方便。
爬虫类定义了log
函数,咱们能够方便的在爬虫类中记录日志。
import scrapy class MySpider(scrapy.Spider): name = 'myspider' start_urls = ['https://scrapinghub.com'] def parse(self, response): self.logger.info('Parse function called on %s', response.url)
日志相关的配置,点击能够跳转到官方文档查看详细信息。
有时候咱们可能但愿爬到必定数量的数据就发送电子邮件进行提醒。scrapy也内置了这个功能。咱们能够经过构造函数参数来建立邮件发送器。
from scrapy.mail import MailSender mailer = MailSender(这里是构造函数参数)
也能够从配置文件实例化。
mailer = MailSender.from_settings(settings)
而后调用send
方法就能够发送邮件了。
mailer.send(to=["someone@example.com"], subject="Some subject", body="Some body", cc=["another@example.com"])
电子邮件相关配置参考官方文档。
这个功能原本是写在官方文档内建服务条目下的,可是实际上这个功能已经变成了一个单独的项目,须要额外安装。
pip install scrapy-jsonrpc
而后在扩展中包含这个功能。
EXTENSIONS = { 'scrapy_jsonrpc.webservice.WebService': 500, }
还须要在配置中启用该功能。
JSONRPC_ENABLED = True
而后在爬虫运行的时候访问http://localhost:6080/crawler便可查看爬虫运行状况了。
该项目的其余配置查看其官方文档。
爬虫项目能够经过修改一些配置进行优化。
并发数能够经过下面的配置进行设置。具体的并发数须要根据服务器的CPU等设置来进行更改。通常来讲服务器CPU使用在80%-90%之间利用率比较高。咱们能够从并发数100开始反复进行测试。
CONCURRENT_REQUESTS = 100
scrapy经过一个线程池来进行DNS查询,增大这个线程池通常也能够提升scrapy性能。
REACTOR_THREADPOOL_MAXSIZE = 20
默认状况下scrapy使用debug
级别来打印日志,经过下降日志级别,咱们能够减小日志打印,从而提升程序运行速度。
LOG_LEVEL = 'INFO'
若是不是必须的,咱们能够经过禁用Cookie来提升性能。若是须要登陆用户才能爬取数据,不要禁用Cookie。
COOKIES_ENABLED = False
频繁重试可能致使目标服务器响应缓慢,咱们本身访问不了别人也访问不了。因此能够考虑关闭重试。
RETRY_ENABLED = False
若是网络链接比较快的话,咱们能够减小下载超时,让爬虫卡住的请求中跳出来,通常能够提升爬虫效率。
DOWNLOAD_TIMEOUT = 15
若是不是必要的话,咱们能够关闭重定向来提升爬虫性能。
REDIRECT_ENABLED = False
scrapy有一个扩展能够自动调节服务器负载,它经过一个算法来肯定最佳的爬虫延时等设置。它的文档在这里。
相关配置以下,点击连接能够跳转到对应文档。
AUTOTHROTTLE_ENABLED
AUTOTHROTTLE_START_DELAY
AUTOTHROTTLE_MAX_DELAY
AUTOTHROTTLE_TARGET_CONCURRENCY
AUTOTHROTTLE_DEBUG
CONCURRENT_REQUESTS_PER_DOMAIN
CONCURRENT_REQUESTS_PER_IP
DOWNLOAD_DELAY
官方文档介绍了两种部署爬虫的方式,能够将爬虫部署到服务器上远程执行。第一种是经过Scrapyd开源项目来部署,也是这里要介绍的方式。第二种是经过scrapy公司提供的商业收费版服务Scrapy Cloud部署,推荐有财力的公司考虑。
首先服务器须要安装scrapyd包,若是是Linux系统还能够考虑使用对应的包管理器来安装。
pip install scrapyd apt-get install scrapyd
而后运行scrapyd服务,若是使用系统包管理器安装,那么可能已经配置好了systemd文件。
scrapyd # 或者 systemctl enable scrapyd
scrapyd附带了一个简单的web界面能够帮助咱们查看爬虫运行状况,默认状况下访问http://localhost:6800/来查看这个界面。
scrapyd的配置文件能够是~/.scrapyd.conf
或者/etc/scrapyd/scrapyd.conf
。下面是一个简单配置,绑定全部端口,这样一来从任意位置均可以访问web界面。
[scrapyd] bind_address = 0.0.0.0
scrapyd的功能能够查看其API文档。
客户端若是要上传爬虫,能够经过服务器API的端点addversion.json来实现,或者安装一个简便工具scrapyd-client。
首先安装客户端工具。
pip install scrapyd-client
这个客户端目前好像有bug,在windows下运行scrapy-deploy
命令不会直接执行,而是弹出一个文件关联对话框。若是你遇到这种状况,能够找到Python安装路径下的脚本路径(例如C:\Program Files\Python36\Scripts
),而后编写一个scrapyd-deploy.bat
批处理文件,内容以下。这样就能够正常运行了。
@"c:\program files\python36\python.exe" "c:\program files\python36\Scripts\scrapyd-deploy" %*
而后切换到项目路径,编辑项目全局配置文件scrapy.cfg
,添加部署路径。
[deploy] url = http://192.168.64.136:6800/ project = quotesbot
而后直接运行scrapy-deploy
命令,就能够看到项目已经成功部署到服务器上了。
运行爬虫须要使用scrapyd的API,例如使用curl,能够用下面的命令。
curl http://192.168.64.136:6800/schedule.json -d project=quotesbot -d spider=toscrape-css
或者使用Jetbrains 系列IDE 2017.3的基于编辑器的HTTP客户端。
而后点击Jobs就能够看到爬虫已经开始运行了。若是要查看状态,点击右边的log便可。
以上就是scrapy的进阶介绍了,利用这些功能,咱们能够编写更加实用的爬虫,并将它们部署到服务器上。