admin管理员组

文章数量:1531726

下面将介绍一些内置的的Processor。

1. Identity

Identity是最简单的Processor,不进行任何处理,直接返回原来的数据。

2. TakeFirst

TakeFirst返回列表的第一个非空值,类似extract_first()的功能,常用作Output Processor,如下所示:

from scrapy.loader.processors import TakeFirst
processor = TakeFirst()
print(processor([‘’, 1, 2, 3]))

输出结果如下所示:

1

经过此Processor处理后的结果返回了第一个不为空的值。

3. Join

Join方法相当于字符串的join()方法,可以把列表拼合成字符串,字符串默认使用空格分隔,如下所示:

from scrapy.loader.processors import Join
processor = Join()
print(processor([‘one’, ‘two’, ‘three’]))

输出结果如下所示:

one two three

它也可以通过参数更改默认的分隔符,例如改成逗号:

from scrapy.loader.processors import Join
processor = Join(‘,’)
print(processor([‘one’, ‘two’, ‘three’]))

运行结果如下所示:

one,two,three

4. Compose

Compose是用给定的多个函数的组合而构造的Processor,每个输入值被传递到第一个函数,其输出再传递到第二个函数,依次类推,直到最后一个函数返回整个处理器的输出,如下所示:

from scrapy.loader.processors import Compose
processor = Compose(str.upper, lambda s: s.strip())
print(processor(’ hello world’))

运行结果如下所示:

HELLO WORLD

在这里我们构造了一个Compose Processor,传入一个开头带有空格的字符串。Compose Processor的参数有两个:第一个是str.upper,它可以将字母全部转为大写;第二个是一个匿名函数,它调用strip()方法去除头尾空白字符。Compose会顺次调用两个参数,最后返回结果的字符串全部转化为大写并且去除了开头的空格。

5. MapCompose

Compose类似,MapCompose可以迭代处理一个列表输入值,如下所示:

from scrapy.loader.processors import MapCompose
processor = MapCompose(str.upper, lambda s: s.strip())
print(processor([‘Hello’, ‘World’, ‘Python’]))

运行结果如下所示:

[‘HELLO’, ‘WORLD’, ‘PYTHON’]

被处理的内容是一个可迭代对象,MapCompose会将该对象遍历然后依次处理。

6. SelectJmes

SelectJmes可以查询JSON,传入Key,返回查询所得的Value。不过需要先安装Jmespath库才可以使用它,命令如下所示:

pip3 install jmespath

安装好Jmespath之后,便可以使用这个Processor了,如下所示:

from scrapy.loader.processors import SelectJmes
proc = SelectJmes(‘foo’)
processor = SelectJmes(‘foo’)
print(processor({‘foo’: ‘bar’}))

运行结果如下所示:

bar

以上内容便是一些常用的Processor,在本节的实例中我们会使用Processor来进行数据的处理。

接下来,我们用一个实例来了解Item Loader的用法。

三、本节目标

我们以中华网科技类新闻为例,来了解CrawlSpider和Item Loader的用法,再提取其可配置信息实现可配置化。官网链接为:http://tech.china/。我们需要爬取它的科技类新闻内容,链接为:http://tech.china/articles/,页面如下图所示。

我们要抓取新闻列表中的所有分页的新闻详情,包括标题、正文、时间、来源等信息。

四、新建项目

首先新建一个Scrapy项目,名为scrapyuniversal,如下所示:

scrapy startproject scrapyuniversal

创建一个CrawlSpider,需要先制定一个模板。我们可以先看看有哪些可用模板,命令如下所示:

scrapy genspider -l

运行结果如下所示:

Available templates:  basic  crawl  csvfeed  xmlfeed

之前创建Spider的时候,我们默认使用了第一个模板basic。这次要创建CrawlSpider,就需要使用第二个模板crawl,创建命令如下所示:

scrapy genspider -t crawl china tech.china

运行之后便会生成一个CrawlSpider,其内容如下所示:

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule

class ChinaSpider(CrawlSpider):
   name = ‘china’
   allowed_domains = [‘tech.china’]
   start_urls = [‘http://tech.china/’]

rules = (
       Rule(LinkExtractor(allow=r’Items/'), callback=‘parse_item’, follow=True),
   )

def parse_item(self, response):
       i = {}
       #i[‘domain_id’] = response.xpath(‘//input[@id=“sid”]/@value’).extract()
       #i[‘name’] = response.xpath(‘//div[@id=“name”]’).extract()
       #i[‘description’] = response.xpath(‘//div[@id=“description”]’).extract()
       return i

这次生成的Spider内容多了一个rules属性的定义。Rule的第一个参数是LinkExtractor,就是上文所说的LxmlLinkExtractor,只是名称不同。同时,默认的回调函数也不再是parse,而是parse_item

五、定义Rule

要实现新闻的爬取,我们需要做的就是定义好Rule,然后实现解析函数。下面我们就来一步步实现这个过程。

首先将start_urls修改为起始链接,代码如下所示:

start_urls = [‘http://tech.china/articles/’]

之后,Spider爬取start_urls里面的每一个链接。所以这里第一个爬取的页面就是我们刚才所定义的链接。得到Response之后,Spider就会根据每一个Rule来提取这个页面内的超链接,去生成进一步的Request。接下来,我们就需要定义Rule来指定提取哪些链接。

当前页面如下图所示。

这是新闻的列表页,下一步自然就是将列表中的每条新闻详情的链接提取出来。这里直接指定这些链接所在区域即可。查看源代码,所有链接都在ID为left_side的节点内,具体来说是它内部的classcon_item的节点,如下图所示。

此处我们可以用LinkExtractorrestrict_xpaths属性来指定,之后Spider就会从这个区域提取所有的超链接并生成Request。但是,每篇文章的导航中可能还有一些其他的超链接标签,我们只想把需要的新闻链接提取出来。真正的新闻链接路径都是以article开头的,我们用一个正则表达式将其匹配出来再赋值给allow参数即可。另外,这些链接对应的页面其实就是对应的新闻详情页,而我们需要解析的就是新闻的详情信息,所以此处还需要指定一个回调函数callback

到现在我们就可以构造出一个Rule了,代码如下所示:

Rule(LinkExtractor(allow=‘article/.*.html’, restrict_xpaths=‘//div[@id=“left_side”]//div[@class=“con_item”]’), callback=‘parse_item’)

接下来,我们还要让当前页面实现分页功能,所以还需要提取下一页的链接。分析网页源码之后可以发现下一页链接是在ID为pageStyle的节点内,如下图所示。

但是,下一页节点和其他分页链接区分度不高,要取出此链接我们可以直接用XPath的文本匹配方式,所以这里我们直接用LinkExtractorrestrict_xpaths属性来指定提取的链接即可。另外,我们不需要像新闻详情页一样去提取此分页链接对应的页面详情信息,也就是不需要生成Item,所以不需要加callback参数。另外这下一页的页面如果请求成功了就需要继续像上述情况一样分析,所以它还需要加一个follow参数为True,代表继续跟进匹配分析。其实,follow参数也可以不加,因为当callback为空的时候,follow默认为True。此处Rule定义为如下所示:

Rule(LinkExtractor(restrict_xpaths=‘//div[@id=“pageStyle”]//a[contains(., “下一页”)]’))

所以现在rules就变成了:

rules = (
   Rule(LinkExtractor(allow=‘article/.*.html’, restrict_xpaths=‘//div[@id=“left_side”]//div[@class=“con_item”]’), callback=‘parse_item’),
   Rule(LinkExtractor(restrict_xpaths=‘//div[@id=“pageStyle”]//a[contains(., “下一页”)]’))
)

接着我们运行代码,命令如下所示:

scrapy crawl china

现在已经实现页面的翻页和详情页的抓取了,我们仅仅通过定义了两个Rule即实现了这样的功能,运行效果如下图所示。

六、解析页面

接下来我们需要做的就是解析页面内容了,将标题、发布时间、正文、来源提取出来即可。首先定义一个Item,如下所示:

from scrapy import Field, Item

class NewsItem(Item):
   title = Field()
   url = Field()
   text = Field()
   datetime = Field()
   source = Field()
   website = Field()

这里的字段分别指新闻标题、链接、正文、发布时间、来源、站点名称,其中站点名称直接赋值为中华网。因为既然是通用爬虫,肯定还有很多爬虫也来爬取同样结构的其他站点的新闻内容,所以需要一个字段来区分一下站点名称。

详情页的预览图如下图所示。

如果像之前一样提取内容,就直接调用response变量的xpath()css()等方法即可。这里parse_item()方法的实现如下所示:

def parse_item(self, response):
   item = NewsItem()
   item[‘title’] = response.xpath(‘//h1[@id=“chan_newsTitle”]/text()’).extract_first()
   item[‘url’] = response.url
   item[‘text’] = ‘’.join(response.xpath(‘//div[@id=“chan_newsDetail”]//text()’).extract()).strip()
   item[‘datetime’] = response.xpath(‘//div[@id=“chan_newsInfo”]/text()’).re_first(‘(\d±\d±\d+\s\d+:\d+:\d+)’)
   item[‘source’] = response.xpath(‘//div[@id=“chan_newsInfo”]/text()’).re_first(‘来源:(.*)’).strip()
   item[‘website’] = ‘中华网’
   yield item

这样我们就把每条新闻的信息提取形成了一个NewsItem对象。

这时实际上我们就已经完成了Item的提取。再运行一下Spider,如下所示:

scrapy crawl china

输出内容如下图所示。

现在我们就可以成功将每条新闻的信息提取出来。

不过我们发现这种提取方式非常不规整。下面我们再用Item Loader,通过add_xpath()add_css()add_value()等方式实现配置化提取。我们可以改写parse_item(),如下所示:

def parse_item(self, response):
   loader = ChinaLoader(item=NewsItem(), response=response)
   loader.add_xpath(‘title’, ‘//h1[@id=“chan_newsTitle”]/text()’)
   loader.add_value(‘url’, response.url)
   loader.add_xpath(‘text’, ‘//div[@id=“chan_newsDetail”]//text()’)
   loader.add_xpath(‘datetime’, ‘//div[@id=“chan_newsInfo”]/text()’, re=‘(\d±\d±\d+\s\d+:\d+:\d+)’)
   loader.add_xpath(‘source’, ‘//div[@id=“chan_newsInfo”]/text()’, re=‘来源:(.*)’)
   loader.add_value(‘website’, ‘中华网’)
   yield loader.load_item()

这里我们定义了一个ItemLoader的子类,名为ChinaLoader,其实现如下所示:

from scrapy.loader import ItemLoader
from scrapy.loader.processors import TakeFirst, Join, Compose

class NewsLoader(ItemLoader):
   default_output_processor = TakeFirst()

class ChinaLoader(NewsLoader):
   text_out = Compose(Join(), lambda s: s.strip())
   source_out = Compose(Join(), lambda s: s.strip())

ChinaLoader继承了NewsLoader类,其内定义了一个通用的Out ProcessorTakeFirst,这相当于之前所定义的extract_first()方法的功能。我们在ChinaLoader中定义了text_outsource_out字段。这里使用了一个Compose Processor,它有两个参数:第一个参数Join也是一个Processor,它可以把列表拼合成一个字符串;第二个参数是一个匿名函数,可以将字符串的头尾空白字符去掉。经过这一系列处理之后,我们就将列表形式的提取结果转化为去重头尾空白字符的字符串。

代码重新运行,提取效果是完全一样的。

至此,我们已经实现了爬虫的半通用化配置。

七、通用配置抽取

为什么现在只做到了半通用化?如果我们需要扩展其他站点,仍然需要创建一个新的CrawlSpider,定义这个站点的Rule,单独实现parse_item()方法。还有很多代码是重复的,如CrawlSpider的变量、方法名几乎都是一样的。那么我们可不可以把多个类似的几个爬虫的代码共用,把完全不相同的地方抽离出来,做成可配置文件呢?

当然可以。那我们可以抽离出哪些部分?所有的变量都可以抽取,如nameallowed_domainsstart_urlsrules等。这些变量在CrawlSpider初始化的时候赋值即可。我们就可以新建一个通用的Spider来实现这个功能,命令如下所示:

scrapy genspider -t crawl universal universal

这个全新的Spider名为universal。接下来,我们将刚才所写的Spider内的属性抽离出来配置成一个JSON,命名为china.json,放到configs文件夹内,和spiders文件夹并列,代码如下所示:

{
 “spider”: “universal”,
 “website”: “中华网科技”,
 “type”: “新闻”,
 “index”: “http://tech.china/”,
 “settings”: {
   “USER_AGENT”: “Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36”
 },
 “start_urls”: [
   “http://tech.china/articles/”
 ],
 “allowed_domains”: [
   “tech.china”
 ],
 “rules”: “china”
}

第一个字段spider即Spider的名称,在这里是universal。后面是站点的描述,比如站点名称、类型、首页等。随后的settings是该Spider特有的settings配置,如果要覆盖全局项目,settings.py内的配置可以单独为其配置。随后是Spider的一些属性,如start_urlsallowed_domainsrules等。rules也可以单独定义成一个rules.py文件,做成配置文件,实现Rule的分离,如下所示:

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import Rule

rules = {
   ‘china’: (
       Rule(LinkExtractor(allow=‘article/.*.html’, restrict_xpaths=‘//div[@id=“left_side”]//div[@class=“con_item”]’),
            callback=‘parse_item’),
       Rule(LinkExtractor(restrict_xpaths=‘//div[@id=“pageStyle”]//a[contains(., “下一页”)]’))
   )
}

这样我们将基本的配置抽取出来。如果要启动爬虫,只需要从该配置文件中读取然后动态加载到Spider中即可。所以我们需要定义一个读取该JSON文件的方法,如下所示:

from os.path import realpath, dirname
import json
def get_config(name):
   path = dirname(realpath(__file__)) + ‘/configs/’ + name + ‘.json’
   with open(path, ‘r’, encoding=‘utf-8’) as f:
       return json.loads(f.read())

定义了get_config()方法之后,我们只需要向其传入JSON配置文件的名称即可获取此JSON配置信息。随后我们定义入口文件run.py,把它放在项目根目录下,它的作用是启动Spider,如下所示:

import sys
from scrapy.utils.project import get_project_settings
from scrapyuniversal.spiders.universal import UniversalSpider
from scrapyuniversal.utils import get_config

做了那么多年开发,自学了很多门编程语言,我很明白学习资源对于学一门新语言的重要性,这些年也收藏了不少的Python干货,对我来说这些东西确实已经用不到了,但对于准备自学Python的人来说,或许它就是一个宝藏,可以给你省去很多的时间和精力。

别在网上瞎学了,我最近也做了一些资源的更新,只要你是我的粉丝,这期福利你都可拿走。

我先来介绍一下这些东西怎么用,文末抱走。


(1)Python所有方向的学习路线(新版)

这是我花了几天的时间去把Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。

最近我才对这些路线做了一下新的更新,知识体系更全面了。

(2)Python学习视频

包含了Python入门、爬虫、数据分析和web开发的学习视频,总共100多个,虽然没有那么全面,但是对于入门来说是没问题的,学完这些之后,你可以按照我上面的学习路线去网上找其他的知识资源进行进阶。

(3)100多个练手项目

我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了,只是里面的项目比较多,水平也是参差不齐,大家可以挑自己能做的项目去练练。

(4)200多本电子书

这些年我也收藏了很多电子书,大概200多本,有时候带实体书不方便的话,我就会去打开电子书看看,书籍可不一定比视频教程差,尤其是权威的技术书籍。

基本上主流的和经典的都有,这里我就不放图了,版权问题,个人看看是没有问题的。

(5)Python知识点汇总

知识点汇总有点像学习路线,但与学习路线不同的点就在于,知识点汇总更为细致,里面包含了对具体知识点的简单说明,而我们的学习路线则更为抽象和简单,只是为了方便大家只是某个领域你应该学习哪些技术栈。

(6)其他资料

还有其他的一些东西,比如说我自己出的Python入门图文类教程,没有电脑的时候用手机也可以学习知识,学会了理论之后再去敲代码实践验证,还有Python中文版的库资料、MySQL和HTML标签大全等等,这些都是可以送给粉丝们的东西。

这些都不是什么非常值钱的东西,但对于没有资源或者资源不是很好的学习者来说确实很不错,你要是用得到的话都可以直接抱走,关注过我的人都知道,这些都是可以拿到的。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里无偿获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

本文标签: 爬虫框架scrapyparseparsestarturl