如何在Scrapy XML Exporter中序列化列表类型的Item字段
我之前刚好碰到过这个问题,Scrapy默认的XmlItemExporter确实会把嵌套的Item用<value>标签包裹,要替换成对应Item的名称,核心就是重写它的serialize_field方法,同时处理好多值字段里的嵌套Item场景。下面给你完整的实现方案:
自定义XmlItemExporter实现子Item标签替换
步骤1:编写自定义导出器类
我们需要继承XmlItemExporter,重写serialize_field方法,针对Item类型(包括单个Item和Item列表)做特殊处理,替换默认的<value>标签为对应Item的名称:
from scrapy.exporters import XmlItemExporter from scrapy.item import Item class CustomXmlItemExporter(XmlItemExporter): # 可选:自定义Item类名到标签名的映射,让标签更简洁 ITEM_TAG_MAP = { 'ReadingAssignment': 'reading', 'Lesson': 'lesson' } def serialize_field(self, field, name, value): # 先处理多值字段中的Item列表 if isinstance(value, list) and all(isinstance(item, Item) for item in value): for item in value: # 获取自定义标签名,没有映射则用类名小写 item_tag = self.ITEM_TAG_MAP.get(item.__class__.__name__, item.__class__.__name__.lower()) self._beautify_newline() self._write_start_tag(item_tag, self._get_item_attrs(item)) # 递归处理Item的子字段 for sub_field_name, sub_field in item.fields.items(): sub_value = item.get(sub_field_name) self.serialize_field(sub_field, sub_field_name, sub_value) self._beautify_newline() self._write_end_tag(item_tag) return # 处理单个嵌套Item的情况(比如Lesson里的assignment字段) elif isinstance(value, Item): item_tag = self.ITEM_TAG_MAP.get(value.__class__.__name__, value.__class__.__name__.lower()) self._beautify_newline() self._write_start_tag(item_tag, self._get_item_attrs(value)) for sub_field_name, sub_field in value.fields.items(): sub_value = value.get(sub_field_name) self.serialize_field(sub_field, sub_field_name, sub_value) self._beautify_newline() self._write_end_tag(item_tag) return # 其他字段(普通单值、非Item列表)调用父类默认处理 super().serialize_field(field, name, value) def _get_item_attrs(self, item): # 可选:给Item标签添加属性,这里默认返回空字典 return {}
步骤2:在Pipeline中使用自定义导出器
在你的项目pipelines.py里,替换默认的XmlItemExporter为我们自定义的类:
from scrapy.exceptions import DropItem from your_project.exporters import CustomXmlItemExporter # 替换成你的项目路径 class CustomXmlExportPipeline: def __init__(self): self.file = None self.exporter = None def open_spider(self, spider): self.file = open('courses_export.xml', 'wb') # 初始化自定义导出器,设置根标签和Item标签 self.exporter = CustomXmlItemExporter( self.file, root_element='courses', item_element='course' ) self.exporter.start_exporting() def close_spider(self, spider): self.exporter.finish_exporting() self.file.close() def process_item(self, item, spider): self.exporter.export_item(item) return item
记得在settings.py中启用这个Pipeline:
ITEM_PIPELINES = { 'your_project.pipelines.CustomXmlExportPipeline': 300, }
步骤3:测试效果
假设你的测试数据是:
reading = ReadingAssignment(textBook="Python Basics", pages="1-20") lesson = Lesson(session="Session 1", topic="Introduction", assignment=reading) course = Course(title="Python 101", lessons=[lesson])
默认导出器会生成这样的XML:
<courses> <course> <title>Python 101</title> <lessons> <value> <session>Session 1</session> <topic>Introduction</topic> <assignment> <value> <textBook>Python Basics</textBook> <pages>1-20</pages> </value> </assignment> </value> </lessons> </course> </courses>
使用自定义导出器后,会得到你期望的结果:
<courses> <course> <title>Python 101</title> <lessons> <lesson> <session>Session 1</session> <topic>Introduction</topic> <assignment> <reading> <textBook>Python Basics</textBook> <pages>1-20</pages> </reading> </assignment> </lesson> </lessons> </course> </courses>
关键说明
- 核心逻辑是在
serialize_field中优先判断值是否为Item/Item列表,跳过默认的<value>标签生成逻辑,直接渲染对应Item名称的标签。 - 通过
ITEM_TAG_MAP可以灵活自定义标签名称,不需要完全依赖类名的小写形式。 - 递归处理嵌套Item,确保多层嵌套的子Item都能正确替换标签。
内容的提问来源于stack exchange,提问作者jox58




