Scrapy源码阅读分析_3_核心组件

 

From:https://blog.csdn.net/weixin_37947156/article/details/74481758

 

这篇这要是关于核心组件,讲解这些核心组件初始化都做了哪些工作。包括:引擎、下载器、调度器、爬虫类、输出处理器 等的初始化。每个核心组件下其实都包含一些小的组件在里面,帮助处理某一环节的各种流程。

  • 核心组件初始化
  • 核心组件交互流程

 

 

爬虫类

 

接着上次代码讲,上次的运行入口执行到最后是执行了 Crawler 的 crawl 方法:

    @defer.inlineCallbacksdef crawl(self, *args, **kwargs):assert not self.crawling, "Crawling already taking place"self.crawling = Truetry:# 到现在,才是实例化一个爬虫实例self.spider = self._create_spider(*args, **kwargs)# 创建引擎self.engine = self._create_engine()# 调用爬虫类的start_requests方法start_requests = iter(self.spider.start_requests())# 执行引擎的open_spider,并传入爬虫实例和初始请求yield self.engine.open_spider(self.spider, start_requests)yield defer.maybeDeferred(self.engine.start)except Exception:# In Python 2 reraising an exception after yield discards# the original traceback (see https://bugs.python.org/issue7563),# so sys.exc_info() workaround is used.# This workaround also works in Python 3, but it is not needed,# and it is slower, so in Python 3 we use native `raise`.if six.PY2:exc_info = sys.exc_info()self.crawling = Falseif self.engine is not None:yield self.engine.close()if six.PY2:six.reraise(*exc_info)raise

在这里,就交由 scrapy 引擎 来处理了。

依次来看,爬虫类是如何实例化的?上文已讲解过,在 Crawler 实例化时,会创建 SpiderLoader,它会根据用户的配置文件settings.py 找到存放爬虫的位置,我们写的爬虫都会放在这里。

然后 SpiderLoader 会扫描这里的所有文件,并找到 父类是 scrapy.Spider 爬虫类,然后根据爬虫类中的 name 属性(在编写爬虫时,这个属性是必填的),最后生成一个 {spider_name: spider_cls} 的 字典,然后根据 scrapy crawl <spider_name> 命令,根据 spider_name 找到对应的爬虫类,然后实例化它,在这里就是调用了 _create_spider 方法:

class Crawler(object):def __init__(self, spidercls, settings=None):if isinstance(settings, dict) or settings is None:settings = Settings(settings)self.spidercls = spiderclsself.settings = settings.copy()self.spidercls.update_settings(self.settings)d = dict(overridden_settings(self.settings))logger.info("Overridden settings: %(settings)r", {'settings': d})self.signals = SignalManager(self)self.stats = load_object(self.settings['STATS_CLASS'])(self)handler = LogCounterHandler(self, level=self.settings.get('LOG_LEVEL'))logging.root.addHandler(handler)if get_scrapy_root_handler() is not None:# scrapy root handler already installed: update it with new settingsinstall_scrapy_root_handler(self.settings)# lambda is assigned to Crawler attribute because this way it is not# garbage collected after leaving __init__ scopeself.__remove_handler = lambda: logging.root.removeHandler(handler)self.signals.connect(self.__remove_handler, signals.engine_stopped)lf_cls = load_object(self.settings['LOG_FORMATTER'])self.logformatter = lf_cls.from_crawler(self)self.extensions = ExtensionManager.from_crawler(self)self.settings.freeze()self.crawling = Falseself.spider = Noneself.engine = None@propertydef spiders(self):if not hasattr(self, '_spiders'):warnings.warn("Crawler.spiders is deprecated, use ""CrawlerRunner.spider_loader or instantiate ""scrapy.spiderloader.SpiderLoader with your ""settings.",category=ScrapyDeprecationWarning, stacklevel=2)self._spiders = _get_spider_loader(self.settings.frozencopy())return self._spiders@defer.inlineCallbacksdef crawl(self, *args, **kwargs):assert not self.crawling, "Crawling already taking place"self.crawling = Truetry:# 到现在,才是实例化一个爬虫实例self.spider = self._create_spider(*args, **kwargs)# 创建引擎self.engine = self._create_engine()# 调用爬虫类的start_requests方法start_requests = iter(self.spider.start_requests())# 执行引擎的open_spider,并传入爬虫实例和初始请求yield self.engine.open_spider(self.spider, start_requests)yield defer.maybeDeferred(self.engine.start)except Exception:# In Python 2 reraising an exception after yield discards# the original traceback (see https://bugs.python.org/issue7563),# so sys.exc_info() workaround is used.# This workaround also works in Python 3, but it is not needed,# and it is slower, so in Python 3 we use native `raise`.if six.PY2:exc_info = sys.exc_info()self.crawling = Falseif self.engine is not None:yield self.engine.close()if six.PY2:six.reraise(*exc_info)raisedef _create_spider(self, *args, **kwargs):# 调用类方法from_crawler实例化return self.spidercls.from_crawler(self, *args, **kwargs)def _create_engine(self):return ExecutionEngine(self, lambda _: self.stop())@defer.inlineCallbacksdef stop(self):if self.crawling:self.crawling = Falseyield defer.maybeDeferred(self.engine.stop)

实例化爬虫比较有意思,它不是通过普通的构造方法进行初始化,而是调用了类方法 from_crawler 进行的初始化,找到scrapy.Spider 类:(scrapy/spiders/__init__.py)

class Spider(object_ref):"""Base class for scrapy spiders. All spiders must inherit from thisclass."""name = Nonecustom_settings = None  # 自定义设置 def __init__(self, name=None, **kwargs):# spider name 必填if name is not None:self.name = nameelif not getattr(self, 'name', None):raise ValueError("%s must have a name" % type(self).__name__)self.__dict__.update(kwargs)# 如果没有设置 start_urls,默认是[]if not hasattr(self, 'start_urls'):self.start_urls = []@propertydef logger(self):logger = logging.getLogger(self.name)return logging.LoggerAdapter(logger, {'spider': self})def log(self, message, level=logging.DEBUG, **kw):"""Log the given message at the given log levelThis helper wraps a log call to the logger within the spider, but youcan use it directly (e.g. Spider.logger.info('msg')) or use any otherPython logger too."""self.logger.log(level, message, **kw)@classmethoddef from_crawler(cls, crawler, *args, **kwargs):spider = cls(*args, **kwargs)spider._set_crawler(crawler)return spiderdef set_crawler(self, crawler):warnings.warn("set_crawler is deprecated, instantiate and bound the ""spider to this crawler with from_crawler method ""instead.",category=ScrapyDeprecationWarning, stacklevel=2)assert not hasattr(self, 'crawler'), "Spider already bounded to a " \"crawler"self._set_crawler(crawler)def _set_crawler(self, crawler):self.crawler = crawler# 把settings对象赋给spider实例self.settings = crawler.settingscrawler.signals.connect(self.close, signals.spider_closed)def start_requests(self):cls = self.__class__if method_is_overridden(cls, Spider, 'make_requests_from_url'):warnings.warn("Spider.make_requests_from_url method is deprecated; it ""won't be called in future Scrapy releases. Please ""override Spider.start_requests method instead (see %s.%s)." % (cls.__module__, cls.__name__),)for url in self.start_urls:yield self.make_requests_from_url(url)else:for url in self.start_urls:yield Request(url, dont_filter=True)def make_requests_from_url(self, url):""" This method is deprecated. """return Request(url, dont_filter=True)def parse(self, response):raise NotImplementedError('{}.parse callback is not defined'.format(self.__class__.__name__))@classmethoddef update_settings(cls, settings):settings.setdict(cls.custom_settings or {}, priority='spider')@classmethoddef handles_request(cls, request):return url_is_from_spider(request.url, cls)@staticmethoddef close(spider, reason):closed = getattr(spider, 'closed', None)if callable(closed):return closed(reason)def __str__(self):return "<%s %r at 0x%0x>" % (type(self).__name__, self.name, id(self))__repr__ = __str__

在这里可以看到,这个类方法其实也是调用了构造方法,进行实例化,同时也拿到了 settings 配置,

再看构造方法干了些什么?就是我们平时编写爬虫类时,最常用的几个属性:name、start_urls、custom_settings

  • name:在运行爬虫时通过它找到对应的爬虫脚本而使用;
  • start_urls:定义种子URL;
  • custom_settings:从字面意思可以看出,爬虫自定义配置,会覆盖配置文件的配置项;

 

 

引擎

 

分析完爬虫类的初始化后,还是回到Crawlercrawl方法(scrapy/crawler.py 中 Crawler 类 的 crawl 方法

紧接着就是创建 引擎对象,也就是 _create_engine 方法,这里直接进行了引擎初始化操作,看看都发生了什么?

在这里能看到,进行了核心组件的定义和初始化,包括:SchedulerDownloaderScrapyer,其中 Scheduler 只进行了类定义,没有实例化。

 

 

调度器

 

调度器初始化发生在引擎的 open_spider 方法中,

我们提前来看一下 调度器 的 初始化 完成了哪些工作?

调度器的初始化主要做了2件事:

  • 实例化请求指纹过滤器:用来过滤重复请求,可自己重写替换之;
  • 定义各种不同类型的任务队列:优先级任务队列、基于磁盘的任务队列、基于内存的任务队列;

 

 

请求指纹过滤器

 

先来看请求指纹过滤器是什么?在配置文件中定义的默认指纹过滤器是 RFPDupeFilter
DUPEFILTER_CLASS = 'scrapy.dupefilters.RFPDupeFilter'

请求指纹过滤器初始化时定义了指纹集合,这个集合使用内存实现的 set,而且可以控制这些指纹是否存入磁盘供下次重复使用。

指纹过滤器的主要职责是:过滤重复请求,可自定义过滤规则。

在下篇文章中会介绍到,每个请求是根据什么规则生成指纹,进而实现重复请求过滤逻辑的。

 

 

任务队列

 

调度器默认定义的2种队列类型:

  • 基于磁盘的任务队列:在配置文件可配置存储路径,每次执行后会把队列任务保存到磁盘上;
  • 基于内存的任务队列:每次都在内存中执行,下次启动则消失;

配置文件默认定义如下:

如果用户在配置文件中定义了 JOBDIR,那么则每次把任务队列保存在磁盘中,下次启动时自动加载。

如果没有定义,那么则使用的是内存队列。

细心的你会发现,默认定义的这些队列结构都是 后进先出 的,什么意思呢?

也就是说:Scrapy默认的采集规则是深度优先采集!

如何改变这种机制,变为 广度优先采集 呢?那么你可以看一下 scrapy.squeues 模块,其中定义了:

# 先进先出磁盘队列(pickle序列化)
PickleFifoDiskQueue = _serializable_queue(queue.FifoDiskQueue, _pickle_serialize, pickle.loads)# 后进先出磁盘队列(pickle序列化)
PickleLifoDiskQueue = _serializable_queue(queue.LifoDiskQueue, _pickle_serialize, pickle.loads)# 先进先出磁盘队列(marshal序列化)
MarshalFifoDiskQueue = _serializable_queue(queue.FifoDiskQueue, marshal.dumps, marshal.loads)# 后进先出磁盘队列(marshal序列化)
MarshalLifoDiskQueue = _serializable_queue(queue.LifoDiskQueue, marshal.dumps, marshal.loads)# 先进先出内存队列
FifoMemoryQueue = queue.FifoMemoryQueue# 后进先出内存队列
LifoMemoryQueue = queue.LifoMemoryQueue

你只需要在配置文件中把队列类修改为 先进先出 队列类就可以了!有没有发现,模块化、组件替代再次发挥威力!

如果你想追究这些队列是如何实现的,可以参考scrapy作者写的 scrapy/queuelib 模块。

 

 

下载器

 

回头继续看引擎的初始化,来看下载器是如何初始化的。

在默认的配置文件 default_settings.py 中,下载器配置如下:

DOWNLOADER = 'scrapy.core.downloader.Downloader'

Downloader 实例化:

这个过程主要是初始化了 下载处理器下载器中间件管理器 以及从配置文件中拿到抓取请求控制相关参数。

下载器 DownloadHandlers 是做什么的?

下载器中间件 DownloaderMiddlewareManager 初始化发生了什么?

 

 

下载处理器

 

下载处理器在默认的配置文件中是这样配置的:

看到这里你应该能明白了,说白了就是需下载的资源是什么类型,就选用哪一种下载处理器进行网络下载,其中最常用的就是http https 对应的处理器。

从这里你也能看出,scrapy的架构是非常低耦合的,任何涉及到的组件及模块都是可重写和配置的。scrapy提供了基础的服务组件,你也可以自己实现其中的某些组件,修改配置即可达到替换的目的。

到这里,大概就能明白,下载处理器的工作就是:管理着各种资源对应的下载器,在真正发起网络请求时,选取对应的下载器进行资源下载。

但是请注意,在这个初始化过程中,这些下载器是没有被实例化的,也就是说,在真正发起网络请求时,才会进行初始化,而且只会初始化一次,后面会讲到。

 

 

下载器中间件管理器

 

下面来看下载器中间件 DownloaderMiddlewareManager 初始化,同样的这里又调用了类方法 from_crawler 进行初始化,DownloaderMiddlewareManager 继承了 MiddlewareManager 类,来看它在初始化做了哪些工作:

(scrapy/core/downloader/middleware.py)

from collections import defaultdict, deque
import logging
import pprintfrom scrapy.exceptions import NotConfigured
from scrapy.utils.misc import create_instance, load_object
from scrapy.utils.defer import process_parallel, process_chain, process_chain_bothlogger = logging.getLogger(__name__)class MiddlewareManager(object):"""所有中间件的父类,提供中间件公共的方法"""component_name = 'foo middleware'def __init__(self, *middlewares):self.middlewares = middlewares# 定义中间件方法self.methods = defaultdict(deque)for mw in middlewares:self._add_middleware(mw)@classmethoddef _get_mwlist_from_settings(cls, settings):# 具体有哪些中间件类,子类定义raise NotImplementedError@classmethoddef from_settings(cls, settings, crawler=None):# 调用子类_get_mwlist_from_settings得到所有中间件类的模块mwlist = cls._get_mwlist_from_settings(settings)middlewares = []enabled = []# 依次实例化for clspath in mwlist:try:# 加载这些中间件模块mwcls = load_object(clspath)mw = create_instance(mwcls, settings, crawler)middlewares.append(mw)enabled.append(clspath)except NotConfigured as e:if e.args:clsname = clspath.split('.')[-1]logger.warning("Disabled %(clsname)s: %(eargs)s",{'clsname': clsname, 'eargs': e.args[0]},extra={'crawler': crawler})logger.info("Enabled %(componentname)ss:\n%(enabledlist)s",{'componentname': cls.component_name,'enabledlist': pprint.pformat(enabled)},extra={'crawler': crawler})# 调用构造方法return cls(*middlewares)@classmethoddef from_crawler(cls, crawler):# 调用 from_settingsreturn cls.from_settings(crawler.settings, crawler)def _add_middleware(self, mw):# 默认定义的,子类可覆盖# 如果中间件类有定义open_spider,则加入到methodsif hasattr(mw, 'open_spider'):self.methods['open_spider'].append(mw.open_spider)# 如果中间件类有定义close_spider,则加入到methods# methods就是一串中间件的方法链,后期会依次调用if hasattr(mw, 'close_spider'):self.methods['close_spider'].appendleft(mw.close_spider)def _process_parallel(self, methodname, obj, *args):return process_parallel(self.methods[methodname], obj, *args)def _process_chain(self, methodname, obj, *args):return process_chain(self.methods[methodname], obj, *args)def _process_chain_both(self, cb_methodname, eb_methodname, obj, *args):return process_chain_both(self.methods[cb_methodname], \self.methods[eb_methodname], obj, *args)def open_spider(self, spider):return self._process_parallel('open_spider', spider)def close_spider(self, spider):return self._process_parallel('close_spider', spider)

create_instance 函数:

def create_instance(objcls, settings, crawler, *args, **kwargs):"""Construct a class instance using its ``from_crawler`` or``from_settings`` constructors, if available.At least one of ``settings`` and ``crawler`` needs to be different from``None``. If ``settings `` is ``None``, ``crawler.settings`` will be used.If ``crawler`` is ``None``, only the ``from_settings`` constructor will betried.``*args`` and ``**kwargs`` are forwarded to the constructors.Raises ``ValueError`` if both ``settings`` and ``crawler`` are ``None``."""if settings is None:if crawler is None:raise ValueError("Specifiy at least one of settings and crawler.")settings = crawler.settings# 如果此中间件类定义了from_crawler,则调用此方法实例化if crawler and hasattr(objcls, 'from_crawler'):return objcls.from_crawler(crawler, *args, **kwargs)# 如果此中间件类定义了from_settings,则调用此方法实例化elif hasattr(objcls, 'from_settings'):return objcls.from_settings(settings, *args, **kwargs)else:# 上面2个方法都没有,则直接调用构造实例化return objcls(*args, **kwargs)

DownloaderMiddlewareManager 实例化:

下载器中间件管理器 继承了 MiddlewareManager 类,然后重写了 _add_middleware 方法,为下载行为定义默认的 下载前、下载后、异常时 对应的处理方法。 

中间件的职责是什么?从这里能大概看出,从某个组件流向另一个组件时,会经过一系列中间件,每个中间件都定义了自己的处理流程,相当于一个个管道,输入时可以针对数据进行处理,然后送达到另一个组件,另一个组件处理完逻辑后,又经过这一系列中间件,这些中间件可再针对这个响应结果进行处理,最终输出。

 

 

Scraper

 

下载器实例化完了之后,回到引擎的初始化方法中,然后是实例化 Scraper,在Scrapy源码分析(一)架构概览中已经大概说到,这个类没有在架构图中出现,但这个类其实是处于 EngineSpidersPipeline 之间,是连通这3个组件的桥梁。

来看它的初始化:scrapy/core/scraper.py

 

 

爬虫中间件管理器

 

SpiderMiddlewareManager 初始化:

爬虫中间件管理器初始化与之前的下载器中间件管理器类似,先是从配置文件中加载了默认的爬虫中间件类,然后依次注册爬虫中间件的一系列流程方法。

配置文件中定义的默认的爬虫中间件类如下:

这些默认的爬虫中间件职责分别如下:

  • HttpErrorMiddleware:会针对响应不是 200 错误进行逻辑处理;
  • OffsiteMiddleware:如果Spider中定义了 allowed_domains,会自动过滤除此之外的域名请求;
  • RefererMiddleware:追加 Referer 头信息;
  • UrlLengthMiddleware:控制过滤URL长度超过配置的请求;
  • DepthMiddleware:过滤超过配置深入的抓取请求;

当然,你也可以定义自己的爬虫中间件,来处理自己需要的逻辑。

 

 

Pipeline管理器

 

爬虫中间件管理器初始化完之后,然后就是 Pipeline 组件的初始化,默认的 Pipeline 组件是 ItemPipelineManager

可以看到 ItemPipelineManager 也是一个中间件管理器的子类,由于它的行为非常类似于中间件,但由于功能较为独立,所以属于核心组件之一。

从 Scraper 的初始化能够看到,它管理着 Spiders 和 Pipeline 相关的交互逻辑。

 

 

总结

 

到这里,所有组件:引擎、下载器、调度器、爬虫类、输出处理器都依次初始化完成,每个核心组件下其实都包含一些小的组件在里面,帮助处理某一环节的各种流程。

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/495945.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微信服务器向公众号推送消息或事件后,微信服务器向公众号推送消息或事件后,得到的回应不合法?...

呼啦08-04加粗标红插入代码插入链接插入图片上传视频请 登录 后发表内容关闭新增或编辑超链接链接地址关闭插入视频视频链接Appid: wxd4170daab0213d6a昵称: 大都会官微UAT时间: 2021-08-04 11:03:44内容: 微信服务器向公众号推送消息或事件后&#xff0c;得到的回应不合法次数…

Lambda 表达式详解~Stream Pipelines

前面我们已经学会如何使用Stream API&#xff0c;用起来真的很爽&#xff0c;但简洁的方法下面似乎隐藏着无尽的秘密&#xff0c;如此强大的API是如何实现的呢&#xff1f;比如Pipeline是怎么执行的&#xff0c;每次方法调用都会导致一次迭代吗&#xff1f;自动并行又是怎么做到…

报告:采用人工智能并不意味着成功

来源&#xff1a;199IT互联网数据中心毕马威发布了新报告“采用人工智能并不意味着成功”&#xff0c;分析了2018年的主要趋势。采用数字化劳动&#xff08;Digital labor&#xff09;、机器人流程自动化、人工智能、机器学习和其他创新技术解决方案是2018年和未来几年的流行趋…

Scrapy源码阅读分析_4_请求处理流程

From&#xff1a;https://blog.csdn.net/weixin_37947156/article/details/74533108 运行入口 还是回到最初的入口&#xff0c;在Scrapy源码分析&#xff08;二&#xff09;运行入口这篇文章中已经讲解到&#xff0c;在执行scrapy命令时&#xff0c;调用流程如下&#xff1a; …

普通电阻触摸屏多点触摸低成本解决方 转载

苹果公司iPhone的成功将多点触摸技术推到了一个前所未有的高度&#xff0c;经典的弹钢琴应用程序可以支持超过5点的同时触摸&#xff0c;虽然这一性能并不见得有太多的实用价值&#xff0c;但绝对带给了用户技术无限领先的震撼感。苹果公司的iPhone采用电容屏和他们的专利技术来…

Gartner:2018年前沿技术预测

本文转载自科技中国&#xff0c;作者&#xff1a;孟海华(上海市科学学研究所)&#xff0c;首发刊载于《科技中国》杂志2018年3月 第3期 预测。一、人工智能全球领先的信息技术研究与顾问公司Gartner认为&#xff0c;2018年将是人工智能大众化应用的开始&#xff0c;将影响到企业…

@font-face

问题描述&#xff1a; 产品展示的界面上有个产品编号&#xff0c;由后台程序动态生成&#xff0c;如图 而"875"的字体是特殊字体&#xff0c;如果客户端系统上没有安装该特殊字体&#xff0c;就达不到原设计效果。 解决办法(我认为的三种)&#xff1a;1。通过FLASH实…

扎克伯格|在美国国会数据门听证会上的证词-中英文全文

来源&#xff1a;网络法前哨美国时间2018年4月10日至11日&#xff0c;Facebook公司CEO马克-扎克伯格&#xff08;Mark Zuckerberg&#xff09;将在美国国会就“剑桥分析丑闻”作证。4月10日&#xff0c;扎克伯格已经参加了美国参议院司法与商业委员会举行的听证会。4月11日&…

Scrapy-Link Extractors(链接提取器)

Link Extractors 中文文档&#xff1a;https://scrapy-chs.readthedocs.io/zh_CN/1.0/topics/link-extractors.html Link Extractors 英文文档&#xff1a;http://doc.scrapy.org/en/latest/topics/link-extractors.html 利用爬虫Scrapy中的LinkExtractor&#xff08;链接提取器…

Java8 Stream详解~Stream 创建

Stream可以通过集合数组创建。 1、通过 java.util.Collection.stream() 方法用集合创建流 List<String> list Arrays.asList("a", "b", "c"); // 创建一个顺序流 Stream<String> stream list.stream(); // 创建一个并行流 Strea…

一张图:AI领域里各领风骚的BAT三巨头

来源&#xff1a;网络大数据近日&#xff0c;国内人工智能初创企业商汤科技完成6亿美元融资&#xff0c;由阿里巴巴集团领投&#xff0c;创下目前为止人工智能领域最大的一笔单轮融资。其实在过去两年里&#xff0c;BAT(百度、阿里巴巴、腾讯)就已在人工智能领域里纷纷交出了漂…

Scrapy - Request 和 Response(请求和响应)

Requests and Responses&#xff1a;http://doc.scrapy.org/en/latest/topics/request-response.html Requests and Responses&#xff08;中文版&#xff09;&#xff1a;https://scrapy-chs.readthedocs.io/zh_CN/latest/topics/request-response.html 请求 和 响应 通常&am…

一篇文章了解生物特征识别六大技术

来源&#xff1a;赵松科学网博客生物识别技术&#xff0c;通过计算机与光学、声学、生物传感器和生物统计学原理等高科技手段密切结合&#xff0c;利用人体固有的生理特性&#xff08;如指纹、脸象、虹膜等&#xff09;和行为特征&#xff08;如笔迹、声音、步态等&#xff09;…

Java8 Stream详解~遍历/匹配(foreach/find/match)

Stream也是支持类似集合的遍历和匹配元素的&#xff0c;只是Stream中的元素是以Optional类型存在的。Stream的遍历、匹配非常简单。 // import已省略&#xff0c;请自行添加&#xff0c;后面代码亦是public class StreamTest {public static void main(String[] args) {List<…

Scrapy-Item Pipeline(项目管道)

Item Pipeline&#xff08;英文版&#xff09;&#xff1a;http://doc.scrapy.org/en/latest/topics/item-pipeline.html Item Pipeline&#xff08;中文版&#xff09;&#xff1a;https://scrapy-chs.readthedocs.io/zh_CN/latest/topics/item-pipeline.html Scrapy 1.3 文…

Java8 Stream详解~筛选:filter

筛选&#xff0c;是按照一定的规则校验流中的元素&#xff0c;将符合条件的元素提取到新的流中的操作。 「案例一&#xff1a;筛选出Integer集合中大于7的元素&#xff0c;并打印出来」 public class StreamTest {public static void main(String[] args) {List<Integer>…

《全球人工智能产业地图》发布(附PPT图片)

来源&#xff1a;中国信息通信研究院CAICT摘要&#xff1a;工业和信息化部、电子信息企业、人工智能企业、互联网企业、电信运营商、研究机构、社团组织、高校等代表参会&#xff0c;一致对《全球人工智能产业地图》表示高度肯定和认可。2018年4月10日&#xff0c;在工业和信息…

Java8 Stream详解~聚合(max/min/count)

max、min、count这些字眼你一定不陌生&#xff0c;没错&#xff0c;在mysql中我们常用它们进行数据统计。Java stream中也引入了这些概念和用法&#xff0c;极大地方便了我们对集合、数组的数据统计工作。 「案例一&#xff1a;获取String集合中最长的元素。」 public class S…

为何学习新知识这么难?因为大脑可能比你想象中更死板

来源&#xff1a;科研圈撰文 John Rennie 翻译 齐睿娟 审校 魏潇某些情况下&#xff0c;大脑的适应能力似乎是用之不竭的。但通过观察学习状态下的大脑活动&#xff0c;科学家们发现&#xff0c;这一过程中大脑的神经元网络功能出乎意料地死板和低效。学习能力是人类智力的…

vs2010 学习Silverlight学习笔记(8):使用用户控件

概要&#xff1a; 这个类似于封装控件样式。不过封装的是整个或是多个控件罢了&#xff0c;然后用的时候就可以直接引用过来了。 创建用户控&#xff1a; 这个也很简单&#xff0c;不过有几个地方需要注意下。这个就不照抄了&#xff0c;咱们也自己写一个。  步骤&#xff1a…