内容
# BoligWatch
[](https://github.com/nille/boligwatch/actions/workflows/ci.yml)
[](https://www.python.org/downloads/)
[](https://github.com/astral-sh/ruff)
[](LICENSE)
用于监测丹麦boligportal.dk网站新租赁列表的命令行工具和MCP服务器。该工具使用网站公开的搜索API,无需账户或API密钥。
## 需求
- Python 3.10+
- 命令行模式仅需标准库(零依赖)
- `pip install curl_cffi` — 推荐,绕过Cloudflare机器人保护(参见[Cloudflare绕过](#cloudflare-绕过))
- `pip install mcp` 用于MCP服务器模式
## 安装
```bash
# 克隆仓库
git clone https://github.com/your-username/boligwatch.git
cd boligwatch
# (可选)创建并激活虚拟环境
python3 -m venv .venv
source .venv/bin/activate
# 命令行模式开箱即用,仅需标准库
python boligwatch.py --help
# 推荐:安装curl_cffi以绕过Cloudflare机器人保护
pip install curl_cffi
# 对于MCP服务器模式,安装MCP SDK
pip install mcp
# 生成配置文件并编辑以匹配您的搜索
python boligwatch.py --init-config
```
## 快速入门
```bash
# 运行一次 - 显示新列表(无过滤器,除非配置文件提供)
python boligwatch.py
# 使用已保存的配置文件进行常规搜索
python boligwatch.py --config boligwatch_config.json
# 监视模式 - 每5分钟轮询一次
python boligwatch.py --config boligwatch_config.json --watch
# 临时搜索,带有内联过滤器
python boligwatch.py --city københavn --rooms-min 3 --max-rent 16000 --balcony
```
## 工作原理
BoligWatch从boligportal.dk的搜索API获取列表,根据您的标准进行过滤,并跟踪您已经看到的列表在本地JSON文件中。每次运行时,它只显示新的列表 - 即以前未见过的列表。
跟踪器还检测**重新上市的公寓**:如果房东重新发布列表,具有相同的ID但更新的`advertised_date`,它会重新出现为新的。这可以防止在列表被删除并重新发布时错过机会。已见文件存储`advertised_date`以及已见的时间戳以进行比较。旧条目(来自旧版本)被透明读取 - 重新上市检测对于没有存储的`advertised_date`的条目被跳过。
有三种使用方式:
1. **命令行** - 运行一次或在监视模式下,管道JSON到其他工具
2. **MCP服务器** - 将搜索和跟踪公开为Claude Code,Claude Desktop或任何MCP客户端的工具
3. **技能** - 一个现成的技能,教Claude将自然语言的公寓查询转换为MCP工具调用
## 命令行参考
### 模式
| 标志 | 描述 |
|------|-------------|
| *(默认)* | 运行一次,打印新列表,标记为已见 |
| `--watch`, `-w` | 连续轮询(默认:每300秒) |
| `--interval N`, `-i N` | 轮询间隔(秒)用于 `--watch` |
| `--json` | 输出新列表为JSON数组,标记为已见 |
| `--peek` | 输出新列表为JSON,不标记为已见(重试安全) |
| `--mcp` | 启动为MCP服务器(stdio传输) |
### 已见列表管理
| 标志 | 描述 |
|------|-------------|
| `--mark-seen ID [ID ...]` | 标记特定列表ID为已见 |
| `--reset` | 清除所有已见列表历史记录,然后运行 |
| `--seen-file PATH` | 自定义已见列表跟踪器的路径(默认:`.boligwatch_seen.json`) |
### 配置和日志记录
| 标志 | 描述 |
|------|-------------|
| `--config PATH`, `-c PATH` | 配置文件的路径 |
| `--init-config` | 在 `--config`路径或默认位置生成配置文件模板 |
| `--log-file PATH` | 将日志写入文件 |
| `--verbose`, `-v` | 詳細(DEBUG级别)日志记录 |
### 搜索过滤器
所有过滤器默认为无限制(未设置)。请参见[过滤器行为](#过滤器行为),了解过滤器如何与配置文件交互。
#### 位置
| 标志 | 类型 | 默认值 | 描述 |
|------|------|---------|-------------|
| `--city NAME` | 字符串(可重复) | `["københavn"]` | 搜索城市。可以重复: `--city københavn --city frederiksberg` |
| `--bbox S,W,N,E` | 浮点数 | *无* | 边界框为 `min_lat,min_lng,max_lat,max_lng`。如果设置,则替换 `--city` |
城市名称使用丹麦语小写字母和原字符:`københavn`,`frederiksberg`,`aarhus`,`odense`,`aalborg`.
#### 尺寸和价格
| 标志 | 类型 | 默认值 | 描述 |
|------|------|---------|-------------|
| `--rooms-min N` | 整数 | *无* | 最少房间数量 |
| `--rooms-max N` | 整数 | *无* | 最大房间数量 |
| `--max-rent N` | 整数 | *无* | 每月最大租金(丹麦克朗) |
| `--min-size N` | 整数 | *无* | 最小尺寸(平方米) |
| `--min-rental-period N` | 整数 | *无* | 最小租赁期限(月) |
| `--max-available-from DATE` | YYYY-MM-DD | *无* | 最新入住日期 |
#### 房产类型
| 标志 | 类型 | 默认值 | 描述 |
|------|------|---------|-------------|
| `--newbuild` | 布尔值 | *无* | 仅新建项目租赁(projektudlejning) |
| `--social-housing` | 布尔值 | *无* | 仅社会住房(almen bolig) |
#### 生活方式
| 标志 | 类型 | 默认值 | 描述 |
|------|------|---------|-------------|
| `--pet-friendly` | 布尔值 | *无* | 仅宠物友好列表 |
| `--senior-friendly` | 布尔值 | *无* | 仅适合老年人的列表 |
| `--student-only` | 布尔值 | *无* | 仅限学生住房 |
| `--shareable` | 布尔值 | *无* | 仅可共享公寓(delevenlig) |
#### 设施
| 标志 | 类型 | 默认值 | 描述 |
|------|------|---------|-------------|
| `--parking` | 布尔值 | *无* | 必须有停车位 |
| `--elevator` | 布尔值 | *无* | 必须有电梯 |
| `--balcony` | 布尔值 | *无* | 必须有阳台或露台 |
| `--ev-charging` | 布尔值 | *无* | 必须有电动汽车充电站(ladestander) |
#### 家电
| 标志 | 类型 | 默认值 | 描述 |
|------|------|---------|-------------|
| `--furnished` | 布尔值 | *无* | 必须配备家具 |
| `--dishwasher` | 布尔值 | *无* | 必须有洗碗机 |
| `--washing-machine` | 布尔值 | *无* | 必须有洗衣机 |
| `--dryer` | 布尔值 | *无* | 必须有烘干机 |
#### 分页
| 标志 | 类型 | 默认值 | 描述 |
|------|------|---------|-------------|
| `--max-pages N` | 整数 | `5` | 最大抓取页数(每页18个列表,最大50) |
### 命令行示例
```bash
# 哥本哈根市内3个房间的宠物友好型公寓,租金不超过12,000克朗
python boligwatch.py --city københavn --rooms-min 2 --rooms-max 2 --max-rent 12000 --pet-friendly
# 哥本哈根市内100m²以上的公寓,有阳台,可于八月入住
python boligwatch.py --city københavn --min-size 100 --balcony --max-available-from 2026-08-01
# 弗雷德里克斯贝格市内的家具齐全、有洗碗机和洗衣机的公寓
python boligwatch.py --city frederiksberg --furnished --dishwasher --washing-machine
# 哥本哈根市内的社会住房公寓,位于边界框内
python boligwatch.py --bbox 55.63,12.48,55.73,12.80 --social-housing
# 新建项目租赁,有电动汽车充电站和电梯
python boligwatch.py --city københavn --newbuild --ev-charging --elevator
# 偷窥新列表为JSON(安全重试,不标记已见)
python boligwatch.py --config boligwatch_config.json --peek
# 监视模式:每2分钟轮询一次,使用保存的配置文件
python boligwatch.py --config boligwatch_config.json --watch --interval 120
# 标记特定列表为已查看
python boligwatch.py --mark-seen 5619969 2987892
# 将输出传递给jq进行处理
python boligwatch.py --peek | jq '.[].url'
```
## 配置文件
配置文件保存您的默认搜索标准,这样您不必每次都传递命令行标志。命令行标志始终覆盖配置文件值。
### 生成模板
```bash
python boligwatch.py --init-config
# 或指定路径:
python boligwatch.py --init-config --config my_search.json
```
### 配置文件格式
所有字段都是可选的。省略字段或将其设置为 `null` 表示无限制。未知键会记录为警告以帮助捕获拼写错误。
```json
{
"categories": ["rental_apartment", "rental_house", "rental_townhouse"],
"city_level_1": ["københavn"],
"city_level_2": null,
"min_lat": null,
"min_lng": null,
"max_lat": null,
"max_lng": null,
"rooms_min": 3,
"rooms_max": null,
"max_rent": 17000,
"min_size_m2": null,
"min_rental_period": 12,
"max_available_from": null,
"pet_friendly": null,
"balcony": null,
"furnished": null,
"parking": null,
"elevator": null,
"shareable": null,
"student_only": null,
"senior_friendly": null,
"social_housing": null,
"newbuild": null,
"electric_charging_station": null,
"dishwasher": null,
"washing_machine": null,
"dryer": null,
"order": "DEFAULT",
"max_pages": 5
}
```
#### 类别
`categories` 字段控制要包含哪些类型的属性:
| 值 | 丹麦语 | 描述 |
|-------|--------|-------------|
| `rental_apartment` | Lejligheder | 公寓 |
| `rental_room` | Værelser | 房间 |
| `rental_house` | Huse | 房屋 |
| `rental_townhouse` | Rækkehuse | 联排别墅 |
#### 位置:城市 vs 边界框
您可以按城市名称或边界框进行筛选,但不能同时进行。当设置边界框坐标时,城市筛选器将被忽略。
- **城市**: `"city_level_1": ["københavn", "frederiksberg"]`
- **边界框**: 设置 `min_lat`, `min_lng`, `max_lat`, `max_lng`
- **子城市**: `"city_level_2": ["amager"]`(在city_level_1内细化)
## MCP服务器
BoligWatch作为本地[MCP](https://modelcontextprotocol.io)服务器,将搜索和跟踪公开为任何MCP客户端的工具。
### 设置
安装MCP SDK(命令行无需安装):
```bash
pip install mcp
```
添加到您的MCP客户端配置(`.mcp.json`, Claude Desktop配置等):
```json
{
"mcpServers": {
"boligwatch": {
"command": "python3",
"args": ["/path/to/boligwatch.py", "--mcp", "--config", "/path/to/boligwatch_config.json"]
}
}
}
```
### 工具
| 工具 | 描述 |
|------|-------------|
| `search_listings` | 搜索并返回所有匹配项为JSON |
| `get_new_listings` | 像搜索一样,但仅返回以前未见列表 |
| `mark_seen` | 标记列表ID为已见 |
| `reset_seen` | 清除所有已见列表历史记录 |
| `get_seen_stats` | 获取跟踪器统计信息(计数,文件路径) |
### 过滤器行为
与CLI和MCP相同的逻辑:
- **未传递过滤器** = 使用配置文件中的完整保存搜索(您的监控查询)
- **传递任何过滤器** = 从头开始。仅结构化设置(类别、位置、顺序、最大页面)会从配置文件继承。限制性过滤器(租金、房间、大小、功能)将被剥离 - 如果您询问“找到200平方米以上的公寓”,它不会无声地限制在17,000克朗,因为您的配置文件。
这意味着 `python boligwatch.py --peek` 使用您保存的搜索,但 `python boligwatch.py --min-size 200` 不会无声地限制在17,000克朗。
### MCP 工具参数
`search_listings` 和 `get_new_listings` 接受相同的过滤器参数:
| 参数 | 类型 | 默认值 | 描述 |
|-----------|------|---------|-------------|
| `cities` | `string[]` | *从配置文件* | 城市名称(例如`["københavn"]`) |
| `min_lat` | `float` | *从配置文件* | 边界框南纬度 |
| `min_lng` | `float` | *从配置文件* | 边界框西经度 |
| `max_lat` | `float` | *从配置文件* | 边界框北纬度 |
| `max_lng` | `float` | *从配置文件* | 边界框东经度 |
| `rooms_min` | `int` | *无限制* | 最少房间数量 |
| `rooms_max` | `int` | *无限制* | 最大房间数量 |
| `max_rent` | `int` | *无限制* | 每月最大租金(丹麦克朗) |
| `min_size_m2` | `int` | *无限制* | 最小尺寸(平方米) |
| `min_rental_period` | `int` | *无限制* | 最小租赁期限(月) |
| `max_available_from` | `string` | *无限制* | 最新入住日期(YYYY-MM-DD) |
| `pet_friendly` | `bool` | *无过滤* | 宠物友好 |
| `balcony` | `bool` | *无过滤* | 有阳台/露台 |
| `furnished` | `bool` | *无过滤* | 已配备 |
| `parking` | `bool` | *无过滤* | 有停车位 |
| `elevator` | `bool` | *无过滤* | 有电梯 |
| `shareable` | `bool` | *无过滤* | 可共享(delevenlig) |
| `student_only` | `bool` | *无过滤* | 仅限学生 |
| `senior_friendly` | `bool` | *无过滤* | 适合老年人 |
| `social_housing` | `bool` | *无过滤* | 社会住房(almen bolig) |
| `newbuild` | `bool` | *无过滤* | 新建/项目租赁 |
| `electric_charging_station` | `bool` | *无过滤* | 电动汽车充电站 |
| `dishwasher` | `bool` | *无过滤* | 有洗碗机 |
| `washing_machine` | `bool` | *无过滤* | 有洗衣机 |
| `dryer` | `bool` | *无过滤* | 有烘干机 |
| `max_pages` | `int` | `5` | 抓取页数(每页18个列表,最大50) |
`get_new_listings` 还接受:
| 参数 | 类型 | 默认值 | 描述 |
|-----------|------|---------|-------------|
| `mark_as_seen` | `bool` | `false` | 如果为真,则将返回的列表标记为已见 |
### MCP 工具调用示例
**搜索哥本哈根市内3个房间的公寓,租金不超过16,000克朗:**
```json
{
"tool": "search_listings",
"arguments": {
"cities": ["københavn"],
"rooms_min": 3,
"max_rent": 16000
}
}
```
**查找配备了洗衣机和洗碗机的家具公寓:
```json
{
"tool": "search_listings",
"arguments": {
"cities": ["københavn"],
"furnished": true,
"washing_machine": true,
"dishwasher": true
}
}
```
**搜索区域内的大型公寓(150m²+):**
```json
{
"tool": "search_listings",
"arguments": {
"min_size_m2": 150,
"max_pages": 10
}
}
```
**有电动汽车充电站的待售新建项目,截止日期为九月:
```json
{
"tool": "search_listings",
"arguments": {
"newbuild": true,
"electric_charging_station": true,
"max_available_from": "2026-09-01"
}
}
```
**检查不可见的列表(偷窥模式):**
```json
{
"tool": "get_new_listings",
"arguments": {}
}
```
**检查新列表并将其标记为已见:
```json
{
"tool": "get_new_listings",
"arguments": {
"mark_as_seen": true
}
}
```
**标记特定列表为已查看:
```json
{
"tool": "mark_seen",
"arguments": {
"ids": [5619969, 2987892]
}
}
```
## 技能:自然语言查询
一个现成的技能包含在 [`skills/bolig-watch/SKILL.md`](skills/bolig-watch/SKILL.md) 中,用于教导Claude将自然语言的公寓查询转换为MCP工具调用。
### 示例查询
一旦MCP服务器和技能配置完成,您可以向Claude提问,例如:
```
帮我找到哥本哈根市内3个房间的公寓,租金不超过15,000克朗
```
```
弗雷德里克斯贝格有带阳台的宠物友好型公寓吗?
```
```
显示当前可用的最大公寓
```
```
自从上次以来有新的列表吗?
```
```
找一个带洗衣机的家具齐全的公寓,至少70平方米
```
```
阿马格尔有哪些可供出租的公寓,租金不超过12k,房间不少于2个?
```
```
显示有电动汽车充电站和电梯的新建项目
```
## 使用Playwright MCP进行浏览器自动化
将BoligWatch与 [Playwright MCP](https://github.com/microsoft/playwright-mcp) 和 [Playwright MCP Bridge](https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm) Chrome扩展程序结合使用。它们允许Claude使用您的浏览器控制您的实际浏览器,并使用您登录的boligportal.dk会话。
这意味着Claude可以导航到列表,读取详细信息,与房东联系,填写表格和发送消息。
### 设置
1. 安装 [Playwright MCP Bridge](https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm) 扩展程序到Chrome
2. 将两个MCP服务器添加到您的配置中:
```json
{
"mcpServers": {
"boligwatch": {
"command": "python3",
"args": ["/path/to/boligwatch.py", "--mcp", "--config", "/path/to/config.json"]
},
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest", "--extension"]
}
}
}
```
### 自动寻找公寓
```
/loop 5m 使用boligwatch MCP检查新的boligportal列表。对于每个新列表,在浏览器中打开它,使用Playwright保存PDF,与房东联系并发送消息,并在Slack上通知我。
```
因为扩展程序桥接到您现有的浏览器会话,所以Claude使用您的凭证进行身份验证 - 无需单独的登录流程,无需存储凭证。
## Cloudflare 绕过
boligportal.dk 使用Cloudflare机器人保护,可以阻止来自标准HTTP客户端(如Python的`urllib`)的请求,并伴有HTTP 403和JavaScript挑战。当发生这种情况时,API变得无法访问。
安装 `curl_cffi` 启用Chrome TLS指纹伪装,绕过挑战:
```bash
pip install curl_cffi
```
当安装了 `curl_cffi` 时,BoligWatch会自动为所有API请求使用它。当未安装时,BoligWatch会回退到stdlib `urllib` - 当Cloudflare不主动挑战请求时,这效果很好。
两个后端都重试HTTP 403、429和5xx错误,并带有指数级退火。
## 列表输出格式
API返回的每个列表项都包含:
| 字段 | 类型 | 描述 |
|-------|------|-------------|
| `id` | int | 唯一列表ID |
| `url` | string | 列表的直接链接 |
| `title` | string | 列表标题 |
| `city` | string | 城市名称 |
| `city_area` | string | 子区域(例如“哥本哈根S”,“阿马格尔”) |
| `postal_code` | string | 邮政编码 |
| `street_name` | string | 街道名称 |
| `street_number` | string | 街道编号 |
| `rooms` | float | 房间数量 |
| `size_m2` | float | 尺寸(平方米) |
| `monthly_rent` | float | 每月租金 |
| `monthly_rent_currency` | string | 货币(通常为“kr”) |
| `deposit` | float | 押金 |
| `prepaid_rent` | float | 预付租金 |
| `available_from` | string | 入住日期(YYYY-MM-DD) |
| `advertised_date` | string | 列表发布时间 |
| `category` | string | 房产类型 |
| `energy_rating` | string | 能源标签(例如“A2010”,“C”) |
| `features` | object | 布尔功能标志(宠物友好,电梯等) |
## 许可
[MIT](LICENSE)
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器