一、个人简介
💖💖作者:计算机编程果茶熊 💙💙个人简介:曾长期从事计算机专业培训教学,担任过编程老师,同时本人也热爱上课教学,擅长Java、微信小程序、Python、Golang、安卓Android等多个IT方向。会做一些项目定制化开发、代码讲解、答辩教学、文档编写、也懂一些降重方面的技巧。平常喜欢分享一些自己开发中遇到的问题的解决办法,也喜欢交流技术,大家有技术代码这一块的问题可以问我! 💛💛想说的话:感谢大家的关注与支持! 💜💜 网站实战项目 安卓/小程序实战项目 大数据实战项目 计算机毕业设计选题 💕💕文末获取源码联系计算机编程果茶熊
二、系统介绍
开发语言:Java+Python 数据库:MySQL 系统架构:B/S 后端框架:SpringBoot(Spring+SpringMVC+Mybatis)+Django 前端:Vue+HTML+CSS+JavaScript+jQuery
本系统是一个基于Spring Boot和Vue技术架构开发的江苏旅游景点推荐平台,采用B/S模式实现旅游信息的智能化展示与管理。系统运用MySQL数据库进行数据存储,通过前后端分离的开发方式,为游客用户和平台管理方提供全方位的旅游信息服务支持。平台核心功能涵盖用户账号的注册与个性化管理、景点分类的多层级组织与标签化归类、旅游景点的详细信息录入与多维度展示、江苏景点爬虫的自动化数据采集与更新、系统管理的权限控制与日志追踪、可视化大屏的数据统计与图表呈现以及个人中心的浏览历史和收藏管理。系统通过整合这些业务模块,构建起一个信息丰富、交互便捷、数据可视的旅游服务体系,特别是利用网络爬虫技术从各大旅游网站自动抓取江苏地区的景点信息、门票价格、开放时间、游客评价等实时数据,经过数据清洗和结构化处理后存入数据库,再结合景点分类筛选、智能推荐算法、可视化大屏展示等功能,有效解决了传统旅游信息查询渠道分散、更新滞后、缺乏直观对比等实际问题,为游客的出行规划和景区的数字化运营提供了实用的技术平台,推动旅游服务向智能化、数据化方向发展。
三、视频解说
四、部分功能展示
五、部分代码展示
/**
* 核心功能一: 江苏景点爬虫数据采集与智能解析
* 处理景点信息爬取、数据清洗、去重入库、更新检测等核心业务
*/
@PostMapping("/crawlJiangsuScenic")
@Transactional(rollbackFor = Exception.class)
public Result crawlJiangsuScenic(@RequestParam String source, @RequestParam Integer pageCount) {
// 验证爬虫任务参数
if (!Arrays.asList("ctrip", "mafengwo", "qunar").contains(source)) {
return Result.error("不支持的数据源,仅支持ctrip/mafengwo/qunar");
}
// 创建爬虫任务记录
CrawlTask task = new CrawlTask();
task.setSource(source);
task.setTargetRegion("江苏");
task.setPageCount(pageCount);
task.setStatus(1); // 进行中
task.setStartTime(new Date());
crawlTaskMapper.insert(task);
int successCount = 0;
int duplicateCount = 0;
int errorCount = 0;
try {
// 根据不同数据源选择爬虫策略
List<ScenicRawData> rawDataList = new ArrayList<>();
if ("ctrip".equals(source)) {
rawDataList = crawlFromCtrip(pageCount);
} else if ("mafengwo".equals(source)) {
rawDataList = crawlFromMafengwo(pageCount);
} else if ("qunar".equals(source)) {
rawDataList = crawlFromQunar(pageCount);
}
// 遍历处理爬取的原始数据
for (ScenicRawData rawData : rawDataList) {
try {
// 数据清洗和标准化处理
String cleanName = cleanScenicName(rawData.getName());
String cleanAddress = cleanAddress(rawData.getAddress());
// 提取景点关键信息
Map<String, Object> parsedData = parseRawData(rawData);
String description = (String) parsedData.get("description");
BigDecimal price = (BigDecimal) parsedData.get("price");
String openTime = (String) parsedData.get("openTime");
Double rating = (Double) parsedData.get("rating");
String contactPhone = (String) parsedData.get("contactPhone");
// 检查景点是否已存在(基于名称和地址相似度判断)
QueryWrapper<TouristScenic> checkWrapper = new QueryWrapper<>();
checkWrapper.eq("name", cleanName)
.or()
.apply("LOCATE({0}, address) > 0", cleanAddress);
TouristScenic existingScenic = touristScenicMapper.selectOne(checkWrapper);
if (existingScenic != null) {
// 已存在则更新信息
boolean needUpdate = false;
// 比对价格变化
if (price != null && !price.equals(existingScenic.getPrice())) {
existingScenic.setPrice(price);
needUpdate = true;
}
// 比对评分变化
if (rating != null && Math.abs(rating - existingScenic.getRating()) > 0.1) {
existingScenic.setRating(rating);
needUpdate = true;
}
// 比对开放时间变化
if (openTime != null && !openTime.equals(existingScenic.getOpenTime())) {
existingScenic.setOpenTime(openTime);
needUpdate = true;
}
if (needUpdate) {
existingScenic.setUpdateTime(new Date());
existingScenic.setDataSource(source);
touristScenicMapper.updateById(existingScenic);
successCount++;
} else {
duplicateCount++;
}
} else {
// 新增景点信息
TouristScenic scenic = new TouristScenic();
scenic.setName(cleanName);
scenic.setAddress(cleanAddress);
scenic.setDescription(description);
scenic.setPrice(price != null ? price : BigDecimal.ZERO);
scenic.setOpenTime(openTime);
scenic.setRating(rating != null ? rating : 0.0);
scenic.setContactPhone(contactPhone);
scenic.setDataSource(source);
// 智能分类匹配
Long categoryId = matchScenicCategory(cleanName, description);
scenic.setCategoryId(categoryId);
// 提取地理位置信息
Map<String, String> location = extractLocation(cleanAddress);
scenic.setCity(location.get("city"));
scenic.setDistrict(location.get("district"));
// 下载并保存景点图片
if (rawData.getImageUrls() != null && !rawData.getImageUrls().isEmpty()) {
String localImagePath = downloadAndSaveImages(rawData.getImageUrls(), cleanName);
scenic.setImages(localImagePath);
}
scenic.setViewCount(0);
scenic.setCollectCount(0);
scenic.setStatus(1); // 已发布
scenic.setCreateTime(new Date());
scenic.setUpdateTime(new Date());
touristScenicMapper.insert(scenic);
successCount++;
}
// 保存爬虫原始数据用于后续分析
CrawlData crawlData = new CrawlData();
crawlData.setTaskId(task.getId());
crawlData.setSource(source);
crawlData.setRawContent(rawData.toJsonString());
crawlData.setCreateTime(new Date());
crawlDataMapper.insert(crawlData);
} catch (Exception e) {
errorCount++;
// 记录错误日志
logError(task.getId(), rawData.getName(), e.getMessage());
}
}
// 更新爬虫任务状态
task.setStatus(2); // 已完成
task.setEndTime(new Date());
task.setSuccessCount(successCount);
task.setDuplicateCount(duplicateCount);
task.setErrorCount(errorCount);
crawlTaskMapper.updateById(task);
Map<String, Object> resultData = new HashMap<>();
resultData.put("successCount", successCount);
resultData.put("duplicateCount", duplicateCount);
resultData.put("errorCount", errorCount);
return Result.success("爬虫任务执行完成", resultData);
} catch (Exception e) {
task.setStatus(3); // 失败
task.setErrorMsg(e.getMessage());
crawlTaskMapper.updateById(task);
return Result.error("爬虫任务执行失败:" + e.getMessage());
}
}
/**
* 核心功能二: 景点智能推荐与个性化筛选
* 处理用户偏好分析、协同过滤推荐、景点排序、收藏管理等业务
*/
@PostMapping("/getRecommendScenic")
public Result getRecommendScenic(@RequestParam(required = false) Long userId,
@RequestParam(required = false) Long categoryId,
@RequestParam(required = false) String city,
@RequestParam(required = false) BigDecimal maxPrice,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
List<TouristScenic> recommendList = new ArrayList<>();
// 如果用户已登录,进行个性化推荐
if (userId != null) {
User user = userMapper.selectById(userId);
if (user != null) {
// 分析用户浏览历史,提取偏好特征
QueryWrapper<BrowseHistory> historyWrapper = new QueryWrapper<>();
historyWrapper.eq("user_id", userId)
.orderByDesc("browse_time")
.last("LIMIT 20");
List<BrowseHistory> historyList = browseHistoryMapper.selectList(historyWrapper);
// 统计用户偏好的景点分类
Map<Long, Integer> categoryPreference = new HashMap<>();
Map<String, Integer> cityPreference = new HashMap<>();
for (BrowseHistory history : historyList) {
TouristScenic scenic = touristScenicMapper.selectById(history.getScenicId());
if (scenic != null) {
categoryPreference.put(scenic.getCategoryId(),
categoryPreference.getOrDefault(scenic.getCategoryId(), 0) + 1);
cityPreference.put(scenic.getCity(),
cityPreference.getOrDefault(scenic.getCity(), 0) + 1);
}
}
// 找出偏好度最高的分类和城市
Long preferredCategoryId = categoryPreference.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.orElse(null);
String preferredCity = cityPreference.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.orElse(null);
// 构建推荐查询条件
QueryWrapper<TouristScenic> recommendWrapper = new QueryWrapper<>();
recommendWrapper.eq("status", 1);
if (categoryId != null) {
recommendWrapper.eq("category_id", categoryId);
} else if (preferredCategoryId != null) {
recommendWrapper.eq("category_id", preferredCategoryId);
}
if (city != null) {
recommendWrapper.eq("city", city);
} else if (preferredCity != null) {
recommendWrapper.eq("city", preferredCity);
}
if (maxPrice != null) {
recommendWrapper.le("price", maxPrice);
}
// 排除已浏览过的景点
List<Long> browsedScenicIds = historyList.stream()
.map(BrowseHistory::getScenicId)
.collect(Collectors.toList());
if (!browsedScenicIds.isEmpty()) {
recommendWrapper.notIn("id", browsedScenicIds);
}
// 按评分和浏览量综合排序
recommendWrapper.orderByDesc("rating * 0.6 + view_count * 0.0001");
Page<TouristScenic> scenicPage = new Page<>(page, size);
Page<TouristScenic> result = touristScenicMapper.selectPage(scenicPage, recommendWrapper);
recommendList = result.getRecords();
// 协同过滤:找相似用户喜欢的景点
if (recommendList.size() < size) {
List<TouristScenic> collaborativeList = findCollaborativeRecommend(userId, browsedScenicIds,
size - recommendList.size());
recommendList.addAll(collaborativeList);
}
}
} else {
// 未登录用户返回热门景点
QueryWrapper<TouristScenic> hotWrapper = new QueryWrapper<>();
hotWrapper.eq("status", 1);
if (categoryId != null) {
hotWrapper.eq("category_id", categoryId);
}
if (city != null) {
hotWrapper.eq("city", city);
}
if (maxPrice != null) {
hotWrapper.le("price", maxPrice);
}
hotWrapper.orderByDesc("view_count + collect_count * 2");
Page<TouristScenic> scenicPage = new Page<>(page, size);
Page<TouristScenic> result = touristScenicMapper.selectPage(scenicPage, hotWrapper);
recommendList = result.getRecords();
}
// 为每个景点添加用户收藏状态
if (userId != null) {
for (TouristScenic scenic : recommendList) {
QueryWrapper<UserCollect> collectWrapper = new QueryWrapper<>();
collectWrapper.eq("user_id", userId)
.eq("scenic_id", scenic.getId());
scenic.setIsCollected(userCollectMapper.selectCount(collectWrapper) > 0);
}
}
return Result.success("推荐景点查询成功", recommendList);
}
六、部分文档展示
七、END
💕💕文末获取源码联系计算机编程果茶熊