目前 fusion-stat 模型总共分为 Competitions, Competition, Team, Staff, Player, Matches, Match, 他们之间自成一派,并没有什么关联,且最大的问题是数据规则是写死的,即开发者制定了所有的规则,用户能修改或定制的部分很少,要么便是自己 import spiders 来重写。
我的设想是统一数据模型。目前为止每个爬虫子模块里面的 item 都是单独定义,很灵活,但当你需要从其他地方导入的时候就不是很优雅,比如会出现大段大段的 spiders.transfermarkt.competition.Item
这样的字段,统一设计数据模型之后,就可以从 models 里面导入。
大概思路是这样,以 competition 作为例子:
class Stat:
def __init__(
self,
id: str,
name: str,
**fields: typing.Any,
) -> None:
self.id = id
self.name = name
self.fields = fields
class Competition(Stat):
def __init__(
self,
id: str,
name: str,
country_code: str,
teams: list['Team'] | None = None,
matches: list['Match'] | None = None,
) -> None:
super().__init__(id, name)
self.country_code = country_code
if teams is None:
teams = []
self.teams = teams
if matches is None:
matches = []
self.matches = matches
每个爬虫子模块的 Competition Item 从父类继承,只要满足父类对于 Competition 定义的字段即可,每个爬虫的特殊字段都塞到 fields 中,至于数据结构,是类还是 dict,又或者其他再讨论。待到从每个爬虫模块收集的数据完毕之后,他们的合并方式,统一在每个类中实现一些 merge 方法,定义了合并之后字段处理的规则,然后再实现 add
方法,每两个数据类结果想要相加,直接调用方法即可,类似于 competition1.add(competition2)
,甚至可以实现 __add__
通过运算符操作
def add(self, new: 'Competition') -> 'Competition':
teams = self._merge_teams(new.teams)
matches = self._merge_matches(new.matches)
fields = self.fields | new.fields
return Competition(
id=self.id,
name=self.name,
country_code=self.country_code,
teams=teams,
matches=matches,
**fields,
)
def _merge_teams(
self,
news: typing.Sequence['Team'],
) -> list['Team']:
results: list['Team'] = []
for query in self.teams:
selected = process.extractOne(
query,
news,
processor=lambda x: x.name,
)
result = selected[0]
results.append(query.add(result))
return results
def _merge_matches(
self,
news: typing.Sequence['Match'],
) -> list['Match']:
...
开发者可以任意调整预先要用到的数据类,把他们 add 到一起,比起现版本
class Competition:
def __init__(
self,
fotmob: spiders.fotmob.competition.Item,
fbref: spiders.fbref.competition.Item,
official: spiders.official.competition.Item,
transfermarkt: spiders.transfermarkt.competition.Item,
) -> None:
self._fotmob = fotmob
self._fbref = fbref
self._official = official
self._transfermarkt = transfermarkt
这样的形式优雅很多,然后定义数据规则方法,提供 api。
如果用户不认同这些规则,也可以自己 add,比起现版本也会方便很多。