在Scrapy中向导出的XML添加属性

时间:2017-09-26 11:59:34

标签: python xml scrapy

我能够从网站上删除数据,但我需要以XML格式导出数据。

为此,我定义了一个类似的序列化器:

class Person(scrapy.Item):
    Name = scrapy.Field(serializer=serialize_name)
    Location = scrapy.Field()

和XMLExportPipeline一样:

class XmlExportPipeline(object):

def __init__(self):
    self.files = {}

@classmethod
def from_crawler(cls, crawler):
    pipeline = cls()
    crawler.signals.connect(pipeline.spider_opened, signals.spider_opened)
    crawler.signals.connect(pipeline.spider_closed, signals.spider_closed)
    return pipeline


def spider_opened(self, spider):
    file = open('%s_people.xml' % spider.name, 'w+b')
    self.files[spider] = file
    self.exporter = XmlItemExporter(file, item_element='Person', root_element='People')
    self.exporter.start_exporting()


def spider_closed(self, spider):
    ...


def process_item(self, person, spider):
    self.exporter.export_item(person)
    return person

这可以工作,并给我一个像这样的XML文件:

<?xml version="1.0" encoding="utf-8"?>
<People><Person><Name>Bob</Name><Location>NYC</Location></Person></People>

如何向标签添加属性?例如,如果我想要

<Person Age="25" Likes="Programming">

我该怎么做呢?

同样快速跟进,为什么输出XML没有格式化like it is supposed to be?我可以将标签中的值转换为CDATA(使用自定义序列化程序来执行此操作)吗?

2 个答案:

答案 0 :(得分:3)

由于此行(XmlItemExporter),scrapy/exporters.py:173的默认实现不允许这样做:

self.xg.startElement(name, {})

第二个参数应该包含每个新元素的属性。因此,解决方法是实现自己的XmlItemExporter子类,添加此参数。

from scrapy.exporters import six, is_listlike, XmlItemExporter

class AttrXmlItemExporter(XmlItemExporter):

    def _export_xml_field(self, name, serialized_value, depth):
        # Custom code:
        attrs = {}
        if isinstance(serialized_value, dict):
            serialized_value = serialized_value.copy()
            attr_keys = [k for k in serialized_value.keys() if k.startswith('_')]
            attrs = {k[1:]: serialized_value.pop(k) for k in attr_keys}

        # Default implementation (except for startElement call)
        self._beautify_indent(depth=depth)
        self.xg.startElement(name, attrs)
        if hasattr(serialized_value, 'items'):
            self._beautify_newline()
            for subname, value in serialized_value.items():
                self._export_xml_field(subname, value, depth=depth + 1)
            self._beautify_indent(depth=depth)
        elif is_listlike(serialized_value):
            self._beautify_newline()
            for value in serialized_value:
                self._export_xml_field('value', value, depth=depth + 1)
            self._beautify_indent(depth=depth)
        elif isinstance(serialized_value, six.text_type):
            self._xg_characters(serialized_value)
        else:
            self._xg_characters(str(serialized_value))
        self.xg.endElement(name)
        self._beautify_newline()

在此示例中,任何项目值(即dict以下划线(_)开头的dict(即子字典)将呈现为属性。

例如,项目:

yield {
    'name': 'Sample',
    'rating': {
        '_rating': '4.5',
        '_max': '5',
    },
}

将呈现为XML:

<item>
  <nam>Sample</name>
  <rating rating="4.5" max="5">
  </rating>
</item>

但是还没有想出办法让它成为一个自闭元素。请注意,标记为属性的所有值必须为字符串。

答案 1 :(得分:0)

关于您的后续问题之一:当您使用自定义XMLItemExporter时,我相信FEED_EXPORT_INDENT设置会被初始化XMLItemExporter时的kwarg覆盖。如果不传递任何内容,则缩进为“无”。创建indent=<number>时,您需要传递kwarg。您可以在原始代码中为根元素和项元素加上侧面kwarg来做到这一点,或者,如果您使用已接受答案中的代码,只需用一次调用这些kwarg来覆盖__init__即可。

关于自动关闭标签的要点:看来解决方案是另一个kwarg short_empty_elements,这次是签名xml.sax.saxutils.XMLGenerator(out=None, encoding='iso-8859-1', short_empty_elements=False)的XMLGenerator。我尚未使用此功能,但文档here看起来很简单。