问题背景
在使用 feapder 爬虫框架时,我们经常需要将爬取的数据封装到 Item 对象中。最近在处理一个企业数据爬虫时,遇到了一个有趣的问题:
python
from feapder import UpdateItem
class SpEnterpriseBusinessItem(UpdateItem):
"""企业业务信息Item"""
__table_name__ = "sp_enterprise_business"
__unique_key__ = ["unique_key"] # 设置唯一索引,用于更新数据
def __init__(self, *args, **kwargs):
self.company_name = None # 公司名称
self.product_name = None # 产品名称
self.unique_key = None # 唯一标识(MD5)
# ... 其他字段 ...
# 使用时
spider_item = SpEnterpriseBusinessItem()
spider_item.company_name = "阿里巴巴"
spider_item.product_name = "淘宝"
# 下面两种赋值方式结果不同
spider_item.unique_key = tools.get_md5(company_name, product_name) # ❌ 无法正确赋值
spider_item['unique_key'] = tools.get_md5(company_name, product_name) # ✅ 正确的赋值方式
看似简单的属性赋值,却有着完全不同的结果。为什么会这样?让我们深入源码一探究竟。
深入分析
1. Item类的实现
feapder 的 Item 类实现了类似字典的访问方式,核心代码如下:
python
class Item(metaclass=ItemMetaclass):
__unique_key__ = []
def __init__(self, **kwargs):
self.__dict__ = kwargs
def __getitem__(self, key):
return self.__dict__[key]
def __setitem__(self, key, value):
self.__dict__[key] = value
@property
def unique_key(self):
return self.__unique_key__ or self.__class__.__unique_key__
@unique_key.setter
def unique_key(self, keys):
if isinstance(keys, (tuple, list)):
self.__unique_key__ = keys
else:
self.__unique_key__ = (keys,)
2. 两种赋值方式的区别
方式一:属性赋值(spider_item.unique_key = value)
- 使用点号(.)访问时,Python会调用属性的setter方法
unique_key
是一个property属性,其setter方法会将值转换为元组或列表- 这个setter是为了支持框架的去重功能设计的,而不是为了存储MD5值
方式二:字典赋值(spider_item['unique_key'] = value)
- 使用中括号[]访问时,会调用
__setitem__
方法 __setitem__
直接将值存储在对象的__dict__
中- 不会触发property的setter方法,因此值会按原样保存
最佳实践
1. 正确的赋值方式
python
# 爬虫代码示例
def parse(self, request, response):
spider_item = SpEnterpriseBusinessItem()
spider_item.company_name = company_name
spider_item.product_name = product_name
# 使用字典方式赋值unique_key
spider_item['unique_key'] = tools.get_md5(company_name, product_name)
yield spider_item
2. 如果一定要使用属性赋值
如果你更倾向于使用统一的属性赋值方式,可以这样修改Item类:
python
class SpEnterpriseBusinessItem(UpdateItem):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._unique_key = None # 添加一个普通属性
@property
def unique_key(self):
return self._unique_key
@unique_key.setter
def unique_key(self, value):
self._unique_key = value
设计思考
-
为什么会有这样的设计?
- feapder框架中的
unique_key
属性主要用于数据去重 - 框架需要将去重键存储为元组或列表格式,以支持多字段联合去重
- 这就导致了property的setter会进行类型转换
- feapder框架中的
-
最佳实践建议
- 对于MD5这样的唯一标识字段,使用字典方式赋值
- 对于普通字段,可以使用属性方式赋值
- 在项目中保持一致的赋值风格,提高代码可维护性
总结
这个看似简单的问题实际上涉及到了Python的属性访问机制和框架设计的权衡。理解这个问题不仅帮助我们正确使用feapder框架,也加深了对Python语言特性的理解。在实际开发中,我们要注意:
- 理解框架的设计意图
- 区分不同场景下的最佳实践
- 在团队中统一编码规范
- 适当的注释说明,避免其他开发者踩坑
希望这篇文章能帮助你更好地理解和使用feapder框架!