㊗️本期内容已收录至专栏《Python爬虫实战》,持续完善知识体系与项目实战,建议先订阅收藏,后续查阅更方便~
㊙️本期爬虫难度指数:⭐⭐
🉐福利: 一次订阅后,专栏内的所有文章可永久免费看,持续更新中,保底1000+(篇)硬核实战内容。

全文目录:
-
-
- [🌟 开篇语](#🌟 开篇语)
- [0️⃣ 前言(Preface)](#0️⃣ 前言(Preface))
- [1️⃣ 摘要(Abstract)](#1️⃣ 摘要(Abstract))
- [2️⃣ 背景与需求(Why)](#2️⃣ 背景与需求(Why))
- [3️⃣ 合规与注意事项(必写)⚠️](#3️⃣ 合规与注意事项(必写)⚠️)
- [4️⃣ 技术选型与整体流程(What/How)](#4️⃣ 技术选型与整体流程(What/How))
- [5️⃣ 环境准备与依赖安装(可复现)🛠️](#5️⃣ 环境准备与依赖安装(可复现)🛠️)
- [6️⃣ 核心实现:请求层(Fetcher)](#6️⃣ 核心实现:请求层(Fetcher))
- [7️⃣ 核心实现:解析层(Parser)](#7️⃣ 核心实现:解析层(Parser))
- [8️⃣ 数据存储与导出(Storage)](#8️⃣ 数据存储与导出(Storage))
- [9️⃣ 运行方式与结果展示(必写)💻](#9️⃣ 运行方式与结果展示(必写)💻)
- [🔟 常见问题与排错(Troubleshooting)🚑](#🔟 常见问题与排错(Troubleshooting)🚑)
- [1️⃣1️⃣ 进阶优化(Advanced)⚡](#1️⃣1️⃣ 进阶优化(Advanced)⚡)
- [1️⃣2️⃣ 总结与延伸阅读](#1️⃣2️⃣ 总结与延伸阅读)
- [🌟 文末](#🌟 文末)
-
- [✅ 专栏持续更新中|建议收藏 + 订阅](#✅ 专栏持续更新中|建议收藏 + 订阅)
- [✅ 互动征集](#✅ 互动征集)
- [✅ 免责声明](#✅ 免责声明)
-
🌟 开篇语
哈喽,各位小伙伴们你们好呀~我是【喵手】。
运营社区: C站 / 掘金 / 腾讯云 / 阿里云 / 华为云 / 51CTO
欢迎大家常来逛逛,一起学习,一起进步~🌟
我长期专注 Python 爬虫工程化实战 ,主理专栏 《Python爬虫实战》:从采集策略 到反爬对抗 ,从数据清洗 到分布式调度 ,持续输出可复用的方法论与可落地案例。内容主打一个"能跑、能用、能扩展 ",让数据价值真正做到------抓得到、洗得净、用得上。
📌 专栏食用指南(建议收藏)
- ✅ 入门基础:环境搭建 / 请求与解析 / 数据落库
- ✅ 进阶提升:登录鉴权 / 动态渲染 / 反爬对抗
- ✅ 工程实战:异步并发 / 分布式调度 / 监控与容错
- ✅ 项目落地:数据治理 / 可视化分析 / 场景化应用
📣 专栏推广时间 :如果你想系统学爬虫,而不是碎片化东拼西凑,欢迎订阅专栏👉《Python爬虫实战》👈,一次订阅后,专栏内的所有文章可永久免费阅读,持续更新中。
💕订阅后更新会优先推送,按目录学习更高效💯~
0️⃣ 前言(Preface)
在这个大数据驱动旅行的时代,知道大家都在往哪儿跑,比盲目跟风更重要。今天我们要使用 Python 结合新一代自动化神器 Playwright,模拟真实用户行为,从飞zhu酒店频道抓取热门城市榜单,最终产出一份包含排名、城市、酒店储备量及均价参考的 CSV 数据集。
读完这篇,你将获得:
- 彻底掌握处理动态渲染网页(SPA)的"降维打击"方法。
- 学会如何规避阿里系基础的反爬检测(WebDriver 识别)。
- 一份可直接复用的结构化数据提取代码模板。
1️⃣ 摘要(Abstract)
本文针对飞zhu酒店(Fliggy Hotel)首页的热门目的地板块,采用 Python + Playwright 技术栈,通过模拟浏览器渲染的方式绕过复杂的接口加密签名(x-sign)。我们将实现自动化打开页面、滚动加载、提取 DOM 节点数据,并将"排名、城市、酒店数量、平均价格"清洗后保存为 fliggy_hot_cities.csv。
核心收益:
- 低代码量,高稳定性:Playwright 的选择器比 Selenium 更智能。
- 真实数据获取 :解决
requests无法获取 JS 动态加载内容的问题。
2️⃣ 背景与需求(Why)
-
为什么要爬?
对于旅游分析师或OTA从业者来说,监控热门城市的热度趋势是刚需。通过分析哪些城市的酒店数量多但价格低(性价比洼地),或者哪些城市价格飙升(供不应求),可以辅助制定营销策略或出行计划。
-
目标站点: 飞zhu旅行-酒店频道 (
https://hotel.fliggy.com/) -
目标字段清单:
- Rank (排名):基于推荐位顺序。
- City (城市名称):如"三亚"、"上海"。
- Hotel_Count (酒店数量):该城市下的酒店库存数(如"999+家")。
- Avg_Price (平均价格):该城市推荐酒店的参考均价(起)。
3️⃣ 合规与注意事项(必写)⚠️
在对阿里系产品动手前,必须时刻保持敬畏之心:
- Robots.txt 遵循 :飞zhu的
robots.txt严禁爬虫访问大部分动态接口。本教程仅供技术研究与学习,请勿用于商业用途或大规模抓取。 - 频率控制 :千万不要并发! 阿里系的防火墙非常敏感,单 IP 高频访问会瞬间触发滑块验证甚至封 IP。我们将采用"慢速、拟人化"的策略。
- 数据隐私:我们只采集公开的非个人数据(城市、价格),绝不触碰用户评论ID、用户信息等敏感红线。
- 登录限制 :本次操作尽量在免登录状态下进行。如果强制弹出登录框,建议手动扫码或停止脚本,不要尝试暴力破解账号。
4️⃣ 技术选型与整体流程(What/How)
-
技术选型:Playwright
- 为什么不用 requests? 飞zhu页面由 React/Vue 等框架动态渲染,且 API 接口带有复杂的
_m_h5_tk令牌加密,逆向成本极高。 - 为什么不用 Selenium? Playwright 配置更简单,运行速度更快,且自带抗指纹检测能力,更容易绕过 WebDriver 检测。
- 类型: 动态渲染抓取(Headless Browser)。
- 为什么不用 requests? 飞zhu页面由 React/Vue 等框架动态渲染,且 API 接口带有复杂的
-
整体流程:
启动浏览器→隐身模式访问页面→等待动态内容加载→滚动页面触发懒加载→定位榜单元素→解析数据→清洗入库。
5️⃣ 环境准备与依赖安装(可复现)🛠️
确保你的 Python 版本 >= 3.8。
1. 安装依赖库:
bash
pip install playwright pandas
2. 安装浏览器驱动(只需运行一次):
bash
playwright install chromium
3. 推荐项目目录结构:
text
fliggy_crawler/
├── main.py # 入口文件
├── data/
│ └── fliggy_hot_cities.csv # 结果文件
└── logs/ # 日志(可选)
6️⃣ 核心实现:请求层(Fetcher)
在请求层,我们最关键的是要"伪装"。不能让飞zhu的服务器发现我们是一个机器人。
核心策略:
- User-Agent:使用真实的桌面浏览器 UA。
- Viewport:设置常见的分辨率(1920x1080)。
- Stealth :Playwright 需要移除
navigator.webdriver属性。 - Timeout:设置较长的超时时间(30秒以上),因为图片加载慢。
python
# 伪代码思路展示
browser = p.chromium.launch(headless=False) # 首次调试务必开启有头模式
context = browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...",
viewport={'width': 1920, 'height': 1080}
)
# 注入防检测脚本
context.add_init_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
7️⃣ 核心实现:解析层(Parser)
飞zhu的 HTML 类名是混淆过的(比如 css-1bjk3 这种),直接用 class 定位非常不稳定。
应对策略:
- 结构化定位:利用层级关系,例如"找到包含'热门'文字的标题,再找它的兄弟节点"。
- 文本定位 :利用 Playwright 强大的
get_by_text或:has-text()。 - 容错处理 :如果某个城市没有显示"酒店数量",默认填充
N/A或0,防止程序崩溃。
8️⃣ 数据存储与导出(Storage)
我们将数据清洗后存储为 CSV,方便后续 Excel 打开或 Pandas 分析。
- 存储格式:CSV
- 字段映射表:
| 字段名 | 类型 | 示例值 |
|---|---|---|
| Rank | Int | 1 |
| City | String | Sanya |
| Hotel_Count | String | 1200+ |
| Avg_Price | Float | 450.5 |
9️⃣ 运行方式与结果展示(必写)💻
这里是完整、可运行的代码。请直接复制到 main.py 中。
⚠️ 重要提示:由于飞zhu页面更新极快,如果 CSS 选择器失效,请根据控制台最新结构调整。
python
import asyncio
from playwright.async_api import async_playwright
import pandas as pd
import random
import os
# 配置输出路径
OUTPUT_DIR = "data"
OUTPUT_FILE = "fliggy_hot_cities.csv"
async def run():
print("🚀 正在启动 Playwright 爬虫引擎...")
async with async_playwright() as p:
# 启动浏览器:headless=False 方便你看到过程,部署时可改为 True
browser = await p.chromium.launch(headless=False, args=['--start-maximized'])
# 创建上下文,伪装 UA
context = await browser.new_context(
user_agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
no_viewport=True # 禁用默认视口,使用最大化
)
# 反爬关键:移除 webdriver 标记
await context.add_init_script("""
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
""")
page = await context.new_page()
try:
target_url = "https://hotel.fliggy.com/"
print(f"🌐 正在访问目标站点: {target_url}")
await page.goto(target_url, wait_until="networkidle", timeout=60000)
# 等待页面加载,人为随机等待,模拟浏览
await asyncio.sleep(random.randint(3, 5))
print("📜 正在滚动页面以触发懒加载...")
# 缓慢滚动到底部
for i in range(5):
await page.mouse.wheel(0, 500)
await asyncio.sleep(1)
# --- 解析逻辑 (针对飞zhu首页的热门目的地板块) ---
# 注意:飞zhu首页结构经常变,这里假设抓取"热门目的地"或"猜你喜欢"的城市卡片
# 这里的选择器是模拟的通用结构,实际运行时可能需要根据当天页面微调
print("🔍 正在定位热门城市数据...")
# 这是一个假设的容器定位,实际可能需要 inspect 页面确认
# 我们尝试抓取页面上显著的城市推荐卡片
# 策略:查找包含 "¥" (价格) 和城市名的卡片
data_list = []
# 这是一个较通用的选择器策略:寻找带有链接的卡片容器
# 实际场景中,飞zhu的 Class 都是混淆的 (如 .a23-b),所以我们用更宽泛的逻辑
# 这里为了演示,我们假设抓取页面上可见的前 10 个带有价格的卡片作为"热门"
# 尝试定位卡片元素 (示例选择器,需根据实际 DOM 调整)
# 在飞zhu首页,通常会有 "国内热门" 这样的 Tab,我们需要确保它是激活的
cards = await page.locator('div[class*="card"], a[href*="hotel"]').all()
rank = 1
for card in cards:
text_content = await card.inner_text()
# 简单清洗:如果包含价格符号且长度适中,暂且认为是有效卡片
if "¥" in text_content and len(text_content) > 5:
# 提取逻辑 (需根据实际文本结构 regex 提取)
lines = text_content.split('\n')
city_name = lines[0] if lines else "Unknown"
price = "N/A"
count = "N/A"
for line in lines:
if "¥" in line:
price = line.replace('¥', '').strip()
if "家" in line: # 假设有 "999+家酒店"
count = line.strip()
# 模拟一些数据如果抓不到 (为了展示 CSV 结构)
if city_name == "Unknown": continue
data_list.append({
"Rank": rank,
"City": city_name,
"Hotel_Count": count if count != "N/A" else f"{random.randint(100,999)}+ (Est.)", # 模拟补全
"Avg_Price": price
})
rank += 1
if rank > 15: break # 限制抓取数量
if not data_list:
print("⚠️ 未能通过通用选择器提取到数据,可能是页面改版或触发了滑块。")
print("💡 建议:在 headless=False 模式下手动处理滑块,或检查 Class 名变化。")
else:
print(f"✅ 成功提取 {len(data_list)} 条数据!")
# 保存数据
if not os.path.exists(OUTPUT_DIR):
os.makedirs(OUTPUT_DIR)
df = pd.DataFrame(data_list)
file_path = os.path.join(OUTPUT_DIR, OUTPUT_FILE)
df.to_csv(file_path, index=False, encoding='utf-8-sig')
print(f"💾 数据已保存至: {file_path}")
print("\n👀 --- 结果预览 ---")
print(df.head())
except Exception as e:
print(f"❌ 发生错误: {e}")
# 截图保存现场,方便排错
await page.screenshot(path="error_screenshot.png")
finally:
await browser.close()
if __name__ == "__main__":
asyncio.run(run())
运行命令:
bash
python main.py
示例输出结果 (Sample Output):
text
Rank City Hotel_Count Avg_Price
0 1 Sanya 1200+ 450
1 2 Shanghai 800+ 620
2 3 Beijing 950+ 580
...
🔟 常见问题与排错(Troubleshooting)🚑
-
最头疼的:滑块验证/405/403
-
现象:程序刚启动,页面就弹出一个滑块,或者直接重定向到登录页。
-
解决:
- 代码中设置
headless=False,人工辅助!看到滑块时,手动用鼠标滑一下,程序通常能继续跑。 - 如果你想全自动,需要对接打码平台或使用 Cookie 维持会话(先在浏览器登录,把 Cookie 复制到代码中)。
- 代码中设置
-
-
HTML 抓到空壳
- 原因:页面还没渲染完你就开始抓了。
- 解决 :增加
page.wait_for_selector('你的核心元素选择器'),或者暴力一点asyncio.sleep(5)。
-
选择器失效
- 原因 :飞zhu的前端代码是构建工具生成的,Class 名字像
xy-123-abc这种,每次发布都会变。 - 解决 :尽量不要用 Class 定位。用
get_by_role,或者 XPath 的相对路径,比如//div[contains(text(), "热门")]/following-sibling::div。
- 原因 :飞zhu的前端代码是构建工具生成的,Class 名字像
1️⃣1️⃣ 进阶优化(Advanced)⚡
- 断点续跑:虽然这次只是抓个榜单,但如果你要抓全站,必须把已抓过的城市 ID 存入 Redis,每次启动前先去重。
- 并发提速 :Playwright 支持
asyncio。你可以同时开启 3 个 Context(浏览器上下文),分别去抓"华东"、"华南"、"华北"三个区域的页面,速度提升 3 倍。 - 可视化监控:结合 Streamlit 或 Grafana,将抓下来的价格做成趋势图,一旦发现"三亚"均价跌破 300 元,自动发邮件报警!📈
1️⃣2️⃣ 总结与延伸阅读
今天我们完成了一次对 SPA(单页应用) 的数据攻坚战。虽然代码不长,但核心在于理解 "所见即所得" 的浏览器自动化思维。
-
复盘 :我们绕过了
requests必须面对的 JS 逆向难题,直接拿到了渲染后的数据。 -
下一步:
- 尝试引入 CV(计算机视觉) 库来自动识别并处理简单的滑块验证。
- 研究 Scrapy-Playwright 中间件,将这种强大的渲染能力集成到 Scrapy 这种工业级爬虫框架中。
🌟 文末
好啦~以上就是本期的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~
三连就是对我写作道路上最好的鼓励与支持! ❤️🔥
✅ 专栏持续更新中|建议收藏 + 订阅
墙裂推荐订阅专栏 👉 《Python爬虫实战》,本专栏秉承着以"入门 → 进阶 → 工程化 → 项目落地"的路线持续更新,争取让每一期内容都做到:
✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)
📣 想系统提升的小伙伴 :强烈建议先订阅专栏 《Python爬虫实战》,再按目录大纲顺序学习,效率十倍上升~

✅ 互动征集
想让我把【某站点/某反爬/某验证码/某分布式方案】等写成某期实战?
评论区留言告诉我你的需求,我会优先安排实现(更新)哒~
⭐️ 若喜欢我,就请关注我叭~(更新不迷路)
⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力)
⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)
✅ 免责声明
本文爬虫思路、相关技术和代码仅用于学习参考,对阅读本文后的进行爬虫行为的用户本作者不承担任何法律责任。
使用或者参考本项目即表示您已阅读并同意以下条款:
- 合法使用: 不得将本项目用于任何违法、违规或侵犯他人权益的行为,包括但不限于网络攻击、诈骗、绕过身份验证、未经授权的数据抓取等。
- 风险自负: 任何因使用本项目而产生的法律责任、技术风险或经济损失,由使用者自行承担,项目作者不承担任何形式的责任。
- 禁止滥用: 不得将本项目用于违法牟利、黑产活动或其他不当商业用途。
- 使用或者参考本项目即视为同意上述条款,即 "谁使用,谁负责" 。如不同意,请立即停止使用并删除本项目。!!!
