1. 为什么选“酒店推荐系统”作为大数据毕设:技术栈组合的底层逻辑
每年到这个时间点,都会有不少人问我毕设选题的事。我收到的需求里,出现频率最高的组合就是“某某推荐系统 + Hadoop + Spark + Hive + 爬虫”。
先说结论:这类题确实适合作为大数据方向的毕业设计,但它不是“随便拼几个热门技术名词”那么简单。酒店推荐系统这个业务场景,几乎把大数据技术栈里最核心的几个环节都串起来了——数据采集(爬虫)、数据存储与管理(HDFS/Hive)、数据计算(Spark)、数据应用(推荐结果)、数据展示(可视化)。导师一眼看过去,能明确看到你对整个大数据处理链路有没有完整的认知,而不是只会在单机上跑个 Python 脚本。
1.1 Hadoop、Spark、Hive 在系统里分别扮演什么角色
很多同学一开始搞不清楚这三个东西的分工,导致做完项目被问“为什么这里用 Hive 不用 Spark SQL”“为什么数据要绕一圈进 HDFS”时答不上来。这里我用一个便于理解的类比说明:如果把整套系统比作一家酒店集团的数据处理中心——
- Hadoop(HDFS + YARN):是仓库和物流调度中心。HDFS 负责把爬虫抓到的日志、订单、评论等海量原始文件分块存起来;YARN 负责给各种计算任务分配“人力”(CPU 和内存)。
- Hive:是把仓库里的货物整理成“货架台账”的部门。它把 HDFS 上的结构化数据映射成一张张二维表,让你用极其接近 SQL 的 HiveQL 去做数据清洗、统计和探查,不需要写 Java 程序。它能解决“数据怎么组织、怎么查”的问题。
- Spark:是把台账拿去“算账”的精算团队。推荐系统要计算用户和酒店的相似度矩阵、要跑协同过滤,这类迭代式计算在 MapReduce 上会慢到让你怀疑人生,而 Spark 基于内存计算,性能要好得多。它能解决“海量数据上的复杂算法怎么算得快”的问题。
从整体流向看,爬虫采集的数据先落到 HDFS,接着在 Hive 里完成 ETL 和初步统计分析,再由 Spark 读取清洗后的结果跑推荐模型,最终把推荐结果写回 MySQL 供 Web 前端查询,同时用可视化图表展示统计结果。这条线路就是一个典型的离线数仓 + 离线推荐的大数据应用闭环。
1.2 业务场景为什么选“酒店”而不是“电影”或“商品”
你可能会问:推荐系统经典案例那么多,电影推荐(MovieLens)、电商商品推荐都很成熟,为什么酒店这个场景特别适合毕业设计?
核心原因有三个。第一,酒店数据的关联维度足够丰富。价格、星级、评分、位置(城市/区域)、设施、评论内容、入住时间等都有清晰的业务含义,方便做多维度统计可视化,也方便从多个角度解释推荐结果,论文不会“没东西可写”。第二,数据爬取相对可控。主流 OTA 平台的酒店列表和详情信息大多在页面结构上有规律可循,静态页面渲染较多,爬虫实现难度适中——比爬电商的强反爬要友好,比爬纯静态站点又更有技术含量。第三,推荐逻辑有真实业务价值。用户订酒店的核心决策因素(位置、价格、星级、评价)能够自然地融入基于物品的协同过滤算法,而不是硬套一个“用户-物品”矩阵就说自己是推荐系统。
我在实际做这套系统时,最深的体会是:酒店推荐在算法上不用做得多花哨,把 Item-Based CF 跑通、能解释清楚每个推荐结果是基于哪些相似酒店得出来的,已经能超过相当一部分只调库不做解释的毕设了。当然,这是后话,后面章节我专门展开算法实现细节。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从 0 到 1 采集数据:爬虫设计、反爬应对与数据落库
巧妇难为无米之炊。推荐系统没有数据就是空壳。我见过不少同学在这一步跌跟头——不是爬不下来,而是爬到一半被封 IP、数据不完整、字段脏乱差,最后清洗时间比爬取时间还长。
2.1 数据源选型与页面结构分析
我建议优先选择对爬虫相对友好的旅行类平台,比如同程、去哪儿的部分静态接口,也可以选择携程的部分搜索结果页。这里不推荐你直接用自己的主力账号去高频测试,也不鼓励去搞高强度分布式爬取——作为毕设演示,只需要覆盖 3-5 个热门城市、每个城市 100-300 家酒店,总数据量在 1 万条左右就足够了。
真正动手之前,先用浏览器 F12 开发者工具分析接口的请求和响应结构。以某 OTA 平台的搜索页为例,刷新后请求一个酒店列表的 Ajax 接口,返回内容是 JSON 格式,里面包含酒店名称、价格、评分、评论数、星级、经纬度等核心字段。这里有一个容易踩的坑:很多同学喜欢用 BeautifulSoup 去解析 HTML 页面,但实际上大多数酒店平台的数据都是通过 XHR 异步加载的,直接解析 HTML 拿不到完整列表,非要用就得上 Selenium。我的建议是直接用 Requests 模拟 Request Headers 请求 JSON 接口,再用 json 模块解析字段,简单高效。
采集字段时,要尽量贴合后续推荐和可视化需要。我的酒店基础信息表设计如下:
| 字段名 | 说明 | 推荐系统用途 |
|---|---|---|
| hotel_id | 酒店唯一标识 | 物品 ID |
| hotel_name | 酒店名称 | 展示用 |
| city | 所在城市 | 用户筛选与可视化 |
| star | 星级(1-5) | 相似度特征 |
| price | 参考价格(元/晚) | 价格带划分、相似度特征 |
| score | 综合评分 | 评分加权 |
| comment_num | 评论数量 | 热度加权 |
| lat / lng | 纬度 / 经度 | 地图可视化 |
另外,必须有用户行为数据才能做“用户协同过滤”。纯爬虫只能拿到酒店信息,拿不到用户的真实历史订单和评分。一个常见的做法是模拟构造用户行为数据:编写 Python 脚本随机生成 1000 个模拟用户(用户 ID 形如 U0001-U1000),再让每个用户随机对住过的 10-50 家酒店打分,分值与酒店真实评分做一定的正相关偏移。这种方式生成的 rating 数据既能支撑推荐系统演示,构图也比较自然。
2.2 爬虫核心代码骨架与反爬策略
爬虫这部分不需要太复杂,但要稳。下面是一个可运行的 Requests 爬虫骨架,我在实际采集时就是在这个基础上加了随机延时和代理切换:
python复制import requests
import time
import random
import json
HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
'(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'application/json, text/plain, */*',
'Referer': 'https://hotel.example.com/'
}
def fetch_hotels(city_id, page):
url = f'https://hotel.example.com/api/list?cityId={city_id}&page={page}'
try:
resp = requests.get(url, headers=HEADERS, timeout=10)
if resp.status_code == 200:
return resp.json()
else:
print(f'请求失败,状态码: {resp.status_code}')
return None
except Exception as e:
print(f'请求异常: {e}')
time.sleep(random.uniform(2, 5))
return None
def parse_hotel_list(data):
hotels = []
if not data or 'hotelList' not in data:
return hotels
for item in data['hotelList']:
hotels.append({
'hotel_id': item['id'],
'hotel_name': item['name'],
'city': item['cityName'],
'star': item.get('star', 0),
'price': item.get('price', 0),
'score': item.get('score', 0),
'comment_num': item.get('commentNum', 0),
'lat': item.get('lat', 0),
'lng': item.get('lng', 0),
})
return hotels
def crawl_all_pages(city_id, max_pages=10, sleep_range=(1, 3)):
