
简介基于PyQt6图形界面框架与深度学习DNN模型实现的网络安全入侵检测及实时抓包分析系统项目包适合安全运维人员、高校网络安全专业学生及流量分析开发者。系统以CIC-IDS2017数据集训练二分类模型配合PyShark抓包引擎完成实时网络异常识别可部署于企业内网、金融机构等安全敏感环境。资源共31个文件、约7.7MB核心包括5个Python脚本主程序、抓包视图、分析视图、训练与预测、5张PNG界面截图、TensorFlow模型文件pb/pyc、CSV数据集与测试流量包pcap另附README和说明文档辅助上手。已有52人学习下载。内容从模型构建、图形界面设计到实时抓包分析形成完整链路既可直接复跑演示也便于按需二次开发可作为网络安全实战项目或毕设参考。1. 先抓流量再判异常PyQt6 DNN PyShark 这套组合到底能干什么如果你在运维或安全岗大概率经历过这样的下午内网流量突然暴涨交换机面板闪个不停手工抓包能看到一堆 TCP 重传和异常连接但就是说不清这是入侵还是业务抖动。这套基于 PyQt6、深度学习 DNN、PyShark 的网络安全入侵检测工程就是把“手工抓包 手工看特征”变成一台能实时出结论的桌面工具。它用 PyShark 做实时抓包把流量聚合成 CIC-IDS2017 数据集同口径的特征交给二分类模型给出正常/异常概率最后在 PyQt6 界面里展示。适合安全运维、攻防研究、毕业论文以及想从“会调包”往“会搭流程”走的开发。它的边界也清楚二分类模型只能给出异常概率不负责告诉你具体攻击类型也不擅长加密流量里的隐藏行为。2. PyQt6 还是 PySide6选型理由和 DNN 二分类的边界条件2.1 为什么选 PyQt6 而不是 PySide6授权、打包、接口差异一次说清讨论区里“pyqt6 和 pyside6 哪个强”几乎是每天都有的问题。就纯界面能力来说两个库同源于 Qt 封装真实差距很小。落到项目上差别主要在三处。授权上PyQt6 采用 GPL/商业双授权PySide6 是 LGPL前者对闭源交付或给客户做安全服务时会有法务风险后者更宽松打包体量上PyQt6 的 wheel 依赖更重但换来的是对 Qt6 小版本跟进更快接口细节上两个库的信号槽和枚举命名有差异比如pyqtSignal与Signal、Qt.AlignmentFlag.AlignCenter这类写法不完全一致。本工程选 PyQt6并不是因为它在功能上碾压 PySide6而是三个现实理由第一PyQt6 的社区示例多抓包、流量表格、仪表盘这类界面项目大量是 PyQt6 写的踩坑时有更大概率搜到同款报错第二从 PyQt5 迁到 PyQt6 的人多exec()、setContentsMargins这些差异早就被整理透了照着改成本低第三在 Qt6 长期支持版本上PyQt6 的绑定发布节奏比 PySide6 更跟手。如果你后续决定换 PySide6迁移成本其实不高核心是把工程结构和线程边界写对界面框架换掉没有想象中那么伤筋动骨。另一种更值得注意的问题是环境兼容。PyQt6 依赖 Qt6 运行库Qt6 对 OpenGL 的依赖在远程桌面场景比 Qt5 敏感。我上一版代码就遇过本地界面正常、远程桌面一打开就黑屏的情况排查到最后是渲染引擎选择问题设置QT_OPENGLsoftware环境变量才解决。这一条先记着后面避坑部分还会提到类似场景。选 PyQt6 不是选“最好的框架”而是选“在当前团队、当前交付环境里最不容易踩坑的框架”这两件事在工程落地阶段经常不是同一个答案。2.2 系统分层抓包层、特征层、推断层、界面层各管一件事很多初版工程会把 PyShark 的每个包对象直接塞进界面线程抓到十个包界面就开始假死。这个资源拆包时我看到的第一眼是它把整个系统分成四层各管一件事。抓包层只做一件事用 PyShark 的 LiveCapture 从网卡读包配合display_filter做粗过滤只留 IP 流量把 ARP、MDNS 这类广播噪声挡在前面。特征层负责把包的统计字段抽出来按五元组聚合成一行 CIC 风格特征这一层是纯 Python 计算不碰任何界面对象。推断层加载训练好的 DNN 模型对特征向量做预测返回概率值和判定结果它不知道界面长什么样。界面层只接收基本类型数据比如字符串、浮点数、时间戳再负责刷新表格和弹告警。这个分层不是摆样子。PyShark 底层调的是 tshark 命令行每抓一批包就有一次进程通信开销如果让界面线程直接干这件事Qt 事件循环会被 tshark 的输出堵死。把抓包放在子线程把模型推断也放在子线程主线程只参与信号槽收结果这是整套工程“界面不卡”的前提。后面第 4 章给出的CaptureWorker和FlowClassifierWorker两个线程类就是围绕这四层边界设计的。2.3 二分类模型能识别什么不该让它背什么锅CIC-IDS2017 数据集里标注了 Benign 与 DoS、PortScan、DDoS、Bot 等多种攻击常见的处理是把非 Benign 全部标成 Attack 做二分类。这样模型学到的其实是“流量统计分布正常与异常”的边界比如连接时长极短、单流包数和字节数偏离基线、SYN 标志位占比过高。它能发现端口扫描、暴力破解、部分 DoS/DDoS甚至某些外联心跳型异常。但它分辨不了攻击类型更看不到载荷内容。二分类模型天然不擅长加密流量里的内部威胁因为加密后的统计分布可以看起来完全正常也不擅长单包级别的注入攻击因为单包统计特征不明显。所以界面里的判定结果我建议写成“疑似异常”而不是“检测到 XX 攻击”否则一个误报会放大成一片警报。模型训练用的特征依赖 CIC-IDS2017 的统计口径真实网络流不会被严格切成固定大小窗口。工程里必须有一个流聚合器把实时包按滑动窗口聚合成“当前状态”这个简化是第 4 章实时管道要解决的关键问题。换句话讲这套工程的正确用法是把它当成流量异常感知的前置过滤器替人工盯住那些统计上不正常的东西再把可疑窗口交给日志和人工研判而不是让它当一个全知全能的检测大脑。3. CIC-IDS2017 数据集加工从原始 pcap 到可训练的二分类特征表3.1 数据准备选 CSV 还是 pcap怎么切出训练窗口CIC-IDS2017 发布时有完整 pcap 和已经抽好的 CSV 特征表两种形态。第一次训练我建议直接用官方 CSV 特征表不要动 pcap每一行已经按流聚合好最后一列 Label 是分类标签pandas 读进来就能开始做特征筛选这样能在最短时间内跑通基线。动 pcap 是后面要做“从抓包到模型”全流程复现时才需要的事。读 CSV 有个很坑的细节官方文件的列名和标签里自带空格比如 Label而且文件行数多pandas 默认按块推断类型容易把数值列读成 object。我一般第一版脚本先做最小读取确认能正常解析再往下走import pandas as pd df pd.read_csv(Friday-WorkingHours.csv, low_memoryFalse) print(df.shape) print(df.columns.tolist()[:10]) print(df[ Label].value_counts())low_memoryFalse是为了避免 pandas 分块读取时把数值列猜成 object否则后面特征筛选全部要再做一次字符串转 float纯属给自己加活。 Label前面的空格是官方 CSV 自带的或者漏了一个建议统一strip()之后再统计类别否则 Benign 和一个带空格的 Benign 会被当成两个类。读取之后做三件清洗去掉Infinity和NaN行把负数值置 0 或直接删除再把标签二值化。CIC 官方特征表里有少量Infinity值不处理的话训练时会被当成缺失值Keras 不会直接告诉你哪一行出了问题只会让 loss 变成 NaNimport numpy as np df[ Label] df[ Label].str.strip() df[ label_binary] (df[ Label] ! Benign).astype(int) df df.replace([np.inf, -np.inf], np.nan).dropna()标签二值化之后攻击类是 1、正常类是 0。这里注意类别不平衡CIC 数据集中正常流量样本量通常远大于攻击样本后面训练时要靠class_weight或欠采样平衡。3.2 特征裁剪与标签二值化让 DNN 只面对它该面对的字段CIC 原始特征表有 80 多列经验做法是只保留基础流量统计列不要全喂进去。DNN 在这个场景里不需要特别宽的特征面特征过宽反而容易记住训练集里的特定噪点。我保留的核心特征如下这份列表也是后面在线抓包特征提取要复用的字段定义特征列含义对异常的意义Flow Duration流时长微秒扫描流极短大流量 DoS 也短Total Length of Fwd Packets正向包总长度方向性吞吐偏差Total Length of Bwd Packets反向包总长度反射型攻击反向异常Fwd Packet Length Mean正向平均包长扫描常见小包Bwd Packet Length Mean反向平均包长同上SYN Flag CountSYN 标志位计数扫描和 SYN Flood 核心特征Label标签二分类目标实际训练时不要只留 6 列我一般会把输入控制在 20 列以内在上面基础上再加 Packet Length Std、Flow IAT Mean、Active Mean 这类方差敏感特征。关键是训练和推理必须共用同一份特征列定义顺序不能乱这一点第 5 章会专门讲。3.3 训练脚本与关键超参Adam、BatchNorm、Dropout 和早停怎么配合DNN 模型结构不需要很复杂三层隐藏层在这个数据规模上已经够用。我用的是 128/64/32 递减结构每层 Dense 之后接 BatchNorm 和 Dropoutimport tensorflow as tf from tensorflow.keras import layers, models model models.Sequential([ layers.Input(shape(feature_dim,)), layers.Dense(128, activationrelu), layers.BatchNormalization(), layers.Dropout(0.3), layers.Dense(64, activationrelu), layers.BatchNormalization(), layers.Dropout(0.3), layers.Dense(32, activationrelu), layers.Dense(1, activationsigmoid) ]) model.compile( optimizertf.keras.optimizers.Adam(learning_rate1e-3), lossbinary_crossentropy, metrics[accuracy, tf.keras.metrics.AUC(nameauc)] )BatchNorm 放在 ReLU 后面是为了把上一层输出重新拉回稳定分布流量特征的方差动辄差几个数量级没有 BN 前面几轮 loss 会跳得很厉害。Dropout 取 0.3防止模型死记某几条流的细节特征。Adam 用 1e-3这是流量二分类里最常见的一组经验值不需要一开始就去调。训练时早停是必须的不然很容易跑满 30 轮把验证集跑崩callbacks [ tf.keras.callbacks.EarlyStopping( monitorval_loss, patience5, restore_best_weightsTrue ), tf.keras.callbacks.ReduceLROnPlateau( monitorval_loss, factor0.5, patience3 ) ] history model.fit( X_train, y_train, validation_data(X_val, y_val), batch_size64, epochs30, callbackscallbacks, class_weight{0: 1.0, 1: 1.5} )class_weight给攻击类 1.5 的权重是因为 CIC 二分类里攻击样本相对少不加权的话模型会倾向把所有样本都判成 Benign准确率看着很高实际一点用没有。评估时也不要只盯准确率这个数据集天生不平衡要同时看精确率、召回率和 F1。如果召回率上不去先把阈值从 0.5 往下调再去看特征是不是选少了。4. PyShark 抓包引擎与模型推断实时管道怎么搭才不卡界面4.1 PyShark 的 LiveCapture 与 Qt 线程边界很多教程会把 PyShark 的sniff_continuously直接写进按钮点击回调这是一个很容易翻车的写法。PyShark 内部是调用 tshark 命令行并读取它的 stdout每个包的获取都涉及进程通信和 JSON 解析必然卡住主线程。我最早一版就是直接在 PyQt6 窗口的按钮回调里跑sniff_continuously界面在前几个包之后就开始无响应根源不是 PyShark 慢而是把阻塞操作放进了 Qt 事件循环。正确的做法是把抓包挪到 QThread 子类里import pyshark from PyQt6.QtCore import QThread, pyqtSignal class CaptureWorker(QThread): on_statistic pyqtSignal(dict, str) def __init__(self, interface: str eth0): super().__init__() self.interface interface self._running True def run(self): cap pyshark.LiveCapture( interfaceself.interface, display_filterip, use_jsonTrue ) for packet in cap.sniff_continuously(): if not self._running: break statistics extract_flow_statistics(packet) if statistics is not None: self.on_statistic.emit(statistics, packet.ip.src)display_filterip是粗过滤只让 IP 包进入 Python 层ARP、MDNS 这些噪声在前端就被 tshark 挡掉了。use_jsonTrue让 tshark 输出 JSON 而不是原始文本PyShark 解析速度更快CPU 占用也更低。_running标志用于在主窗口关闭时通知线程退出否则抓包线程会一直阻塞在sniff_continuously里进程退不干净。4.2 在线特征提取与实时推断线程和队列把抓、判、显分开抓包线程拿到包之后下一步是特征提取和模型推断。这里我建议用一个 worker 串行做“抓包→特征→预测”不要把特征提取和模型推断拆成两个线程因为流量顺序本身就是天然队列拆开反而要处理锁和同步。在线推理的骨架大致是这样class FlowClassifierWorker(QThread): result pyqtSignal(str, str, float) def __init__(self, capture_conf, feature_cols, model): super().__init__() self.capture_conf capture_conf self.feature_cols feature_cols self.model model self._running True def run(self): cap pyshark.LiveCapture(**self.capture_conf) for packet in cap.sniff_continuously(): if not self._running: break vec build_feature_vector(packet, self.feature_cols) if vec is None: continue prob self.model.predict(vec.reshape(1, -1), verbose0)[0][0] self.result.emit(packet.ip.src, packet.ip.dst, float(prob))build_feature_vector是特征层的核心函数。它维护一个按五元组分键的滑动窗口字典窗口内累计包数、字节数、SYN 计数每隔固定时间或固定包数刷新一次特征向量。这个设计有一个重要取舍CIC 特征是在流结束后统计的但实时检测不能等流结束只能计算“窗口内当前状态”所以在线特征和离线训练特征的分布天然存在偏差。这是正常的要在工程里接受它而不是试图让模型去完美对齐。4.3 告警更新节奏界面只消费结果不接触包对象PyQt6 的信号槽在跨线程传递时只保证基本类型安全pyshark 的 Packet 对象不要试图直接 emit否则会触发元类型转换错误或者在最不该出问题的时候变成隐式崩溃。上面代码里只传字符串和浮点数这是刻意为之。界面侧接收信号的写法def on_result(self, src, dst, score): self.table_model.append_row(src, dst, f{score:.4f}) if score 0.85: self.tray.showMessage(流量异常, f{src} - {dst})这里有三个注意点。第一不要用定时器轮询抓包线程里的全局变量用信号驱动刷新避免 Qt 定时器高频轮询和线程变量竞争。第二表格刷新要控制节奏比如每 100 条结果刷新一次视图不要每秒插入几十行直接把 QTableView 拖垮。第三告警阈值 0.85 是经验值不是固定值可以拿一周真实流量跑出分数分布后再调。实时流量噪声比离线数据集大阈值太低会变成“每秒都报警”阈值太高又失去检测意义。5. PyQt6 加 PyShark 加 DNN实时抓包集成里绕不开的五个坑5.1 现象PyShark 过滤条件写对了却一直抓不到包原因PyShark 本身不实现抓包协议它只是 tshark 的封装。如果系统没装 Wireshark或者 Wireshark 安装目录没进 PATHLiveCapture会静默返回空结果而不是抛一个明确的“找不到 tshark”异常。解决先打印路径确认后端存在python -c import pyshark; print(pyshark.get_tshark_path())如果输出为空去装 Wireshark 并把安装目录加进 PATH。另外 PyShark 和 tshark 的版本有兼容关系遇到tshark: The installed version of Wireshark is not compatible这类报错时不要硬凑直接调 PyShark 版本或 Wireshark 版本把验证过的一组版本写进工程 README避免后来接手的人在这上面浪费半天。5.2 现象离线模型指标很高一接实时流量就狂报警原因在线抓包拿到的不是完整流而是滑动窗口里的中间态统计值。模型训练时用的是 CIC 数据集的完整流聚合特征分布错位导致误报放大。这不是模型坏了是训练和推理的特征口径没对齐。解决把实时特征提取改成和训练口径对齐训练和推理共用同一份FEATURE_COLUMNS常量不要各写一套字段顺序。同时把告警阈值从 0.5 提到 0.85 左右。实时流量里正常业务抖动也会造成统计偏移阈值适当提高能压掉大量伪警报代价是漏报增加这个阈值要在具体网络环境里反复试。5.3 现象Qt 界面在抓包线程启动后会不定时崩溃原因最常见的两个。一是把 pyshark 的 Packet 对象或自定义特征字典直接传给pyqtSignal元类型无法在跨线程队列里正确转换二是窗口关闭时抓包线程还在阻塞等待包线程对象被销毁后代码还在跑。解决信号只发基本类型字符串、浮点数、整型都安全在closeEvent里先置worker._running False再调用worker.wait(2000)等线程退出超时再强杀。还有前面提到的远程桌面黑屏问题设QT_OPENGLsoftware可以绕开 Qt6 在远程环境下的渲染选择异常这个也归在环境坑里。5.4 现象CIC 数据集随机切分后训练集 0.99、验证集直接崩原因随机train_test_split会把同一条流的前后包切进训练集和验证集模型靠记住同源分布而不是学习异常逻辑验证集一旦遇到新时段流量就露馅。CIC 是时间序列数据集时间相关性很强不能当时序无关的 i.i.d 数据切。解决按时间排序后取前 70% 做训练、后 30% 做验证或者先按五元组或 flow ID 分组再对组编号切分。不要只看random_state42就当作没问题关键是保证任何一条流的记录不会跨集合存在。5.5 现象同一个包离线特征和在线抓包特征对不上原因列顺序不一致或者数值规范化方式不一致。离线 CSV 用 pandas 读列顺序取决于表头在线抓包用字典解析可能出现 key 顺序不同。另一个更隐蔽的问题是 scaler有的代码对整批在线流量重新计算均值和方差这会把在线分布污染进标准化参数导致推理输入漂移。解决训练结束后把feature_names列表和训练集上拟合好的 scaler 一起用 pickle 保存import pickle with open(model_package.pkl, wb) as f: pickle.dump({ model: model, feature_names: FEATURE_COLUMNS, scaler: scaler }, f)推理时只从 pickle 里加载 scaler对每一条特征向量做独立transform绝不重新计算均值和方差。这样离线特征和在线特征至少能保证在字段和标准化上说的是同一套口径。6. 进阶用法把二分类扩成多分类并导出可复核的特征记录6.1 从二分类扩多分类把 label 换成攻击类型如果不想只停留在“正常/异常”的二元判断可以把label_binary换回原始Label输出层改成 softmax输出类别就是数据集里的攻击类型。隐藏层结构不需要动DNN 前面 128/64/32 的特征提取能力可以复用。需要改动的只有三个位置输出层Dense(num_classes, activationsoftmax)loss 换成categorical_crossentropy标签换成 one-hot。真正费工夫的是类别不平衡CIC 里 PortScan 样本远多于其他攻击类型需要按类别设置权重或者对少数类做过采样。6.2 可复核记录每次告警都留一份特征哈希比多分类更实用的一个习惯是给每次告警留可复核记录。把窗口首包时间、五元组、模型版本、告警阈值和特征向量哈希写进 JSONL 文件存放在 worker 线程里不碰界面层。这样事后可以说清楚“这条报警当时模型是怎么判的、用了哪些特征、阈值是多少”而不是留下一张只有时间戳的截图。对交付审计和误报排查这个文件的价值比模型本身更高。我以前做过一版把抓包直接塞进界面主线程的工程结果就是界面假死、线程退出不干净、误报无处追溯三件事同时爆炸。后来每次改完抓包逻辑我都强制先拿一段离线 pcap 回放一遍确认特征口径和训练时一致再切实时接口这个习惯帮我挡掉了至少三次半夜误报。希望帮到你。本文还有配套的精品资源点击获取