尧图精选

共享单车数据分析系统:Flask+Hadoop+爬虫实战

🕒 发布时间:2026/10/2 3:58:12 📁 来源:尧图网络
我最早接触这类项目就是照着课程设计需求做了一套共享单车数据分析系统。说实话用FlaskHadoop爬虫这套组合去搭一个完整的“采集—存储—计算—可视化”链路听起来像把一堆热门技术硬凑在一起但真正跑通了之后会发现它其实是理解大数据项目全流程最划算的一条路。这篇博文就把我当时做这个共享单车数据分析与辅助管理系统时踩过的坑、调通的配置、还有核心代码逻辑一次性整理出来。这个系统能解决什么问题往小里说它能定时抓取共享单车的位置和订单数据通过Hadoop做分布式的存储和统计分析再用Flask把结果暴露成API接口最后用ECharts在前端展示成热力图、折线图和区域排行榜。往大里说这套技术选型几乎就是小型大数据应用的模板换掉数据源它可以是网约车热力分析也可以是农产品价格可视化所以在很多课程设计和求职项目里这套组合非常常见。适合谁看如果你正在做大数据方向的课程设计、毕业设计或者想从纯后端转数据分析、想搞明白大数据项目各层之间怎么衔接这篇文章应该能帮你少走不少弯路。1. 整体架构与数据流设计1.1 为什么是FlaskHadoop爬虫这套组合先聊聊选型因为很多新手拿到这种题目第一反应是“为什么不直接用MySQLSpring Boot”连爬虫都搞出来是不是有点夸张。我的理解是这样的共享单车数据本身带有明显的位置属性数据量一旦铺到全城范围订单表和位置表会膨胀得很快如果所有计算都靠单机MySQL扛热点区域统计这种聚合查询很快就会变得很吃力。虽然课程设计的数据量远没到非用分布式不可的程度但用Hadoop可以完整体验HDFS的存储方式和MapReduce的计算模型这恰恰是大数据技术栈的核心。后端我选了Flask而不是Django原因很简单这个项目的主体是接口和数据展示不需要Django那种大而全的Admin后台、ORM和模板体系。Flask的灵活性强几分钟就能起一个服务而且和ECharts做前后端分离特别顺手。爬虫用requests加正则表达式就够了除非你要抓的数据量级到了需要并发调度的程度否则没必要上Scrapy那种重型框架。再补充一句很多网上的共享单车项目喜欢直接把CSV灌进MySQL再用Flask读MySQL出图这样确实能跑但那就把Hadoop硬生生架空了。真正合理的做法是让数据从收集开始就进入分布式体系这样答辩的时候不管问存储还是问计算你都有实打实的东西可以讲。1.2 数据流转链路拆解整个系统的数据流我画成了一条清晰的管道采集层爬虫定时抓取接口数据→ 落地为本地CSV/JSON → 上传HDFS → MapReduce批量统计 → 统计结果回写HDFS → Flask读取结果 → 输出JSON → ECharts渲染图表。这里最容易被忽略的是第一个传输环节爬虫抓到的数据先落本地再由shell脚本上传HDFS。很多人会想“反正最终要进HDFS不如爬虫直接写HDFS”我试过之后发现这是个坑。原因是爬虫的请求频率是不稳定的网络抖动会让HDFS连接的写入吞吐忽高忽低一旦中途断掉很容易出现半条损坏记录而且直接把HDFS写在业务代码里会让爬虫脚本和Hadoop集群强耦合每次想改上传逻辑都得动爬虫代码。落地本地的本质是把抓取和上传解耦。爬虫只负责把数据完整存下来上传步骤用crontab定时跑就行。哪怕某一次上传失败了也可以事后补推而不会污染之前的采集结果。这个设计习惯帮我省了很多处理脏数据的功夫后面细说。2. 数据采集层爬虫设计与数据清洗2.1 数据源与采集策略共享单车数据去哪找不要打那些已经关停或收紧接口的APP的主意重点找开放数据平台或者模拟测试接口。我当时用的是某城市公共交通数据开放平台上可以公开获取的单车位置数据接口返回JSON字段包括车辆编号、经度、纬度、电量、锁状态、抓取时间。采集策略上我建议用一个轻量的定时任务每15分钟抓一次全量位置快照。这样既能拿到足够的样本量又不会因为频率太高被封IP。请求头里的User-Agent要轮换然后每次请求之间用time.sleep加一个随机延时延时范围在0.5到1.5秒之间这个时间窗口比固定延时更能降低被识别为脚本的概率。抓下来的数据原始JSON长这样{data: {bike_id: 100123, lat: 39.9087, lng: 116.3975, battery: 87, lock: 0, timestamp: 2024-01-05 08:23:11}}如果某一天发现某条记录的经纬度跑到城市外面去了基本可以断定是接口返回了异常值就要在清洗阶段做过滤。2.2 正则表达式清洗与字段规整热词里反复出现的“spider爬虫正则表达式”其实就藏在这个环节。虽然接口返回JSON可以直接用json库解析但真实情况下你会遇到各种奇怪的脏数据有的是字段加了转义符有的是返回了一堆嵌套字符串用正则表达式做一层兜底清洗非常管用。我常用的几个正则场景包括从字符串里抠出合法的经纬度、过滤掉非数字电池量、校验时间戳格式。举个例子如果接口某个字段有时会带单位或者括号直接json解析会报错这时候先用正则把有效部分提出来再转类型会稳很多import re def parse_battery(raw_value): match re.search(r(\d{1,3}), str(raw_value)) if match: value int(match.group(1)) return value if 0 value 100 else None return None def parse_latlng(raw_value): match re.search(r([-]?\d\.\d{4,}), str(raw_value)) return float(match.group(1)) if match else None坐标偏移也是单车数据的经典坑。国内地图用的坐标系基本是GCJ-02加密后的火星坐标系而一些开放接口返回的是原始GPS的WGS-84坐标如果你直接把WGS-84的数据丢到高德底图上点位会整体偏移几百米画出来的热点图全都歪了。处理办法是写一个坐标系转换函数把WGS-84转成GCJ-02转换算法的代码网上很多核心是判断点位是否在国界外的is_out_of_china函数然后套用公式。清洗完的数据我统一整理成这样的CSV格式bike_id车辆编号lng经度已纠偏lat纬度已纠偏battery电量百分比lock_status0表示解锁1表示锁定region行政区域编号写脚本按经纬度打点分配collect_time采集时间戳这一步看起来很基础但它是后面所有统计能否准确的前提。我见过不少同学的数据清洗做得不彻底最后热点区域全堆在杭州西湖边或者上海人民广场就是因为缺失了坐标系转换和异常值剔除。3. 数据存储与计算Hadoop实战记录3.1 伪分布式搭建的几个重点很多新手一上来就想搭三台节点的集群但课程设计的环境往往是单机。单机一样可以用伪分布式模式跑通HDFS和MapReduce除了NameNode和DataNode在同一台机器上其余机制和真集群没有本质区别。所谓伪分布式就是把原来分散在多个节点上的进程全部压在一台机器上学习性价比最高排错也相对容易。我搭建时用的版本是Hadoop 3.3.x。核心配置文件主要有三个core-site.xml、hdfs-site.xml、yarn-site.xml。关键的参数就几个别被配置文件里的一大堆注释吓到core-site.xml里设置NameNode地址property namefs.defaultFS/name valuehdfs://localhost:9000/value /propertyhdfs-site.xml里重点关注副本数和NameNode目录。伪分布式环境只有一个DataNode副本数如果保持默认的3会产生大量冗余存储白白占用磁盘空间。我把它改成1既能保证数据完整保存又不会让单机磁盘很快爆掉property namedfs.replication/name value1/value /property如果你需要做高可用相关演示那就得引入Zookeeper做NameNode的自动故障切换。不过要注意Zookeeper整合HA需要额外配置journalnode和多个NameNode课程设计如果没强制要求其实没必要给自己加这个复杂度。伪分布式把核心链路跑通就够了。启动前必须先格式化NameNode不格式化直接start-dfs.sh会报各种找不到路径的错误hdfs namenode -format start-dfs.sh start-yarn.sh启动完用jps检查进程看到NameNode、DataNode、ResourceManager、NodeManager四个进程都在就说明Hadoop层就绪了。3.2 数据入库与MapReduce任务设计HDFS建目录的命名习惯要养好按数据分层来建hdfs dfs -mkdir -p /share_bike/data/raw hdfs dfs -mkdir -p /share_bike/output上传数据直接用put命令hdfs dfs -put /data/bike/2024_01_05.csv /share_bike/data/raw/到了计算层这是整条链路里最能体现“大数据”感觉的地方。我用的是Hadoop Streaming加Python脚本的方式来做MapReduce而不是写Java类。原因有两点一是Python的代码量小清洗逻辑还能和爬虫那边的函数复用二是Streaming方便调试本地跑通了再丢到Hadoop上执行排错效率高很多。分享一个典型的统计任务按小时统计全城车辆解锁量。Mapper负责把原始CSV按collect_time字段截取出小时值输出“小时, 1”Reducer对相同小时的值求和。mapper.py#!/usr/bin/env python import sys import re for line in sys.stdin: line line.strip() parts line.split(,) if len(parts) 7: continue collect_time parts[6].strip() hour_match re.search(r(\d{2}):, collect_time) if hour_match: hour hour_match.group(1) print(f{hour}\t1)reducer.py#!/usr/bin/env python import sys current_hour None count 0 for line in sys.stdin: line line.strip() if not line: continue hour, value line.split(\t) hour hour.zfill(2) value int(value) if current_hour hour: count value else: if current_hour is not None: print(f{current_hour}\t{count}) current_hour hour count value if current_hour is not None: print(f{current_hour}\t{count})提交命令hadoop jar /usr/local/hadoop/share/hadoop/tools/lib/hadoop-streaming-3.3.6.jar \ -mapper /usr/bin/python3 /home/user/mapper.py \ -reducer /usr/bin/python3 /home/user/reducer.py \ -input /share_bike/data/raw \ -output /share_bike/output/hour_stat注意-output目录必须是不存在的新路径否则Hadoop会直接报错。跑完之后去HDFS上查看结果文件hdfs dfs -ls /share_bike/output/hour_stat hdfs dfs -cat /share_bike/output/hour_stat/part-00000这套逻辑还可以扩展到区域统计、平均骑行时长、热点车辆密度等多个指标只需要调整Mapper的输出key和Reducer的聚合粒度。我实际项目中做了三个指标按小时统计使用量找潮汐规律、按行政区统计车辆分布找供需失衡区域、按经纬度网格聚合车辆密度画热力图。4. Flask后端与可视化前端4.1 Flask项目结构与API设计后端的职责很纯粹把HDFS上的统计结果暴露成接口让前端能拉数据画图。这里需要注意Flask直接读HDFS文件需要安装hdfs包并且要和NameNode建立一个WebHDFS连接在伪分布式环境里这玩意儿权限和端口设置都比较烦。所以我用的方案是MapReduce跑完结果文件导出一份到本地Flask直接读本地静态JSON文件。这个方式牺牲了一点点实时性但换来的是部署简单、调试方便课程设计完全够用。Flask项目我按最简单的结构组织project/ ├── app.py ├── templates/ │ └── index.html ├── static/ │ ├── css/ │ └── js/ └── data/ ├── hour_stat.json ├── region_stat.json └── heatmap_data.jsonapp.py里的核心接口代码from flask import Flask, jsonify, render_template import json app Flask(__name__) app.route(/) def index(): return render_template(index.html) app.route(/api/hour_stat) def hour_stat(): with open(data/hour_stat.json, encodingutf-8) as f: data json.load(f) return jsonify(data) app.route(/api/region_stat) def region_stat(): with open(data/region_stat.json, encodingutf-8) as f: data json.load(f) return jsonify(data) if __name__ __main__: app.run(host0.0.0.0, port5000, debugTrue)接口的返回格式建议统一成“字段名数据数组”的结构前端拿到之后直接遍历渲染别搞太花哨。比如小时统计返回的就是这样的结构{hours: [00, 01, ...], counts: [234, 198, ...]}4.2 ECharts可视化与前后端联调可视化这块我用了ECharts因为这个库对大数据量图表支持好折线图、柱状图、散点图、地图都内置了而且社区里现成的配置模板特别多。前端页面只用了一个index.html里面通过Ajax从Flask接口拉数据再调用ECharts的setOption完成渲染。前端拉数据并画折线图的逻辑!DOCTYPE html html langzh-CN head meta charsetUTF-8 title共享单车大数据可视化/title script srchttps://cdn.jsdelivr.net/npm/echarts5.5.0/dist/echarts.min.js/script /head body div idhourChart stylewidth: 100%; height: 450px;/div script fetch(/api/hour_stat) .then(response response.json()) .then(data { const chart echarts.init(document.getElementById(hourChart)); chart.setOption({ title: { text: 24小时骑行量变化趋势 }, tooltip: { trigger: axis }, xAxis: { data: data.hours }, yAxis: { type: value }, series: [{ name: 骑行量, type: line, smooth: true, data: data.counts }] }); }); /script /body /html跨域问题值得说道说道。如果前端页面和后端是同一个Flask服务提供的就不存在跨域这也是我把index.html放在templates里的原因。如果你非要前后端完全分离Flask跑5000端口前端Vue跑8080端口那必须装flask-cors插件否则浏览器会拦截接口请求from flask_cors import CORS CORS(app)热力图表是这套系统最有“大数据感”的部分。我拿经纬度网格聚合数据做成散点热力图每个点代表一个500米x500米的网格颜色深浅代表车辆密度。ECharts里用scatter类型加visualMap组件可以很自然地实现这种效果配置里设置好lng/lat坐标范围和颜色梯度就行。前端展示我做了三个核心图表24小时骑行趋势折线图、各行政区车辆占比饼图、城市车辆热力分布图。选这三个的原因很直接它们分别对应时间维度的潮汐规律、空间维度的供需分布、以及最直观的密度可视化。这套组合基本能覆盖评委想看的分析维度。5. 常见问题与排查技巧实录5.1 问题速查表这个项目横跨爬虫、Hadoop、Flask三个技术栈任何一层出问题都会断掉整条链路。我整理了一张踩坑速查表基本都是我实际遇到过的问题现象根因解决办法爬虫抓到数据量始终为0请求被识别为脚本导致IP被限流轮换User-Agent、降低请求频率、增加随机延时热力图点位整体偏移几百米坐标是WGS-84但底图用GCJ-02写坐标系转换函数做一次纠偏Hadoop任务提交即报Output directory already exists输出路径已存在提交前删掉旧输出目录或换新路径Container killed on request. Exit code is 143YARN内存配置不足容器被杀适当调大yarn.nodemanager.resource.memory-mbHDFS磁盘很快被占满副本数未修改默认为3hdfs-site.xml里把dfs.replication改成1Flask接口返回中文变成\uXXXXjson.dumps默认ensure_ascii为True写入JSON时设置ensure_asciiFalseECharts图表无数据但接口有数据前端字段名与JSON不匹配用控制台Network面板确认返回字段逐一对应伪分布式跑大任务时NameNode无响应虚拟机内存太小JVM堆设置过大把hadoop-env.sh里的HADOOP_HEAPSIZE调小到512MB5.2 几个容易忽视的细节Hadoop这层最容易出问题的是内存。我当时用默认配置跑任务结果MapReduce任务刚启动就被ResourceManager杀掉日志里一直提示Exit code是143这是典型的“被系统kill”信号。后来把yarn.nodemanager.resource.memory-mb从默认值调高了一点并且把mapreduce.map.memory.mb控制在合理范围任务才稳定跑完。伪分布式机器内存如果只有8G建议别同时跑HDFS、YARN和Flask很容易物理内存耗尽直接卡死。Flask这层的坑更多在编码。MapReduce输出结果是纯文本手动转成JSON时如果用json.dumps默认会把中文转成\uXXXX这样的Unicode转义。虽然前端用JSON解析之后看不出区别但肉眼排查数据时很难受而且有些老版本的浏览器对特殊字符处理会有异常。这里我建议写文件时强制指定ensure_asciiFalse顺带用utf-8编码写出。另外Flask的debugTrue在课程设计阶段没问题但到了演示的时候要记得关掉不然调试器会暴露一堆内部信息也不安全。同一天要演示多个Flask项目的话记得换端口5000端口被占时服务起不来的现象太常见了。5.3 让系统更好用的小扩展如果做完基础链路还想加点亮点我推荐两个方向。第一个是接入定时调度用crontab写一个定时任务每15分钟执行一次爬虫脚本、每1小时执行一次HDFS数据上传和MapReduce统计。这样整个系统就变成了“准实时”的辅助管理工具而不是跑一次就完事的离线分析。第二个方向是把统计数据导成PDF报告用Flask做接口让前端一键下载实践过的人都知道这在答辩现场的演示效果比纯页面强很多。踩过几次坑之后我自己有个体会这个项目最大的价值不是里面用了多高深的技术而是它还原了一个真实数据应用从零到一的全过程。你会同时面对爬虫数据质量不稳定、Hadoop环境配置繁琐、前后端联调不一致这些问题而这些恰恰是课本里不会写、但工作里天天要面对的。最后再分享一个小技巧调试阶段先把所有链路的输入输出都打成日志文件爬虫抓了多少条、上传HDFS多少条、MapReduce统计出多少条、Flask接口返回多少条四个数字能对得上整个系统基本就是通的。我当时靠这个办法省下了大量排查时间你照做基本也能一遍跑通。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →