Scrapy框架
架构
- Engine - 引擎:处理数据流、触发事务。
- item - 项目:数据结构,类。
- Schedul - 调度器:处理请求队列。
- Download - 下载器:请求。
- Spiders - 蜘蛛:爬取逻辑和网页解析规则。
- item Pipeline - 项目管道:处理结果数据,清洗入库等。
- Downloader Midddlewares - 下载器中间件
- Spider Midddlewares - 蜘蛛中间件
数据流
命令行调用
- 子项目
- Engine找到对应的Spider,并获取第一个url。
- spider将第一个url发送给调度器。
- 调度器返回队列中的url给engine,engine将url通过中间件发送给Downloader下载器请求页面。
- 下载器请求完毕,将响应结果发给engine,engine转发给spider。
- spider处理response,提取到item或新的请求。返回给engine。
- engine把item发送给pipline,url发送给调度器。
项目结构
scrapy startproject tutorial
scrapy genspider qutes qutes.toscrape.com
项目
item
用于保存数据对象,用法与字典类似(item[‘title’]=‘标题’)。定义需要的字段。
spider
- name:Spider子项目的名字。(启动子项目,scrapy crawl name ,唯一)
- allowed_domains:允许这个子项目爬取的域名。
- start_urls:Spider最开始的启动url列表。
- parse():start_urls里面的url被请求之后,返回的响应体就会传到这个方法做处理,然后返回item或者新的请求。
def parse(self, response): article_list = response.css(".common-list") article_list = article_list.xpath(".//ul/li") for article in article_list: item = ArticleItem() item['link'] = article.xpath(".//a/@href").extract_first() item['title'] = article.xpath(".//a/@title").extract_first() item['content'] = article.xpath(".//a/text()").re_first('(.*)') # yield item # 返回item对象 交给piplines处理 # yield scrapy.Request(url=item['link'],meta={'item':item},callback=self.parse_detail) # 返回一个新的请求,请求之后的响应体交给自己定义的parse_detail方法处理,若有参数需要传递,用meta传递和提取。
pipline
数据提取完成,item构造完毕之后,可以在启动子项目时以添加命令行参数的方式保存到文件中,如:
- scrapy crawl quotes -o quotes.json # 保存为JSON文件
- scrapy crawl quotes -o quotes.csv # 保存为csv|xml|pickle|marshal文件
- scrapy crawl quotes -o ftp://user:pass@ftp.example.com/path/to/quote/quotes/csv # 也可以保存到远程
除此之外,如果有更复杂的需求,可以在pipline中编写:
-
pipline用法
- pipline表达为一个Pipline类,在这个类中定义一个process_item()方法,启用这个类后,就会调用这个方法。这个方法必须返回一个item对象或者一个DropItem异常(需要导入 from scrapy.exceptions import DropItem)。
- 在下例中,定义了两个pipline类:
- 第一个类ArticlePipeline希望实现对item对象中数据的清洗。如,截取标题的前50个字。
- 第二个类MongoPipeline则希望将item对象存入mongodb数据库中。在from_crawler类方法中通过全局配置参数cralwer获取到settings.py中的全局配置。open_spider、close_spider会分别在Spider开启或关闭时调用。
-
pipeline调用方式
- spider返回item之后按优先级顺序调用所有的Pipeline类方法。
- Pipeline类定义好之后,如需调用需要在settings.py中,将类添加到ITEM_PIPELINES。
用法
spider的用法
定义爬取网站的动作;分析爬取下来的网页。
- name:爬虫名称,是定义Spider名字的字符串。Spider的名字定义了Scrapy如何定位并初始化Spider,它必须是唯一的。常用的命名方式是以域名的名称来命名。
- allowed_domains:允许爬取的域名,是可选配置,不在此范围的连接不会被跟进爬取。
- start_urls:起始url。相当于调度器初始值。
- custom_settings:spider配置,覆盖全局设置。类变量
- crawler:由from_crawler()设置,用于读取配置信息。
- settings:它是一个Setting对象,用于获取全局变量。
- start_requests():初始请求。必须返回一个可迭代对象。
- parse():当Response响应体没有回调函数时,就会调用这个函数。
- closed():Spider关闭时调用。用于释放资源等。
下载中间件Dowload Middleware的用法
调度器将请求发送给下载器以及下载器将响应发送给Spider时会经过下载中间件
说明
下载中间件可以用于修改User-Agent、处理重定向、设置代理、失败重试、设置Cookies等功能。
- 每个中间件包含一个或多个方法,其中核心方法有三个,
- process_request(request,spider) # 在请求被engine发送给下载器前调用 #return requests\response\None\exception
- process_response(request,response,spider) # 下载器执行请求之后将响应体发送给sppider之前 #return response\None\exception
- process_exception(request,exception,spider) # 当下载器或process_request抛出异常时调用 #return requests\response\None
- scrapy 提供失败重试、自动重定向等等中间件,其中每个中间件按优先级先后执行。对于process_request()方法来说,优先级数字越小越显被调用,对于process_response()方法来说优先级越大越先调用
返回值
- process_request返回值为None、Response对象、Request对象之一,或者抛出IgnoreRequest异常
- 返回为None,按优先级后续的中间件接着执行process_request方法(可以理解为修改request方法
- 返回为Response对象,执行低优先级的process_response方法(相当于对响应体信息处理
- 返回为Request对象,低优先级中间件停止执行,然后将这个Request添加到调度器。(相当于重构了一个新的请求
- 抛出IgnoreRequest异常,低优先级的process_exception就会依次执行,若最终还是异常就回调这个请求的errorback(),若还是没有处理,则被忽略
- process_response返回值为Response对象、Request对象之一,或者抛出IgnoreRequest异常。
- 返回为Response对象,低优先级的中间件继续执行process_response()
- 返回为Request对象,低优先级的中间件中的process_response()则不会调用,产生一个新的请求发送给调度器。
- 抛出IgnoreRequest异常,交给请求的callback()方法回调。
3.process_exception返回值为None、Response对象、Request对象之一。 - 返回为None,低优先级中中间件的process_exception继续调用。
- 返回为Response对象,低优先级的process_response继续调用。
- 返回为Request对象,低优先级的中间件不再被调用。产生一个新的请求发送给调度器。
使用
在上例中,定义了一个中间件。
- 在process_request方法中,随机返回了请求头。
- 在process_response方法中,把响应体的状态码302替换成了200。
- 然后添加到 settings.py
scrapy原生下载中间件
DOWNLOADER_MIDDLEWARES_BASE = { # Engine side 'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100, 'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware': 300, 'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': 350, 'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 400, 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 500, 'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550, 'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware': 560, 'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware': 580, 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 590, 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': 600, 'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': 700, 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 750, 'scrapy.downloadermiddlewares.stats.DownloaderStats': 850, 'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': 900, # Downloader side }
Spider Middleware的用法
下载器把响应体发送给Spider之前以及Spider生成Item或请求之后都会经过Spider Middleware处理。
说明
核心方法
每个中间件都定义了以下一个或四个方法:
- process_spide_input(response,spider)
- 参数:response - 被处理的响应体,spider - 对应的Spider
- 返回值:None或抛出一个异常
- 返回为None时,继续调用低优先级的中间件。
- 抛出异常时,低优先级的中间件将不会执行。调用该请求的errback(),errback的输出将会被重新输入到中间件,调用process_spider_output()。
- process_spider_output(response,result,spider)
- 参数:response - 被处理的响应体,result - 包含请求或者item对象的可迭代对象,也就是Spider的返回值。
- 返回值:返回请求或者item的可迭代对象
- process_spider_exception(response,exception,spider)
- 参数:response - 被处理的响应体,exception - 被抛出的异常,spider - 抛出该异常的spider
- 返回值:None或者(包含请求或者item的可迭代对象)
- 返回为None时,继续调用低优先级的中间处理该异常。
- 返回为可迭代对象时,调用低优先级的process_spider_output()。
- process_start_requests(start_request,spider)
- 参数:start_request - 包含请求的可迭代对象,spider - 即Spider对象
- 返回值:包含请求的可迭代对象。
使用
class OffsiteMiddleware: def __init__(self, stats): self.stats = stats @classmethod def from_crawler(cls, crawler): o = cls(crawler.stats) crawler.signals.connect(o.spider_opened, signal=signals.spider_opened) return o def process_spider_output(self, response, result, spider): for x in result: if isinstance(x, Request): if x.dont_filter or self.should_follow(x, spider): yield x else: domain = urlparse_cached(x).hostname if domain and domain not in self.domains_seen: self.domains_seen.add(domain) logger.debug( "Filtered offsite request to %(domain)r: %(request)s", {'domain': domain, 'request': x}, extra={'spider': spider}) self.stats.inc_value('offsite/domains', spider=spider) self.stats.inc_value('offsite/filtered', spider=spider) else: yield x
scrapy原生Spider Middleware
SPIDER_MIDDLEWARES_BASE = { # Engine side 'scrapy.spidermiddlewares.httperror.HttpErrorMiddleware': 50, 'scrapy.spidermiddlewares.offsite.OffsiteMiddleware': 500, 'scrapy.spidermiddlewares.referer.RefererMiddleware': 700, 'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware': 800, 'scrapy.spidermiddlewares.depth.DepthMiddleware': 900, # Spider side }
item Pipline的用法
item pipline主要功能:
- 清理HTML数据
- 验证爬取数据,检查爬取字段
- 查重并丢弃重复内容
- 将爬取结果保存到数据库
核心方法
除了必须要有的process_item之外,还有其余几个比较实用的方法:
- open_spider(spider)
- close_spider(spider)
- from_crawler(cls,crawler)
Scrapy提供了专门处理下载的Pipeline包括文件下载和图片下载。下载文件和图片的原理与抓取页面的原理一样,因此下载过程支持异步和多线程,下载十分高效。
内置的图片下载Pipline
class ImagesPipeline(FilesPipeline): """Abstract pipeline that implement the image thumbnail generation logic """ MEDIA_NAME = 'image' # Uppercase attributes kept for backward compatibility with code that subclasses # ImagesPipeline. They may be overridden by settings. MIN_WIDTH = 0 MIN_HEIGHT = 0 EXPIRES = 90 THUMBS = {} DEFAULT_IMAGES_URLS_FIELD = 'image_urls' DEFAULT_IMAGES_RESULT_FIELD = 'images'
DEFAULT_IMAGES_URLS_FIELD = 'image_urls'
DEFAULT_IMAGES_RESULT_FIELD = 'images'
def __init__(self, store_uri, download_func=None, settings=None): ...... if not hasattr(self, "IMAGES_RESULT_FIELD"): self.IMAGES_RESULT_FIELD = self.DEFAULT_IMAGES_RESULT_FIELD if not hasattr(self, "IMAGES_URLS_FIELD"): self.IMAGES_URLS_FIELD = self.DEFAULT_IMAGES_URLS_FIELD self.images_urls_field = settings.get( resolve('IMAGES_URLS_FIELD'), self.IMAGES_URLS_FIELD ) def get_media_requests(self, item, info): urls = ItemAdapter(item).get(self.images_urls_field, []) return [Request(u) for u in urls]
可以看到这个Pipeline会默认读取item中的image_urls字段,并且认为该字段为一个列表。在get_media_requests方法中,遍历image_urls字段并生成一个请求的迭代对象。交给调度器请求。
根据需求可以改写ImagePipline类:
from scrapy import Request from scrapy.pipelines.images import ImagesPipeline class ImagesPipeline(ImagesPipeline): def get_media_requests(self, item, info): yield Request(item['image_url'])
此处将get_media_requestsg改写为直接取item中image_url字段发起请求。
同理
class ImagesPipeline(ImagesPipeline): ... ... def file_path(self, request, response=None, info=None, *, item=None): url = request.url file_name = url.split("/")[-1] return file_name def item_completed(self, results, item, info): images_paths = [x['path'] for ok, x in results if ok] if not images_paths: item['image_url'] = None return item
改写file_path为取图片的链接地址的一部分做文件名。改写item_competed为图片链接请求完成之后,若下载失败则修改item的image_url字段为None。
最后添加到settings.py
ITEM_PIPELINES = { 'tutorial.pipelines.ImagesPipeline': 301, 'tutorial.pipelines.ArticlePipeline': 301, 'tutorial.pipelines.MongoPipeline': 400, }
Cookies池对接
代理池对接
crawl模板
查看模板
以模板形式创建spider
scrapy genspider -t crawl china tech.china.com
rules,爬取属性规则,包含一个或多个Rule对象的列表。每个Rule对爬取网站的动作都做了定义。
定义Rule
针对不同的爬取对象,要实现不同的解析函数。
class ChinaSpider(CrawlSpider): name = 'china' allowed_domains = ['tech.china.com'] start_urls = ['http://tech.china.com/articles'] rules = ( # Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True), Rule(LinkExtractor(allow='article\/.*\.html', restrict_xpaths='//div[@id="left_side"]//div[@class="com_item"]'), callback='parse_item'), Rule(LinkExtractor(restrict_xpaths='//div[@id="pageStyle"]//a[contains(text(),"下一页")]')) ) def parse_item(self, response): loadder = ChinaLoader(item=NewsItem(), response=response) loadder.add_xpath('title', '//h1[@id="chan_newsTtile"]/text()') loadder.add_value('content', '新闻内容') yield loadder.load_item()
gerapy框架
常用命令
(需要先启动scrapyd服务,需要安装scrapyd库(pip install scrapyd
- pip install gerapy
- gerapy init
- gerapy migrate
- gerapy createsuperuser
- 创建用户
- gerapy runserver