Skip to content

Repository files navigation

fangdi-collector

fangdi-collector

上海网上房地产数据采集器 · 瑞数 6 代(RS6)后缀池方案
Hot path ~21ms · Suffix Pool · Multi-lane session rotation · One-command CSV export

setup perf license python node

English · 中文 · How it works · Disclaimer


为什么会有人 Star 这个项目

痛点 这个仓库怎么解
瑞数 6 直接 requests 只能拿到 412 Cookie 对 + 一次性 URL 后缀自动搞定
每次都开浏览器 / Playwright,慢到没法用 常驻 Node worker + 后缀池,热路径中位 21ms
后缀「用一次就废」 一次 VM 批量掐断 XHR,白拿 200 张未使用的票
不知道怎么采全站数据 python collect_all.py → 11 个接口,350+ 行 CSV

这不是「再包一层 Selenium」。核心是:票不发出去,服务端就不知道它存在 —— 再把票摊薄到池子里供热路径消费。


English (30 seconds)

fangdi-collector collects public housing stats from Shanghai's fangdi.com.cn behind Ruishu / River Security 6.

  • Resident Node worker runs the site's own challenge JS in sdenv
  • Python keeps a multi-lane suffix pool so business calls stay ~21ms
  • FangdiClient API + collect_all.py for one-shot CSV export
pip install -r requirements.txt
cd worker && npm install && cd ..
python start_worker.py   # optional but recommended
python example.py
# or dump everything:
python collect_all.py
from fangdi import FangdiClient
import config as C

with FangdiClient(paths=C.ALL_PATHS, batch=5) as fd:
    for e in fd.get_all_estates():   # ~97 projects, no filters
        print(e["districtname"], e["project"], e["leaving_num"])

中文

上海「网上房地产」(www.fangdi.com.cn)的数据采集工具。 站点有瑞数 6 代防护,直接请求业务接口只会拿到 412。 本工具自动处理 Cookie + 一次性后缀,让你像调普通 API 一样拿数据。

实测性能

业务接口 30/30 通过
单次 中位 21.5ms  p95 23.0ms  最快 18.5ms  最慢 25.2ms
热路径同步补池次数: 0

一次采齐(collect_all.py)约 7 秒产出 350+ 行,覆盖楼盘 / 板块 / 成交 / 排行等。


五分钟跑起来

1. 环境

  • Python 3.10+
  • Node.js 18+(node -v 检查)

2. 安装

git clone https://github.com/lyt26/fangdi-collector.git
cd fangdi-collector

pip install -r requirements.txt
cd worker && npm install && cd ..

3. 启动计算进程(建议)

python start_worker.py

平时约 0.8s 起来。npm install 后第一次可能要更久(杀软扫依赖),只发生一次。

4. 拿数据

python example.py
# 或一键全量 CSV:
python collect_all.py

输出目录默认 ./output/,UTF-8 BOM,Excel 可直接打开。


查全部(什么都不限制)

网页上的「区域 / 面积 / 均价 → 不限」是前端筛选。 服务端这些接口大多不接收筛选参数,直接返回全量。

所以「什么都不限制」= 不传参数直接调。

from fangdi import FangdiClient
import config as C

with FangdiClient(paths=C.ALL_PATHS, batch=5) as fd:
    estates = fd.get_all_estates()     # ~97 个楼盘
    regions = fd.get_all_regions()     # ~121 个板块(districtID="")

    pudong = [e for e in estates if e["districtname"] == "浦东新区"]

11 个内置接口

方法 内容
get_all_estates() 全市楼盘清单
get_all_regions() 全部板块字典
get_district_bargain() 各区在售(站点接口本身约 5s)
get_more_district_bargain() 各区成交
get_sell_upcoming() 即将上市
get_seven_days_ranking() 近七日排行
get_house_sold_stat() / get_sh_sold_stat() 一手 / 存量可售统计
get_sh_uptown_clinch() / get_sh_clinch_total() 小区成交 / 汇总
get_index_message() 首页汇总

完整字段与排错见下文,或直接读 fangdi.py / config.py。


FangdiClient 速查

FangdiClient(
    paths=C.ALL_PATHS,  # 必须提前声明要采的接口(票与路径绑定)
    lanes=2,            # 独立会话轮转数
    batch=200,          # 一次挑战每接口出多少张票
)
方法 说明
request(path, data=None) 打任意已声明的受保护接口
list_districts() / list_projects() 首页数据拍平
status() 池子状态;waits 正常应为 0
close() 释放后台线程(with 自动调用)

加新接口三步:

  1. F12 → Network 抄下 .action 路径(去掉 ?XJlCTRRM=...)
  2. 写进 config.py 的常量并加入 ALL_PATHS
  3. fd.request(C.PATH_MY_NEW, data={...})

怎么工作的

防护长什么样

打业务接口必须同时满足:

  1. Cookie:UA1L1zGonajvO(服务端发)+ UA1L1zGonajvP(客户端算)
  2. URL 一次性参数:?XJlCTRRM=...(约 119 字符)
  3. 该参数从未被用过

类比:Cookie = 身份证;后缀 = 一次性门票。

三条硬规则(已实测)

规则 结果
票用过就废 第二次 → 400
票和 Cookie 绑定 串会话 → 400
票和接口路径绑定 串路径 → 400

核心思路:后缀池

[造会话] 首页 412 → 拿到 O Cookie + 挑战
        Node 跑原版 JS:算 P Cookie + 连开 N 次 XHR 并掐断
        → 白拿 N 张服务端没见过的票

[热路径] 取票 + Cookie → HTTP 业务请求 ≈ 21ms

再叠加:

  • 多会话轮转(lanes)
  • 后台预补(低于水位 / 快过期时提前建替补)

于是热路径几乎永远不卡:status()["waits"] == 0。

88 字节后缀全部受完整性保护,目前做不到纯代数拼装;必须跑一次原版 JS 批量收票。摊薄后每票约 2.8ms 量级。


仓库结构

fangdi-collector/
├── fangdi.py            # 对外 API(日常只用这个)
├── collect_all.py       # 一键采齐 → CSV
├── example.py           # 最小示例
├── test_acceptance.py   # 验收
├── start_worker.py      # 启停常驻 worker
├── config.py            # 站点 / 池子 / 接口常量
├── client.py            # 挑战 + HTTP
├── pool.py              # 后缀池
├── worker/              # Node 计算核(sdenv)
├── docs/banner.png      # GitHub 封面
└── output/              # CSV 输出(本地生成,不入库)

验收

python test_acceptance.py 30

期望:全部 200、中位 ~21ms、waits=0。


排错

现象 处理
worker 依赖没装 cd worker && npm install
启动失败 看 worker.log;Node ≥ 18;改 WORKER_PORT
全 400 路径没写进 paths / Cookie 与票不匹配
全 412 挑战失败或站点改版 → 清 js_cache/ 重跑
waits 持续 > 0 调大 batch 或 lanes

已知限制

  1. 新接口必须进 paths(否则自动整池重建,慢约 1s)
  2. getDistrictBargain 站点本身约 5s(与本工具无关);可用首页 housingSupply 代替
  3. 无服务端筛选 / 翻页 —— 拉全量后本地筛
  4. 默认单出口 IP;大规模请自行给 session 挂代理
  5. 会话 TTL 保守 150s(SESSION_TTL_SEC)
  6. 请控制频率,别打挂目标站

免责声明 / Disclaimer

本项目仅供学习研究瑞数类防护的交互模型、会话绑定与性能优化。

  • 数据来自公开页面接口;请遵守目标站点服务条款与当地法律法规
  • 禁止用于未授权大规模爬取、倒卖数据或干扰服务
  • 作者不对使用本工具造成的任何后果负责

Use at your own risk. Educational / research purposes only.


License

MIT


If this saved you a weekend of fighting 412 pages — ⭐ Star it.
如果你也烦透了 412 / 慢自动化,点个 Star 让更多人看见这套后缀池思路。

About

上海网上房地产采集器 · 瑞数6后缀池 · ~21ms hot path · One-command CSV

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages