Python爬取香港租房数据完整教程 - 28Hse抓取+Claude分析Pipeline代码实现
文章目录
Python爬取香港租房数据完整教程 - 28Hse抓取+Claude分析Pipeline代码实现
2026-06-05 更新:本文 Python 代码已在 28Hse 公开页面上实测可跑通。如果你打开文章时 28Hse 改版导致 CSS 类名变化,请回到文末「踩坑记录」第 1 条。

一、问题背景
我是 2022 年来港的 FinTech 工程师,刚来时租房全靠中介报价,完全不知道市场价分位。后来把 28Hse 上的在租盘自己爬了一遍,再让 Claude 做归因分析,才搞清楚"将军澳到底有没有被低估"这种问题。
整个 Pipeline 分 4 步:抓取 → 清洗 → Claude 归因 → 可视化。下面给出可复现的完整代码。
二、环境信息
| 依赖 | 版本 | 作用 |
|---|---|---|
| Python | 3.11.6 | 解释器 |
| requests | 2.32.3 | HTTP 请求 |
| beautifulsoup4 | 4.12.3 | HTML 解析 |
| pandas | 2.2.2 | 数据清洗 |
| anthropic | 0.39.0 | Claude API 客户端 |
| matplotlib | 3.9.0 | 图表绘制 |
pip install requests beautifulsoup4 pandas anthropic matplotlib
API key 请到 Anthropic Console 申请,通过环境变量
ANTHROPIC_API_KEY注入。不要把 key 写在代码里。
三、Pipeline 流程图
四、抓取 28Hse 公开租盘(核心代码)
# hk_rent_pipeline.py
import requests
from bs4 import BeautifulSoup
import pandas as pd
import time
import random
import re
from pathlib import Path
DISTRICTS = [
"central", "wan-chai", "causeway-bay",
"mong-kok", "yau-ma-tei", "hung-hom",
"tseung-kwan-o", "sha-tin", "tai-wai",
]
BASE_URL = "https://www.28hse.com/zh-hk/rent/residential/{district}"
HEADERS = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/126.0.0.0 Safari/537.36",
"Accept-Language": "zh-HK,zh;q=0.9,en;q=0.8",
}
def fetch_district(district: str, max_pages: int = 3) -> list[dict]:
rows = []
for page in range(1, max_pages + 1):
url = BASE_URL.format(district=district) + f"?page={page}"
resp = requests.get(url, headers=HEADERS, timeout=15)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "html.parser")
for item in soup.select(".item-info-wrap"):
try:
title = item.select_one(".item-title").get_text(strip=True)
price_text = item.select_one(".item-price").get_text(strip=True)
area_text = item.select_one(".item-area").get_text(strip=True)
rooms = item.select_one(".item-room").get_text(strip=True)
price = int(re.sub(r"[^\d]", "", price_text))
sqft = int(re.search(r"(\d+)\s*呎", area_text).group(1))
rows.append({
"district": district, "title": title, "rooms": rooms,
"sqft": sqft, "rent_hkd": price,
"rent_per_sqft": round(price / sqft, 2),
})
except (AttributeError, ValueError):
continue
time.sleep(random.uniform(8, 15))
return rows
def main():
all_rows = []
for d in DISTRICTS:
print(f"抓取 {d} ...")
all_rows.extend(fetch_district(d))
df = pd.DataFrame(all_rows)
df.to_csv("hk_rent_raw.csv", index=False, encoding="utf-8-sig")
print(f"写入 hk_rent_raw.csv,共 {len(df)} 条")
if __name__ == "__main__":
main()
关键点:限速 8-15 秒随机,参考人类浏览节奏。爬得太快会被封 IP。
五、清洗与聚合
import pandas as pd
from pathlib import Path
raw = pd.read_csv("hk_rent_raw.csv")
clean = raw[
(raw["rent_per_sqft"] >= 20) & (raw["rent_per_sqft"] <= 100)
].copy()
DISTRICT_CN = {
"central": "中环", "wan-chai": "湾仔", "causeway-bay": "铜锣湾",
"mong-kok": "旺角", "yau-ma-tei": "油麻地", "hung-hom": "红磡",
"tseung-kwan-o": "将军澳", "sha-tin": "沙田", "tai-wai": "大围",
}
clean["district_cn"] = clean["district"].map(DISTRICT_CN)
summary = clean.groupby(["district_cn", "rooms"]).agg(
count=("rent_hkd", "size"),
rent_median=("rent_hkd", "median"),
rent_mean=("rent_hkd", "mean"),
sqft_median=("sqft", "median"),
price_per_sqft_median=("rent_per_sqft", "median"),
).reset_index()
summary.to_csv("hk_rent_summary.csv", index=False, encoding="utf-8-sig")
print(summary.sort_values("price_per_sqft_median"))
六、用 Claude 做归因分析
import os
import pandas as pd
import anthropic
from pathlib import Path
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
df = pd.read_csv("hk_rent_summary.csv")
table_md = df[["district_cn", "rooms", "count",
"rent_median", "price_per_sqft_median"]].to_markdown(index=False)
prompt = f"""你是香港地产数据分析师。下面是 28Hse 抓到的 {len(df)} 行
区域-户型聚合数据(2026 年 6 月初抓取):
{table_md}
请输出:
1. 按 price_per_sqft_median 排序,找出 3 个"被低估"的区域(同户型中位数
低于全港中位数 15% 以上),并猜测可能原因(交通/校网/楼龄/海景)
2. 找出 3 个"明显高估"的区域,给出同样归因
3. 给一个 3-5 行的总结:内地人去香港工作,预算 12,000-18,000/月,
应该优先看哪 3 个区域?避雷哪 2 个?
4. 用 Markdown 输出
硬约束:
- 只能基于表里的数据推断
- 样本 <5 的区域标"样本不足"
- 不要编造具体地址或楼盘名
"""
msg = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=2048,
messages=[{"role": "user", "content": prompt}],
)
Path("hk_rent_report.md").write_text(msg.content[0].text, encoding="utf-8")
七、可视化
import pandas as pd
import matplotlib.pyplot as plt
df = pd.read_csv("hk_rent_summary.csv")
df = df[df["rooms"] == "2 房"].sort_values("price_per_sqft_median")
plt.figure(figsize=(10, 6))
plt.barh(df["district_cn"], df["price_per_sqft_median"], color="#00D4FF")
plt.xlabel("呎租 (HKD / sqft)")
plt.title("2026-06 香港各区 2 房呎租中位数(28Hse 实测)")
plt.tight_layout()
plt.savefig("hk_rent_chart.png", dpi=150)

八、踩坑记录
1. 28Hse 列表页面 CSS 类名不稳定。 我 6 月初跑时是 .item-info-wrap,5 月底还是 .item-detail。先 print(soup.prettify()[:2000]) 看当前结构,不要硬编码类名。
2. 限速是红线,不是建议。 time.sleep(1) 跑 200 条就被封 IP,改成 8-15 秒随机延迟才稳。
3. 月租字符串里有时混"HK$“、有时带"起”。 “HK$15,000起"被解析成 15000,但实际可能 18,000。清洗阶段过滤"起"字,或标注"含起价”。
4. Claude 的 max_tokens 别设太大。 设 8192 账单会爆,2048 足够 1500-2500 行分析。超出说明 prompt 没写清楚。
5. API key 不要硬编码。 用 os.environ["ANTHROPIC_API_KEY"] 从环境变量读。
九、扩展阅读
- 差饷物业估价署 - 物业市场统计资料(香港政府官方租金/售价指数,权威基准)
- Anthropic Claude API Python SDK(本文用的
claude-sonnet-4-5模型文档) - 28Hse 香港租盘(数据源,公开页面无登录要求)
本文代码经实测可复现,但 28Hse 页面结构可能改版,爬虫需自行维护。如需 LangChain 多轮 Agent 实战或港铁延误数据爬取,评论里告诉我,下一篇安排。

更多推荐
所有评论(0)