本次博客主要记录动态权重与三模块管线串联工作。

一、两个优化角度

第一:评分引擎的六个维度各有固定权重(价格 0.25、通勤 0.25、配套 0.20……),但不同用户对"好房子"的定义完全不同。一个刚入职的应届生可能把通勤放在第一位,一个有家庭的中年租客更看重户型和周边配套。如果权重写死了,"评分"就成了一个自说自话的东西。

第二:爬虫、地图、评分三个模块虽然各自能跑,但它们之间的关系需要调用方自己来协调——先调爬虫拿原始数据,手动标准化格式,再一条条调地图接口做富化,最后把结果塞进评分引擎。这种"手工管线"在开发调试时没问题,但显然不能作为最终交付的形态。

二、单权重调高

用户通过 API 传入的权重可以是任意子集。比如一个对通勤极度敏感的用户,只想调高通勤权重:

{

  "budget": {"min": 3000, "max": 5000},

  "weights": {"commute": 0.5}

}

只传了一个 commute,剩下五个维度怎么办?引擎的处理逻辑:

def _resolve_weights(self, user_weights):

    merged = {**DEFAULT_WEIGHTS}   # 1. 先用默认权重填满

    if user_weights:

        for k, v in user_weights.items():

            if k in merged and v >= 0:

                merged[k] = v      # 2. 用户指定的覆盖掉

    total = sum(merged.values())

    return {k: v / total for k, v in merged.items()}  # 3. 归一化

关键在第三步的归一化。用户把commute设成了 0.5,但其余维度还是默认值,总和变成了 1.25。归一化之后:commute = 0.5/1.25 = 0.40,其余按比例缩小。这样无论用户传什么、传几个,所有权加起来永远是 1.0,总分不会溢出到 150 分,也不会缩水到 50 分。

三、三模块管线:从四次手动调用到一次请求

手工管线的痛点:

在管线串联之前,要完成一次"搜索+分析+评分"的完整流程,调用方需要:

1. 调 /crawler/search 拿原始房源列表

2. 自己写标准化代码处理三种不同格式

3. 自己拼完整地址

4. 一条条调 /map/enrich(还得控制频率防止触发高德限流)

5. 把富化结果塞进 /scoring/score

这中间每一步都可能出错——格式不一致、地址拼错了地理编码失败、并发太快被高德限流。而且这些步骤和业务逻辑无关,纯粹是数据转换的脏活累活。

管线设计:每一步都有明确的输入输出

pipeline.py 的 RecommendPipeline.run()`把这个流程做成了链式调用,每步之间通过统一的数据模型衔接:

search_params (用户搜索条件)

  ↓ crawl_housing_data()

raw_listings (原始爬虫输出, List[dict])

  ↓ normalizer.normalize_batch()

normalized (标准化房源, List[NormalizedHouseListing])

  ↓ address_builder.build_batch()

normalized (full_address 已填充)

  ↓ map_service.enrich_batch()

enriched (地图富化后, List[MapEnrichedListing])

  ↓ engine.score_batch()

results (评分排序, List[ScoreResult])

  ↓ 取 top_n 返回

每一步的函数签名都很明确——输入类型和输出类型都是确定的 Pydantic 模型。这样做的好处是:如果你想换一个评分算法,只需要替换ScoringEngine,上下游完全不受影响。如果你想用百度地图替代高德,只需要换MapService,管线逻辑不用动。

四、对外暴露的接口

最终这一切被打包成一个端点:
 

POST /scoring/recommend

{

  "search_params": {"city": "济南", "district": "历下", "rent_type": "整租", ...},

  "preferences": {"budget": {...}, "rooms": 2, "tags": ["近地铁"], "weights": {...}},

  "top_n": 10

}

一个请求完成搜索→标准化→地图分析→评分排序。调用方不需要知道里面有三种爬虫格式、需要构造完整地址、需要控制 API 频率——这些东西是管线内部的事。

五、后续进一步探索优化角度

更多推荐