曼谷游玩攻略一文搞懂:3个代码模块搞定行程避坑 曼谷游玩攻略一文搞懂:3个代码模块搞定行程避坑 看了一堆教程还是不会写项目?别急,我们把复杂的旅游数据拆解成可执行的代码。这篇曼谷游玩攻略一文搞懂,不聊虚的,直接上实战。 很多开发者觉得旅游APP就是查地图,其实核心是数据清洗与逻辑判断。我们以曼谷为案例,构建一个轻量级行程规划器。它能自动识别“坑点”,比如高价餐厅或拥挤时段,并给出替代方案。这不是简单的爬虫,而是一个完整的决策引擎。 项目目标 我们要解决的核心痛点是:信息过载导致的决策瘫痪。曼谷景点多、交通乱、消费差异大。用户输入日期、预算、兴趣标签,系统输出最优路径。 目标拆解如下: 数据标准化:统一景点、酒店、餐厅的JSON格式。 权重算法:基于距离、评分、价格计算综合得分。 避坑逻辑:内置黑名单机制,过滤高溢价或差评聚集地。 可视化输出:生成简单的文本报告或Markdown行程表。 这不是为了做一个花哨的UI,而是验证后端逻辑的健壮性。就像RFC规范中定义的协议标准,我们的数据接口必须严格遵循约定,否则前端对接时会出各种幺蛾子。 目录结构 保持扁平化,便于维护。项目结构如下: bangkok-trip-planner/ ├── data/ │ ├── attractions.json # 景点基础数据 │ ├── restaurants.json # 餐饮数据 │ └── blacklist.json # 避坑黑名单 ├── core/ │ ├── __init__.py │ ├── calculator.py # 评分计算核心 │ └── filter.py # 过滤逻辑 ├── main.py # 入口文件 ├── requirements.txt └── README.md data 目录存放静态数据。这里强调一点,数据质量决定上限。blacklist.json 是灵魂,它记录了那些“本地人都不去”的地方。例如,某些网红夜市摊位的卫生评级极低,或者某些景点在特定月份关闭维护。 core 目录封装业务逻辑。calculator.py 负责计算,filter.py 负责剔除。这种分离设计符合单一职责原则,方便后续单元测试。 核心代码实现 1. 数据加载与预处理 先看 main.py 的初始化部分。 import json import os def load_data(filename): 加载JSON数据文件 filepath = os.path.join('data', filename) if not os.path.exists(filepath): raise FileNotFoundError(f数据文件 {filename} 不存在) with open(filepath, 'r', encoding='utf-8') as f: return json.load(f) # 全局数据加载 attractions = load_data('attractions.json') restaurants = load_data('restaurants.json') blacklist = load_data('blacklist.json') 代码很直白,但细节决定成败。encoding='utf-8' 必须显式指定,否则读取中文地名或泰文翻译时会报UnicodeDecodeError。这是很多新手容易踩的坑,尤其是在Linux服务器上部署时。 2. 评分算法核心 core/calculator.py 是项目的引擎。我们采用加权评分法。 def calculate_score(item, user_profile): 计算单个景点/餐厅的综合得分 :param item: 景点或餐厅的字典对象 :param user_profile: 用户画像,包含预算敏感度、兴趣偏好 :return: 0-100的浮点数得分 base_score = item.get('rating', 0) * 20 # 基础分,满分100 # 价格惩罚机制 if user_profile['budget'] == 'low': if item['price'] 500: # 泰铢 base_score -= 20 elif user_profile['budget'] == 'medium': if item['price'] 1000: base_score -= 10 # 兴趣匹配加分 user_interests = user_profile.get('interests', []) item_tags = item.get('tags', []) match_count = len(set(user_interests) set(item_tags)) base_score += match_count * 5 # 距离衰减因子 distance_km = item.get('distance_from_hub', 10) distance_penalty = min(distance_km * 2, 30) # 最多扣30分 base_score -= distance_penalty return max(0, min(100, base_score)) # 限制在0-100之间 逐行讲解: 基础分:利用现有的评分数据(如Google Maps评分)作为锚点。乘以20是因为原始评分通常是1-5星制。 价格惩罚:根据用户预算动态调整。低预算用户对高价敏感,扣分力度更大。这里用的是硬编码阈值,实际项目中应改为配置项。 兴趣匹配:使用集合交集计算匹配度。每个匹配标签加5分,简单有效。 距离衰减:这是关键。曼谷交通拥堵,距离远意味着时间成本高。线性衰减虽然粗糙,但足以反映“近水楼台”的优势。 3. 避坑过滤逻辑 core/filter.py 负责清洗数据。 def apply_blacklist_filter(items, blacklist_ids): 移除黑名单中的项目 :param items: 待过滤的列表 :param blacklist_ids: 黑名单ID集合 :return: 过滤后的列表 filtered_items = [] for item in items: if item['id'] not in blacklist_ids: # 额外检查:如果评分低于3.0且评论数少于10条,视为数据不足,暂时剔除 if item.get('rating', 5) 3.0 and item.get('review_count', 0) 10: continue filtered_items.append(item) return filtered_items def filter_by_opening_hours(items, current_time_str): 根据当前时间过滤未开放的项目 :param items: 项目列表 :param current_time_str: 当前时间字符串 HH:MM :return: 当前开放的项目 open_items = [] current_hour = int(current_time_str.split(':')[0]) for item in items: open_hour = item.get('open_hour', 0) close_hour = item.get('close_hour', 24) # 处理跨夜营业情况,如22:00-02:00 if open_hour = close_hour: if open_hour = current_hour close_hour: open_items.append(item) else: if current_hour = open_hour or current_hour close_hour: open_items.append(item) return open_items 注意 filter_by_opening_hours 中的逻辑。曼谷有些夜市凌晨才开,有些博物馆下午关门。简单的 start current end 会失效,必须处理跨夜场景。这是很多逻辑bug的源头。 运行与测试 编写 main.py 的主流程。 from core.calculator import calculate_score from core.filter import apply_blacklist_filter, filter_by_opening_hours import datetime def main(): # 模拟用户输入 user_profile = { 'budget': 'medium', 'interests': ['history', 'food'], 'start_location': 'Sukhumvit' } current_time = 14:30 # 1. 合并数据源 all_items = attractions + restaurants # 2. 黑名单过滤 blacklist_ids = {item['id'] for item in blacklist} filtered = apply_blacklist_filter(all_items, blacklist_ids) # 3. 时间过滤 available = filter_by_opening_hours(filtered, current_time) # 4. 评分排序 scored_items = [] for item in available: score = calculate_score(item, user_profile) item['final_score'] = score scored_items.append(item) scored_items.sort(key=lambda x: x['final_score'], reverse=True) # 5. 输出Top 5 print(f当前时间: {current_time}) print(- * 30) for i, item in enumerate(scored_items[:5], 1): print(f{i}. {item['name']} (Score: {item['final_score']:.1f})) print(f Type: {item['type']} | Price: {item['price']} THB) print(- * 30) if __name__ == '__main__': main() 运行结果示例: 当前时间: 14:30 ------------------------------ 1. Wat Pho (Score: 85.0) Type: Attraction | Price: 300 THB ------------------------------ 2. Khao Soi Khun Yai (Score: 82.5) Type: Restaurant | Price: 250 THB ------------------------------ 3. Jim Thompson House (Score: 78.0) Type: Attraction | Price: 200 THB ------------------------------ 测试要点: 修改 current_time 为 23:00,验证夜市数据是否出现。 将某景点ID加入 blacklist.json,验证其是否消失。 调整 budget 为 'low',观察高价餐厅得分是否显著下降。 单元测试建议使用 pytest,对 calculate_score 和 filter_by_opening_hours 编写边界值测试。特别是跨夜时间逻辑,务必覆盖 23:59 和 00:01 的情况。 优化扩展 当前版本是单机脚本,如何扩展为服务? 数据动态化: 目前数据是静态JSON。实际中,价格和营业时间会变。建议接入API,如Google Places API。但要注意频率限制,避免被封IP。参考RFC 7231标准中的HTTP方法语义,GET请求用于查询,POST用于更新。 缓存机制: 使用Redis缓存热点数据。曼谷核心区域的景点数据变化频率低,设置1小时TTL(Time To Live)即可。 路径规划集成: 引入GraphHopper或Valhalla等开源路由引擎。计算两点间最短路径,而非直线距离。曼谷的道路网络复杂,直线距离往往误导用户。 个性化推荐: 引入协同过滤算法。如果用户A喜欢某餐厅,且用户B有相似历史行为,则推荐用户A去过的其他餐厅。这需要积累大量用户行为数据,初期可用基于内容的推荐替代。 多语言支持: 泰国旅游旺季,中文、英文、日文用户占比高。使用i18n框架,将界面文本与代码分离。 小结 这个曼谷游玩攻略一文搞懂的项目,看似简单,实则涵盖了数据处理、算法设计、边界测试等多个环节。它不是一个大而全的旅游APP,而是一个可复用的决策模块。 你可以将其嵌入到你的博客系统中,生成每日推荐;也可以作为独立API服务,供前端调用。核心在于逻辑的严密性。就像RFC规范定义了网络通信的基础,我们的代码逻辑定义了数据流动的规则。 避坑不仅仅是排除黑名单,更是对数据质量的把控。一条错误的营业时间数据,可能导致用户白跑一趟。因此,数据清洗和验证的重要性不亚于算法本身。 这个知识点你面试被问过吗?留言说说,比如你是如何处理跨夜时间逻辑的,或者在推荐系统中如何平衡冷启动问题。