日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

如何使用scrapy中的ItemLoader提取数据?

發布時間:2023/12/18 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 如何使用scrapy中的ItemLoader提取数据? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

如何使用scrapy中的ItemLoader提取數據?

1. 簡述

  • 我們在用scrapy爬取數據時,首先就要明確我們要爬取什么數據。scrapy提供了Item對象這種簡單的容器,我們可以通過Item定義提取數據的格式,需要爬取哪些字段,其提供了類似于字典的API以及用于聲明可用字段的簡單語法。如下所示:
  • 下面以爬取伯樂在線文章詳情頁為范例:http://blog.jobbole.com/all-posts/
# 文件items.py # Item使用簡單的class定義語法以及 Field 對象來聲明。 import scrapyclass articleDetailItem(scrapy.Item):# 標題title = scrapy.Field()# 文章創建時間create_date = scrapy.Field()# 文章鏈接地址url = scrapy.Field()# url經過md5映射后的值url_object_id = scrapy.Field()# 文章中圖片地址front_image_url = scrapy.Field()# 文件下載后本地保存的地址front_image_path = scrapy.Field()# 贊的個數praise_nums = scrapy.Field()# 評論數comment_nums = scrapy.Field()# 收藏數fav_nums = scrapy.Field()# 所有標簽tags = scrapy.Field()# 文章內容content = scrapy.Field(serializer = str)
  • Item字段說明:
    • Field 對象指明了每個字段的元數據(metadata)。例如上面例子中 content 字段中指明了該字段的序列化函數為str。
    • 可以為每個字段指明任何類型的元數據。Field 對象對接受的值沒有任何限制。Field 對象中保存的每個鍵可以由多個組件使用,并且只有這些組件知道這個鍵的存在。設置 Field 對象的主要目的就是在一個地方定義好所有的元數據。
    • 需要注意的是,用來聲明item的 Field 對象并沒有被賦值為class的屬性。 不過可以通過 Item.fields 屬性進行訪問。
  • 然后在spider.py中,按照一定的規則來進行數據的提取,如下:
# 文件 boleSpider.py from ArticleSpider.items import articleDetailItem#...........此處省略.......... def parseArticelDetail(self, response):articleObject = articleDetailItem()# 提取出的內容是:6 收藏fav_nums = response.xpath("//span[contains(@class, 'bookmark-btn')]/text()").extract()[0]# 用正則表達式提取其中的數字6match_re = re.match(".*?(\d+).*", fav_nums)if match_re:fav_nums = match_re.group(1)else:fav_nums = 0
  • 但是當項目很大,提取的字段數以百計,那么各種提取規則會越來越多,按照這種方式來做,維護的工作將會是一場噩夢!
  • 所以scrapy就提供了ItemLoader這樣一個容器,在這個容器里面可以配置item中各個字段的提取規則。可以通過函數分析原始數據,并對Item字段進行賦值,非常的便捷。
  • 可以這么來看 Item 和 Itemloader:Item提供保存抓取到數據的容器,而 Itemloader提供的是填充容器的機制。
  • Itemloader提供的是一種靈活,高效的機制,可以更方便的被spider或source format (HTML, XML, etc)擴展并重寫,更易于維護,尤其是分析規則特別復雜繁多的時候。

2. 環境

  • 系統:win7
  • Scrapy 1.4.0
  • python 3.6.1

3. ItemLoader使用步驟

3.1. 實例化ItemLoader對象

# 文件 boleSpider.py from scrapy.loader import ItemLoader
  • 要使用Itemloader,必須先將它實例化。可以使用類似字典的對象或者我們之前定義的Item對象來進行實例化。
# 文件 boleSpider.py import scrapy from scrapy.loader import ItemLoader# 如上面所示,我們首先在items.py中定義了一個articleDetailItem類(繼承自scrapy.Item),用于保存我們抓取到的數據# 解析函數def parse_detail(self, response):# 需要實例化ItemLoader, 注意第一個參數必須是實例化的對象...atricleItemLoader = ItemLoader(item = articleDetailItem(), response=response)# 調用xpath選擇器,提起title信息atricleItemLoader.add_xpath('title', '//div[@class="entry-header"]/h1/text()')# 將提取好的數據load出來articleInfo = atricleItemLoader.load_item()# 輸出:articleInfo = {'title': ['在 Linux 中自動配置 IPv6 地址']}print(f"articleInfo = {articleInfo}")
  • 參數說明:重要的參數有兩個
    • 第一個參數:item對象, 傳遞進來的 Item是之前定義的,也可以是一個類似字典的對象。特別需要注意的是,傳遞的是一個實例,不是類名。……(當然不使用對象也可以,當不用對象進行實例化的時候,Item會自動使用ItemLoader.default_item_class 屬性中指定的Item 類在Item Loader constructor中實例化)
    • 第二個參數:response,指定用于提取數據的源數據。

3.2. ItemLoader填充數據的三種方法

  • 實例化ItemLoader對象之后,接下來,就要開始收集數值到ItemLoader了。ItemLoader提供了三個重要的方法將數據填充進來:
# 文件 boleSpider.py # 解析頁面函數def parse_detail(self, response):# 需要實例化ItemLoader, 注意第一個參數必須是實例化的對象...atricleItemLoader = ItemLoader(item = articleDetailItem(), response=response)# 調用xpath選擇器,提取title信息atricleItemLoader.add_xpath('title', '//div[@class="entry-header"]/h1/text()')# 調用css選擇器,提取praise_nums信息atricleItemLoader.add_css('praise_nums', '.vote-post-up h10::text')# 直接給字段賦值,尤其需要注意,不管賦值的數據是什么,都會自動轉換成list類型atricleItemLoader.add_value('url', response.url)# 將提取好的數據load出來articleInfo = atricleItemLoader.load_item()# 觀察一下,發現三種方式填充的數據,均為List類型'''輸出結果:articleInfo = {'praise_nums': ['2'],'title': ['100 倍價值的工程師'],'url': ['http://blog.jobbole.com/113710/']}'''print(f"articleInfo = {articleInfo}")
  • 使用說明:
    • 第一個參數:指定字段名,如title。
    • 第二個參數:指定對應的提取規則,或者傳值。
    • 前面調用add_xpath等只是將提取的數據收集起來。最終,當所有數據被收集起來之后,還需要調用 ItemLoader.load_item() 方法, 實際上填充并且返回了之前通過調用 add_xpath(),add_css(),and add_value() 所提取和收集到的數據。
    • 特別注意:默認情況下,這些字段填入的全部是list類型。就算是傳值,傳遞了一個url,但是結果依然是一個list。
    • 從boleSpider.py核心代碼來看,我們可以對每個字段進行配置,匹配映射,非常的清晰,大大方便了可配置性和可維護性。
  • 但是實際項目中,一個字段的提取一般不會是直接配置一個規則,還需要更進一步的處理。那如何添加其他處理方法呢?接著往下看…

3.3. ItemLoader填充數據面臨的問題。

  • 從上面的示例中,可以看到,存在兩個問題:
    • 第一,提取的數據,填充進去的對象都是List類型。而我們大部分的需求是要取第一個數值,取List中的第一個非空元素,那么如何實現取第一個呢?
    • 第二,在做item字段解析時,經常需要再進一步解析,過濾出我們想要的數值,例如用正則表達式將 $10 price中的數字10提取出來。那么又如何對字段加一些處理函數呢?

3.4. 輸入處理器input_processor和輸出處理器output_processor

  • 首先來改寫一下articleDetailItem的定義:
# items.py import datetime import scrapy# 定義一個時間處理轉換函數 # 將 '\r\n\r\n 2018/03/06 · ' 轉換成 datetime.date(2018, 3, 14) def date_convert(value):try:create_date = datetime.datetime.strptime(value, "%Y/%m/%d").date()except Exception as e:create_date = datetime.datetime.now().date()return create_date# 用于存儲解析文章的詳細信息 class articleDetailItem(scrapy.Item):# 標題title = scrapy.Field()# 文章創建時間create_date = scrapy.Field(# 轉換前是'create_date':'\r\n\r\n 2018/03/14 · '# 轉換后是'create_date': datetime.date(2018, 3, 14),input_processor = MapCompose(date_convert),output_processor = TakeFirst())# 文章鏈接地址url = scrapy.Field(# 轉換前是'url': ['http://blog.jobbole.com/113771/']# 轉換后是'url': 'http://blog.jobbole.com/113699/'output_processor = TakeFirst())# url經過md5映射后的值url_object_id = scrapy.Field()# 文章中圖片地址front_image_url = scrapy.Field()# 文件下載后本地保存的地址front_image_path = scrapy.Field()# 贊的個數praise_nums = scrapy.Field()# 評論數comment_nums = scrapy.Field()# 收藏數fav_nums = scrapy.Field()# 所有標簽tags = scrapy.Field()# 文章內容content = scrapy.Field()
  • 然后在 boleSpider.py 中提取數據:
# 文件boleSpider.py# 解析頁面函數def parse_detail(self, response):# 需要實例化ItemLoader, 注意第一個參數必須是實例化的對象...atricleItemLoader = ItemLoader(item = articleDetailItem(), response=response)# 調用xpath選擇器,提取title信息atricleItemLoader.add_xpath('title', '//div[@class="entry-header"]/h1/text()')# 調用xpath選擇器,提取create_date信息atricleItemLoader.add_xpath('create_date', "//p[@class='entry-meta-hide-on-mobile']/text()")# 調用css選擇器,提取praise_nums信息atricleItemLoader.add_css('praise_nums', '.vote-post-up h10::text')# 直接給字段賦值,尤其需要注意,不管賦值的數據是什么,都會自動轉換成list類型atricleItemLoader.add_value('url', response.url)# 將提取好的數據load出來articleInfo = atricleItemLoader.load_item()'''輸出結果:articleInfo = {'create_date': datetime.date(2018, 3, 14),'praise_nums': ['1'],'title': ['在 Linux 中自動配置 IPv6 地址'],'url': 'http://blog.jobbole.com/113771/'}'''print(f"articleInfo = {articleInfo}")
  • Field 字段事實上有兩個參數:
    • 第一個是輸入處理器(input_processor) ,當這個item,title這個字段的值傳過來時,可以在傳進來的值上面做一些預處理。
    • 第二個是輸出處理器(output_processor) , 當這個item,title這個字段被預處理完之后,輸出前最后的一步處理。
  • 總結一下,每個字段的數據的處理過程是:

    • 第一步, 通過 add_xpath(), add_css() 或者 add_value() 方法),提取到數據。
    • 第二步,將提取到的數據,傳遞到輸入處理器(input_processor)中進行處理,處理結果被收集起來,并且保存在ItemLoader內(但尚未分配給該Item)。
    • 第三步,最后調用輸出處理器(output_processor)來處理之前收集到的數據(這是最后一步對數據的處理)。然后再存入到Item中,輸出處理器的結果是被分配到Item的最終值。
    • 第四步,收集到所有的數據后, 調用ItemLoader.load_item() 方法來填充,并得到填充后的 Item 對象。
  • 需要注意的是:input_processor和output_processor都是可調用對象,調用時傳入需要被分析的數據, 處理后返回分析得到的值。因此你可以使用任意函數作為輸入、輸出處理器。唯一需注意的是它們必須接收一個(并且只是一個)迭代器性質的參數。

3.5. 處理原來的兩個問題

  • 再回到原來的問題,如何解決:

3.5.1. 如何取第一個?

# 文件items.pyimport scrapy# TakeFirst()是Scrapy提供的內置處理器,用于提取List中的第一個非空元素 class articleDetailItem(scrapy.Item):# 文章鏈接地址url = scrapy.Field(# 轉換前是'url': ['http://blog.jobbole.com/113771/']# 轉換后是'url': 'http://blog.jobbole.com/113699/'output_processor = TakeFirst())

3.3.2. 如何在字段上加一些處理函數?

# 文件items.py import datetime import scrapy# 定義一個時間處理轉換函數 # 將 '\r\n\r\n 2018/03/06 · ' 轉換成 datetime.date(2018, 3, 14) def date_convert(value):try:create_date = datetime.datetime.strptime(value, "%Y/%m/%d").date()except Exception as e:create_date = datetime.datetime.now().date()return create_date# 用于存儲解析文章的詳細信息 class articleDetailItem(scrapy.Item):# 文章創建時間create_date = scrapy.Field(# 轉換前是'create_date':'\r\n\r\n 2018/03/14 · '# 轉換后是'create_date': datetime.date(2018, 3, 14),input_processor = MapCompose(date_convert),output_processor = TakeFirst())

3.6. scrapy內置的處理器

  • 參考源碼: E:\Miniconda\Lib\site-packages\scrapy\loader\processors.py
  • 從上面的例子來看,我們可以自定義一下處理函數,作為輸入輸出處理器,但是Scrapy還提供了一些常用的處理器。如MapCompose(能把多個函數執行的結果按順序組合起來,產生最終的輸出,通常用于輸入處理器),TakeFirst(取第一個非空的元素)。

3.6.1. TakeFirst

  • 返回第一個非空(non-null/ non-empty)值,常用于單值字段的輸出處理器,無參數。
# 源碼 # class scrapy.loader.processors.TakeFirst class TakeFirst(object):def __call__(self, values):for value in values:if value is not None and value != '':return value # 單獨直接使用 from scrapy.loader.processors import TakeFirstproc = TakeFirst()# 接收對象是一個可迭代的對象,如list result = proc(['', 'one', 'two', 'three'])# 結果:result = one print(f"result = {result}")

3.6.2. Identity

  • 最簡單的處理器,不進行任何處理,直接返回原來的數據。無參數。
# 源碼 # class scrapy.loader.processors.Identity class Identity(object):def __call__(self, values):return values # 單獨直接使用 from scrapy.loader.processors import Identityproc = Identity()# 接收對象是一個可迭代的對象,如list result = proc(['', 'one', 'two', 'three'])# 結果:result = ['', 'one', 'two', 'three'] print(f"result = {result}")

3.6.3. Join

  • 返回用分隔符連接后的值。分隔符默認為空格。不接受Loader contexts。
  • 當使用默認分隔符的時候,這個處理器等同于如下這個:
u' '.join # 源碼 # class scrapy.loader.processors.Join(separator=u’ ‘) class Join(object):def __init__(self, separator=u' '):self.separator = separatordef __call__(self, values):return self.separator.join(values) # 單獨直接使用 from scrapy.loader.processors import Join# 如果不指定連接符,默認是使用空格連接 proc = Join(";")# 接收對象是一個可迭代的對象,如list result = proc(['', 'one', 'two', 'three'])# 結果:result = ;one;two;three print(f"result = {result}")

3.6.4. Compose

  • 用給定的多個函數的組合,來構造的處理器。list對象(注意不是指list中的元素),依次被傳遞到第一個函數,然后輸出,再傳遞到第二個函數,一個接著一個,直到最后一個函數返回整個處理器的輸出。
  • 默認情況下,當遇到None值(list中有None值)的時候停止處理。可以通過傳遞參數stop_on_none = False改變這種行為。
class Compose(object):def __init__(self, *functions, **default_loader_context):self.functions = functionsself.stop_on_none = default_loader_context.get('stop_on_none', True)self.default_loader_context = default_loader_contextdef __call__(self, value, loader_context=None):if loader_context:context = MergeDict(loader_context, self.default_loader_context)else:context = self.default_loader_contextwrapped_funcs = [wrap_loader_context(f, context) for f in self.functions]for func in wrapped_funcs:if value is None and self.stop_on_none:breakvalue = func(value)return value # 單獨直接使用 from scrapy.loader.processors import Compose# stop_on_none=True, 指定在遇到None時,不用中斷,還繼續處理 # lambda v: v[0], 指定取第一個元素 # str.upper , 大寫 proc = Compose(lambda v: v[0], str.upper, stop_on_none=True)# 接收對象是一個可迭代的對象,如list result = proc(['one', 'two', None, 'three'])# 結果:result = ONE print(f"result = {result}")
  • 每個函數可以選擇接收一個loader_context參數。

3.6.5. MapCompose

  • 與Compose處理器類似,區別在于各個函數結果在內部傳遞的方式(會涉及到list對象解包的步驟):
    • 輸入值是被迭代的處理的,List對象中的每一個元素被單獨傳入,第一個函數進行處理,然后處理的結果被連接起來形成一個新的迭代器,并被傳入第二個函數,以此類推,直到最后一個函數。最后一個函數的輸出被連接起來形成處理器的輸出。
    • 每個函數能返回一個值或者一個值列表,也能返回None(會被下一個函數所忽略)
    • 這個處理器提供了很方便的方式來組合多個處理單值的函數。因此它常用于輸入處理器,因為傳遞過來的是一個List對象。
# 源碼 # class scrapy.loader.processors.MapCompose(*functions, **default_loader_context) class MapCompose(object):def __init__(self, *functions, **default_loader_context):self.functions = functionsself.default_loader_context = default_loader_contextdef __call__(self, value, loader_context=None):values = arg_to_iter(value)if loader_context:context = MergeDict(loader_context, self.default_loader_context)else:context = self.default_loader_contextwrapped_funcs = [wrap_loader_context(f, context) for f in self.functions]for func in wrapped_funcs:next_values = []for v in values:next_values += arg_to_iter(func(v))values = next_valuesreturn values # 單獨直接使用from scrapy.loader.processors import MapComposedef add_firstStr(value):return value + "_firstAdd"def add_secondStr(value):return value + "_secondAdd"# stop_on_none=True, 指定在遇到None時,不用中斷,還繼續處理 # 依次處理每個list元素 proc = MapCompose(add_firstStr, add_secondStr, str.upper, stop_on_none=True)# 接收對象是一個可迭代的對象,如list result = proc(['one', 'two', 'three'])# 結果:result = ['ONE_FIRSTADD_SECONDADD', 'TWO_FIRSTADD_SECONDADD', 'THREE_FIRSTADD_SECONDADD'] print(f"result = {result}")
  • 與Compose處理器類似,它也能接受Loader context。

3.7. 重用和擴展ItemLoaders

3.7.1. 添加默認的處理機制

  • 從上面的信息來看,ItemLoaders是非常靈活的,但是假設有個需求,所有的字段,我們都要去取第一個,那么如果有300個字段,我們就要添加300次,每個都要寫,就會覺得很麻煩。那么有沒有辦法統一設置呢,答案是有的,如下:
    • 如果想要實現每個字段都只取第一個,那么可以定義一個自己的ItemLoader類:ArticleItemLoader(繼承自ItemLoader類)
    • 我們首先可以看一下原始的 ItemLoader 的定義:
# E:\Miniconda\Lib\site-packages\scrapy\loader\__init__.py class ItemLoader(object):default_item_class = Item# 可以看到是有默認的輸入/輸出處理器的,而且默認是什么都不做default_input_processor = Identity()default_output_processor = Identity()default_selector_class = Selector
  • 可以定義一個自己的ItemLoader類:ArticleItemLoader,繼承自ItemLoader類, 同時改寫(重寫)default_output_processor
# 文件items.py from scrapy.loader import ItemLoader# 需要繼承內置的ItemLoader類 class ArticleItemLoader(ItemLoader):# 自定義itemloader,默認的輸出處理器為取第一個非空元素default_output_processor = TakeFirst()
  • 然后在boleSpider中使用時,我們就不能再簡單的使用原有的ItemLoader,而是使用我們自己定義的 ArticleItemLoader 來填充數據:
# 文件boleSpider.py from ArticleSpider.items import articleDetailItem, ArticleItemLoader# 使用自定義的ArticleItemLoader實例化一個item_loader 對象 # 然后發現,結果都是從list中取出了一個值:說明我們的設置已經生效了。 item_loader = ArticleItemLoader(item = articleDetailItem(), response=response) item_loader.add_xpath('title', '//div[@class="entry-header"]/h1/text()')

3.7.2. 重寫,覆蓋默認的處理機制

  • 上面我們實現了所有字段都只取第一個的功能,但是如果有一些字段,我不需要取第一個,而是有其他的處理方式呢?
  • 那就需要重寫這個字段的輸出處理器(output_processor)。 下面的例子是,首先在輸入處理器中將 “評論” 這樣的字符過濾掉,然后將list中所有的元素用”,” 連接起來,成為一個字符串。
def removeCommentTags(value):# 去掉Tags中提取的評論字符if "評論" in value:return ""else:return value# Tags是一個list,我們需要用","將他們連接起來, 變成了字符串。 # 但是“評論”我們不需要。去掉。 如何去掉“評論”,在input_processor中,判斷value是否==“評論”,如果是,就去掉 class articleDetailItem(scrapy.Item):tags = scrapy.Field(# 去掉評論input_processor = MapCompose(removeCommentTags),# 將list中的元素,通過“,”連接起來output_processor = Join(","))
  • 而如果,有些字段我們不想做任何處理,也不想去取第一個元素,那么我們怎么做呢?
  • 因為,目前所有的字段都默認設置為去取第一個非空元素,所以,我們需要將這個處理去掉。這個地方尤其要引起重視,因為很容易遺忘自己有這個默認設置。處理方式如下:
def returnValue(value):return valueclass articleDetailItem(scrapy.Item):content = scrapy.Field(# 會覆蓋掉默認的default_outoutput_processor = MapCompose(returnValue)# 或者使用Identity# output_processor = Identity())

總結

以上是生活随笔為你收集整理的如何使用scrapy中的ItemLoader提取数据?的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。