thomasproject-stack/dld-transactions-pipeline

GitHub: thomasproject-stack/dld-transactions-pipeline

通过对迪拜土地局开放数据 SPA 的逆向工程,构建了一条从批量抓取、双语 CSV 合并、Parquet 规范化到自动化 EDA 报告的完整数据管线。

Stars: 0 | Forks: 1

# DLD 交易数据 Pipeline **对迪拜土地局(DLD)公共开放数据 API 进行逆向工程,批量下载迪拜房地产登记数据,将其标准化为双语的 CSV/Parquet 文件,并生成 DuckDB + Plotly 分析报告。** Playwright 捕获该 SPA 的 XHR 流量,发现其 reCAPTCHA 只是一个*仅限于客户端*的功能开关,随后直接从服务器端使用 `httpx` 重放带有签名的请求 payload,从而获取 11 个数据集中共计约 57.5 万条记录。 ## 为什么这个项目很有趣 DLD 将迪拜的所有交易登记数据作为“开放数据”发布,但这必须通过一个隐藏在隐形 reCAPTCHA 背后的 JavaScript 单页应用才能访问——它没有官方文档化的 API,不提供 CSV 下载,也不支持批量导出。本项目将这个 SPA 转化为一个可复现的数据 pipeline。 - **网络抓包与逆向工程。** 一个 headless-Chromium 会话记录了 SPA 向 `gateway.dubailand.gov.ae` 发出的每一个请求,梳理出未公开文档化的 endpoint、它们所需的精确 header 集合,以及 9 个数据命令 + 3 个查找命令各自的精确 JSON payload schema。 - **客户端限制在服务器端的重放。** 事实证明,reCAPTCHA 的强制执行仅限于*浏览器端*,而非由网关进行验证。确认这一点后,就可以在任何地方使用普通的 HTTP client 重放捕获到的 payload——不需要浏览器,不需要验证码破解工具,也不需要付费 API——这正是能够免费进行批量下载的根本原因。 - **针对整个登记库的分析,而不仅仅是图表。** 1.1 GB 的交易数据导出文件以流式处理的方式被转换为标准的 ZSTD Parquet 文件(由 DuckDB 完成,不使用 pandas,不会导致内存暴增),然后由一个单一的生成器输出一个独立的 HTML 探索性数据分析 (EDA) 报告——每张图表都附带了可展开的 DuckDB SQL 语句,确保每一个数据都是可复现的。 - **优先免费且支持双语。** 每个数据集都包含了并行的英文/阿拉伯文列(`*_EN` / `*_AR`),与 DLD 提供的原始格式完全一致。 ## 提取流程的工作原理 如果请求中没有 `consumer-id` header 和验证码 token,DLD 网关将予以拒绝,并返回 `INVALID_REQUEST_PARAMETERS`(错误码 `4001`)。`consumer-id` 是 SPA 明文内置的公共前端标识符;而事实证明,验证码仅仅是一个前端功能开关(feature flag)。 | 阶段 | 脚本 | 机制 | |---|---|---| | **1. 发现 (Discover)** | `capture_api.py` | headless Chromium(隐身模式标志:`navigator.webdriver=false`,伪造的 plugins/languages)加载 SPA 并记录发往网关的每一个 XHR 请求——包括 URL、header 和请求体。 | | **2. 解锁 (Unlock)** | `captcha_bypass.py` | 将 `AppInfo.featureGate.captchaEnabled = false; window.cacheVerified = true` 注入页面,填写日期表单并提交,然后捕获最终*已签名*的请求 payload。 | | **3. 重放 (Replay)** | `bulk_downloader.py` | 在服务器端使用 `httpx` 重新发送该 payload——无需浏览器。按月份进行分块(`P_FROM_DATE`/`P_TO_DATE` 格式为 `MM/DD/YYYY`)以避免触发网关超时,使用 `P_TAKE = -1` 获取完整的查询窗口,并在失败时进行 3 次带有线性退避策略的重试。 | | **4. 整合 (Consolidate)** | `to_csv.py` | 将 Gzip 压缩的每月 JSON 数据展平,为每个数据集生成一个双语的 CSV 文件,保持稳定的列顺序,并生成一个 `_summary.csv` 清单文件。 | | **5. 规范化 (Canonicalise)** | `build_parquet.py` | 通过 DuckDB 的流式处理 `COPY … TO … (FORMAT PARQUET, COMPRESSION 'ZSTD')` 对日期和数字进行类型转换,并丢弃非公历(伊斯兰历/Hijri)的日期数据,同时设置了 3 GB 的内存上限,确保其能在配置较低的服务器上以流式方式运行。 | | **6. 分析 (Analyse)** | `build_eda_report.py` | 使用 DuckDB 在内存中打开 Parquet 文件,并将约十几个主题板块(时间、地理、定价、期房 vs 现房、租赁、数据质量、交叉分析)渲染为 Plotly 图表,最终整合到一个 HTML 文件中。 | 为 `transactions` 命令发现的精确请求契约: ``` POST {gateway}/transactions Headers: consumer-id: (env: DLD_CONSUMER_ID) content-type: application/json; charset=UTF-8 origin/referer: https://dubailand.gov.ae Body: { "P_FROM_DATE": "MM/DD/YYYY", "P_TO_DATE": "MM/DD/YYYY", "P_AREA_ID": "", "P_USAGE_ID": "", "P_PROP_TYPE_ID": "", "P_TAKE": "-1", "P_SKIP": "0", "P_SORT": "" } ``` 这 9 个命令中的每一个都有其专属的参数 schema(定义在 `bulk_downloader.py` 的 `SCHEMAS` 中);包含日期的命令(`transactions`、`rents`、`projects`、`developers`、`valuations`)按月份进行分块抓取,而快照类命令(`lands`、`brokers`、`buildings`、`units`)则进行全量拉取。 ## 架构 ``` Dubai Land Department open-data SPA (dubailand.gov.ae) │ Playwright · headless Chromium · stealth ▼ capture_api.py ─────► record every XHR to the gateway │ (endpoints · headers · exact JSON payloads) ▼ captcha_bypass.py ──► disable the client-side reCAPTCHA feature gate, │ submit the form, capture the signed payload ▼ bulk_downloader.py ─► replay payloads server-side with httpx │ month-chunked + retries ──► data/raw/*.json.gz ▼ to_csv.py ─────────► consolidate gzip JSON ──► bilingual EN/AR CSV per dataset ▼ build_parquet.py ──► DuckDB streaming COPY ──► canonical ZSTD Parquet (1998–2026) ▼ build_eda_report.py ► DuckDB-on-Parquet + Plotly ──► self-contained HTML EDA report ``` ## 技术栈 Python · Playwright / headless Chromium · httpx · DuckDB · pandas · NumPy · Plotly · Parquet (ZSTD) ## 本地运行 ``` python3 -m venv .venv && source .venv/bin/activate pip install -r requirements.txt playwright install chromium cp .env.example .env # then fill in DLD_CONSUMER_ID (captured in step 2) # 发现 API surface,然后捕获已签名的 payload python3 scripts/capture_api.py python3 scripts/captcha_bypass.py # 批量下载所有数据集 → data/raw/*.json.gz python3 scripts/bulk_downloader.py # lookups + snapshots + dated python3 scripts/inventory.py # tally what was pulled # 合并为双语 CSV → data/csv/*.csv python3 scripts/to_csv.py # 构建规范的 Parquet,然后生成 EDA 报告 export DLD_TRANSACTIONS_CSV=data/raw/transactions_full.csv python3 scripts/build_parquet.py # → data/dubai_transactions.parquet python3 scripts/build_eda_report.py # → data/eda_report.html ``` 所有路径均可通过环境变量进行配置(参见 `.env.example`):`DLD_DATA_DIR`、`DLD_CONSUMER_ID`、`DLD_PARQUET`、`DLD_TRANSACTIONS_CSV`、`DLD_EDA_OUT`、`DLD_PARQUET_URL`。 ## 数据与隐私 本仓库仅包含**代码**。不提供任何交易数据、CSV、Parquet 文件、Gzip 抓包记录或数据库。该 pipeline 产生的所有内容均派生自**迪拜土地局的公共开放数据网关**——这是一个官方的政府数据集。只需提供你自己抓取的 `consumer-id`,该 pipeline 就能在本地重建这些数据集。 此处的“绕过 reCAPTCHA”仅仅是通过禁用一个**客户端**的功能开关,以便读取那些已经作为开放政府数据发布的内容;本项目没有任何突破服务器端身份验证或访问非公开记录的行为。 ## 许可证 MIT — 详见 [LICENSE](LICENSE)。
标签:DuckDB, Playwright, 云资产清单, 代码示例, 房地产数据, 数据分析, 数据爬虫, 特征检测, 运行时操纵, 逆向工具, 逆向工程