技术札记

实战香港电影公司:用 HHY 做一次可复现的维基百科研究

用 MediaWiki API 搜索候选页面、并发抓取详情、做语义筛选,并把动态网络结果与确定性回归测试分开。

HOUHUIYANG.COM

扫码继续阅读

正在生成…

实战香港电影公司:用 HHY 做一次可复现的维基百科研究

houhuiyang.com/zh/notes/researching-hong-kong-film-companies-with-hhy

“抓取香港电影公司”听起来像一个很小的爬虫题目,但真正动手后,问题很快从 HTTP 变成了研究方法:搜索结果是不是公司?外部数据变化后测试怎么办?失败页面要不要拖垮整批任务?输出如何保留来源?

这个项目使用维基百科官方 MediaWiki API。先按“香港電影公司”搜索候选页面,再并发获取 page ID、正式 URL、更新时间和首段简介,筛选公司语义,最后原子写入 CSV 和 JSON。

香港电影公司项目的真实目录

搜索与详情是两个阶段

搜索接口适合发现候选,不适合直接当最终数据。程序先获取 query.search,按 pageid 去重,再用 parallel(parallelism) 请求详情。

return search_results
    |> stream
    |> group_by { result -> result.pageid }
    |> map { group -> group.values[0] }
    |> parallel(parallelism) { result ->
        let detail = attempt { /* MediaWiki detail request */ }
        // return success or structured failure
    }
    |> collect

每个请求有 10 秒 timeout、两次 retry 和 250 ms backoff。attempt 隔离单页失败,因此第七页超时不会抹掉前六页已经取得的数据。

详情请求把简介限制为 600 字符。这不是为了省几个字节,而是给 worker 返回值建立边界。并发任务如果可以无上限携带外部响应,吞吐很容易变成内存问题。

筛选承认它只是启发式

最终规则要求简介同时包含“香港”、“電影/电影”和“公司”,随后按标题排序:

pages
    |> stream
    |> where { page -> page.fetched }
    |> where { page ->
        contains(page.summary, "香港") and
        (contains(page.summary, "電影") or contains(page.summary, "电影")) and
        contains(page.summary, "公司")
    }
    |> sort_by { page -> page.title }
    |> collect

这个规则可解释、可测试,但不是公司注册数据库。2026-08-26 的真实运行从 10 个候选中保留 7 条,包括公司页面和“香港電影公司列表”。我选择在文章和数据里明确这个边界,而不是把搜索结果包装成权威名录。

HHY 真实联网抓取维基百科的运行结果

CSV 保留 wikipedia_url、page ID、更新时间与简介,读者可以回到来源核验。公开再发布简介时,也应遵循对应页面的许可和署名要求。

动态数据与回归测试必须分开

真实 Wikipedia 结果会变化,网络也会抖动。如果测试直接断言今天仍然是七家公司,它测到的更可能是互联网,而不是代码。

因此 self-test.sh 启动本地模拟 MediaWiki API:固定 5 个候选,经相同的去重、并发请求与语义筛选后必须得到 3 家公司。测试检查 CSV 字段、顺序、URL 和 JSON 统计。

sh practical-projects/hong-kong-film-companies/self-test.sh

真实运行负责回答“现在能查到什么”,fixture 负责回答“程序是否仍按约定工作”。两者都重要,但不能混成同一种证据。

一个兼容性细节

配置中的中文搜索词预先做了百分号编码,因为 HHY 1.1.1 对非 ASCII query Map 值还有编码限制;详情请求的 ASCII 参数仍由 query Map 构造。我更愿意把这个限制写进项目,而不是用一段看似聪明的隐式修复藏起来。

这也是实战项目的意义:它会迫使语言面对真实 API、真实字符和真实失败,最终让路线图不再只是功能清单。

参考

返回技术札记