基于监督学习的Web入侵检测毕设源码:从数据到模型全流程解析

发布时间:2026/10/25 13:50:45
基于监督学习的Web入侵检测毕设源码:从数据到模型全流程解析

简介:这份资源是面向计算机相关专业学生与从业者的高分毕业设计源码包,主题为基于监督学习的Web入侵检测系统,采用Python实现,评审得分达97分,可直接用于毕业设计、期末课程设计或课程大作业等场景。压缩包共60个文件,约2.25MB,以18个ipynb实验笔记、14个txt样本与词表、14个html页面样本、8个py脚本为主,另含pkl与pickle模型文件、md说明文档等,覆盖数据采集、字符处理、去重、特征提取与模型保存等完整流程。内容预览显示项目包含爬虫脚本、正常与恶意请求样本、关键词表及多份Jupyter实验记录,便于理解监督学习在Web入侵检测中的特征工程与建模思路。目前已有188人学习下载,适合需要完整项目方案、可运行代码与排错参考的读者借鉴使用。

1. 从一份 97 分毕设源码说起:监督学习做 Web 入侵检测到底靠不靠谱

很多同学做毕设时都会卡在同一个地方:题目选好了,方向也定了,但真正动手时发现要么数据找不到,要么模型跑不通,要么代码写完自己都不知道对不对。这份基于监督学习的 Web 入侵检测系统 Python 源码,就是冲着这个痛点来的——它是一套完整可运行的毕设级项目,从 HTTP 请求日志的采集、payload 参数提取、字符级特征处理,到监督学习模型训练和检测,整条链路都有对应的脚本和 notebook。

它解决的核心问题是:把原始 Web 访问日志变成模型能吃的特征向量,再用监督学习算法判断某条请求是正常访问还是恶意攻击。适合计算机相关专业的本科生做毕业设计、期末大作业,也适合刚入行安全方向、想动手跑一遍入侵检测流程的从业者。源码里包含了爬虫脚本、字符处理脚本、去重脚本、参数提取 notebook、模型核心代码以及多个 pickle/joblib 序列化文件,结构上覆盖了从数据到模型的全流程。下面我按实际拆包的顺序,把这份资源怎么用、参数怎么调、哪里容易翻车讲清楚。

2. 拆开源码包:reptile.py 到 core.py 的数据链路与模块职责

2.1 从 reptile.py 到 payload.txt:原始请求怎么变成可训练数据

拿到源码包后,第一件事不是急着跑模型,而是先理清数据从哪来、经过哪些脚本、最终变成什么格式。这份资源的根目录下有几个关键文件:reptile.py负责爬取或采集原始 HTTP 请求数据,wordProcess.py做字符级处理,removeRepeat.py去重,makeChar.py生成字符集,core.py是模型训练和检测的核心逻辑。src目录下存放的是原始素材,包括大量ff_*.html、ff_*.txt以及access_log_payload*.txt、payload.txt、normal_require.txt、safe_key.txt、normal-use1.txt等文件。

这些文件的分工大致是这样的:ff_*.html和ff_*.txt是原始请求样本,access_log_payload*.txt是从访问日志中提取出的 payload 片段,payload.txt是汇总后的恶意 payload 集合,normal_require.txt和normal-use1.txt是正常请求样本,safe_key.txt是安全关键词白名单。整个数据链路可以概括为:原始日志 → payload 提取 → 字符处理 → 去重 → 特征向量 → 模型训练。

我一般会先打开README.md确认作者标注的运行顺序,然后用count.py统计一下各类样本的数量分布。这一步很关键,因为监督学习最怕的就是正负样本严重不均衡。如果恶意样本只有几十条而正常样本有几千条,后面训练出来的模型大概率会偏向多数类,检测效果会很难看。

# 先看目录结构和文件数量,心里有个底 ls -la find . -name "*.py" | head -20 find . -name "*.txt" | wc -l find . -name "*.html" | wc -l # 统计关键样本文件的行数,判断正负样本比例 wc -l src/payload.txt wc -l src/normal_require.txt wc -l src/normal-use1.txt wc -l src/safe_key.txt

上面这几条命令的作用是快速摸清数据规模。find . -name "*.py"列出所有 Python 脚本,确认核心文件是否齐全;wc -l统计样本行数,如果payload.txt只有几十行而normal_require.txt有上千行,那后面就必须做样本均衡处理,否则模型会严重偏斜。常见做法是用欠采样把正常样本降到和恶意样本同一量级,或者用 SMOTE 做过采样,但这份源码里没有内置均衡逻辑,需要自己补。

2.2 wordProcess.py 与 removeRepeat.py:字符级特征处理的两个关键脚本

wordProcess.py是整个特征工程的核心入口。它的作用是把原始请求字符串拆成字符序列,再做归一化处理。Web 入侵检测和自然语言处理有个相似点:请求 payload 本身就是一串字符,SQL 注入、XSS、命令注入这些攻击在字符层面有比较明显的模式,比如大量单引号、尖括号、分号、union select、../等。所以用字符级特征做监督学习是合理的,不一定非要上词向量。

removeRepeat.py负责去重,避免同一条 payload 在训练集里反复出现导致模型过拟合。这个脚本通常会读取payload.txt和normal_require.txt,对每一行做哈希或字符串比对,输出去重后的文件。makeChar.py则是生成字符集字典,把所有出现过的字符映射成整数索引,供模型输入使用。

# wordProcess.py 的典型处理逻辑(根据源码结构还原) import re def normalize_payload(payload: str) -> str: """对原始 payload 做归一化:转小写、URL 解码、去除多余空白""" payload = payload.strip().lower() # URL 解码,把 %27 还原成单引号等 payload = re.sub(r'%([0-9a-fA-F]{2})', lambda m: chr(int(m.group(1), 16)), payload) # 把连续空白压成一个空格 payload = re.sub(r'\s+', ' ', payload) return payload def char_tokenize(payload: str, char2idx: dict, max_len: int = 200): """把字符串转成整数索引序列,超长截断,不足补零""" ids = [char2idx.get(ch, char2idx.get('<UNK>', 0)) for ch in payload[:max_len]] ids += [0] * (max_len - len(ids)) return ids

这段代码的逻辑说明:normalize_payload先做小写化和 URL 解码,因为攻击者经常用编码绕过简单规则;char_tokenize把字符映射成整数,max_len控制序列长度,太短会丢信息,太长会增加计算量。参数方面,max_len=200是一个经验值,Web 请求的 payload 通常不会超过这个长度,如果数据集里有很多超长请求,可以调到 500 甚至 1000,但要注意显存或内存占用。<UNK>是未知字符的兜底索引,防止测试时遇到训练集没出现过的字符直接报错。

提示:wordProcess.py和wordProcess.pyc同时存在,说明作者可能用py_compile编译过。直接跑.py就行,.pyc是缓存,不用管。

2.3 core.py 与 pickle/joblib 文件:模型训练与持久化的实际流程

core.py是模型训练和推理的主入口。从目录里的save_with_pickle.ipynb、save_with_joblib.ipynb、test.pickle、jpblib.pkl、joblib.pkl可以看出,作者用了 pickle 和 joblib 两种方式做模型持久化。core.ipynb和core.py内容应该基本一致,notebook 方便调试,.py方便直接运行。

监督学习部分,常见做法是用 scikit-learn 里的分类器,比如朴素贝叶斯、逻辑回归、随机森林或 SVM。考虑到字符级特征维度较高,朴素贝叶斯和逻辑回归比较轻量,随机森林效果通常更稳但训练慢一些。源码里没有明确写用哪个算法,但从core.py的命名和getNormalPara.py、getPayloadPrarmaters.ipynb这些文件来看,作者应该是先提取正常请求和恶意请求的参数特征,再喂给分类器。

# core.py 的典型训练流程(根据源码结构还原) import joblib import numpy as np from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report # 假设 X 是字符索引矩阵,y 是标签(0 正常,1 恶意) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) clf = RandomForestClassifier( n_estimators=100, # 树的数量,越多越稳但越慢 max_depth=20, # 控制过拟合,太深会记住训练集 min_samples_leaf=2, # 叶子节点最少样本数,防止噪声影响 random_state=42, n_jobs=-1 # 用满所有 CPU 核心 ) clf.fit(X_train, y_train) # 输出评估报告,重点看恶意类的 recall y_pred = clf.predict(X_test) print(classification_report(y_test, y_pred, target_names=['normal', 'malicious'])) # 持久化模型,后面检测时直接加载 joblib.dump(clf, 'web_ids_rf.joblib')

参数说明:n_estimators=100是随机森林的默认起点,如果训练集很大可以加到 200 或 300;max_depth=20是为了防止树长得太深导致过拟合,实际调参时可以用交叉验证选;stratify=y保证训练集和测试集里正负样本比例一致,这个在样本不均衡时特别重要。评估时不要只看 accuracy,因为如果恶意样本只占 5%,全预测成正常也有 95% 的准确率,但实际毫无用处。重点看 malicious 类的 recall 和 F1-score。

save_with_pickle.ipynb和save_with_joblib.ipynb的区别在于序列化方式。pickle 是 Python 标准库,通用但跨版本兼容性差;joblib 对 numpy 数组优化更好,文件更小、加载更快。我一般优先用 joblib,尤其是模型里包含大量数组参数时。

3. 把源码跑起来:环境配置、notebook 执行顺序与参数调整

3.1 Python 环境与依赖安装:numpy、scikit-learn、joblib 一个都不能少

这份源码是纯 Python 项目,没有复杂的深度学习框架依赖,所以环境配置不算麻烦。但有几个库是必须的:numpy做数值计算,scikit-learn做模型训练和评估,joblib做模型持久化,pandas可能在 notebook 里用于数据查看。如果reptile.py里用了 requests 或 scrapy,还需要装对应的爬虫库。

# 建议用虚拟环境,避免污染系统 Python python -m venv venv source venv/bin/activate # Linux/Mac # venv\Scripts\activate # Windows # 安装核心依赖 pip install numpy scikit-learn joblib pandas pip install requests beautifulsoup4 # reptile.py 可能需要 # 确认版本,避免 scikit-learn 版本差异导致模型加载失败 python -c "import sklearn; print(sklearn.__version__)" python -c "import numpy; print(numpy.__version__)"

这里有个血泪经验:scikit-learn 不同版本之间,pickle/joblib 文件的兼容性可能有问题。如果加载test.pickle时报AttributeError或ModuleNotFoundError,大概率是版本不匹配。解决办法是看README.md里有没有标注版本,或者用pip install scikit-learn==1.x.x装一个接近的版本。如果实在加载不了,就重新跑一遍core.py训练,不要死磕旧模型文件。

注意:jpblib.pkl这个文件名看起来像是joblib.pkl的拼写变体,加载时先确认实际文件名,不要直接照抄代码里的路径。

3.2 notebook 执行顺序:从 firstTry 到 core 的推荐路径

源码包里有一堆.ipynb文件:firstTry.ipynb、justTest.ipynb、some test.ipynb、getPayloadPrarmaters.ipynb、getNormalParamaters.ipynb、core.ipynb、save_with_pickle.ipynb、save_with_joblib.ipynb。这些 notebook 不是随便放的,它们反映了作者的开发顺序。我建议按以下顺序执行:

第一步,打开firstTry.ipynb,看看作者最初怎么读取数据、怎么定义标签。这个文件通常比较粗糙,但能帮你快速理解数据格式。第二步,跑getPayloadPrarmaters.ipynb和getNormalParamaters.ipynb,这两个负责从恶意和正常样本中提取参数特征。第三步,跑wordProcess.py和removeRepeat.py,生成去重后的字符序列。第四步,跑core.ipynb或core.py,训练模型并输出评估结果。最后,用save_with_joblib.ipynb保存模型,方便后续检测调用。

# 在 notebook 里检查数据加载是否正常 import pandas as pd # 读取 payload 和正常请求,注意编码问题 with open('src/payload.txt', 'r', encoding='utf-8', errors='ignore') as f: malicious = [line.strip() for line in f if line.strip()] with open('src/normal_require.txt', 'r', encoding='utf-8', errors='ignore') as f: normal = [line.strip() for line in f if line.strip()] print(f"恶意样本数: {len(malicious)}") print(f"正常样本数: {len(normal)}") print(f"正负比: 1:{len(normal)/max(len(malicious),1):.1f}") # 看几条样本,确认数据质量 for s in malicious[:3]: print("MAL:", s[:120]) for s in normal[:3]: print("NOR:", s[:120])

这段代码的关键是errors='ignore',因为原始日志里可能有非 UTF-8 字符,不加这个参数会直接报UnicodeDecodeError。打印正负比是为了判断要不要做样本均衡。如果比例超过 1:10,建议先欠采样正常样本再训练。查看前几条样本可以直观感受数据质量,如果恶意样本里混了大量正常请求,说明数据标注有问题,需要手动清洗。

3.3 关键参数怎么调:max_len、n_estimators 与 test_size 的取舍

这份源码里没有集中的配置文件,参数散落在各个脚本和 notebook 里。根据我的经验,有三个参数对结果影响最大:字符序列长度max_len、随机森林的n_estimators、以及训练集划分的test_size。

max_len决定每条请求保留多少字符。设得太小,比如 50,长 payload 的关键攻击特征会被截掉;设得太大,比如 1000,特征维度爆炸,训练慢且容易过拟合。Web 请求的 payload 大部分在 100 到 300 字符之间,所以max_len=200到300是比较稳的选择。可以在 notebook 里画一下 payload 长度分布,选覆盖 90% 样本的长度作为max_len。

n_estimators是随机森林的树数量。树越多,模型越稳定,但训练和推理时间线性增长。100 棵树在几千条样本上通常够用,如果样本上万,可以加到 200。但不要盲目加到 1000,收益递减很明显,而且模型文件会变大。

test_size一般设 0.2 到 0.3。如果样本量很小,比如总共只有几百条,可以设 0.3 让测试集更有代表性;如果样本上万,0.2 就够了。关键是加stratify=y,保证正负样本在训练集和测试集里比例一致。

# 画 payload 长度分布,辅助选择 max_len import matplotlib.pyplot as plt lengths = [len(s) for s in malicious + normal] plt.hist(lengths, bins=50) plt.xlabel('payload length') plt.ylabel('count') plt.title('Payload Length Distribution') plt.axvline(x=200, color='r', linestyle='--', label='max_len=200') plt.legend() plt.show() # 输出 90 分位数,作为 max_len 参考 import numpy as np print(f"90% 分位数: {np.percentile(lengths, 90):.0f}") print(f"95% 分位数: {np.percentile(lengths, 95):.0f}")

这段代码帮你用数据说话,而不是拍脑袋定max_len。如果 90 分位数是 180,那max_len=200就能覆盖绝大多数样本;如果 90 分位数是 500,那就要考虑调大或者对长 payload 做特殊处理。plt.axvline画出参考线,直观看到截断位置。

4. 避坑与排查:这份源码跑不起来时先查这五件事

4.1 现象:加载 pickle/joblib 文件报版本不兼容 → 原因:scikit-learn 版本差异 → 解决:重训或降版本

这是最常见的问题。test.pickle、jpblib.pkl、joblib.pkl这些文件是在作者的环境里序列化的,如果你本地的 scikit-learn 版本和作者不一致,加载时就会报AttributeError: module 'sklearn.ensemble' has no attribute '...'或者ValueError: node array from the pickle has an incompatible dtype。解决办法有两个:一是看README.md里有没有写版本号,装一个接近的版本;二是直接删掉旧模型文件,重新跑core.py训练一遍。我一般选第二种,因为重训虽然花几分钟,但能保证环境和代码完全匹配,后面调试也省心。

4.2 现象:notebook 里中文注释乱码或读取文件报 UnicodeDecodeError → 原因:编码不一致 → 解决:统一用 utf-8 + errors='ignore'

原始日志和 HTML 文件可能来自不同系统,编码格式不统一。Windows 下可能是 GBK,Linux 下可能是 UTF-8,混在一起就会报UnicodeDecodeError。解决办法是在所有open()调用里加encoding='utf-8', errors='ignore'。如果确认是 GBK 文件,就单独用encoding='gbk'读。notebook 里如果中文注释显示乱码,检查文件本身的编码,用 VS Code 或 Notepad++ 转成 UTF-8。

4.3 现象:模型评估 accuracy 很高但实际检测效果差 → 原因:样本不均衡导致模型偏向多数类 → 解决:看 recall 和 F1,做欠采样或过采样

如果正常样本有 5000 条,恶意样本只有 200 条,模型全预测成正常就能拿到 96% 的 accuracy,但恶意请求一条都抓不到。这是监督学习在入侵检测里最典型的翻车场景。解决办法是不要只看 accuracy,重点看classification_report里 malicious 类的 recall 和 F1-score。如果 recall 低于 0.8,就要做样本均衡:欠采样正常样本到和恶意样本同一量级,或者用 SMOTE 生成合成恶意样本。这份源码里没有内置均衡逻辑,需要自己在core.py里加。

4.4 现象:reptile.py 跑不动或爬取超时 → 原因:目标站点反爬或网络环境变化 → 解决:跳过爬虫,直接用 src 目录里的现成数据

reptile.py是爬虫脚本,依赖外部站点的可用性。如果目标站点改了结构、加了反爬,或者你的网络环境访问不了,这个脚本就跑不通。但好消息是src目录里已经包含了大量现成的ff_*.html、ff_*.txt和access_log_payload*.txt,完全可以直接用这些数据做训练,跳过爬虫步骤。我一般会先检查src目录的数据量,如果够用就不跑reptile.py,省得折腾。

4.5 现象:joblib.dump 保存的模型文件特别大 → 原因:随机森林树太多或 max_depth 太大 → 解决:限制树数量和深度,或改用逻辑回归

随机森林的模型大小和树的数量、深度成正比。如果n_estimators=500且max_depth=None,模型文件可能几百 MB,加载慢、传输也不方便。解决办法是把n_estimators降到 100 到 200,max_depth限制在 20 到 30。如果对模型大小有严格要求,可以改用逻辑回归或朴素贝叶斯,模型文件通常只有几 KB 到几十 KB,虽然效果可能略差,但部署方便很多。

5. 从能跑到好用:用交叉验证和混淆矩阵把检测效果压榨出来

5.1 用交叉验证替代单次 train_test_split,结果更可信

单次train_test_split的结果受随机种子影响很大,换一个random_state可能 F1 差好几个百分点。更稳的做法是做 5 折或 10 折交叉验证,看平均 F1 和标准差。如果标准差很大,说明模型不稳定,需要调参或增加数据。

from sklearn.model_selection import cross_val_score from sklearn.ensemble import RandomForestClassifier clf = RandomForestClassifier(n_estimators=100, max_depth=20, random_state=42, n_jobs=-1) scores = cross_val_score(clf, X, y, cv=5, scoring='f1') print(f"5 折 F1: {scores}") print(f"平均 F1: {scores.mean():.4f} (+/- {scores.std():.4f})")

cv=5表示 5 折交叉验证,scoring='f1'直接优化 F1 而不是 accuracy。如果平均 F1 在 0.9 以上且标准差小于 0.03,说明模型比较稳;如果标准差超过 0.05,就要检查数据里是不是有噪声样本或者分布不均。

5.2 混淆矩阵告诉你模型到底错在哪

classification_report给的是汇总指标,混淆矩阵能让你看到具体有多少正常请求被误判成恶意、多少恶意请求被漏掉。在入侵检测场景里,漏报(恶意判正常)通常比误报(正常判恶意)更严重,所以要根据业务需求调整分类阈值。

from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay import matplotlib.pyplot as plt cm = confusion_matrix(y_test, y_pred) disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=['normal', 'malicious']) disp.plot(cmap='Blues') plt.title('Confusion Matrix') plt.show() # 手动计算漏报率和误报率 tn, fp, fn, tp = cm.ravel() print(f"漏报率(恶意判正常): {fn/(fn+tp):.4f}") print(f"误报率(正常判恶意): {fp/(fp+tn):.4f}")

cm.ravel()按[tn, fp, fn, tp]顺序展开。漏报率是fn/(fn+tp),误报率是fp/(fp+tn)。如果漏报率太高,可以降低判定为恶意的阈值,牺牲一点误报率来换取更低的漏报。如果误报率太高,就提高阈值。这个权衡没有标准答案,取决于你的系统能承受多少误报。

5.3 一个实用技巧:把模型预测概率存下来,方便后续调阈值

默认的clf.predict()直接输出 0 或 1,但很多时候我们需要根据业务调整阈值。用clf.predict_proba()拿到恶意类的概率,存成 CSV,后面想改阈值不用重新训练。

import pandas as pd proba = clf.predict_proba(X_test)[:, 1] # 恶意类的概率 result = pd.DataFrame({ 'true_label': y_test, 'malicious_proba': proba, 'pred_label': (proba >= 0.5).astype(int) }) result.to_csv('detection_scores.csv', index=False) # 试几个阈值,看漏报和误报怎么变 for thresh in [0.3, 0.4, 0.5, 0.6, 0.7]: pred = (proba >= thresh).astype(int) tn, fp, fn, tp = confusion_matrix(y_test, pred).ravel() print(f"阈值 {thresh}: 漏报率={fn/(fn+tp):.4f}, 误报率={fp/(fp+tn):.4f}")

这段代码把预测概率和真实标签一起存成 CSV,后面不管是用 Excel 分析还是写脚本调阈值都很方便。for thresh in [...]循环试了五个阈值,输出对应的漏报率和误报率,帮你找到业务上最能接受的平衡点。我一般会把这个 CSV 留着,答辩时直接展示不同阈值下的效果对比,比只报一个 accuracy 有说服力得多。

从那以后我每次跑完入侵检测模型,都会强制走一遍混淆矩阵和阈值扫描,不看到漏报率和误报率的具体数字,心里就不踏实。希望这份源码和上面的步骤能帮你少走点弯路,顺利把毕设跑通。

本文还有配套的精品资源,点击获取