feapder 與 scrapy 對(duì)比分析

本篇文章在源碼層面比對(duì)feapder、scrapy岩灭、scrapy-redis的設(shè)計(jì)拌倍,閱讀本文后,會(huì)加深您對(duì)scrapy以及feapder的了解川背,以及為什么推薦使用feapder

scrapy分析

1. 解析函數(shù)或數(shù)據(jù)入庫(kù)出錯(cuò)贰拿,不會(huì)重試,會(huì)造成一定的數(shù)據(jù)丟失

scrapy自帶的重試中間件只支持請(qǐng)求重試熄云,解析函數(shù)內(nèi)異撑蚋或者數(shù)據(jù)入庫(kù)異常不會(huì)重試,但爬蟲(chóng)在請(qǐng)求數(shù)據(jù)時(shí)缴允,往往會(huì)有一些意想不到的頁(yè)面返回來(lái)荚守,若我們解析異常了,這條任務(wù)豈不是丟了练般。

當(dāng)然有些大佬可以通過(guò)一些自定義中間件的方式或者加異常捕獲的方式來(lái)解決矗漾,我們這里只討論自帶的。

2. 運(yùn)行方式薄料,需借助命令行敞贡,不方便調(diào)試

若想直接運(yùn)行,需編寫(xiě)如下文件摄职,麻煩

from scrapy import cmdline


name = 'spider_name'
cmd = 'scrapy crawl {0}'.format(name)
cmdline.execute(cmd.split()

為什么必須通過(guò)命令行方式呢誊役? 因?yàn)閟crapy是通過(guò)這種方式來(lái)加載項(xiàng)目中的settings.py文件的

3. 入庫(kù)pipeline,不能批量入庫(kù)

class TestScrapyPipeline(object):
    def process_item(self, item, spider):
        return item

pipelines里的item是一條條傳過(guò)來(lái)的谷市,沒(méi)法直接批量入庫(kù)蛔垢,但數(shù)據(jù)量大的時(shí)候,我們往往是需要批量入庫(kù)的迫悠,以節(jié)省數(shù)據(jù)庫(kù)的性能開(kāi)銷鹏漆,加快入庫(kù)速度

scrapy-redis分析

scrapy-redis任務(wù)隊(duì)列使用redis做的,初始任務(wù)存在 [spider_name]:start_urls里创泄,爬蟲(chóng)產(chǎn)生的子鏈接存在[spider_name]:requests下艺玲,那么我們先看下redis里的任務(wù)

1. redis中的任務(wù)可讀性不好

image

我們看下子鏈任務(wù),可以看到存儲(chǔ)的是序列化后的鞠抑,這種可讀性不好

2. 取任務(wù)時(shí)直接彈出板驳,會(huì)造成任務(wù)丟失

我們分析下scrapy-redis幾種任務(wù)隊(duì)列,取任務(wù)時(shí)都是直接把任務(wù)彈出來(lái)碍拆,如果任務(wù)剛彈出來(lái)爬蟲(chóng)就意外退出,那剛彈出的這條任務(wù)就會(huì)丟失。

  1. FifoQueue(先進(jìn)先出隊(duì)列) 使用list集合

    image
  2. PriorityQueue(優(yōu)先級(jí)隊(duì)列)感混,使用zset集合

    image
  3. LifoQueue(先進(jìn)后出隊(duì)列)端幼,使用list集合

    image

scrapy-redis默認(rèn)使用PriorityQueue隊(duì)列,即優(yōu)先級(jí)隊(duì)列

3. 去重耗內(nèi)存

使用redis的set集合對(duì)request指紋進(jìn)行去重弧满,這種面對(duì)海量數(shù)據(jù)去重對(duì)redis內(nèi)存容量要求很高

4. 需單獨(dú)維護(hù)個(gè)下發(fā)種子任務(wù)的腳本

feapder分析

feapder內(nèi)置 AirSpider婆跑、SpiderBatchSpider三種爬蟲(chóng)庭呜,AirSpider對(duì)標(biāo)Scrapy滑进,Spider對(duì)標(biāo)scrapy-redis,BatchSpider則是應(yīng)于周期性采集的需求募谎,如每周采集一次商品的銷量等場(chǎng)景

上述問(wèn)題解決方案

1. 解析函數(shù)或數(shù)據(jù)入庫(kù)出錯(cuò)扶关,不會(huì)重試,會(huì)造成一定的數(shù)據(jù)丟失

feapder對(duì)請(qǐng)求数冬、解析节槐、入庫(kù)進(jìn)行了全面的異常捕獲,任何位置出現(xiàn)異常會(huì)自動(dòng)重試請(qǐng)求拐纱,若有不想重試的請(qǐng)求也可指定

2. 運(yùn)行方式铜异,需借助命令行,不方便調(diào)試

feapder支持直接運(yùn)行秸架,跟普通的python腳本沒(méi)區(qū)別揍庄,可以借助pycharm調(diào)試。

除了斷點(diǎn)調(diào)試东抹,feapder還支持將爬蟲(chóng)轉(zhuǎn)為Debug爬蟲(chóng)蚂子,Debug爬蟲(chóng)模式下,可指定請(qǐng)求與解析函數(shù)府阀,生產(chǎn)的任務(wù)與數(shù)據(jù)不會(huì)污染正常環(huán)境

3. 入庫(kù)pipeline缆镣,不能批量入庫(kù)

feapder 生產(chǎn)的數(shù)據(jù)會(huì)暫存內(nèi)存的隊(duì)列里,積攢一定量級(jí)或每0.5秒批量傳給pipeline试浙,方便批量入庫(kù)

def save_items(self, table, items: List[Dict]) -> bool:
    pass

這里有人會(huì)有疑問(wèn)

  1. 數(shù)據(jù)放到內(nèi)存里了董瞻,會(huì)不會(huì)造成擁堵?

    答:不會(huì)田巴,這里限制了最高能積攢5000條的上限钠糊,若到達(dá)上限后,爬蟲(chóng)線程會(huì)強(qiáng)制將數(shù)據(jù)入庫(kù)壹哺,然后再生產(chǎn)數(shù)據(jù)

  2. 若爬蟲(chóng)意外退出抄伍,數(shù)據(jù)會(huì)不會(huì)丟?

    答:不會(huì)管宵,任務(wù)會(huì)在數(shù)據(jù)入庫(kù)后再刪除截珍,若意外退出了攀甚,產(chǎn)生這些數(shù)據(jù)的任務(wù)會(huì)重做

  3. 入庫(kù)失敗了怎么辦?

    答:入庫(kù)失敗岗喉,任務(wù)會(huì)重試秋度,數(shù)據(jù)會(huì)重新入庫(kù),若失敗次數(shù)到達(dá)配置的上限會(huì)報(bào)警

4. redis中的任務(wù)可讀性不好

feapder對(duì)請(qǐng)求里常用的字段沒(méi)有序列化钱床,只有那些json不支持的對(duì)象才進(jìn)行序列化

image

5. 取任務(wù)時(shí)直接彈出荚斯,會(huì)造成任務(wù)丟失

feapder在獲取任務(wù)時(shí),沒(méi)直接彈出查牌,任務(wù)采用redis的zset集合存儲(chǔ)事期,每次只取小于當(dāng)前時(shí)間搓分?jǐn)?shù)的任務(wù),同時(shí)將取到的任務(wù)分?jǐn)?shù)修改為當(dāng)前時(shí)間搓+10分鐘纸颜,防止其他爬蟲(chóng)取到重復(fù)的任務(wù)兽泣。若爬蟲(chóng)意外退出,這些取到的任務(wù)其實(shí)還在任務(wù)隊(duì)列里懂衩,并沒(méi)有丟失

6. 去重耗內(nèi)存

feapder支持三種去重方式

  1. 內(nèi)存去重:采用可擴(kuò)展的bloomfilter結(jié)構(gòu)撞叨,基于內(nèi)存,去重一萬(wàn)條數(shù)據(jù)約0.5秒浊洞,一億條數(shù)據(jù)占用內(nèi)存約285MB
  2. 臨時(shí)去重:采用redis的zset集合存儲(chǔ)數(shù)據(jù)的md5值牵敷,去重可指定時(shí)效性。去重一萬(wàn)條數(shù)據(jù)約0.26秒法希,一億條數(shù)據(jù)占用內(nèi)存約1.43G
  3. 永久去重:采用可擴(kuò)展的bloomfilter結(jié)構(gòu)枷餐,基于redis,去重一萬(wàn)條數(shù)據(jù)約0.5秒苫亦,一億條數(shù)據(jù)占用內(nèi)存約285MB

7. 分布式爬蟲(chóng)需單獨(dú)維護(hù)個(gè)下發(fā)種子任務(wù)的腳本

feapder沒(méi)種子任務(wù)和子鏈接的分別毛肋,yield feapder.Request都會(huì)把請(qǐng)求下發(fā)到任務(wù)隊(duì)列,我們可以在start_requests編寫(xiě)下發(fā)種子任務(wù)的邏輯

這里又有人會(huì)有疑問(wèn)了

  1. 我爬蟲(chóng)啟動(dòng)多份時(shí)屋剑,start_requests不會(huì)重復(fù)調(diào)用润匙,重復(fù)下發(fā)種子任務(wù)么?

    答:不會(huì)唉匾,分布式爬蟲(chóng)在調(diào)用start_requests時(shí)孕讳,會(huì)加進(jìn)程鎖,保證只能有一個(gè)爬蟲(chóng)調(diào)用這個(gè)函數(shù)巍膘。并且若任務(wù)隊(duì)列中有任務(wù)時(shí)厂财,爬蟲(chóng)會(huì)走斷點(diǎn)續(xù)爬的邏輯,不會(huì)執(zhí)行start_requests

  2. 那支持手動(dòng)下發(fā)任務(wù)么

    答:支持峡懈,按照f(shuō)eapder的任務(wù)格式璃饱,往redis里扔任務(wù)就好,爬蟲(chóng)支持常駐等待任務(wù)

三種爬蟲(chóng)簡(jiǎn)介

1. AirSpider

使用PriorityQueue作為內(nèi)存任務(wù)隊(duì)列肪康,不支持分布式荚恶,示例代碼

import feapder


class AirSpiderDemo(feapder.AirSpider):
    def start_requests(self):
        yield feapder.Request("https://www.baidu.com")

    def parse(self, request, response):
        print(response)


if __name__ == "__main__":
    AirSpiderDemo().start()

2. Spider

分布式爬蟲(chóng)撩穿,支持啟多份,爬蟲(chóng)意外終止裆甩,重啟后會(huì)斷點(diǎn)續(xù)爬

import feapder


class SpiderDemo(feapder.Spider):
    # 自定義數(shù)據(jù)庫(kù)冗锁,若項(xiàng)目中有setting.py文件,此自定義可刪除
    __custom_setting__ = dict(
        REDISDB_IP_PORTS="localhost:6379", REDISDB_USER_PASS="", REDISDB_DB=0
    )

    def start_requests(self):
        yield feapder.Request("https://www.baidu.com")

    def parse(self, request, response):
        print(response)


if __name__ == "__main__":
    SpiderDemo(redis_key="xxx:xxx").start()

3. BatchSpider

批次爬蟲(chóng)嗤栓,擁有分布式爬蟲(chóng)所有特性,支持分布式

import feapder


class BatchSpiderDemo(feapder.BatchSpider):
    # 自定義數(shù)據(jù)庫(kù)箍邮,若項(xiàng)目中有setting.py文件茉帅,此自定義可刪除
    __custom_setting__ = dict(
        REDISDB_IP_PORTS="localhost:6379",
        REDISDB_USER_PASS="",
        REDISDB_DB=0,
        MYSQL_IP="localhost",
        MYSQL_PORT=3306,
        MYSQL_DB="feapder",
        MYSQL_USER_NAME="feapder",
        MYSQL_USER_PASS="feapder123",
    )

    def start_requests(self, task):
        yield feapder.Request("https://www.baidu.com")

    def parse(self, request, response):
        print(response)


if __name__ == "__main__":
    spider = BatchSpiderDemo(
        redis_key="xxx:xxxx",  # redis中存放任務(wù)等信息的根key
        task_table="",  # mysql中的任務(wù)表
        task_keys=["id", "xxx"],  # 需要獲取任務(wù)表里的字段名,可添加多個(gè)
        task_state="state",  # mysql中任務(wù)狀態(tài)字段
        batch_record_table="xxx_batch_record",  # mysql中的批次記錄表
        batch_name="xxx",  # 批次名字
        batch_interval=7,  # 批次周期 天為單位 若為小時(shí) 可寫(xiě) 1 / 24
    )

    # spider.start_monitor_task() # 下發(fā)及監(jiān)控任務(wù)
    spider.start() # 采集

任務(wù)調(diào)度過(guò)程:

  1. 從mysql中批量取出一批種子任務(wù)
  2. 下發(fā)到爬蟲(chóng)
  3. 爬蟲(chóng)獲取到種子任務(wù)后锭弊,調(diào)度到start_requests堪澎,拼接實(shí)際的請(qǐng)求,下發(fā)到redis
  4. 爬蟲(chóng)從redis中獲取到任務(wù)味滞,調(diào)用解析函數(shù)解析數(shù)據(jù)
  5. 子鏈接入redis樱蛤,數(shù)據(jù)入庫(kù)
  6. 種子任務(wù)完成,更新種子任務(wù)狀態(tài)
  7. 若redis中任務(wù)量過(guò)少剑鞍,則繼續(xù)從mysql中批量取出一批未做的種子任務(wù)下發(fā)到爬蟲(chóng)

封裝了批次(周期)采集的邏輯昨凡,如我們指定7天一個(gè)批次,那么如果爬蟲(chóng)3天就將任務(wù)做完蚁署,爬蟲(chóng)重啟也不會(huì)重復(fù)采集便脊,而是等到第7天之后啟動(dòng)的時(shí)候才會(huì)采集下一批次。

同時(shí)批次爬蟲(chóng)會(huì)預(yù)估采集速度光戈,若按照當(dāng)前速度在指定的時(shí)間內(nèi)采集不完哪痰,會(huì)發(fā)出報(bào)警

feapder項(xiàng)目結(jié)構(gòu)

上述的三種爬蟲(chóng)例子修改配置后可以直接運(yùn)行,但對(duì)于大型項(xiàng)目久妆,可能會(huì)有就好多爬蟲(chóng)組成晌杰。feapder支持創(chuàng)建項(xiàng)目,項(xiàng)目結(jié)構(gòu)如下:

image

main.py 為啟動(dòng)入口

feapder部署

feapder有對(duì)應(yīng)的管理平臺(tái)feaplat筷弦,當(dāng)然這個(gè)管理平臺(tái)也支持部署其他腳本

  1. 在任務(wù)列表里配置啟動(dòng)命令肋演,調(diào)度周期以及爬蟲(chóng)數(shù)等。爬蟲(chóng)數(shù)這個(gè)對(duì)于分布式爬蟲(chóng)是非常爽的奸笤,可一鍵啟動(dòng)幾十上百份爬蟲(chóng)惋啃,再也不需要一個(gè)個(gè)部署了

    -w1791
  1. 任務(wù)啟動(dòng)后,可看到實(shí)例及實(shí)時(shí)日志

    -w1785
  1. 爬蟲(chóng)監(jiān)控面板可實(shí)時(shí)看到爬蟲(chóng)運(yùn)行情況监右,監(jiān)控?cái)?shù)據(jù)保留半年边灭,滾動(dòng)刪除

    image

采集效率測(cè)試

請(qǐng)求百度1萬(wàn)次,線程都開(kāi)到300健盒,測(cè)試耗時(shí)

scrapy:

class BaiduSpider(scrapy.Spider):
    name = 'baidu'
    allowed_domains = ['baidu.com']
    start_urls = ['https://baidu.com/'] * 10000

    def parse(self, response):
        print(response)

結(jié)果

{'downloader/request_bytes': 4668123,
 'downloader/request_count': 20002,
 'downloader/request_method_count/GET': 20002,
 'downloader/response_bytes': 17766922,
 'downloader/response_count': 20002,
 'downloader/response_status_count/200': 10000,
 'downloader/response_status_count/302': 10002,
 'finish_reason': 'finished',
 'finish_time': datetime.datetime(2021, 9, 13, 12, 22, 26, 638611),
 'log_count/DEBUG': 20003,
 'log_count/INFO': 9,
 'memusage/max': 74240000,
 'memusage/startup': 58974208,
 'response_received_count': 10000,
 'scheduler/dequeued': 20002,
 'scheduler/dequeued/memory': 20002,
 'scheduler/enqueued': 20002,
 'scheduler/enqueued/memory': 20002,
 'start_time': datetime.datetime(2021, 9, 13, 12, 19, 58, 489472)}

耗時(shí):148.149139秒

feapder:

import feapder
import time


class AirSpiderDemo(feapder.AirSpider):
    def start_requests(self):
        for i in range(10000):
            yield feapder.Request("https://www.baidu.com")

    def parse(self, request, response):
        print(response)

    def start_callback(self):
        self.start_time = time.time()

    def end_callback(self):
        print("耗時(shí):{}".format(time.time() - self.start_time))


if __name__ == "__main__":
    AirSpiderDemo(thread_count=300).start()

結(jié)果:耗時(shí):136.10122799873352

總結(jié)

本文主要分析了scrapyscrapy-redis的痛點(diǎn)以及feapder是如何解決的绒瘦,當(dāng)然scrapy也有優(yōu)點(diǎn)称簿,比如社區(qū)活躍、中間件靈活等惰帽。但在保證數(shù)據(jù)及任務(wù)不丟的場(chǎng)景憨降,報(bào)警監(jiān)控等場(chǎng)景feapder完勝scrapy。并且feapder是基于實(shí)際業(yè)務(wù)该酗,做過(guò)大大小小100多個(gè)項(xiàng)目授药,耗時(shí)5年打磨出來(lái)的,因此可滿足絕大多數(shù)爬蟲(chóng)需求

效率方面呜魄,請(qǐng)求百度1萬(wàn)次悔叽,同為300線程的情況下,feapder耗時(shí)136秒爵嗅,scrapy耗時(shí)148秒娇澎,算上網(wǎng)絡(luò)的波動(dòng),其實(shí)效率差不多睹晒。

feapder爬蟲(chóng)文檔https://boris-code.gitee.io/feapder/#/

image

feaplat管理平臺(tái)https://boris-code.gitee.io/feapder/#/feapder_platform/%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F

  1. 爬蟲(chóng)管理系統(tǒng)不僅支持 feapder趟庄、scrapy,且支持執(zhí)行任何腳本伪很,可以把該系統(tǒng)理解成腳本托管的平臺(tái) 戚啥。

  2. 支持集群

  3. 工作節(jié)點(diǎn)根據(jù)配置定時(shí)啟動(dòng),執(zhí)行完釋放是掰,不常駐

  4. 一個(gè)worker內(nèi)只運(yùn)行一個(gè)爬蟲(chóng)虑鼎,worker彼此之間隔離,互不影響键痛。

  5. 支持管理員普通用戶兩種角色

  6. 可自定義爬蟲(chóng)端鏡像

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末炫彩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子絮短,更是在濱河造成了極大的恐慌江兢,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件丁频,死亡現(xiàn)場(chǎng)離奇詭異杉允,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)席里,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門叔磷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人奖磁,你說(shuō)我怎么就攤上這事改基。” “怎么了咖为?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵秕狰,是天一觀的道長(zhǎng)稠腊。 經(jīng)常有香客問(wèn)我,道長(zhǎng)鸣哀,這世上最難降的妖魔是什么架忌? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮我衬,結(jié)果婚禮上叹放,老公的妹妹穿的比我還像新娘。我一直安慰自己挠羔,他們只是感情好许昨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著褥赊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪莉恼。 梳的紋絲不亂的頭發(fā)上拌喉,一...
    開(kāi)封第一講書(shū)人閱讀 51,737評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音俐银,去河邊找鬼尿背。 笑死,一個(gè)胖子當(dāng)著我的面吹牛捶惜,可吹牛的內(nèi)容都是我干的田藐。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼吱七,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼汽久!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起踊餐,我...
    開(kāi)封第一講書(shū)人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤景醇,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后吝岭,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體三痰,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年窜管,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了散劫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡幕帆,死狀恐怖获搏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蜓肆,我是刑警寧澤颜凯,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布谋币,位于F島的核電站,受9級(jí)特大地震影響症概,放射性物質(zhì)發(fā)生泄漏蕾额。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一彼城、第九天 我趴在偏房一處隱蔽的房頂上張望诅蝶。 院中可真熱鬧,春花似錦募壕、人聲如沸调炬。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)缰泡。三九已至,卻和暖如春代嗤,著一層夾襖步出監(jiān)牢的瞬間棘钞,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工干毅, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宜猜,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓硝逢,卻偏偏與公主長(zhǎng)得像姨拥,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子渠鸽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容