Scrapy笔记05- Item详解

Item是保留构造数据的处所,Scrapy可以将解析成果以字典情势返回,但是Python中字典缺乏构造,在大型爬虫体系中很不便利。

Item供给了类字典的API,并且可以很便利的声明字段,很多Scrapy组件可以应用Item的其他信息。

定义Item

定义Item非常简略,只须要继承scrapy.Item类,并将所有字段都定义为scrapy.Field类型便可

1
2
3
4
5
6
7
import scrapy

class Product(scrapy.Item):
name = scrapy.Field()
price = scrapy.Field()
stock = scrapy.Field()
last_updated = scrapy.Field(serializer=str)

Item Fields

Field对象可用来对每一个字段指定元数据。例如上面last_updated的序列化函数指定为str,可任意指定元数据,不过每种元数据对不同的组件意义不一样。

Item应用示例

你会看到Item的应用跟Python中的字典API非常相似

创立Item

1
2
3
>>>product = Product(name="Desktop PC", price=1000)
>>>print product
Product(name="Desktop PC", price=1000)

获得值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
>>>product["name"]
Desktop PC
>>>product.get("name")
Desktop PC

>>>product["price"]
1000

>>>product["last_updated"]
Traceback (most recent call last):
...
KeyError: "last_updated"

>>>product.get("last_updated", "not set")
not set

>>>product["lala"] # getting unknown field
Traceback (most recent call last):
...
KeyError: "lala"

>>>product.get("lala", "unknown field")
"unknown field"

>>>"name" in product # is name field populated?
True

>>>"last_updated" in product # is last_updated populated?
False

>>>"last_updated" in product.fields # is last_updated a declared field?
True

>>>"lala" in product.fields # is lala a declared field?
False

设置值

1
2
3
4
5
6
7
8
>>>product["last_updated"] = "today"
>>>product["last_updated"]
today

>>>product["lala"] = "test" # setting unknown field
Traceback (most recent call last):
...
KeyError: "Product does not support field: lala"

拜访所有的值

1
2
3
4
5
>>>product.keys()
["price", "name"]

>>>product.items()
[("price", 1000), ("name", "Desktop PC")]

Item Loader

Item Loader为我们供给了生成Item的相当便利的办法。Item为抓取的数据供给了容器,而Item Loader可让我们非常便利的将输入填充到容器中。

下面我们通过一个例子来展现一般应用办法:

1
2
3
4
5
6
7
8
9
10
11
from scrapy.loader import ItemLoader
from myproject.items import Product

def parse(self, response):
l = ItemLoader(item=Product(), response=response)
l.add_xpath("name", "//div[@class="product_name"]")
l.add_xpath("name", "//div[@class="product_title"]")
l.add_xpath("price", "//p[@id="price"]")
l.add_css("stock", "p#stock]")
l.add_value("last_updated", "today") # you can also use literal values
return l.load_item()

注意上面的name字段是从两个xpath路径添累加后得到。

输入/输来路置器

每一个Item Loader对每一个Field都有一个输入处置器和一个输来路置器。输入处置器在数据被接收到时履行,当数据搜集完后调用ItemLoader.load_item()时再履行输来路置器,返回终究成果。

1
2
3
4
5
6
l = ItemLoader(Product(), some_selector)
l.add_xpath("name", xpath1) # (1)
l.add_xpath("name", xpath2) # (2)
l.add_css("name", css) # (3)
l.add_value("name", "test") # (4)
return l.load_item() # (5)

履行流程是这样的:

  1. xpath1中的数据被提取出来,然后传输到name字段的输入处置器中,在输入处置器处置完后生成成果放在Item Loader里面(这时候候没有赋值给item)
  2. xpath2数据被提取出来,然后传输给(1)中一样的输入处置器,由于它们都是name字段的处置器,然后处置成果被附加到(1)的成果后面
  3. 跟2一样
  4. 跟3一样,不过这次是直接的字面字符串值,先转换成一个单元素的可迭代对象再传给输入处置器
  5. 上面4步的数据被传输给name的输来路置器,将终究的成果赋值给name字段

自定义Item Loader

应用类定义语法,下面是一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
from scrapy.loader import ItemLoader
from scrapy.loader.processors import TakeFirst, MapCompose, Join

class ProductLoader(ItemLoader):

default_output_processor = TakeFirst()

name_in = MapCompose(unicode.title)
name_out = Join()

price_in = MapCompose(unicode.strip)

# ...

通过_in_out后缀来定义输入和输来路置器,并且还可以定义默许的ItemLoader.default_input_processorItemLoader.default_input_processor.

在Field定义中声明输入/输来路置器

还有个处所可以非常便利的添加输入/输来路置器,那就是直接在Field定义中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import scrapy
from scrapy.loader.processors import Join, MapCompose, TakeFirst
from w3lib.html import remove_tags

def filter_price(value):
if value.isdigit():
return value

class Product(scrapy.Item):
name = scrapy.Field(
input_processor=MapCompose(remove_tags),
output_processor=Join(),
)
price = scrapy.Field(
input_processor=MapCompose(remove_tags, filter_price),
output_processor=TakeFirst(),
)

优先级:

  1. 在Item Loader中定义的field_infield_out
  2. Filed元数据(input_processoroutput_processor症结字)
  3. Item Loader中的默许的

Tips:一般来说,将输入处置器定义在Item Loader的定义中field_in,然后将输来路置器定义在Field元数据中

Item Loader高低文

Item Loader高低文被所有输入/输来路置器同享,比如你有一个解析长度的函数

1
2
3
4
def parse_length(text, loader_context):
unit = loader_context.get("unit", "m")
# ... length parsing code goes here ...
return parsed_length

初始化和修正高低文的值

1
2
3
4
5
6
7
loader = ItemLoader(product)
loader.context["unit"] = "cm"

loader = ItemLoader(product, unit="cm")

class ProductLoader(ItemLoader):
length_out = MapCompose(parse_length, unit="cm")

内置的处置器

  1. Identity 啥也不做
  2. TakeFirst 返回第一个非空值,通常常使用作输来路置器
  3. Join 将成果连起来,默许应用空格’ ‘
  4. Compose 将函数连接起来构成管道流,发生最后的输出
  5. MapCompose 跟上面的Compose相似,区分在于内部成果在函数中的传递方法.它的输入值是可迭代的,首先将第一个函数顺次作用于所有值,发生新的可迭代输入,作为第二个函数的输入,最后生成的成果连起来返回终究值,一般用在输入处置器中。
  6. SelectJmes 应用json路径来查询值并返回成果