最近刚帮学弟审核完他的毕业设计,一个基于大数据的旅游景点分析系统。这个项目特别有意思,它抓取了国内主流旅游平台的公开数据,用Spark做清洗分析,最后通过ECharts实现可视化。我整理了下他的实现思路,加上自己做过类似项目的经验,分享给需要做数据分析类毕设的同学参考。
这类项目最大的价值在于:它能直观展示如何将大数据技术栈应用到真实业务场景。不像很多教科书案例用的都是清洗过的标准数据集,这个项目从数据采集、清洗到分析建模都是真实环境下的完整流程。对于计算机、统计、旅游管理相关专业的学生来说,既锻炼技术能力,又能产出有实际参考价值的分析结论。
项目采用典型的Lambda架构,兼顾批处理和实时分析需求:
选择这套方案主要考虑三点:
原始方案直接用Scrapy爬取,在实际运行中发现三个问题:
改进后的采集方案:
python复制# 使用中间件随机切换UserAgent
class RotateUserAgentMiddleware:
def process_request(self, request, spider):
request.headers['User-Agent'] = random.choice(USER_AGENT_LIST)
# 对接Selenium处理动态加载
def parse_with_selenium(response):
driver = webdriver.Chrome(options=chrome_options)
driver.get(response.url)
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CLASS_NAME, "review-list"))
)
# 解析动态加载后的页面内容
# 坐标统一转换
def convert_coordinate(lng, lat):
if '°' in lng: # 处理度分秒格式
return dms2decimal(lng), dms2decimal(lat)
return float(lng), float(lat)
景点热度由六个维度加权得出:
计算公式:
code复制热度指数 =
标准化(访问量)×0.3
+ 标准化(评分)×0.2
+ 评论增长率×0.15
+ 周边配套指数×0.15
+ 媒体指数×0.1
+ 季节系数×0.1
其中季节系数采用时间序列预测:
python复制from statsmodels.tsa.seasonal import seasonal_decompose
result = seasonal_decompose(visits_data, model='multiplicative', period=12)
seasonal = result.seasonal
通过LDA主题模型提取评论关键词:
python复制from sklearn.feature_extraction.text import CountVectorizer
from sklearn.decomposition import LatentDirichletAllocation
vectorizer = CountVectorizer(max_df=0.95, min_df=2)
tf = vectorizer.fit_transform(comments)
lda = LatentDirichletAllocation(n_components=5)
lda.fit(tf)
# 输出各主题关键词
def print_top_words(model, feature_names, n_top_words):
for topic_idx, topic in enumerate(model.components_):
print(f"Topic #{topic_idx}:")
print(" ".join([feature_names[i] for i in topic.argsort()[:-n_top_words - 1:-1]]))
分析发现家庭游客最关注:
而年轻游客更在意:
原始ECharts热力图在渲染全国数据时出现卡顿,通过以下方案优化:
javascript复制function aggregateData(points, zoomLevel) {
const gridSize = 10 - zoomLevel; // 动态调整网格大小
return heatmapPoints.reduce((acc, point) => {
const gridX = Math.floor(point[0] / gridSize) * gridSize;
const gridY = Math.floor(point[1] / gridSize) * gridSize;
const key = `${gridX},${gridY}`;
acc[key] = (acc[key] || 0) + point[2]; // 累加热度值
return acc;
}, {});
}
实现景点对比功能的关键代码:
javascript复制myChart.on('click', function(params) {
if (selectedSpots.size >= 3) return; // 限制最多对比3个景点
selectedSpots.add(params.name);
updateComparisonChart();
});
function updateComparisonChart() {
const seriesData = Array.from(selectedSpots).map(name => ({
name,
type: 'line',
data: getTrendData(name),
smooth: true
}));
comparisonChart.setOption({ series: seriesData });
}
在Spark处理用户行为日志时,某些热门景点数据量是普通景点的100倍以上,导致少数task执行时间过长。通过三重方案解决:
scala复制val skewedRDD = rawRDD.map {
case (spotId, data) if hotspotIds.contains(spotId) =>
(s"${Random.nextInt(10)}_$spotId", data)
case normal => normal
}
scala复制val partialAgg = skewedRDD.reduceByKey(partialFunc)
val finalAgg = partialAgg.map {
case (key, value) =>
if(key.contains("_")) (key.split("_")(1), value)
else (key, value)
}.reduceByKey(finalFunc)
不同数据源的坐标系不一致导致地图标注偏移,建立统一转换体系:
java复制public class CoordinateConverter {
private static double x_PI = 3.14159265358979324 * 3000.0 / 180.0;
public static double[] gcj02ToWgs84(double lng, double lat) {
if (outOfChina(lng, lat)) return new double[]{lng, lat};
double dlat = transformLat(lng - 105.0, lat - 35.0);
double dlng = transformLng(lng - 105.0, lat - 35.0);
double radlat = lat / 180.0 * PI;
double magic = Math.sin(radlat);
magic = 1 - ee * magic * magic;
double sqrtmagic = Math.sqrt(magic);
dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);
dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);
double mglat = lat + dlat;
double mglng = lng + dlng;
return new double[]{lng * 2 - mglng, lat * 2 - mglat};
}
}
java复制DataStream<VisitorCount> counts = env
.addSource(new KafkaSource())
.keyBy("spotId")
.timeWindow(Time.minutes(5))
.aggregate(new CountAggregator());
counts.map(new PredictionModel())
.addSink(new AlertSink());
python复制def build_graph():
G = nx.Graph()
G.add_nodes_from(user_ids, bipartite=0)
G.add_nodes_from(spot_ids, bipartite=1)
G.add_edges_from(visits_edges)
return G
# 使用Personalized PageRank计算推荐分数
ppr = nx.pagerank(G, personalization=user_dict)
python复制from transformers import pipeline
sentiment_analyzer = pipeline(
"text-classification",
model="bert-base-chinese",
tokenizer="bert-base-chinese"
)
def analyze_sentiment(text):
result = sentiment_analyzer(text[:512]) # 处理长文本截断
return {
'label': result[0]['label'],
'score': result[0]['score']
}
这个项目最值得借鉴的是它完整的技术闭环设计。从数据采集到最终可视化,每个环节都考虑了工程实践中的典型问题。特别是在有限硬件资源下,通过合理的架构设计和参数调优,实现了千万级数据的高效处理。建议同学们做类似项目时,不要过度追求技术复杂度,而是聚焦在如何用合适的技术解决实际的业务问题。