每次假期前夕,抢票、抢酒店几乎成了大家的共同习惯。特别是一些热门目的地,例如东京、京都、北京或三亚,酒店价格常常在短时间内剧烈波动,普通用户往往一不小心就错过了“划算价”。而如果能提前一步,实时掌握某地酒店价格的涨跌趋势,那无论是做数据分析、工具开发,还是单纯为了自己出行准备,都会变得更从容。
这次,我试着搭建了一个“网页数据实时采集+分析”的小方案,目标是让我们能第一时间抓取到携程网站上的酒店搜索结果,并从中提取关键的价格、名称等信息,还能实现多城市、多关键词并发处理。技术栈上,我选择了模拟浏览器行为的方式来访问页面,并使用了一套流处理方案来处理采集下来的内容。
为什么不直接用传统方式“抓数据”?
以前我做这种事情时,大多数情况是写一个Python脚本,用requests之类的库去爬网页。但很快你就会发现:现在很多大型网站已经不是静态页面了。页面上要等JavaScript加载完成,数据才会出现,而且结构也越来越复杂,用传统的HTML解析方法常常抓不到真正的内容。
再加上假期期间的数据变化很快,用定时器每隔几分钟抓一次,往往等你分析出来,价格已经变了。于是我开始考虑,能不能用一种更及时、更灵活的方案来解决这个问题。
一个可行的做法:模拟浏览器 + 分布式流处理
具体来说,我这次做了两件事:
- 用一个自动化浏览器(我用的是 Playwright)来模拟真实用户在页面上的行为,从而完整加载携程酒店搜索页上的动态内容,并配合代理IP(爬虫代理加强版)解决IP频繁访问的问题。同时还加上了用户信息的伪装,避免被识别为自动化程序。
- 把抓到的网页内容发到Kafka队列里,然后用一个支持流处理的系统(比如Spark Streaming)来持续接收、解析这些内容,提取出酒店的名称、价格等字段,再输出成结构化数据。
数据抓取部分:浏览器模拟 + 代理配置
以下是我抓取页面用的核心代码。它会打开浏览器,加载携程搜索页,然后抓取HTML源码,并将内容推送给Kafka消费者:
from playwright.sync_api import sync_playwright
from kafka import KafkaProducer
# 配置代理信息(参考亿牛云代理示例 www.16yun.cn)
PROXY = {
"server": "http://proxy.16yun.cn:3100",
"username": "16YUN",
"password": "16IP"
}
headers = {
"User-Agent": "Mozilla/5.0 ..."
}
cookies = [
{"name": "ctoken", "value": "cookie值", "domain": ".ctrip.com", "path": "/"}
]
def fetch_hotel_data(destination="Tokyo", checkin="2025-08-01", checkout="2025-08-05", keyword="温泉"):
with sync_playwright() as p:
browser = p.chromium.launch(proxy={"server": PROXY["server"], "username": PROXY["username"], "password": PROXY["password"]}, headless=True)
context = browser.new_context(extra_http_headers=headers)
context.add_cookies(cookies)
page = context.new_page()
url = f"https://www.ctrip.com/hotels/{destination}-hotels?checkin={checkin}&checkout={checkout}&keyword={keyword}"
page.goto(url, timeout=60000)
page.wait_for_timeout(5000)
content = page.content()
browser.close()
return content
# 推送至Kafka队列
producer = KafkaProducer(bootstrap_servers='localhost:9092')
html = fetch_hotel_data()
producer.send('ctrip_hotel_html', value=html.encode('utf-8'))
实时处理部分:网页结构分析 + 内容提取
当数据被推到Kafka之后,我用了一段流处理的逻辑来接收这些网页内容,并用BeautifulSoup分析页面结构,把前10个酒店的名称和价格提取出来。大概长这样:
from pyspark.streaming import StreamingContext
from pyspark.streaming.kafka import KafkaUtils
from bs4 import BeautifulSoup
def parse_html(html):
soup = BeautifulSoup(html, 'html.parser')
hotels = []
for item in soup.select('.hotel_new_list .hotel_item')[:10]:
name = item.select_one('.hotel_name').get_text(strip=True)
price = item.select_one('.price').get_text(strip=True)
hotels.append({"name": name, "price": price})
return hotels
# 初始化流处理逻辑(假设已有SparkContext)
ssc = StreamingContext(sc, 10)
kvs = KafkaUtils.createDirectStream(ssc, ["ctrip_hotel_html"], {"metadata.broker.list": "localhost:9092"})
lines = kvs.map(lambda x: x[1])
def process_rdd(rdd):
for html in rdd.collect():
result = parse_html(html)
for hotel in result:
print(hotel)
lines.foreachRDD(process_rdd)
ssc.start()
ssc.awaitTermination()
什么场景下这种方式更适合?
在我看来,这套架构适合那种“数据变化快、监控维度多、不能错过窗口期”的应用场景,比如:
- 假期临近,多个城市的酒店预订价格需要同时监控;
- 用户希望在第一时间内掌握折扣信息或房源紧张状态;
- 想要根据实时搜索结果,做一些动态推荐、分析等策略;
- 页面结构复杂,不能用传统的静态抓取方式解决问题。
而如果只是每天或每周跑一遍,把结果存起来慢慢分析,那就没必要上这种流式架构,用Scrapy、requests写个定时任务,反而更轻量。
一点感想
做这个小系统的过程中我最大的感受是:现在网页越来越不像“网页”了,很多你看得见的数据,其实要经过好几层“动态加载”才能拿到。而要让数据采集尽可能接近真实用户体验,模拟浏览器的方式已经成了刚需。
另外,传统的“抓了再处理”的方式,在这种时效性强的场景下确实有点慢。如果你对这种方案感兴趣,不妨试试结合流处理工具,把采集和处理整合到一起。
如果你需要的是稳定、分布式、高并发的数据采集框架,这条路可能会有些门槛,但绝对值得试试看。