add support for a nested loaders

This commit is contained in:
Daniel Collins 2015-08-29 12:04:00 -07:00 committed by Daniel Collins
parent 70fa3ae9a5
commit 1f95af3c07
2 changed files with 145 additions and 4 deletions

View File

@ -24,16 +24,46 @@ class ItemLoader(object):
default_output_processor = Identity()
default_selector_class = Selector
def __init__(self, item=None, selector=None, response=None, **context):
def __init__(self, item=None, selector=None, response=None, parent=None, **context):
if selector is None and response is not None:
selector = self.default_selector_class(response)
self.selector = selector
context.update(selector=selector, response=response)
if item is None:
item = self.default_item_class()
self.item = context['item'] = item
self.context = context
self._values = defaultdict(list)
self.parent = parent
self._local_item = context['item'] = item
self._local_values = defaultdict(list)
@property
def _values(self):
if self.parent is not None:
return self.parent._values
else:
return self._local_values
@property
def item(self):
if self.parent is not None:
return self.parent.item
else:
return self._local_item
def nested_loader(self, xpath=None, css=None):
if xpath is not None and css is not None:
raise ValueError("Cannot nest a loader with both a xpath selector and a css selector")
if xpath is not None:
selector = self.selector.xpath(xpath)
if css is not None:
selector = self.selector.css(css)
subloader = self.__class__(
item=self.item, selector=selector, parent=self
)
return subloader
def add_value(self, field_name, value, *processors, **kw):
value = self.get_value(value, *processors, **kw)
@ -84,6 +114,10 @@ class ItemLoader(object):
value = self.get_output_value(field_name)
if value is not None:
item[field_name] = value
# for loader in self._subloaders:
# loader.load_item()
return item
def get_output_value(self, field_name):
@ -168,5 +202,4 @@ class ItemLoader(object):
csss = arg_to_iter(csss)
return flatten([self.selector.css(css).extract() for css in csss])
XPathItemLoader = create_deprecated_class('XPathItemLoader', ItemLoader)

View File

@ -19,11 +19,24 @@ class TestItem(NameItem):
summary = Field()
class TestNestedItem(Item):
name = Field()
name_div = Field()
name_value = Field()
url = Field()
image = Field()
# test item loaders
class NameItemLoader(ItemLoader):
default_item_class = TestItem
class NestedItemLoader(ItemLoader):
default_item_class = TestNestedItem
class TestItemLoader(NameItemLoader):
name_in = MapCompose(lambda v: v.title())
@ -600,6 +613,101 @@ class SelectortemLoaderTest(unittest.TestCase):
self.assertEqual(l.get_output_value('url'), [u'scrapy.org'])
class SubselectorLoaderTest(unittest.TestCase):
response = HtmlResponse(url="", encoding='utf-8', body=b"""
<html>
<body>
<header>
<div id="id">marta</div>
<p>paragraph</p>
</header>
<footer class="footer">
<a href="http://www.scrapy.org">homepage</a>
<img src="/images/logo.png" width="244" height="65" alt="Scrapy">
</footer>
</body>
</html>
""")
def test_nested_xpath(self):
l = NestedItemLoader(response=self.response)
nl = l.nested_loader(xpath="//header")
nl.add_xpath('name', 'div/text()')
nl.add_css('name_div', '#id')
nl.add_value('name_value', nl.selector.xpath('div[@id = "id"]/text()').extract())
self.assertEqual(l.get_output_value('name'), [u'marta'])
self.assertEqual(l.get_output_value('name_div'), [u'<div id="id">marta</div>'])
self.assertEqual(l.get_output_value('name_value'), [u'marta'])
self.assertEqual(l.get_output_value('name'), nl.get_output_value('name'))
self.assertEqual(l.get_output_value('name_div'), nl.get_output_value('name_div'))
self.assertEqual(l.get_output_value('name_value'), nl.get_output_value('name_value'))
def test_nested_css(self):
l = NestedItemLoader(response=self.response)
nl = l.nested_loader(css="header")
nl.add_xpath('name', 'div/text()')
nl.add_css('name_div', '#id')
nl.add_value('name_value', nl.selector.xpath('div[@id = "id"]/text()').extract())
self.assertEqual(l.get_output_value('name'), [u'marta'])
self.assertEqual(l.get_output_value('name_div'), [u'<div id="id">marta</div>'])
self.assertEqual(l.get_output_value('name_value'), [u'marta'])
self.assertEqual(l.get_output_value('name'), nl.get_output_value('name'))
self.assertEqual(l.get_output_value('name_div'), nl.get_output_value('name_div'))
self.assertEqual(l.get_output_value('name_value'), nl.get_output_value('name_value'))
def test_nested_replace(self):
l = NestedItemLoader(response=self.response)
nl1 = l.nested_loader(xpath='//footer')
nl2 = nl1.nested_loader(xpath='a')
l.add_xpath('url', '//footer/a/@href')
self.assertEqual(l.get_output_value('url'), [u'http://www.scrapy.org'])
nl1.replace_xpath('url', 'img/@src')
self.assertEqual(l.get_output_value('url'), [u'/images/logo.png'])
nl2.replace_xpath('url', '@href')
self.assertEqual(l.get_output_value('url'), [u'http://www.scrapy.org'])
def test_nested_ordering(self):
l = NestedItemLoader(response=self.response)
nl1 = l.nested_loader(xpath='//footer')
nl2 = nl1.nested_loader(xpath='a')
nl1.add_xpath('url', 'img/@src')
l.add_xpath('url', '//footer/a/@href')
nl2.add_xpath('url', 'text()')
l.add_xpath('url', '//footer/a/@href')
self.assertEqual(l.get_output_value('url'), [
u'/images/logo.png',
u'http://www.scrapy.org',
u'homepage',
u'http://www.scrapy.org',
])
def test_nested_load_item(self):
l = NestedItemLoader(response=self.response)
nl1 = l.nested_loader(xpath='//footer')
nl2 = nl1.nested_loader(xpath='img')
l.add_xpath('name', '//header/div/text()')
nl1.add_xpath('url', 'a/@href')
nl2.add_xpath('image', '@src')
item = l.load_item()
assert item is l.item
assert item is nl1.item
assert item is nl2.item
self.assertEqual(item['name'], [u'marta'])
self.assertEqual(item['url'], [u'http://www.scrapy.org'])
self.assertEqual(item['image'], [u'/images/logo.png'])
class SelectJmesTestCase(unittest.TestCase):
test_list_equals = {
'simple': ('foo.bar', {"foo": {"bar": "baz"}}, "baz"),