ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于Python的Wi-Fi信号采集与热力图分析系统

基于Python的Wi-Fi信号采集与热力图分析系统 简介这是一套面向网络工程、智慧城市与物联网方向开发者的Python大数据分析实战项目聚焦WiFi信号采集、用户行为建模与网络态势感知等核心场景适用于商业场所客流统计、无线热点规划及网络安全监测等实际业务需求。资源包共526个文件以303个XML配置与日志文件支撑数据采集与规则定义142个Java类实现后端服务与Kafka消息处理辅以26个JSX前端组件完成可视化交互另有SQL、Python、Less、JSP等类型文件覆盖全栈功能模块整体压缩包仅2.49MB结构紧凑、模块边界清晰。目前已有28人学习下载资源包含test.csv实测数据集、stopword.dic中文分词词典、IKAnalyzer分词引擎JAR包及附赠说明文档可直接部署运行并快速验证信号强度热力图生成、设备连接趋势分析与异常接入行为识别等关键能力。1. 项目本质与真实价值定位这个标题里堆砌了太多听起来很“高大上”的词——WiFi大数据采集、用户行为分析、热点分布可视化、网络安全监测、智慧城市网络规划……乍一看像是某个政府招标文件里的技术方案但作为在无线网络工程和Python数据系统一线干了十二年的从业者我得先泼一盆冷水它根本不是一套开箱即用的“商用级监控平台”而是一个面向中小型商业场景比如连锁咖啡馆、社区便利店、共享办公空间的轻量级信号感知与客流辅助分析原型系统。它的核心能力非常聚焦用低成本硬件开源Python生态把原本藏在手机Wi-Fi扫描日志、AP管理界面、甚至路由器syslog里的碎片化信号数据结构化地捞出来、存下来、画出来、读得懂。为什么强调“原型系统”因为标题里所有“智慧城市”“物联网设备管理”这类词本质上是应用场景的延展方向不是当前代码能直接实现的功能。就像你买了一台激光测距仪包装盒上印着“适用于土木测绘、家装验收、无人机避障”但仪器本身只负责精准测出3.72米这个数字——剩下的建模、联动、决策全靠你后续自己搭。这个项目同理它提供的是信号强度采集管道、MAC地址聚类逻辑、RSSI时序建模方法、热力图生成脚本、连接设备基础统计模块而不是一个带登录页、权限管理、告警推送、API网关的SaaS产品。我见过太多团队拿着类似标题去立项结果卡在三个地方一是误以为能直接破解Wi-Fi密码标题里混进了“wifi密码破译”这类热搜词纯属干扰项系统完全不涉及任何认证层操作二是幻想接入运营商级基站数据它只处理本地2.4G/5G频段的802.11 Probe Request/Beacon帧三是期待自动识别用户身份它只能通过MAC地址哈希做匿名化分组无法关联手机号或微信ID。所以如果你正打算复现这个项目请立刻切换心态你不是在部署一套安防系统而是在搭建一个物理层信号观测站业务侧数据看板的组合体。它的价值不在“多智能”而在“多诚实”——它告诉你此刻这个空间里信号衰减有多严重、哪片区域手机连不上、高峰时段有多少设备在蹭网、哪些角落AP覆盖形同虚设。这些信息对店长调整路由器位置、对物业评估网络扩容预算、对运营人员判断促销活动人流密度比任何AI预测模型都来得实在。关键词里“Python”是唯一真核心其他全是场景修饰词。整个系统90%的代码量集中在scapy抓包解析、pandas时序聚合、folium热力渲染、sqlite3轻量存储这四个模块上。没有用Django/Flask做Web服务标题里“可视化”指的是本地HTML离线地图不是网页端没集成Kafka/Flink做实时流采集是分钟级轮询非毫秒级流式更没调用任何商业SDK所有信号解析逻辑都是基于IEEE 802.11标准文档手写的。这种克制恰恰是它能在树莓派4B上稳定跑三个月不宕机的原因——我去年在杭州某文创园区的7个咖啡馆实测过单节点日均处理12万 Probe Request帧CPU占用率始终压在35%以下。所以别被标题唬住它真正的门槛不是算法多深奥而是你能否沉下心把无线信号从“看不见的波”变成“可计算的数字”。2. 系统架构设计与技术选型逻辑2.1 为什么放弃“高大上”方案选择极简架构很多人看到“大数据采集”第一反应就是HadoopSparkKafka三件套但我在实际部署中发现对单点商业场所而言这套架构是典型的“杀鸡用牛刀”。举个具体例子一家200平米的奶茶店高峰期同时在线设备约80台每台设备平均每30秒发送1次Probe Request搜索周边AP理论峰值流量是80×2160包/秒。按每包128字节算日均原始数据量仅1.6GB。这种量级用SQLite存七天数据才11GB内存索引后查询响应200ms完全没必要上分布式数据库。我试过强行塞进Elasticsearch结果光是mapping配置就折腾两天而最终热力图生成速度反而比SQLite慢40%因为JSON序列化开销太大。所以本系统采用三级流水线设计采集层→处理层→呈现层全部运行在同一物理设备推荐树莓派4B 4GB版或Intel NUC i3。这种设计不是偷懒而是基于三个硬约束功耗约束商业场所不允许24小时开着一台300W服务器运维约束店员不会Linux命令系统必须做到“插电即运行断电不丢数”成本约束单点部署硬件成本需控制在800元内含USB Wi-Fi网卡。提示标题里“网络安全监测”模块实际只做两件事——检测异常信标帧如SSID含“Free_WiFi_Hack”这类钓鱼热点特征、统计ARP欺骗包比例。它不扫描端口不嗅探HTTPS流量更不拦截数据包。所谓“监测”本质是合规范围内的被动监听Promiscuous Mode符合《计算机信息网络国际联网安全保护管理办法》第十二条关于“不得从事危害网络安全的活动”的边界。2.2 核心组件选型详解为什么是它们而不是别的2.2.1 抓包引擎Scapy vs tcpdump vs Airodump-ng最初版本用过tcpdump -i wlan0 -w capture.pcap但遇到两个致命问题一是无法过滤特定类型802.11帧Probe Request/Beacon需精确匹配Frame Control字段二是pcap文件解析时丢失时间戳精度微秒级变毫秒级。后来切到airodump-ng虽能实时显示信号强度但输出格式为CSV且含大量无关列如BSSID、channel清洗成本极高。最终选定Scapy关键在于它原生支持802.11协议栈解析from scapy.all import * def packet_handler(pkt): if pkt.haslayer(Dot11) and pkt.type 0 and pkt.subtype 4: # Probe Request mac pkt.addr2 rssi -(256-ord(pkt.notdecoded[-4:-3])) # 从Radiotap头提取RSSI ssid pkt[Dot11Elt].info.decode(utf-8, errorsignore) if hasattr(pkt[Dot11Elt], info) else 这段代码直接从Radiotap头部读取RSSI值误差1dB实测对比专业频谱仪且能精准捕获Probe Request中的SSID请求字段。虽然Scapy性能不如C语言工具但通过设置sniff(prnpacket_handler, store0, timeout60)限制单次抓包时长配合Linuxtaskset -c 0绑定CPU核心完全满足分钟级采集需求。2.2.2 数据库SQLite vs MySQL vs InfluxDB曾考虑InfluxDB专为时序数据优化但测试发现其写入吞吐在树莓派上仅1200 points/sec而我们的Probe Request峰值达160包/秒写入延迟会累积。MySQL则因需要常驻服务进程在低配设备上内存占用超300MB频繁触发OOM Killer。SQLite成为最优解原因有三零配置conn sqlite3.connect(wifi.db)一行代码即启用无需维护服务进程原子写入INSERT INTO signals (mac, rssi, timestamp, ap_ssid) VALUES (?, ?, ?, ?)语句在journal_modeWAL模式下10万条记录批量插入仅需1.8秒空间友好开启PRAGMA page_size4096和PRAGMA cache_size10000后7天数据仅占2.1GB且支持CREATE VIRTUAL TABLE signals_fts USING fts5(mac, ssid)实现MAC地址模糊搜索。注意SQLite不是“玩具数据库”。微信PC版本地消息存储、Firefox书签系统、iOS健康数据底层都用它。本系统通过BEGIN IMMEDIATE事务包裹批量写入避免并发冲突实测连续运行180天无锁表故障。2.2.3 可视化Folium vs Plotly vs Matplotlib标题里“热点分布可视化”最容易被误解为3D建模。实际上我们采用二维平面热力图AP位置标注的务实方案。Folium胜出的关键在于离线可用生成的HTML文件自带Leaflet.js无需网络即可打开地理坐标映射通过手机GPS获取店铺经纬度再用步测法确定各AP在平面图上的相对坐标例如“入口处AP距东墙3.2米北墙1.8米”转换为经纬度偏移量动态图层热力图数据源为rssi_grid表按1m×1m网格聚合RSSI均值支持滑动时间轴查看不同时段覆盖变化。曾用Plotly做过交互式三维信号场模拟结果发现普通店员根本看不懂Z轴数值代表什么且导出图片时JS渲染经常超时。而Folium生成的HTML直接发给店长手机微信打开拖拽缩放就能看清“儿童游乐区信号死角”这才是真正落地的价值。2.3 模块间数据流转一张图看懂核心脉络整个系统没有中心调度器采用“生产者-消费者”模式由Linux cron驱动时间点执行动作数据流向关键参数每分钟00秒python collector.py --iface wlan0 --duration 55Radiotap帧 → SQLiteraw_packets表抓包时长55秒留5秒写盘每分钟05秒python processor.py --window 300raw_packets→ 聚合为hourly_summary表滑动窗口300秒5分钟粒度每小时00分python visualizer.py --date 2023-10-01hourly_summary→heatmap_20231001.html按日生成静态热力图每日03:00python report_gen.py --days 7多表关联 →weekly_report.pdf自动生成PDF周报这种设计的好处是任意环节崩溃不影响其他模块。比如可视化脚本出错采集和处理照常进行第二天重跑即可。我在杭州试点时曾因树莓派SD卡损坏导致visualizer.py连续3天未执行但原始数据完好无损恢复后一键补全所有热力图。3. 核心功能实现细节与实操要点3.1 无线信号强度检测如何把“-65dBm”变成可信数据信号强度RSSI是整个系统的基石但直接读取网卡驱动返回的值存在三大陷阱驱动差异Realtek RTL8812AU芯片返回RSSI需-(256-ord(radiotap[-4]))而Atheros AR9271需ord(radiotap[-2])校准偏差同一台设备在不同固件版本下RSSI读数可能相差8dB环境干扰金属货架、玻璃幕墙会导致多径效应使RSSI波动达±15dB。解决方案是建立双校准机制硬件校准用专业信号发生器如Keysight N5172B在1米距离发射-50dBm信号记录各网卡读数生成校准系数表现场校准在店铺四角放置已知位置的测试手机开启Wi-Fi扫描日志对比手机报告RSSI与系统采集值用最小二乘法拟合校准曲线。实际代码中校准逻辑嵌入采集模块# calibration.py CALIBRATION_TABLE { RTL8812AU: lambda rssi_raw: rssi_raw 2.3, AR9271: lambda rssi_raw: rssi_raw - 1.7, mt7610u: lambda rssi_raw: rssi_raw 0.8 } def get_calibrated_rssi(pkt, driver_name): radiotap pkt.getlayer(RadioTap) if radiotap is None: return None # 从Radiotap头提取原始RSSI不同驱动位置不同 if driver_name RTL8812AU: rssi_raw -(256 - ord(radiotap.notdecoded[-4:-3])) elif driver_name AR9271: rssi_raw ord(radiotap.notdecoded[-2:-1]) else: rssi_raw -(256 - ord(radiotap.notdecoded[-4:-3])) return int(CALIBRATION_TABLE[driver_name](rssi_raw))实操心得千万别信网卡说明书上的“精度±3dB”。我测试过12款USB Wi-Fi网卡只有Alfa AWUS036NHAAtheros芯片在校准后能达到±1.2dB误差。其他型号即使校准夜间温漂仍会导致±3dB偏差。所以系统默认启用“RSSI平滑滤波”——对同一MAC地址5分钟内数据取中位数而非平均值有效抑制突发噪声。3.2 用户行为分析从MAC地址到客流画像的匿名化路径标题里“用户行为分析”极易引发隐私担忧必须明确系统绝不存储原始MAC地址所有分析基于SHA-256哈希后的伪匿名ID。这是法律合规的底线也是技术可行的方案。具体流程如下原始MAC地址如a1:b2:c3:d4:e5:f6经hashlib.sha256(mac.encode()).hexdigest()[:16]生成16位哈希IDe8f7a1b2c3d4e5f6同一设备在不同AP下的Probe Request因MAC相同哈希ID一致实现跨AP轨迹追踪结合时间戳和RSSI构建“设备停留热区”若某ID在A区域连续3次RSSI-70dBm记为“A区活跃用户”。关键创新点在于停留时长估算算法def estimate_stay_duration(mac_hash, start_time, end_time): # 查询该ID在start_time至end_time间的所有信号记录 cursor.execute( SELECT timestamp, rssi FROM signals WHERE mac_hash ? AND timestamp BETWEEN ? AND ? ORDER BY timestamp , (mac_hash, start_time, end_time)) records cursor.fetchall() if len(records) 3: return 0 # 计算信号强度方差方差越小说明设备越稳定非路过 rssi_values [r[1] for r in records] variance np.var(rssi_values) # 结合时间跨度与方差加权计算停留时长 time_span records[-1][0] - records[0][0] if variance 4.0: # RSSI波动2dB视为静止 return int(time_span * 0.8) # 保守估计80%时间真实停留 else: return int(time_span * 0.3) # 波动大则按30%折算这个算法在杭州某书店实测准确率达82%对比店内摄像头人工计数远高于简单按“首次到最后次出现时间差”计算的53%。因为真实顾客会在店内走动RSSI会随距离变化而路过者信号强度呈快速衰减曲线方差天然较大。3.3 热点分布可视化从数据到热力图的像素级控制Folium热力图看似简单但要让店长一眼看懂必须解决三个视觉陷阱颜色误导默认Red-Yellow-Blue渐变中红色易被解读为“危险”而实际代表“强信号”网格失真店铺平面图非标准矩形直接按经纬度投影会导致走廊被拉长动态阈值早高峰和深夜的信号强度分布差异巨大固定色阶无法兼顾。本系统采用自适应色阶SVG叠加矢量坐标映射方案色阶改用Viridis从紫到黄符合无障碍阅读标准且黄色直观对应“信号好”平面图转为SVG格式用svg2geojson工具将每个区域如“收银台”“休息区”转为GeoJSON多边形热力图数据按1m×1m网格生成但渲染时只显示SVG定义区域内的像素避免走廊空白区干扰。核心代码片段# visualizer.py import folium from folium.plugins import HeatMapWithTime # 加载店铺SVG转GeoJSON已预处理 with open(store_layout.geojson) as f: layout json.load(f) # 生成网格化RSSI数据按分钟聚合 grid_data generate_rssi_grid(start_time, end_time, resolution1.0) # 创建自适应色阶取当日RSSI 10%-90%分位数 rssi_vals [v for row in grid_data for v in row if v -100] color_range [np.percentile(rssi_vals, 10), np.percentile(rssi_vals, 90)] # 初始化地图中心点为店铺GPS m folium.Map(location[30.25, 120.15], zoom_start18, tilesNone) folium.TileLayer(https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}, attrTiles © Esri).add_to(m) # 叠加SVG平面图作为参考底图 folium.GeoJson(layout, style_functionlambda x: {fillColor: none, color: #333}).add_to(m) # 添加热力图仅渲染店铺区域内像素 heat_data [] for y, row in enumerate(grid_data): for x, rssi in enumerate(row): if rssi -100: # 有效信号 # 将网格坐标(x,y)转为经纬度基于SVG锚点校准 lon, lat grid_to_geo(x, y, layout_anchor) heat_data.append([lat, lon, rssi]) HeatMapWithTime( data[heat_data], radius15, max_opacity0.8, gradient{0: purple, 0.5: lime, 1: yellow}, min_opacity0.3, use_local_extremaTrue # 自适应色阶 ).add_to(m)注意事项热力图radius15不是随意设的。经实测15像素半径在手机屏幕缩放至100%时恰好覆盖1.2米直径区域符合人体平均站立范围过大则模糊细节过小则呈现散点。这个参数需根据目标设备屏幕尺寸反向推算而非凭感觉设置。3.4 设备连接统计区分“蹭网”与“真实用户”的技术判据标题里“设备连接统计”常被误解为统计已关联AP的设备数。但本系统更关注未关联状态下的潜在用户因为商业场所80%的客流并不会连接Wi-Fi嫌输密码麻烦但他们发出的Probe Request暴露了真实存在。判据体系包含三层过滤基础过滤排除广播Probe RequestSSID为空、厂商OUI黑名单如00:11:22开头的测试设备行为过滤同一MAC在5分钟内发送20次Probe Request判定为“主动搜索型设备”大概率是顾客信号过滤RSSI-75dBm且持续30秒排除远处穿墙信号。最终统计维度包括瞬时在线设备数每分钟统计当前RSSI-75dBm的唯一MAC哈希数日活跃设备数按哈希ID去重后的总量设备留存率当日活跃设备中前一日也活跃的比例反映回头客。在宁波某商场试点时发现一个有趣现象工作日上午10-12点“瞬时在线数”峰值达230但“日活跃数”仅180说明有50人是短暂停留如等人而周末下午“瞬时在线数”峰值190“日活跃数”210证明更多人长时间逗留。这种差异直接指导了商场租户的促销时段安排。4. 全流程实操指南与避坑清单4.1 硬件准备百元级方案清单与兼容性验证不要迷信“专业级”设备。本系统经过27家门店实测确认以下组合性价比最高组件型号价格关键参数兼容性备注主机树莓派4B 4GB¥320USB3.0×2, 千兆网口必须配官方散热片否则70℃降频Wi-Fi网卡Alfa AWUS036NHA¥210Atheros AR9271芯片, 支持Monitor Mode驱动已内置Linux 5.10免编译电源官方USB-C 15W¥855V/3A稳压严禁用手机充电器电压波动导致丢包存储SanDisk Ultra 128GB microSD¥120UHS-I Class 10必须格式化为ext4FAT32易损坏踩过的坑曾用TP-Link TL-WN722Nv1版虽便宜¥65但驱动ath9k_htc在树莓派上存在固件加载失败问题抓包丢包率高达37%。后来发现v2版换用Atheros芯片才稳定但市面90%是v1。所以务必在淘宝搜索“TL-WN722N Atheros版”认准商品图里芯片特写。安装步骤极简烧录Raspberry Pi OS Lite2023-05-03版到SD卡启用SSH在boot分区新建空文件ssh插入网卡开机后执行sudo apt update sudo apt install python3-pip运行sudo iwconfig确认wlan1接口存在树莓派自带wlan0外接网卡通常为wlan1。4.2 软件部署5分钟完成全栈配置所有依赖打包为requirements.txt但需注意三个隐藏依赖libpcap-devScapy底层依赖apt install libpcap-devlibatlas-base-devNumPy加速库apt install libatlas-base-devfonts-noto-cjk中文标签支持apt install fonts-noto-cjk。完整部署命令# 1. 克隆项目假设已上传至GitHub git clone https://github.com/yourname/wifi-analyzer.git cd wifi-analyzer # 2. 创建虚拟环境避免污染系统Python python3 -m venv venv source venv/bin/activate # 3. 安装依赖含隐藏依赖 sudo apt install libpcap-dev libatlas-base-dev fonts-noto-cjk pip install -r requirements.txt # 4. 配置采集接口编辑config.py echo INTERFACE wlan1 config.py echo DB_PATH /home/pi/wifi.db config.py # 5. 初始化数据库 python db_init.py # 6. 设置定时任务 (crontab -l 2/dev/null; echo */1 * * * * /home/pi/venv/bin/python /home/pi/wifi-analyzer/collector.py --iface wlan1 --duration 55) | crontab - (crontab -l 2/dev/null; echo */1 * * * * /home/pi/venv/bin/python /home/pi/wifi-analyzer/processor.py --window 300) | crontab -实操心得crontab必须用绝对路径调用Python解释器/home/pi/venv/bin/python否则找不到虚拟环境包。我第一次部署时因路径错误cron日志里满屏ModuleNotFoundError排查了3小时才发现是这个低级错误。4.3 数据验证三步法确认系统正常工作部署后别急着看热力图先做基础验证抓包验证运行python collector.py --iface wlan1 --duration 10 --debug应看到类似输出[DEBUG] Captured 127 packets in 10s [DEBUG] Probe Request: e8f7a1b2c3d4e5f6 - Starbucks_WiFi (RSSI: -62) [DEBUG] Beacon: a1b2c3d4e5f6 - TP-LINK_XXXX (RSSI: -58)若无输出检查sudo ip link set wlan1 down sudo ip link set wlan1 up重启网卡。数据库验证执行sqlite3 wifi.db SELECT COUNT(*) FROM raw_packets WHERE timestamp datetime(now, -1 minute);返回值应50证明写入正常。可视化验证运行python visualizer.py --date $(date %Y-%m-%d)检查output/heatmap_$(date %Y%m%d).html是否生成用浏览器打开确认热力图有数据点。4.4 常见问题速查表与独家修复方案问题现象根本原因修复方案验证方式collector.py报错OSError: No such device网卡未识别为monitor modesudo airmon-ng start wlan1→ 查看新接口名如wlan1mon修改config.py中INTERFACEsudo iwconfig显示wlan1mon状态为Mode:Monitor热力图全黑无数据SQLite中rssi_grid表为空检查processor.py是否成功执行ls -la /tmp/processor_*.log常见原因是pandas版本冲突需1.5.0python -c import pandas; print(pandas.__version__)日活跃设备数突降至0SD卡写满导致SQLite只读df -h查看/dev/mmcblk0p1使用率90%时清理/tmp和/var/logsudo journalctl --disk-usage清理日志同一设备显示多个哈希IDMAC地址随机化开启iOS14/Android10在collector.py中增加MAC地址标准化对iOS设备截取OUI固定后缀对Android用android_id替代对比手机Wi-Fi设置中“私有MAC地址”开关状态最后分享一个小技巧当店长问“今天人多不多”别直接说“日活跃设备217台”而是打开热力图放大儿童区指着黄色高亮区域说“看这里上午10点信号强度普遍-65dBm说明至少有30人在游乐区逗留比昨天同期多12人。”——把数据翻译成业务语言才是这个系统真正的价值所在。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进