Scrapy框架全

Stella981
• 阅读 502

架构介绍

Engine:引擎,处理整个系统的数据流处理,触发事物,是整个框架的核心

Item:项目,他定义了爬取结果的数据结构,爬取的数据会被赋值成该Item对象

Scheduler:调度器,接受引擎发过来的请求并将其加入到队列中,在引擎再次请求的时候将请求提供给引擎

Downloader:下载器,下载网页内容,并将网页内容返回给spider

Spider:蜘蛛,其内部定义了爬取的逻辑和网页的解析规则,他负责解析响应并生成提取结果和新的请求

Item Pipline:项目管道,负责处理由蜘蛛从网页中抽取的项目,他的主要任务是清洗,验证和存储数据

Downloader Middlewares:下载器中间件,位于引擎和下载器之间钩子框架,主要处理引擎和下载器之间的请求和响应

Spider Middlewares:蜘蛛中间件,位于引擎和蜘蛛之间的钩子框架,主要处理蜘蛛输入的响应和输出的结果及新的请求

数据流

Scrapy中的数据流由引擎控制,数据流的过程如下:

1.Engine首先打开一个网站,找到处理该网站的Spider,并向该Spider请求第一个要爬取的URL.

2.Engine从Spider中获取到第一个要爬取的URL,并通过Scheduler以Request的形式调度.

3.Engine向Scheduler请求下一个要爬取的URL

4.Scheduler返回下一个要爬取的URL给Engine,Engine将URL通过Downloader Middlewares转发给Downloader下载

5.一旦页面下载完毕,Downloader生成该页面的Response,并将其通过Downloader Middlewares发送给Engine

6.Engine从下载器中接收到Response,并将其通过Spider Middlewares发送给 Spider处理

7.Spider处理Response,并返回爬取到的Item及新的Request给Engine

8.Engine将Spider返回的Item给Item Pipline,将新的Request给Scheduler

9.重复2-8,知道Scheduler中没有更多的Request,Engine关闭该网站,爬取结束

通过多个组件的相互协作,不同组件完成工作的不同,组件对异步处理的支持,Scrapy最大限度的利用了网络带宽,大大提高了数据爬取和处理的效率

 雀氏纸尿裤

创建项目:

scrapy startproject 项目名(可以在几乎任意文件夹下创建,创建的项目就在该文件夹下)在命令行中执行

2:创建Spider:

Spider是自己定义的类,Scrapy用它从网页中抓取内容,并解析抓取的结果.不过这个类必须继承Scrapy提供的Spider类scrapy.Spider,还要定义Spider的名称和起始请求,以及怎样处理爬取结果的方法:(不如命令行方便)

也可以使用命令行创建一个Spider,比如要生成sb这个spider,(示例爬取校花网图片)

执行如下命令:

cd 项目名

scrapy genspider sb news.daxues.cn

进入刚才创建的项目文件夹,然后执行上诉命令,第一个参数是Spider的名称,第二个参数是网站域名,执行完毕之后,spiders文件夹中就多了一个sb.py,他就是刚刚创建的Spider,内容如下:

import scrapy

class SbSpider(scrapy.Spider):    name = 'sb'    allowed_domains = ['news.daxues.cn']    start_urls = ['http://news.daxues.cn/xiaohua/ziliao/'](这里有所改动xiaohua/ziliao/为后来添加的方便爬取)    def parse(self, response):        pass

这里有三个属性--name,allowed_domains和start_urls,还有一个parse方法

name:他是项目中唯一的名字,用于区分不同Spider

allowed_domains,他是允许爬取的域名,如果初始或后续的请求链接不是这个域名下的,则请求链接会被过滤掉

start_urls:它包含了Spider在启动时爬取的url列表,初始请求是由他定义的

parse:他是Spider的一个方法,默认情况下,被调用时start_url里面的链接构成的请求完成下载执行后,返回的响应就会作为唯一的参数传递给这个函数.该方法负责解析返回的响应,提取数据或者进一步生成要处理的请求.

 3.创建Item

Item是保存爬取数据的容器,他的使用方法和字典类似,创建Item需要继承scrapy.Item类,并且定义类型为scrapy.Field的字段

定义Item,此时将items.py修改如下:

class NewsItem(scrapy.Item):    # define the fields for your item here like:    # name = scrapy.Field()    name = scrapy.Field()    info = scrapy.Field()    img = scrapy.Field()

这里定义了三个字段,接下来的爬取将会使用这个Item

4.解析Response

parse()方法的参数response是start_urls里面的链接爬取后的结果,所以在parse()方法中.我们可以直接对response变量包含的内容进行解析,比如浏览请求结果的源代码,或者进一步分析源代码内容,或找出结果中的链接,得到下一个请求

有时网页中既有我们想要的结果又有下一页的链接,都需要进行处理.:

可以在浏览器上查看网页结构:

在类名为xh_list的div下有多个dl标签,里面包含我们想要的数据

提取内容的方式可以是CSS选择器或Xpath选择器,这里使用CSS选择器进行选择

parse()方法改写如下:

class SbSpider(scrapy.Spider):    name = 'sb'    allowed_domains = ['news.daxues.cn']    start_urls = ['http://news.daxues.cn/xiaohua/ziliao/']    def parse(self, response):        div_list = response.css('.xh_list').css('dl')        url = 'https://news.daxues.cn'        for div in div_list:            name = div.css('dt').css('a::text').extract_first()            info = div.css('.d::text').extract_first()            img = url+div.css('img::src').extract_first()

5.使用Item

上文定义Item,接下来就要使用它.Item可以理解为一个字典,不过在声明的时候需要实例化,然后依次用刚才解析的结果赋值Item的每一个字段,最后将Item返回即可

SbSpider的改写如下所示:

import scrapyfrom news.items import NewsItemclass SbSpider(scrapy.Spider):    name = 'sb'    allowed_domains = ['news.daxues.cn']    start_urls = ['http://news.daxues.cn/xiaohua/ziliao/']    def parse(self, response):        div_list = response.css('.xh_list').css('dl')        url = 'https://news.daxues.cn'        for div in div_list:            item = NewsItem()            item['name'] = div.css('dt').css('a::text').extract_first()            item['info'] = div.css('.d::text').extract_first()            item['img'] = url+div.css('img::attr(src)').extract_first()            yield item

如此一来,首页的所有内容被解析出来,并被赋值成了一个个NewsItem

6.后续Request

上面的操作只实现了从初始页面抓取内容,那么下一页的内容该如何抓取,这需要我们从当前页面中找到信息来生成下一个请求,然后在下一个请求的页面里找到信息再构造再下一个请求,这样循环往复迭代,从而实现整站的爬取.

将页面拉到底部,这里有一个下一页按钮,查看他的源代码发现,他的链接是/xiaohua/ziliao/index_2.html,全链接就是:

http://news.daxues.cn/xiaohua/ziliao/index_2.html

通过这个链接我们就可以构造下一个请求.

构造请求时需要用到scrapy.Request.这里我们传入两个参数---url和callback,这两个参数的说明如下.

url:他是请求链接

callback:它是回调函数,当指定了该回调函数的请求完成之后,获取到响应,引擎会将该响应作为参数传递给这个回调函数.回调函数进行解析或生成下一个请求,回调函数如上文的parse()所示:

由于parse()就是解析name,info,img的方法,而下一页的结构和刚才已经解析的页面结构是一样的,所以我们可以再次使用parse(0方法来做页面解析.

接下来就是利用选择器得到下一页链接并生成请求,在parse()方法后追加如下的代码:

next = response.css('.page a::attr(href)').extract_first()(选择器语法可能不正确,后续需要改动)url2 = url+nextyield scrapy.Request(url=url2,callback=self.parse)

第一句代码首先通过CSS选择器获取下一个链接,,即要获取a超链接中的href属性.这里用到了::attr(href)操作.然后再调用extract_first()方法获取内容

第二句代码生成一个url路径

第三句代码通过url和callback变量构造了一个新的请求,回调函数callback依然使用parse()方法,这个请求完成后,响应会重新经过parse方法处理,得到第二页的解析结果,然后生成第二页的下一页,也就是第三页的请求,这样爬虫就进入了一个循环,直到最后一页.

改写后的整个Spider类如下所示:

import scrapyfrom news.items import NewsItemclass SbSpider(scrapy.Spider):    name = 'sb'    allowed_domains = ['news.daxues.cn']    start_urls = ['http://news.daxues.cn/xiaohua/ziliao/']    def parse(self, response):        div_list = response.css('.xh_list').css('dl')        url = 'https://news.daxues.cn'        for div in div_list:            item = NewsItem()            item['name'] = div.css('dt').css('a::text').extract_first()            item['info'] = div.css('.d::text').extract_first()            item['img'] = url+div.css('img::attr(src)').extract_first()            yield item        next = response.css('.page a::attr(href)').extract_first()        url2 = url+next        yield scrapy.Request(url=url2,callback=self.parse)

7运行:

接下来进入目录,运行如下命令:

scrapy crawl news

就可以看到运行结果了

首先Scrapy输出了当前的版本号以及正在启动的项目名称,接着输出了当前settings.py中一些重写后的配置,然后输出了当前所应用的Middlewares和Piplines.Middlewares默认是启用的,可以在settings中修改,Piplines默认为空,同样可以在settings.py中配置.

接下来就是整个页面的抓取结果,可以看到爬虫一边解析,一边翻页,直至将所有内容抓取完毕,然后终止.

最后Scrapy输出了整个抓取过程的统计信息,如请求的字节数,请求次数,响应次数,完成原因等等

整个Scrapy程序成功运行.

由于本人对于Scrapy的CSS选择器不熟悉,因此除了点bug,但问题不大

8保存到文件

Scrapy提供的Feed Exports可以轻松的抓取结果输出

如果想要将输出结果保存成json文件,可以执行以下命令:

scrapy crawl sb -o sb.jl

scrapy crawl sb -o sb.jsonlines

输出格式还支持很多种,例如csv,xml,pickle,marshal等,还支持ftp,s3等远程输出,另外还可以通过自定义ItemExporter来实现其他的输出.

例如下面的命令对应的输出分别为csv,xml,,pickle,marshal格式以及ftp远程输出:

scrapy crawl sb -o sb.csv

scrapy crawl sb -o sb.xml

scrapy crawl sb -o sb.pickle

scrapy crawl sb -o sb.marshal

scrapy crawl sb -o ftp://user:pass@ftp.example.com/path/to/sb.csv

其中,ftp输出需要正确配置用户名,密码,地址,输出路径,否则会报错

通过Scrapy提供的Feed Exports我们可以轻松的输出抓取结果到文件,如果想要更复杂的输出,我们可以使用Item Pipline来完成

9使用Item Pipeline 

如果想进行更复杂的操作,如将结果保存到MongoDB数据库,或者筛选某些有用的Item,我们可以定义Item Pipeline来实现

Item Pipline为项目管道,当Item生成后,他会自动被送到Item Pipline进行处理,我们常用Item Pipline来做如下操作:

清理HTML数据

验证爬取数据,检查爬取字段

查重并丢弃重复内容

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

要实现Item Pipeline很简单只需要定义一个类并实现process_item()方法即可,启用Item Pipeline后,Item Pipeline会自动调用这个方法,process_item()方法必须返回包含数据的字典或Item对象,或者抛出DropItem异常.

process_item()方法有两个参数,一个参数是item,每次Spider生成的Item都会作为参数传递过来,另一个参数是spider,就是Spider的实例.

接下来实现一个Item Pipeline,筛掉info长度大于200的Item,并将结果保存到MongoDB.

修改项目里的pipelines.py文件,之前用命令行自动生成的文件内容可以删掉,增加一个TextPipeline类,内容如下所示:

from scrapy.exceptions import DropItemclass InfoPipeline():    def __init__(self):        self.limit = 200    def process_item(self,item,spider):        if item['info']:            if len(item['info']) > self.limit:                item['info'] = item['info'][0:self.limit].rstrip()+'...'            return item        else:            return DropItem('Missing Info')

这段代码在构造方法里定义了限制长度为200,实现了process_item()方法,其参数是item和spider,首先该方法判断item的info属性是否存在,不存在则抛出DropItem异常,存在则判断是否大于200,大于则删减,然后返回item

接下来将处理后的item存入MongoDB,定义另外一个Pipeline,同样在pipelines.py中,我们实现零一个类MongoPipeline,内容如下所示:

import pymongoclass MongoPipeline():    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_DB'))    def open_spider(self,spider):        self.client = pymongo.MongoClient(self.mongo_uri)        self.db = self.client[self.mongo_db]    def process_item(self,item,spider):        name = item.__class__.__name__        self.db[name].insert(dict(item))        return item    def close_spider(self,spider):        self.client.close()

MongoPipeline类实现了API定义的另外几个方法.

from_crawler:他是一个类方法,用@classmethod标识,是一种依赖注入的方式,他的参数就是crawler,通过crawler我们可以拿到全局配置的每个配置信息,在全局配置settings.py中,我们定义MONGO_URI和MONGO_DB来指定MongoDB连接需要的地址和数据库名称,拿到配置信息后返回类对象即可,所以这个方法主要用于获取settings.py中的配置的

open_spider:当Spider开启时,这个方法被调用,上文程序主要进行了一些初始化操作

close_spider:当Spider关闭时,这个方法被调用,上文程序中将数据库连接关闭

最主要的process_item()方法则执行了数据插入操作

定义好InfoPipeline和MongoPipeline这两个类后,我们需要在settings.py中使用他们,MongoDB的连接信息还需要定义.

我们在settings.py中加入如下内容:

ITEM_PIPELINES={'sb.pipelines.InfoPipeline':300,

'sb.pipelines.MongoPipeline':400,}

MONGO_URI = 'localhost'

MONGO_DB = 'sb'

赋值ITEM_PIPELINES字典,键名是Pipeline的类名称,键值是调用优先级,是一个数字,数字越小则对应的Pipeline越先被调用.

再重新爬取,命令如下所示:

scrapy crawl sb

爬取结束后,MongoDB中创建了一个news的数据库,sbitem的表,

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
2年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这