scrapy框架
目录:
一 介绍
Scrapy一个开源和协作的框架,其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的,使用它可以以快速、简单、可扩展的方式从网站中提取所需的数据。但目前Scrapy的用途十分广泛,可用于如数据挖掘、监测和自动化测试等领域,也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。
Scrapy 是基于twisted框架开发而来,twisted是一个流行的事件驱动的python网络框架。因此Scrapy使用了一种非阻塞(又名异步)的代码来实现并发。整体架构大致如下
框架组件
- 引擎(EGINE)
- 引擎负责控制系统是由组件之间的数据流,并在某些动作发生时触发事件
- 调度器(SCHEDULER)
- 用来接收引擎发送过来的请求,压入队列中,并在引擎再次请求的时间返回。可以想象出一个URLde 优先级队列,由它来决定下一个要抓取的网址是什么,同时去除重复的网址
- 下载器(DOWNLOADER)
- 用于下载网页内容,并将网页内容返回给EGINE。下载器是建立在twisted这个高效的异步模型上的。
- 爬虫(SPIDERS)
- SPIDERS是开发者自定义的类,用来解析response,并且提取items,或者发送新的请求
- 项目管道(ITEMPIPELINES)
- 用来处理item,主要包括清理、验证、持久化(比如存到数据库)等操作
- 下载器中间件(Downloader Middleware)
- 位于scrape引擎到下载器之间,主要用来处理从egine到downloader的请求request,和从Downloader到egine的响应response。可以在中间件做如下操作
- 处理一个在发送到downloader之间的请求
- 在发送到spider之前改变一个被接收的响应
- 直接发送一个新请求,代替发送响应给spider
- 如果没有查询到web页面,就不发送响应
- 悄悄的删除一些请求
- 爬虫中间件(Spider MIddleware)
- 位于EGINE和SPIDERS之间,主要工作是处理SPIDERS的输入(即response)和输出(即requests)
数据流
- 引擎首先从spider获取一个初始请求爬行
- 引擎通过SCHEDULER分配这个请求,并开始请求下一个请求去爬行
- 调度器返回一个请求给引擎
- 引擎通过中间件把请求发送给下载器
- 当页面下载完毕之后,下载器会通过中间件把返回的响应发送给引擎
- 引擎接收到响应之后,会通过中间件把响应发给spider去处理
- spider会处理响应,并把爬取得items对象和新的请求通过中间件返回给引擎
- 引擎则会把接收到的处理的items对象发送给项目管道,在发送被处理的请求给调度器看看有没有下一个请求过来
- 重复过程1,直到调度器没有请求需求为止。
二 安装
1 #Windows平台 2 1、pip3 install wheel #安装后,便支持通过wheel文件安装软件,wheel文件官网:https://www.lfd.uci.edu/~gohlke/pythonlibs 3 3、pip3 install lxml 4 4、pip3 install pyopenssl 5 5、下载并安装pywin32:https://sourceforge.net/projects/pywin32/files/pywin32/ 6 6、下载twisted的wheel文件:http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted 7 7、执行pip3 install 下载目录\Twisted-17.9.0-cp36-cp36m-win_amd64.whl 8 8、pip3 install scrapy 9 10 #Linux平台 11 1、pip3 install scrapy
三 命令集
1 # 1 帮助命令 2 scrapy -h 3 scrapy <command> -h 4 5 # 2 global命令 6 startproject # 创建项目 7 genspider # 创建爬虫程序 8 settings # 如果是在项目目录下,则得到的是该项目的配置,否则是全局的配置 9 runspider # 运行一个独立的python文件,不必创建项目 10 shell # scrapy shell url 地址,在交互调试,如选择器规则正确与否 11 fetch # 独立与线程单纯地爬取一个页面,可以拿到请求头 12 view # 下载完毕后直接弹出浏览器,以此可以分辨出那些数据是ajax请求 13 version # scrapy vewsion 查看scrapy版本,scrapy version -v查看scrapy依赖库的版本 14 15 # 3 项目命令(必须切换到项目文件下才能执行) 16 crawl # 运行爬虫,必须创建项目,确保配置文件中ROBOTSTXT_OBEY = Fasle 17 check # 检测项目中有无语法错误 18 list # 列出项目中所包含的爬虫名 19 edit # 编辑器,用来编辑文件使用,一般不用 20 parse # scrapy parse url地址 -- callback 回调函数 # 以此可以验证我们的回调函数是否正确 21 bench # scrapy bentch压力测试 22 23 #3 官网链接 24 https://docs.scrapy.org/en/latest/topics/commands.html
1 #1、执行全局命令:请确保不在某个项目的目录下,排除受该项目配置的影响 2 scrapy startproject MyProject 3 4 cd MyProject 5 scrapy genspider baidu www.baidu.com 6 7 scrapy settings --get XXX #如果切换到项目目录下,看到的则是该项目的配置 8 9 scrapy runspider baidu.py 10 11 scrapy shell https://www.baidu.com 12 response 13 response.status 14 response.body 15 view(response) 16 17 scrapy view https://www.taobao.com #如果页面显示内容不全,不全的内容则是ajax请求实现的,以此快速定位问题 18 19 scrapy fetch --nolog --headers https://www.taobao.com 20 21 scrapy version #scrapy的版本 22 23 scrapy version -v #依赖库的版本 24 25 26 #2、执行项目命令:切到项目目录下 27 scrapy crawl baidu 28 scrapy check 29 scrapy list 30 scrapy parse http://quotes.toscrape.com/ --callback parse 31 scrapy bench
四 项目结构以及应用
1、结构
1.1 文件说明:
- scrapy.cfg 项目的主配置信息,用来部署scrapy时使用,爬虫相关的配置信息在settings.py文件中
- settings.py 配置文件,如:递归的层数、并发数。延迟下载等。变量名必须大写。
- pipelines.py 数据处理行为,如:一般结构化的数据持久化
- middlewares.py 中间件,用来在请求和响应的过程当中做一些操作
- items.py 设置数据存储模板,用于结构化数据,如:Django的model
- spiders 爬虫目录,如:创建文件,编写爬虫规则,lagou.py即是已经创建的一个爬虫规则
注意:一般创建爬虫文件时,以网站域名命名
2、 配置在pycharm中可以直接执行爬虫程序
1 # 在项目目录下新建:entrypoint.py 2 from scrapy.cmdline import execute 3 4 execute(['scrapy, 'crawl', 'xiaohuar'])
3、 关于windows编码问题
1 import sys, os 2 3 sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='gb18030')
五 Spiders
1、介绍
Spider是由一系列类(定义了一个网站或一组网址将被爬取)组成,具体包括如何执行爬去任务并且如果从页面中提取结构化数据
也就是说,Spiders是我们为了一个特定的网址或一组网址自定义爬取和解析页面行为的地方。
2、Spiders任务
- 生成初始的requests来爬取第一个urls,并且标识一个回调函数
第一个请求定义在start_requests()方法内,默认从start_urls列表中获取url来生成request请求,默认的回调函数是parse方法,回调函数在下载完成返回response时自动触发
2. 在回调函数中,解析response并且返回值
返回值可以有4种:
-> 包含解析数据的字典
-> Item对象
-> 新的Request对象(新的Requests也需要指定一个回调函数)
-> 可迭代对象(包含Items或Request)
3. 在回调函数中解析页面内容
通常使用Scrapy自带的Seletors,但很明显你也可以使用Beautifulsoup, lxml或其他的模块去解析
4. 最后,针对返回的items对象将会被持久化到数据库
通过Item Pipeline组件存到数据库:
https://docs.scrapy.org/en/latest/topics/item-pipeline.html#topics-item-pipeline
或到处到不同的文件:
或到处到不同的文件:
(通过Feed exports:https://docs.scrapy.org/en/latest/topics/feed-exports.html#topics-feed-exports)
3、Spiders提供的五种类
1 # 1 scrapy.spiders.Spider # scrapy.Spider等同于scrapy.spiders.Spider 2 # 2 scrapy.spiders.CrawSpider 3 # 3 scrapy.spiders.XMLFeedSpider 4 # 4 scrapy.spiders.CSVFeedSpider 5 # 5 scrapy.spiders.SitemapSpider
4、导入使用以及基本配置
1 from scrapy 2 from scrapy.spiders import Spider, CrawSpider, XMLFeedSpider, CSVFeedSpider, SitemapSpider 3 4 class AmazonSpider(scrapy.Spider): # 自定义类,继承Spider提供的基类 5 # class scrapy.spiders.Spider 6 # 这是最简单的spider类,任何其他的spider类都需要继承它(包含你自己定义的) 7 # 该类不提供任何特殊的功能,它仅提供了一个默认的start_requests方法,默认从start_urls中读取url地址发送requests请求,并且默认parse作为回调函数 8 name = 'amazon' 9 allowed_domains = ['www.amazon.cn'] 10 start_urls = ['http://www.amazon.cn/'] 11 12 # 自定制配置文件,会首先读取这个配置文件 13 custom_settings = { 14 'BOT_NAME' : 'Egon_Spider_Amazon', 15 'REQUEST_HEADERS' : { 16 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 17 'Accept-Language': 'en', 18 } 19 } 20 21 def parse(self, response): 22 pass
1 # 1、name='amazon' 2 定义爬虫名,scrapy会根据该值定位爬虫程序 3 所以它必须要有且必须唯一(In Python 2 this must be ASCII only) 4 5 # 2、allowed_domains = ['www.amazon.cn'] 6 定义允许爬取得域名,如果offsiteMiddleware启动(默认就启动), 7 那么不属于该列表的域名及子域名都不允许爬取 8 如果爬去的网址为:https://www.example.com/1.html,那么就添加"example.com"到列表 9 10 # 3、start_urls = ['http://www.amazon.cn'] 11 如果没有指定url,就从该列表中读取url来生成第一个请求 12 13 # 4、custom_settings 14 值为一个字典,定义一些配置信息,在运行爬虫程序时,这些配置覆盖项目级别的配置 15 所以custom_settings必须被定义成一个类属性,由于settings会在类实例化前被加载 16 17 # 5、settings 18 通过self.settings['配置项的名字']可以访问settings.py中的配置,如果定义了custom_settings还是以自己的为准 19 20 # 6、logger 21 日志名默认为spider的内容 22 self.logger.debug('==>%s' %self.settings['BOT_NAME']) 23 24 # 7、crawler 25 该属性必须被定义到类方法from_crawler中 26 27 # 8、from_crawler(crealer, *args, **kwargs) 28 You probably won’t need to override this directly because the default implementation acts as a proxy to the __init__() method, calling it with the given arguments args and named arguments kwargs. 29 30 # 9、start_requests() 31 该方法用来发起第一个Requests请求,且必须返回一个可迭代的对象。它在爬虫程序打开时就被Scrapy调用,Scrapy只调用它一次。 32 默认从start_urls里取出每个url来生成Rquest(url, dont_filter=True) 33 如果你想要改变起始爬取得Requests,你就需要覆盖整个方法,例如你想要起始发送一个POST请求,如下: 34 class MySpider(scrapy.Spider): 35 name = 'myspider' 36 37 def start_requests(self): 38 return [scrapy.FormRequest("http://www.example.com/login", formdata={"user": "join", "pass": "secret"}, callback=self.logged_in)] 39 40 def logged_in(self, response): 41 # here you would extract links to follow and return Requests for each of them, with another callback 42 pass 43 44 # 10、parse(reqponse) 45 这个是默认的回调函数,所有的回调函数必须返回an iteranle of Request and/or dicts or Item objects. 46 47 # 11、log(message[, level, component]) 48 Wrapper that sends a log message through the Spider's logger, kept for backwards compatibility. 49 For more information see Logging from Spiders 50 51 # 12、closed(reason) 52 爬虫程序结束时自动触发
1 去重规则应该多个爬虫共享,但凡一个爬虫爬取了,其他都不要爬了 2 3 # 方法一: 4 1、新增类属性 5 visited = set() # 类属性 6 7 2、回调函数parse方法内 8 def parse(self, response): 9 if rsponse.url in self.visited: 10 return None 11 ... 12 self.visited.add(response.url) 13 14 # 方法一改进:针对url可能过长,所以我们存放url的hash值 15 def parse(self, response): 16 url = md5(response.request.url) 17 if url in self.visited: 18 return None 19 ... 20 self.visited.add(url) 21 22 23 # 方法二:Scrapy自带去重功能 24 配置文件: 25 DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter' # 默认的去重规则,组重规则在内存中 26 DUPEFILTER_DEBUG = False 27 JOBDIR = '保存范文记录的日志路径,如:/root/' # 最终路径为/root/requests.seen,去重规则放文件中 28 29 scrapy自带去重规则默认为RFPDupeFilter,只需要我们指定 30 Request(..., dont_filter=False), 如果dont_filter=True则告诉scrapy这个rul不参与去重 31 32 33 # 方法三:我们可以仿照RFPDupeFilter自定义去重规则 34 from scrapy.dupefilter import RFPDupeFilter # 看源码,仿照BaseDupeFilter 35 # 步骤1:在项目目录下自定义去重文件dup.py 36 class UrlFilter(object): 37 def __init__(self): 38 self.visited = set() # 或者放到数据库 39 40 @classmethod 41 def from_settings(cls, settings): 42 return cls() 43 def request_seen(self, request): 44 if request.url in self.visited: 45 return True 46 self.visited.add(request.url) 47 48 def open(self): # can return deferred 49 pass 50 51 def close(self, reason): # can return a deferred 52 pass 53 54 def log(self, request, spider): # log that a request has been filtered 55 pass 56 57 # 步骤二:配置文件settings.py 58 DUPEFILTER_CLASS = "项目名.dup.UrlFilter" 59 60 # 源码分析 61 from scrapy.core.scheduler import Scheduler 62 # 见Scheduler下的enqueue_request方法:self.df.request_seen(request)
1 import scrapy 2 3 class MySpider(scrapy.Spider): 4 name = 'example.com' 5 allowed_domains = ['example.com'] 6 start_urls = [ 7 ‘http://www.example.com/1.html', 8 'http://www.example.com/2.html', 9 ] 10 11 def parse(self, response): 12 self.logger.info('A response from %s just arrived!', response.url) 13 14 # 实例二 15 import scrapy 16 17 class MySpider(scrapy.Spider): 18 name = 'example.com' 19 allowed_domains = ['example.com'] 20 start_urls = [ 21 ‘http://www.example.com/1.html', 22 'http://www.example.com/2.html', 23 ] 24 25 def parse(self, response): 26 for h2 in response.xpath('//h2').extract(): 27 yield {'title':h2} 28 29 for url in response.xpath('//a/@href').extract(): 30 yield scrapy.Request(url, callback=self.parse) 31 32 33 # 例三:在start_requests()内直接指定起始爬取得urls,start_urls就没有用了 34 import scrapy 35 from myproject.items import MyItem 36 37 class MySpider(scrapy.Spider): 38 name = 'example.com' 39 allowed_domains = ['example.com'] 40 41 def start_requests(self): 42 yield scrapy.Request('http://www.example.com/1.html', self.parse) 43 yield scrapy.Request('http://www.example.com/2.html', self.parse) 44 yield scrapy.Request('httl://www.example.com/3.html', self.parse) 45 46 def parse(self, response): 47 for h3 in response.xpath('//h3').extract(): 48 yield MyItem(title=h3) 49 50 for url in response.xpath('//a/@href').extract(): 51 yield scrapy.Request(url, callback=self.parse)
1 # 我们可能需要在命令行尾爬虫程序传递参数,比如传递初始的url 2 # 执行命令行 3 scrapy crawl myspider -a category=electronics 4 5 # 在__init__方法中可以接收外部传进来的参数 6 import scrapy 7 8 class MySpider(scrapy.Spider): 9 name = 'myspider' 10 11 def __init__(self, category=None, *args, **kwargs): 12 super(MySpider, self).__init__(*args, **kwargs) 13 self.start_urls = ['http://www.example.com/categories/%s' % category] 14 # 主要接收的参数全都是字符串,如果想要结构化数据,你需要用类似json.loads的方法