mirror of https://github.com/scrapy/scrapy.git
Added support for variants when applied to tag attributes. fixed handling of variants with single attribute. Removed unneeded object attribute surrounds_variant. Added new tests cases for fixes.
This commit is contained in:
parent
a5a6c2ae31
commit
41a85f9d14
|
|
@ -194,18 +194,17 @@ class AnnotationTag(object):
|
|||
"""
|
||||
__slots__ = ('surrounds_attribute', 'start_index', 'end_index',
|
||||
'tag_attributes', 'annotation_text', 'variant_id',
|
||||
'surrounds_variant','match_common_prefix', 'metadata')
|
||||
'match_common_prefix', 'metadata')
|
||||
|
||||
def __init__(self, start_index, end_index, surrounds_attribute=None,
|
||||
annotation_text=None, tag_attributes=None, variant_id=None,
|
||||
surrounds_variant=None, match_common_prefix=False):
|
||||
match_common_prefix=False):
|
||||
self.start_index = start_index
|
||||
self.end_index = end_index
|
||||
self.surrounds_attribute = surrounds_attribute
|
||||
self.annotation_text = annotation_text
|
||||
self.tag_attributes = tag_attributes or []
|
||||
self.variant_id = variant_id
|
||||
self.surrounds_variant = surrounds_variant
|
||||
self.match_common_prefix = match_common_prefix
|
||||
self.metadata = {}
|
||||
|
||||
|
|
|
|||
|
|
@ -201,29 +201,32 @@ class TemplatePageParser(InstanceLearningParser):
|
|||
|
||||
self.extra_required_attrs.extend(jannotation.pop('required', []))
|
||||
|
||||
variant_id = jannotation.pop('variant', 0)
|
||||
if variant_id > 0:
|
||||
self.variant_stack.append(variant_id)
|
||||
annotation.surrounds_variant = variant_id
|
||||
attribute_annotations = jannotation.pop('annotations', {}).items()
|
||||
for extract_attribute, tag_value in attribute_annotations:
|
||||
if extract_attribute == 'content':
|
||||
annotation.surrounds_attribute = tag_value
|
||||
else:
|
||||
annotation.tag_attributes.append((extract_attribute, tag_value))
|
||||
|
||||
|
||||
variant_id = jannotation.pop('variant', 0)
|
||||
if variant_id > 0:
|
||||
if annotation.surrounds_attribute is not None:
|
||||
self.variant_stack.append(variant_id)
|
||||
else:
|
||||
annotation.variant_id = variant_id
|
||||
|
||||
annotation.metadata = jannotation
|
||||
|
||||
if annotation.annotation_text is None:
|
||||
self.next_tag_index += 1
|
||||
if self.variant_stack:
|
||||
if self.variant_stack and annotation.variant_id is None:
|
||||
variant_id = self.variant_stack[-1]
|
||||
if variant_id == '0':
|
||||
variant_id = None
|
||||
annotation.variant_id = variant_id
|
||||
|
||||
# look for a closing tag if the content is important
|
||||
if annotation.surrounds_attribute or annotation.surrounds_variant:
|
||||
if annotation.surrounds_attribute:
|
||||
self.labelled_tag_stacks[html_tag.tag].append(annotation)
|
||||
else:
|
||||
annotation.end_index = annotation.start_index + 1
|
||||
|
|
@ -272,9 +275,9 @@ class TemplatePageParser(InstanceLearningParser):
|
|||
self.next_tag_index += 1
|
||||
if len(labelled_tags) == 0:
|
||||
del self.labelled_tag_stacks[html_tag.tag]
|
||||
if annotation.surrounds_variant and self.variant_stack:
|
||||
if annotation.variant_id and self.variant_stack:
|
||||
prev = self.variant_stack.pop()
|
||||
if prev != annotation.surrounds_variant:
|
||||
if prev != annotation.variant_id:
|
||||
raise ValueError("unbalanced variant annotation tags")
|
||||
|
||||
def handle_data(self, html_data_fragment):
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ def build_extraction_tree(template, type_descriptor, trace=True):
|
|||
extractors = BasicTypeExtractor.create(template.annotations, attribute_map)
|
||||
if trace:
|
||||
extractors = TraceExtractor.apply(template, extractors)
|
||||
for cls in (RepeatedDataExtractor, AdjacentVariantExtractor, RepeatedDataExtractor,
|
||||
for cls in (AdjacentVariantExtractor, RepeatedDataExtractor, AdjacentVariantExtractor, RepeatedDataExtractor,
|
||||
RecordExtractor):
|
||||
extractors = cls.apply(template, extractors)
|
||||
if trace:
|
||||
|
|
@ -465,9 +465,8 @@ class AdjacentVariantExtractor(RecordExtractor):
|
|||
continue
|
||||
if vid in adjacent_variants:
|
||||
adjacent_variants.remove(vid)
|
||||
elif len(list(egroup)) > 1:
|
||||
else:
|
||||
adjacent_variants.add(vid)
|
||||
|
||||
new_extractors = []
|
||||
for variant, group_seq in groupby(extractors, variantf):
|
||||
group_seq = list(group_seq)
|
||||
|
|
|
|||
|
|
@ -633,6 +633,121 @@ EXTRACT_PAGE20 = u"""
|
|||
</body></html>
|
||||
"""
|
||||
|
||||
ANNOTATED_PAGE21 = u"""
|
||||
<html><body>
|
||||
<img src="image.jpg" data-scrapy-annotate="{"required": [], "variant": 0, "annotations": {"src": "image_urls"}}">
|
||||
<p>
|
||||
<table>
|
||||
|
||||
<tr><td><img src="swatch1.jpg" data-scrapy-annotate="{"required": [], "variant": 1, "annotations": {"src": "swatches"}}"></td></tr>
|
||||
|
||||
<tr><td><img src="swatch2.jpg"></td></tr>
|
||||
|
||||
<tr><td><img src="swatch3.jpg"></td></tr>
|
||||
|
||||
<tr><td><img src="swatch4.jpg" data-scrapy-annotate="{"required": [], "variant": 2, "annotations": {"src": "swatches"}}"></td></tr>
|
||||
|
||||
</table>
|
||||
|
||||
<div data-scrapy-annotate="{"required": [], "variant": 0, "annotations": {"content": "category"}}">tables</div>
|
||||
|
||||
</body></html>
|
||||
"""
|
||||
|
||||
EXTRACT_PAGE21 = u"""
|
||||
<html><body>
|
||||
<img src="image.jpg">
|
||||
<p>
|
||||
<table>
|
||||
|
||||
<tr><td><img src="swatch1.jpg"></td></tr>
|
||||
|
||||
<tr><td><img src="swatch2.jpg"></td></tr>
|
||||
|
||||
<tr><td><img src="swatch3.jpg"></td></tr>
|
||||
|
||||
<tr><td><img src="swatch4.jpg"></td></tr>
|
||||
|
||||
</table>
|
||||
|
||||
<div>chairs</div>
|
||||
</body></html>
|
||||
"""
|
||||
|
||||
ANNOTATED_PAGE22 = u"""
|
||||
<html><body>
|
||||
<img src="image.jpg" data-scrapy-annotate="{"required": [], "variant": 0, "annotations": {"src": "image_urls"}}">
|
||||
<p>
|
||||
<table>
|
||||
|
||||
<tr><td>
|
||||
<p data-scrapy-annotate="{"required": [], "variant": 1, "annotations": {"content": "name"}}">product 1</p>
|
||||
<b data-scrapy-annotate="{"required": [], "variant": 1, "annotations": {"content": "price"}}">$67</b>
|
||||
<img src="swatch1.jpg" data-scrapy-annotate="{"required": [], "variant": 1, "annotations": {"src": "swatches"}}">
|
||||
</td></tr>
|
||||
|
||||
<tr><td>
|
||||
<p>product 2</p>
|
||||
<b>$70</b>
|
||||
<img src="swatch2.jpg">
|
||||
</td></tr>
|
||||
|
||||
<tr><td>
|
||||
<p>product 3</p>
|
||||
<b>$73</b>
|
||||
<img src="swatch3.jpg">
|
||||
</td></tr>
|
||||
|
||||
<tr><td>
|
||||
<p data-scrapy-annotate="{"required": [], "variant": 2, "annotations": {"content": "name"}}">product 4</p>
|
||||
<b data-scrapy-annotate="{"required": [], "variant": 2, "annotations": {"content": "price"}}">$80</b>
|
||||
<img src="swatch4.jpg" data-scrapy-annotate="{"required": [], "variant": 2, "annotations": {"src": "swatches"}}">
|
||||
</td></tr>
|
||||
|
||||
</table>
|
||||
|
||||
<div data-scrapy-annotate="{"required": [], "variant": 0, "annotations": {"content": "category"}}">tables</div>
|
||||
|
||||
</body></html>
|
||||
"""
|
||||
|
||||
EXTRACT_PAGE22 = u"""
|
||||
<html><body>
|
||||
<img src="image.jpg">
|
||||
<p>
|
||||
<table>
|
||||
|
||||
<tr><td>
|
||||
<p>product 1</p>
|
||||
<b>$70</b>
|
||||
<img src="swatch1.jpg">
|
||||
</td></tr>
|
||||
|
||||
<tr><td>
|
||||
<p>product 2</p>
|
||||
<b>$80</b>
|
||||
<img src="swatch2.jpg">
|
||||
</td></tr>
|
||||
|
||||
<tr><td>
|
||||
<p>product 3</p>
|
||||
<b>$90</b>
|
||||
<img src="swatch3.jpg">
|
||||
</td></tr>
|
||||
|
||||
<tr><td>
|
||||
<p>product 4</p>
|
||||
<b>$100</b>
|
||||
<img src="swatch4.jpg">
|
||||
</td></tr>
|
||||
|
||||
</table>
|
||||
|
||||
<div>chairs</div>
|
||||
</body></html>
|
||||
"""
|
||||
|
||||
|
||||
SAMPLE_DESCRIPTOR1 = ItemDescriptor('test', 'product test', [
|
||||
A('name', "Product name", required=True),
|
||||
A('price', "Product price, including any discounts and tax or vat",
|
||||
|
|
@ -813,6 +928,35 @@ TEST_DATA = [
|
|||
{'price': ['330'], 'name': ['Queen']},
|
||||
]},
|
||||
),
|
||||
('variants with swatches', [ANNOTATED_PAGE21], EXTRACT_PAGE21, None,
|
||||
{u'category': [u'chairs'],
|
||||
u'image_urls': [u'image.jpg'],
|
||||
u'variants': [
|
||||
{'swatches': ['swatch1.jpg']},
|
||||
{'swatches': ['swatch2.jpg']},
|
||||
{'swatches': ['swatch3.jpg']},
|
||||
{'swatches': ['swatch4.jpg']},
|
||||
]
|
||||
},
|
||||
),
|
||||
('variants with swatches complete', [ANNOTATED_PAGE22], EXTRACT_PAGE22, None,
|
||||
{u'category': [u'chairs'],
|
||||
u'variants': [
|
||||
{u'swatches': [u'swatch1.jpg'],
|
||||
u'price': [u'$70'],
|
||||
u'name': [u'product 1']},
|
||||
{u'swatches': [u'swatch2.jpg'],\
|
||||
u'price': [u'$80'],
|
||||
u'name': [u'product 2']},
|
||||
{u'swatches': [u'swatch3.jpg'],
|
||||
u'price': [u'$90'],
|
||||
u'name': [u'product 3']},
|
||||
{u'swatches': [u'swatch4.jpg'],
|
||||
u'price': [u'$100'],
|
||||
u'name': [u'product 4']}
|
||||
],
|
||||
u'image_urls': [u'image.jpg']},
|
||||
),
|
||||
]
|
||||
|
||||
class TestExtraction(TestCase):
|
||||
|
|
|
|||
|
|
@ -155,6 +155,32 @@ Description
|
|||
</body></html>
|
||||
"""
|
||||
|
||||
LABELLED_PAGE9 = u"""
|
||||
<html><body>
|
||||
<img src="image.jpg" data-scrapy-annotate="{"required": [], "variant": 0, "annotations": {"src": "image_urls"}}">
|
||||
<p data-scrapy-annotate="{"required": [], "variant": 1, "annotations": {"content": "name"}}">product 1</p>
|
||||
<b data-scrapy-annotate="{"required": [], "variant": 1, "annotations": {"content": "price"}}">$67</b>
|
||||
<p data-scrapy-annotate="{"required": [], "variant": 2, "annotations": {"content": "name"}}">product 2</p>
|
||||
<b data-scrapy-annotate="{"required": [], "variant": 2, "annotations": {"content": "price"}}">$70</b>
|
||||
<div data-scrapy-annotate="{"required": [], "variant": 0, "annotations": {"content": "category"}}">tables</div>
|
||||
</body></html>
|
||||
"""
|
||||
|
||||
LABELLED_PAGE10 = u"""
|
||||
<html><body>
|
||||
<img src="image.jpg" data-scrapy-annotate="{"required": [], "variant": 0, "annotations": {"src": "image_urls"}}">
|
||||
<p data-scrapy-annotate="{"required": [], "variant": 1, "annotations": {"content": "name"}}">product 1</p>
|
||||
<b data-scrapy-annotate="{"required": [], "variant": 1, "annotations": {"content": "price"}}">$67</b>
|
||||
<img src="swatch1.jpg" data-scrapy-annotate="{"required": [], "variant": 1, "annotations": {"src": "swatches"}}">
|
||||
|
||||
<p data-scrapy-annotate="{"required": [], "variant": 2, "annotations": {"content": "name"}}">product 2</p>
|
||||
<b data-scrapy-annotate="{"required": [], "variant": 2, "annotations": {"content": "price"}}">$70</b>
|
||||
<img src="swatch2.jpg" data-scrapy-annotate="{"required": [], "variant": 2, "annotations": {"src": "swatches"}}">
|
||||
|
||||
<div data-scrapy-annotate="{"required": [], "variant": 0, "annotations": {"content": "category"}}">tables</div>
|
||||
</body></html>
|
||||
"""
|
||||
|
||||
def _parse_page(parser_class, pagetext):
|
||||
htmlpage = HtmlPage(None, {}, pagetext)
|
||||
parser = parser_class(TokenDict())
|
||||
|
|
@ -268,7 +294,29 @@ class TestPageParsing(TestCase):
|
|||
"""Test parsing of extra required attributes"""
|
||||
p = _parse_page(TemplatePageParser, LABELLED_PAGE8)
|
||||
self.assertEqual(p.extra_required_attrs, ["description"])
|
||||
|
||||
|
||||
def test_variants(self):
|
||||
"""Test parsing of variant annotations"""
|
||||
annotations = _parse_page(TemplatePageParser, LABELLED_PAGE9).annotations
|
||||
self.assertEqual(annotations[0].variant_id, None)
|
||||
self.assertEqual(annotations[1].variant_id, 1)
|
||||
self.assertEqual(annotations[2].variant_id, 1)
|
||||
self.assertEqual(annotations[3].variant_id, 2)
|
||||
self.assertEqual(annotations[4].variant_id, 2)
|
||||
self.assertEqual(annotations[5].variant_id, None)
|
||||
|
||||
def test_variants_in_attributes(self):
|
||||
"""Test parsing of variant annotations in attributes"""
|
||||
annotations = _parse_page(TemplatePageParser, LABELLED_PAGE10).annotations
|
||||
self.assertEqual(annotations[0].variant_id, None)
|
||||
self.assertEqual(annotations[1].variant_id, 1)
|
||||
self.assertEqual(annotations[2].variant_id, 1)
|
||||
self.assertEqual(annotations[3].variant_id, 1)
|
||||
self.assertEqual(annotations[4].variant_id, 2)
|
||||
self.assertEqual(annotations[5].variant_id, 2)
|
||||
self.assertEqual(annotations[6].variant_id, 2)
|
||||
self.assertEqual(annotations[7].variant_id, None)
|
||||
|
||||
def test_site_pages(self):
|
||||
"""
|
||||
Tests from real pages. More reliable and easy to build for more complicated structures
|
||||
|
|
|
|||
Loading…
Reference in New Issue