SandiRidwan/tiktok-hotel-search-aggregator

GitHub: SandiRidwan/tiktok-hotel-search-aggregator

该项目通过捕获 TikTok 移动端会话,批量抓取应用内 Places 搜索中的酒店列表和多家 OTA 平台的实时价格数据,存入 SQLite 并导出 CSV。

Stars: 0 | Forks: 0

Sandi Ridwan Tagline
![Python](https://img.shields.io/badge/Python-3.12-AB47BC?style=for-the-badge&logo=python&logoColor=white) ![mitmproxy](https://img.shields.io/badge/mitmproxy-Traffic_Interception-AB47BC?style=for-the-badge&logo=wireshark&logoColor=white) ![FastAPI](https://img.shields.io/badge/FastAPI-RPC_Sign_Server-009688?style=for-the-badge&logo=fastapi&logoColor=white) ![SQLite](https://img.shields.io/badge/SQLite-WAL_Mode-003B57?style=for-the-badge&logo=sqlite&logoColor=white) ![TikTok](https://img.shields.io/badge/TikTok-Places_API-000000?style=for-the-badge&logo=tiktok&logoColor=white) ![Keywords](https://img.shields.io/badge/Keywords-1%2C617%2F1%2C617-00C853?style=for-the-badge) ![License](https://img.shields.io/badge/License-MIT-0D1117?style=for-the-badge)
``` ╔══════════════════════════════════════════════════════════════════════════╗ ║ ║ ║ 📍 T I K T O K H O T E L S E A R C H A G G R E G A T O R ║ ║ ║ ║ keyword ──▶ search/place/ ──▶ hotel + OTA price ──▶ SQLite ║ ║ ║ ║ 1,617 keywords · Multi-country · Live progress monitor ║ ╚══════════════════════════════════════════════════════════════════════════╝ ``` ## 🎬 演示
Watch full demo
Click to watch — live terminal monitor, sample query run, CSV export walkthrough


完整演示 —— 关键词运行、实时仪表盘、最终数据集
## 🧠 概述 **TikTok Hotel Search & Price Aggregator** 致力于发现并抓取 TikTok 应用内“Places”搜索标签页中展示的酒店和住宿信息,捕捉印度尼西亚及亚洲部分地区的 **1,617 个基于位置的关键词** 的实时 OTA 价格(Trip.com、Agoda、Traveloka、tiket.com、Rakuten Travel)——整个过程完全无需触及 TikTok 对网页爬虫极不友好的公共入口。用于与 TikTok 私有移动 API 通信的会话和设备指纹是通过 mitmproxy 直接从真实的应用流量中捕获的,而不是从头伪造的。
| 指标 | 值 | |-------:|:------| | 🔑 已处理关键词 | **1,617 / 1,617 — 100% 完成** | | 🏨 目标 | TikTok 搜索 → "Places" 标签页(酒店/住宿结果) | | 💰 已捕获价格来源 | Trip.com · Agoda · Traveloka · tiket.com · Rakuten Travel | | 🌏 覆盖范围 | 印度尼西亚城市 + 部分亚洲地区(甲米、横滨、清迈、乔治市) | | 🔐 会话方法 | mitmproxy 流量捕获 → 真实的 `sessionid` / `device_id` / `install_id` | | 🧩 签名层 | 本地 FastAPI RPC 签名服务器——作为轻量级签名替代方案,并非密码学破解 | | 🗄️ 存储 | SQLite WAL 模式 · 基于 `(poi_id, merchant)` 去重 · 支持按关键词检查点/恢复 | | 📊 实时监控 | 终端仪表盘,3秒刷新,检查进度无需停止爬虫 | | 📁 导出 | 一键 CSV 导出,完整数据集,随时可交付给客户 |
## 🏗️ 架构 ``` ┌──────────────────────────────────────────────────────────────────────┐ │ SIGN SERVER (FastAPI · :8080) │ │ POST /sign → { X-Gorgon, X-Khronos } per request │ └──────────────────────────────┬───────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────────────┐ │ MASS SCRAPER (tiktok_mass_scraper) │ │ real session (sessionid/device_id from mitmproxy capture) │ │ + signed headers from Sign Server │ │ → GET search/place/?keyword=...&offset=N │ │ → loop offset until empty response │ └──────────────────────────────┬───────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────────────┐ │ SQLite (WAL mode) │ │ data_hotel — hotel + merchant price rows, UNIQUE(poi_id, merchant) │ │ progres_keyword — checkpoint table, resume-safe on restart │ └──────────────────────────────┬───────────────────────────────────────┘ │ ┌────────────┴─────────────┐ ▼ ▼ monitor_live.py export_csv.py (3s refresh dashboard) (full dataset → CSV) ``` ## ⚡ 解决的技术挑战 ### 挑战 1 — TikTok 的私有移动 API 没有公开文档 **问题:** TikTok 应用内搜索的 "Places" 标签页并不是公开的 API——既没有文档,也没有 SDK,而且请求需要浏览器爬虫根本不具备的设备身份和签名 header。 **解决方案:** 在 Android 设备/模拟器上手动执行特定的用户操作(搜索酒店关键词,点击 Places 标签页)时,使用 **mitmproxy** 捕获真实的 App 流量。这为我们揭示了真实的 endpoint、确切的查询参数以及有效的会话——这是构建一切其他功能的基础。 ``` # 从拦截的流量中捕获的 session 值 — 而非猜测。 # 通过 environment variables 替换为你自己捕获的值,切勿硬编码。 SESSION_ID = os.getenv("TIKTOK_SESSION_ID") HEADERS = { "Cookie": f"sessionid={SESSION_ID}; install_id=...; store-country-code=id;", "User-Agent": "", } ``` ### 挑战 2 — 签名 Header 要求 (`X-Gorgon`) **问题:** TikTok 的移动端 endpoint 需要经过签名的 `X-Gorgon` / `X-Khronos` header 对。真实的算法存在于原生的 C++ (`libcms.so`) 中——要完全对其进行逆向工程需要使用 Unidbg 或 Frida 等工具,这超出了本项目的范围和时间进度。 **解决方案:** 构建了一个小型的本地 FastAPI "签名服务器",用于生成替代签名。事实证明,只要 **会话和设备身份是真实的**,Places endpoint 就会接受它——这些是从 mitmproxy 流量中捕获的,而不是依赖于签名本身。在代码中已透明地将此记录为一种轻量级技术,并未声称是完全的密码学破解。 ``` # sign_server.py — 本地 RPC,并非对 X-Gorgon 的密码学逆向工程 @app.post("/sign") def generate_mock_gorgon(url: str, ts: int): # Lightweight signature stand-in — works because the session/device # identity (captured via mitmproxy) carries the real trust signal. # A full native implementation (libcms.so via Unidbg) is a documented # next step, not what's running here. return {"X-Gorgon": hashlib.md5(f"{url}{ts}salt".encode()).hexdigest()} ``` ### 挑战 3 — 项目中期的 Schema 迁移且不丢失进度 **问题:** 在运行 1,617 个关键词的过程中,schema 需要进行两项更改——用正式的 `nomor_urut` 排名列替换 `offset_ke`,并新增一个 `harga_asli`(折扣前价格)列。在修复落地之前,有数百个关键词因为 `no column named offset_ke` 而保存失败。 **解决方案:** 使用最终的 14 列 schema 重建了 `data_hotel` 表,然后重置了 `progres_keyword` 检查点表,这样每一个在修复前失败的关键词都会在下一次运行时自动重试——无需手动整理“哪些失败了”的列表。 ``` # 重置 checkpoint — schema 修复之前失败的关键词将自动重试 cursor.execute("DELETE FROM progres_keyword") conn.commit() # 下次运行会重新尝试每个尚未标记为完成的关键词 ``` ### 挑战 4 — 长时间运行的任务需要在不停机的情况下具备可见性 **问题:** 运行 1,617 个关键词需要很长时间。仅仅为了检查进度而停止爬虫会浪费数小时的运行时间。 **解决方案:** 一个独立的 `monitor_live.py` 脚本会每 3 秒轮询一次同一个 SQLite 数据库,并打印出清晰的终端仪表盘——包含酒店总数、已完成关键词、唯一商家以及最新数据行——同时爬虫继续运行,不受影响。 ``` while True: cursor.execute("SELECT COUNT(*) FROM data_hotel") total_hotel = cursor.fetchone()[0] cursor.execute("SELECT COUNT(*) FROM progres_keyword") keywords_done = cursor.fetchone()[0] os.system("cls" if os.name == "nt" else "clear") print(f"Hotels: {total_hotel} | Keywords done: {keywords_done}/1617") time.sleep(3) ``` ## 📁 文件结构 ``` tiktok-hotel-aggregator/ ├── tiktok_mass_scraper.py # ⭐ Main scraping engine — keyword loop + pagination ├── sign_server.py # FastAPI RPC — local signature stand-in ├── tiktok_api_client.py # Request builder — headers, params, session ├── database_setup.py # Schema creation — data_hotel + progres_keyword ├── upgrade_db.py # Schema migration helper (ALTER TABLE) ├── reset_db.py # Checkpoint reset for re-runs ├── monitor_live.py # Live terminal dashboard, 3s refresh ├── export_csv.py # One-command full dataset → CSV ├── check_db.py # Quick DB sanity check ├── target_hotel_keywords.csv # Keyword list — city + scroll depth ├── tiktok_data_center.db # SQLite output (WAL mode) └── README.md ``` ## 🚀 快速开始 ### 1. 安装 ``` git clone https://github.com/sandiridwan/tiktok-hotel-aggregator.git cd tiktok-hotel-aggregator python -m venv venv && source venv/bin/activate # Windows: venv\Scripts\activate pip install -r requirements.txt ``` ### 2. 配置会话 ``` # 通过 mitmproxy 捕获你自己的 session,然后设置: export SESSION_ID="your_captured_sessionid" export DEVICE_ID="your_captured_device_id" ``` ### 3. 运行 ``` # 启动本地 sign server python sign_server.py & # 针对你的关键词列表运行 scraper python tiktok_mass_scraper.py # 在第二个终端中 — 查看实时进度 python monitor_live.py # 完成后 — 将所有内容导出为 CSV python export_csv.py ``` ## 📊 实时运行结果 ``` Run summary — TikTok Hotel Search & Price Aggregator ✅ Keywords processed 1,617 / 1,617 — 100% complete ✅ Hotel + price rows 22,973+ (last documented checkpoint, final count higher) ✅ Merchants captured Trip.com · Agoda · Traveloka · tiket.com · Rakuten Travel ✅ Dedup UNIQUE(poi_id, merchant) — zero duplicate rows ✅ Schema migration Recovered cleanly mid-run, zero data loss on retry ────────────────────────────────────────────────────────────── Status: Completed · Delivered to client ``` ## 🛠️ 技术栈
| 层级 | 技术 | |-------|------------| | **语言** | Python 3.12 | | **流量捕获** | mitmproxy — 提取真实的会话/设备身份 | | **签名层** | FastAPI — 本地 RPC 签名服务器 | | **HTTP 客户端** | requests,附带真实捕获的会话 header | | **数据库** | SQLite — WAL 模式,检查点-恢复模式 | | **监控** | 自定义终端仪表盘(3秒轮询) | | **导出** | csv 模块 — 按需全量数据集导出 |
## 📝 经验教训 1. **对于某些 endpoint,会话身份胜过签名复杂性** — 一个真实的 `sessionid` 和设备指纹比格式完美但虚假的签名具有更高的信任度。在寻求最困难的解决方案之前,务必先检查系统实际验证的是什么。 2. **mitmproxy 是了解未公开移动 API 的最快途径** — 观察一个真实的请求胜过数小时的猜测参数名称。 3. **诚实对待技术范围很重要** — 将签名替代方案如实标注(而非密码学破解),可以在客户或审查者要求技术细节时使工作经得起推敲。 4. **检查点表挽救了长时间运行的任务** — 运行中期的单一 schema 不匹配并没有导致从零开始重启;检查点重置使得恢复过程自动化了。 5. **实时仪表盘在长任务中物超所值** — 无需为了检查进度而暂停长达数天的抓取工作。 ## 👤 作者
**数据自动化工程师 · 网页抓取专家 · AI 自动化构建者** 📍 印度尼西亚,中苏拉威西省,帕卢 [![Upwork](https://img.shields.io/badge/Upwork-Hire_Me-AB47BC?style=for-the-badge&logo=upwork&logoColor=white)](https://www.upwork.com/freelancers/sandiridwan) [![LinkedIn](https://img.shields.io/badge/LinkedIn-Connect-0077B5?style=for-the-badge&logo=linkedin&logoColor=white)](https://linkedin.com/in/sandi-ridwan) [![GitHub](https://img.shields.io/badge/GitHub-Follow-181717?style=for-the-badge&logo=github&logoColor=white)](https://github.com/SandiRidwan)
## 📄 许可证 MIT 许可证 — 仅用于教育和客户交付目的。
标签:Python, SQLite, 命令控制, 数据采集, 无后门, 逆向工具, 酒店数据