Scrapy简介

Python开发的一个快速、高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据。Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试。

Scrapy吸引人的地方在于它是一个框架,任何人都可以根据需求方便的修改。它也提供了多种类型爬虫的基类,如BaseSpider、sitemap爬虫等,最新版本又提供了web2.0爬虫的支持。

基本功能

Scrapy是一个为遍历爬行网站、分解获取数据而设计的应用程序框架,它可以应用在广泛领域:数据挖掘、信息处理和或者历史片(历史记录)打包等等

尽管Scrapy原本是设计用来屏幕抓取(更精确的说,是网络抓取)的目的,但它也可以用来访问API来提取数据,比如Amazon的AWS或者用来当作通常目的应用的网络蜘蛛

Scrapy框架

Scrapy是用Python实现的一个为了爬取网站数据,提取结构性数据而编写的应用框架。可以应用在包括数据挖掘、信息处理或存储历史数据等一系列的程序中。

Scrapy使用Twisted基于事件的高效异步网络框架来处理网络通信,可以加快下载速度,不用自己去实现异步框架,并且包含了各种中间件接口,可以灵活的完成各种需求。

Scrapy架构

Scrapy Engine

引擎,负责控制数据流在系统中所有组件中流动,并在相应动作发生时触发事件。此组件相当于爬虫的“大脑”,是整个爬虫的调度中心

调度器(Scheduler)

调度器接收从引擎发送过来的request,并将他们入队,以便之后引擎请求他们时提供给引擎

初始的爬取URL和后续在页面中获取的待爬取的URL将放入调度器中,等待爬取。同时调度器会自动去除重复的URL(如果特定的URL不需要去重也可以通过设置实现,如post请求的URL)

下载器(Downloader)

下载器负责获取页面数据并提供给引擎,而后提供给spider

Spiders爬虫

Spider是编写的类,作用如下:

Scrapy用户编写用于分析response并提取item(即获取到的item)

额外跟进的URL,将额外跟进的URL提交给引擎,加入到Scheduler调度器中。将每个spider负责处理一个特定(或一些)网站

Item Pipeline

Item Pipeline负责处理被spider提取出来的item。典型的处理有清理、 验证及持久化(例如存取到数据库中)

当页面被爬虫解析所需的数据存入Item后,将被发送到项目管道(Pipeline),并经过设置好次序的pipeline程序处理这些数据,最后将存入本地文件或存入数据库

类似管道 $ ls | grep test 或者类似于Django 模板中的过滤器

以下是item pipeline的一些典型应用:

清理HTML数据

验证爬取的数据(检查item包含某些字段)

查重(或丢弃)

将爬取结果保存到数据库中

下载器中间件(Downloader middlewares)

简单讲就是自定义扩展下载功能的组件。

下载器中间件,是在引擎和下载器之间的特定钩子(specific hook),处理它们之间的请求request和响应response。

它提供了一个简便的机制,通过插入自定义代码来扩展Scrapy功能

通过设置下载器中间件可以实现爬虫自动更换user-agent、IP等功能

Spider中间件(Spider middlewares)

Spider中间件,是在引擎和Spider之间的特定钩子(specific hook),处理spider的输入(response)和输出(items或requests)。

也提供了同样的简便机制,通过插入自定义代码来扩展Scrapy功能。

数据流(Data flow)

1.引擎打开一个网站(open a domain),找到处理该网站的Spider并向该spider请求第一个(批)要爬取的URL(s)

2.引擎从Spider中获取到第一个要爬取的URL并加入到调度器(Scheduler)作为请求以备调度

3.引擎向调度器请求下一个要爬取的URL

4.调度器返回下一个要爬取的URL给引擎,引擎将URL通过下载中间件并转发给下载器(Downloader)

5.一旦页面下载完毕,下载器生成一个该页面的Response,并将其通过下载中间件发送给引 擎

6.引擎从下载器中接收到Response,然后通过Spider中间件发送给Spider处理

7.Spider处理Response并返回提取到的Item及(跟进的)新的Request给引擎

8.引擎将Spider返回的Item交给Item Pipeline,将Spider返回的Request交给调度器

9.(从第二步)重复执行,直到调度器中没有待处理的request,引擎关闭

注意:

只有当调度器中没有任何request了,整个程序才会停止执行。如果有下载失败的URL,会重新下载

安装scrapy

安装wheel支持

$ pip install wheel

安装scrapy框架

$ pip install scrapy

window下,为了避免windows编译安装twisted依赖,安装下面的二进制包

$ pip install Twisted-18.4.0-cp35-cp35m-win_amd64.whl

windows下出现如下问题:

copyingsrc\twisted\words\xish\xpathparser.g->build\lib.win-amd64-3.5\twisted\words\xishrunningbuild_extbuilding'twisted.test.raiser'extensionerror:MicrosoftVisualC++14.0isrequired.Getitwith"MicrosoftVisualC++BuildTools":http://landinghub.visualstudio.com/visual-cpp-build-tools解决方案是,下载编译好的twisted,PythonExtensionPackagesforWindowspython3.5下载Twisted-18.4.0-cp35-cp35m-win_amd64.whlpython3.6下载Twisted-18.4.0-cp36-cp36m-win_amd64.whl安装twisted$pipinstallTwisted-18.4.0-cp35-cp35m-win_amd64.whl之后在安装scrapy就没有什么问题了

安装好,使用scrapy命令看看

1.>scrapy2.Scrapy1.5.0-noactiveproject3.4.Usage:5.scrapy<command>[options][args]6.7.Availablecommands:8.benchRunquickbenchmarktest9.checkCheckspidercontracts10.crawlRunaspider11.editEditspider12.fetchFetchaURLusingtheScrapydownloader13.genspiderGeneratenewspiderusingpre-definedtemplates14.listListavailablespiders15.parseParseURL(usingitsspider)andprinttheresults16.runspiderRunaself-containedspider(withoutcreatingaproject)17.settingsGetsettingsvalues18.shellInteractivescrapingconsole19.startprojectCreatenewproject20.versionPrintScrapyversion21.viewOpenURLinbrowser,asseenbyScrapy

Scrapy开发

项目编写流程

1.创建项目

使用 scrapy startproject proname 创建一个scrapy项目

scrapy startproject <project_name> [project_dir]

2.编写item

在items.py中编写Item类,明确从response中提取的item

3.编写爬虫

编写spiders/proname_spider.py,即爬取网站的spider并提取出item

4.编写item pipeline

item的处理,可以存储

1 创建项目

1.1 豆瓣书评爬取

标签为“编程”,第一页、第二页链接:

https://book.douban.com/tag/%E7%BC%96%E7%A8%8B?start=0&type=T

https://book.douban.com/tag/%E7%BC%96%E7%A8%8B?start=20&type=T

随便找一个目录来创建项目,执行下面命令

$ scrapy startproject first .

会产生如下目录和文件

first├─scrapy.cfg└─first├─items.py├─middlewares.py├─pipelines.py├─settings.py├─__init__.py└─spiders└─__init__.py

first:

外部的first目录是整个项目目录,内部的first目录是整个项目的全局目录

scrapy.cfg:

必须有的重要的项目的配置文件

first 项目目录

__init__.py 必须有,包文件

items.py 定义Item类,从scrapy.Item继承,里面定义scrapy.Field类实例

pipelines.py 重要的是process_item()方法,处理item

settings.py:

BOT_NAME 爬虫名

ROBOTSTXT_OBEY = True 是否遵从robots协议

USER_AGENT = '' 指定爬取时使用

CONCURRENT_REQEUST = 16 默认16个并行

DOWNLOAD_DELAY = 3 下载延时,一般要设置,不宜过快发起连续请求

COOKIES_ENABLED = False 缺省是启用,一般需要登录时才需要开启cookie

SPIDER_MIDDLEWARES 爬虫中间件

DOWNLOADER_MIDDLEWARES 下载中间件

'firstscrapy.pipelines.FirstscrapyPipeline': 300item交给哪一个管道处理,300 越小优先

级越高

ITEM_PIPELINES 管道配置

'first.middlewares.FirstDownloaderMiddleware': 543543 越小优先级越高

spiders目录

__init__.py 必须有,可以在这里写爬虫类,也可以写爬虫子模块

1.#first/settings.py参考2.BOT_NAME='first'3.SPIDER_MODULES=['first.spiders']4.NEWSPIDER_MODULE='first.spiders'5.6.USER_AGENT="Mozilla/5.0(WindowsNT6.1)AppleWebKit/537.36(KHTML,likeGecko)Chrome/55.0.2883.75Safari/537.36"7.ROBOTSTXT_OBEY=False8.9.DOWNLOAD_DELAY=310.11.#Disablecookies(enabledbydefault)12.COOKIES_ENABLED=False

注意一定要更改User-Agent,否则访问https://book.douban.com/会返回403

2 编写Item

1.在items.py中编写2.importscrapy3.classBookItem(scrapy.Item):4.title=scrapy.Field()#书名5.rate=scrapy.Field()#评分

3 编写爬虫

为爬取豆瓣书评编写爬虫类,在spiders目录下:

编写的爬虫类需要继承自scrapy.Spider,在这个类中定义爬虫名、爬取范围、其实地址等

在scrapy.Spider中parse方法未实现,所以子类应该实现parse方法。该方法传入response对象

1.#scrapy源码中2.classSpider():3.defparse(self,response):#解析返回的内容4.raiseNotImplementedError

爬取读书频道,tag为“编程”的书名和评分:

https://book.douban.com/tag/%E7%BC%96%E7%A8%8B?start=20&type=T

使用模板创建spider, $ scrapy genspider -t basic book https://www.douban.com/

1.importscrapy2.3.classBookSpider(scrapy.Spider):#BookSpider4.name='doubanbook'#爬虫名,可修改,重要5.allowed_domains=['豆瓣']#爬虫爬取范围6.url='豆瓣图书标签:编程'7.start_urls=[url]#起始URL8.9.#下载器获取了WEBServer的response就行了,parse就是解析响应的内容10.defparse(self,response):11.print(type(response),'~~~~~~~~~')#scrapy.http.response.html.HtmlResponse12.print(response)13.print('-'*30)

使用crawl爬取子命令

1.$scrapylist2.$scrapycrawl-h3.scrapycrawl[options]<spider>4.5.指定爬虫名称开始爬取6.$scrapycrawldoubanbook7.8.可以不打印日志9.$scrapycrawldoubanbook--nolog

如果在windows下运行发生twisted的异常 ModuleNotFoundError: No module named 'win32api' ,请安装 $ pip install pywin32。

response是服务器端HTTP响应,它是scrapy.http.response.html.HtmlResponse类。

由此,修改代码如下

1.importscrapy2.fromscrapy.http.response.htmlimportHtmlResponse3.4.classBookSpider(scrapy.Spider):#BookSpider5.name='doubanbook'#爬虫名6.allowed_domains=['豆瓣']#爬虫爬取范围7.url='豆瓣图书标签:编程'8.start_urls=[url]#起始URL9.10.#下载器获取了WEBServer的response就行了,parse就是解析响应的内容11.defparse(self,response:HtmlResponse):12.print(type(response))#scrapy.http.response.html.HtmlResponse13.print('-'*30)14.print(type(response.text),type(response.body))15.print('-'*30)16.print(response.encoding)17.withopen('o:/testbook.html','w',encoding='utf-8')asf:18.try:19.f.write(response.text)20.f.flush()21.exceptExceptionase:22.print(e)

3.1 解析HTML

爬虫获得的内容response对象,可以使用解析库来解析。

scrapy包装了lxml,父类TextResponse类也提供了xpath方法和css方法,可以混合使用这两套接口解析HTML。

选择器参考:

https://scrapy-chs.readthedocs.io/zh_CN/0.24/topics/selectors.html#id3

1.importscrapy2.fromscrapy.http.response.htmlimportHtmlResponse3.4.response=HtmlResponse('file:///O:/testbook.html',encoding='utf-8')#构造对象5.6.withopen('o:/testbook.html',encoding='utf8')asf:7.response._set_body(f.read())#填充数据8.#print(response.text)9.1O.#获取所有标题及评分11.#xpath解析12.subjects=response.xpath('//li[@class="subject-item"]')13.forsubjectinsubjects:14.title=subject.xpath('.//h3/a/text()').extract()#list15.print(title[0].strip())16.17.rate=subject.xpath('.//span[@class="rating_nums"]/text()').extract()18.print(rate[0].strip())19.2O.print('-'*30)21.#css解析22.subjects=response.css('li.subject-item')23.forsubjectinsubjects:24.title=subject.css('h3a::text').extract()25.print(title[0].strip())26.27.rate=subject.css('span.rating_nums::text').extract()28.print(rate[0].strip())29.print('-'*30)30.31.#xpath和css混合使用、正则表达式匹配32.subjects=response.css('li.subject-item')33.forsubjectinsubjects:34.#提取链接35.href=subject.xpath('.//h3').css('a::attr(href)').extract()36.print(href[0])37.38.#使用正则表达式39.id=subject.xpath('.//h3/a/@href').re(r'\d*99\d*')40.ifid:41.print(id[0])42.43.#要求显示9分以上数据44.rate=subject.xpath('.//span[@class="rating_nums"]/text()').re(r'^9.*')45.#rate=subject.css('span.rating_nums::text').re(r'^9\..*')46.ifrate:47.print(rate)

3.2 item封装数据

1.#spiders/bookspider.py2.importscrapy3.fromscrapy.http.response.htmlimportHtmlResponse4.from..itemsimportBookItem5.6.classBookSpider(scrapy.Spider):#BookSpider7.name='doubanbook'#爬虫名8.allowed_domains=['豆瓣']#爬虫爬取范围9.url='豆瓣图书标签:编程'10.start_urls=[url]#起始URL11.12.#下载器获取了WEBServer的response就行了,parse就是解析响应的内容13.defparse(self,response:HtmlResponse):14.items=[]15.#xpath解析16.subjects=response.xpath('//li[@class="subject-item"]')17.forsubjectinsubjects:18.title=subject.xpath('.//h3/a/text()').extract()19.rate=subject.xpath('.//span[@class="rating_nums"]/text()').extract_first()20.item=BookItem()21.item['title']=title[0].strip()22.item['rate']=rate.strip()23.items.append(item)24.25.print(items)26.27.returnitems#一定要return,否则保存不下来28.29.#使用命令保存return的数据30.#scrapycrawl-h31.#--output=FILE,-oFILEdumpscrapeditemsintoFILE(use-forstdout)32.#文件扩展名支持'json','jsonlines','jl','csv','xml','marshal','pickle'33.#scrapycrawldoubanbook-odbbooks.json

得到下图数

注意上图的数据已经是unicode字符,汉字的unicode表达。

4 pipeline处理

将bookspider.py中BookSpider改成生成器,只需要把 return items 改造成 yield item ,即由产生一个列表变成yield一个个item

脚手架帮我们创建了一个pipelines.py文件和一个类

4.1 开启pipeline

1.#Configureitempipelines2.#SeeItemPipeline-Scrapy1.8.0documentation3.ITEM_PIPELINES={4.'first.pipelines.FirstPipeline':300,5.}

整数300表示优先级,越小越高。

取值范围为0-1000

4.2常用方法

1.classFirstPipeline(object):2.def__init__(self):#全局设置3.print('~~~~~~~~~~init~~~~~~~~~~~~')4.5.defopen_spider(self,spider):#当某spider开启时调用6.print(spider,'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')7.8.defprocess_item(self,item,spider):9.#item获取的item;spider获取该item的spider10.returnitem11.12.defclose_spider(self,spider):#当某spider关闭时调用13.print(spider,'========================================')14.

需求

通过pipeline将爬取的数据存入json文件中

1.#spider/bookspider.py2.importscrapy3.fromscrapy.http.response.htmlimportHtmlResponse4.from..itemsimportBookItem5.6.classBookSpider(scrapy.Spider):#BookSpider7.name='doubanbook'#爬虫名8.allowed_domains=['豆瓣']#爬虫爬取范围9.url='豆瓣图书标签:编程'10.start_urls=[url]#起始URL11.12.#spider上自定义配置信息13.custom_settings={14.'filename':'o:/books.json'15.}16.#下载器获取了WEBServer的response就行了,parse就是解析响应的内容17.defparse(self,response:HtmlResponse):18.#items=[]19.#xpath解析20.subjects=response.xpath('//li[@class="subject-item"]')21.forsubjectinsubjects:22.title=subject.xpath('.//h3/a/text()').extract()23.rate=subject.xpath('.//span[@class="rating_nums"]/text()').extract_first()24.item=BookItem()25.item['title']=title[0].strip()26.item['rate']=rate.strip()27.#items.append(item)28.29.yielditem30.#returnitems31.32.#pipelines.py33.importsimplejsonasjson34.35.classFirstPipeline(object):36.def__init__(self):#全局设置37.print('~~~~~~~~~~init~~~~~~~~~~~~')38.39.defopen_spider(self,spider):#当某spider开启时调用40.print('{}~~~~~~~~~~~~~~~~~~~~'.format(spider))41.print(spider.settings.get('filename'))42.self.file=open(spider.settings['filename'],'w',encoding='utf-8')43.self.file.write('[\n')44.45.defprocess_item(self,item,spider):46.#item获取的item;spider获取该item的spider47.self.file.write(json.dumps(dict(item))+',\n')48.returnitem49.50.defclose_spider(self,spider):#当某spider关闭时调用51.self.file.write(']')52.self.file.close()53.print('{}======================='.format(spider))54.print('-'*30)

5 url提取

如果要爬取下一页内容,可以自己分析每一页的页码变化,也可以通过提取分页栏的链接

1.#spider/bookspider.py2.importscrapy3.fromscrapy.http.response.htmlimportHtmlResponse4.from..itemsimportBookItem5.6.classBookSpider(scrapy.Spider):#BookSpider7.name='doubanbook'#爬虫名8.allowed_domains=['豆瓣']#爬虫爬取范围9.url='豆瓣图书标签:编程'10.start_urls=[url]#起始URL11.12.#spider上自定义配置信息13.custom_settings={14.'filename':'o:/books.json'15.}16.17.#下载器获取了WEBServer的response就行了,parse就是解析响应的内容18.defparse(self,response:HtmlResponse):19.#items=[]20.#xpath解析21.#获取下一页,只是测试,所以使用re来控制页码22.print('-'*30)23.urls=response.xpath('//div[@class="paginator"]/span[@class="next"]/a/@href').re(24.r'.*start=[24]\d[^\d].*')25.print(urls)26.print('-'*30)27.yieldfrom(scrapy.Request(response.urljoin(url))forurlinurls)28.print('++++++++++++++++++++++++')29.30.subjects=response.xpath('//li[@class="subject-item"]')31.forsubjectinsubjects:32.#解决图书副标题拼接33.title="".join(map(lambdax:x.strip(),subject.xpath('.//h3/a//text()').extract()))34.rate=subject.xpath('.//span[@class="rating_nums"]/text()').extract_first()35.#print(rate)#有的没有评分,要注意可能返回None36.37.item=BookItem()38.item['title']=title39.item['rate']=rate40.#items.append(item)41.yielditem42.43.#returnitems