
简介这份资源是面向计算机相关专业学生与从业者的高分毕业设计源码主题为基于监督学习的Web入侵检测系统采用Python实现评审分达97分可直接用于毕业设计、期末课程设计或课程大作业。压缩包共60个文件约2.25MB以18个ipynb实验笔记、14个txt样本与词表、14个html页面样本、8个py脚本为主另含pkl与pickle序列化模型文件、pyc编译文件及README说明覆盖数据采集、字符处理、去重、特征提取与模型保存等完整流程。资源中既有爬虫与日志解析脚本也有正常与恶意请求样本、关键词表及参数提取笔记便于读者理解Web入侵检测从原始流量到监督学习建模的全链路。目前已有188人学习下载适合需要完整项目方案、可运行代码与排错思路的读者参考复用。1. 从一份毕设源码说起监督学习怎么做 Web 入侵检测很多同学做毕设时都会遇到这个场景导师给了一个题目叫「基于监督学习的 Web 入侵检测系统」听起来方向明确但真正动手时才发现不知道从哪下手。数据集去哪找、特征怎么提、模型选哪个、检测效果怎么评估每一步都是坑。这份 95 分以上的毕设源码核心思路就是用 Python 把监督学习落地到 HTTP 请求流量上通过提取 URL、请求方法、参数等特征训练分类模型来区分正常请求和恶意攻击。它适合正在做安全方向毕设的本科生也适合想入门机器学习安全应用的开发者。读完你能搞清楚整个系统的技术链路知道每个模块为什么这么设计也能自己动手复现一套可运行的检测流程。2. 监督学习做入侵检测为什么选它、数据从哪来2.1 监督学习在 Web 入侵检测里的定位Web 入侵检测本质上是一个二分类或多分类问题给你一条 HTTP 请求判断它是正常访问还是攻击行为。监督学习的优势在于只要你有标注好的数据集就能训练出一个可用的分类器。常见做法是用公开的 Web 攻击数据集比如 CSIC 2010、ECML/PKDD 2007这些数据集里每条请求都标了 normal 或 anomalous。和基于规则匹配的 WAF 相比监督学习的好处是能泛化到没见过的攻击变种。规则匹配遇到编码变形、参数污染就容易漏报而模型学的是统计模式对变形有一定的鲁棒性。当然代价是需要标注数据而且模型的可解释性不如规则。我一般会先把问题定义清楚输入是一条 HTTP 请求的原始文本输出是 0正常或 1攻击。特征工程的核心就是把原始文本转成数值向量这一步直接决定模型上限。2.2 数据集获取与预处理的最小流程CSIC 2010 是最常用的入门数据集包含 36000 多条正常请求和 25000 多条攻击请求覆盖 SQL 注入、XSS、路径遍历等类型。下载后你会得到几个文本文件每行是一条完整的 HTTP 请求。预处理的第一步是解析原始请求。用 Python 的urllib.parse把 URL 拆成路径、查询参数、参数值。下面是一个最小可用的解析函数from urllib.parse import urlparse, parse_qs, unquote def parse_http_request(raw_line): 解析一行原始 HTTP 请求返回结构化字段 raw_line 格式示例: GET /search?qtest HTTP/1.1 parts raw_line.strip().split( ) if len(parts) 2: return None method parts[0] url parts[1] parsed urlparse(url) path unquote(parsed.path) # 解码路径还原 %2e 等编码 query parse_qs(parsed.query) # 解析查询参数为字典 # 把参数值拼成一个字符串方便后续做字符级特征 param_values .join([v for vals in query.values() for v in vals]) return { method: method, path: path, query: parsed.query, param_values: param_values, raw: raw_line.strip() }这段代码的逻辑是先按空格切分请求行拿到方法和 URL然后用urlparse拆出路径和查询串unquote做一次 URL 解码因为攻击者经常用百分号编码绕过检测parse_qs把查询串转成字典方便按参数名取值。参数说明raw_line是数据集里的一行文本method和path后续会做独热编码param_values用来提取特殊字符比例等特征。预处理完还要做标签对齐。CSIC 2010 的正常请求和攻击请求是分开存放的你需要给每条请求打上 0 或 1 的标签然后合并成一个 DataFrame。常见做法是用 pandas 读入加一列label最后 shuffle 一下再切分训练集和测试集。注意切分数据集时一定要用train_test_split的stratify参数保证训练集和测试集的类别比例一致否则评估结果会失真。3. 特征工程把 HTTP 请求变成模型能吃的数字3.1 三类核心特征与提取代码特征工程是这套系统里最需要经验的部分。我一般把特征分成三组字符统计特征、路径结构特征、参数语义特征。字符统计特征最直接统计请求里特殊字符的出现次数和比例。SQL 注入里常见单引号、双引号、分号、注释符XSS 里常见尖括号、script 关键字。下面这个函数提取一组基础统计量import re import numpy as np def extract_char_features(parsed_req): 从解析后的请求中提取字符级统计特征 返回一个固定长度的数值列表 raw parsed_req[raw] path parsed_req[path] params parsed_req[param_values] features [] # 特殊字符计数 special_chars [, , ;, , , (, ), , %] for ch in special_chars: features.append(raw.count(ch)) # 特殊字符占总长度的比例 total_len len(raw) 1e-6 features.append(sum(features) / total_len) # 路径深度 features.append(path.count(/)) # 参数值长度 features.append(len(params)) # 是否包含 SQL 关键字 sql_keywords [select, union, insert, update, delete, drop] features.append(sum(1 for kw in sql_keywords if kw in raw.lower())) # 是否包含 XSS 关键字 xss_keywords [script, alert, onerror, onload, iframe] features.append(sum(1 for kw in xss_keywords if kw in raw.lower())) # 数字和字母的比例 digits sum(c.isdigit() for c in raw) letters sum(c.isalpha() for c in raw) features.append(digits / total_len) features.append(letters / total_len) return features逻辑说明前 10 个特征统计特殊字符出现次数第 11 个是特殊字符占比第 12 个是路径层级第 13 个是参数值总长度第 14、15 个是 SQL 和 XSS 关键字命中数最后两个是数字和字母占比。参数方面parsed_req是上一节解析函数的输出1e-6是防止除零。这些特征加起来一共 17 维配合后面的模型足够跑出一个不错的基线。3.2 特征归一化和维度控制提取出来的特征量纲不统一比如字符计数可能是 0 到几百而比例特征是 0 到 1。直接喂给逻辑回归会导致大数值特征主导梯度。常见做法是用StandardScaler做标准化或者用MinMaxScaler缩到 0 到 1。from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split import pandas as pd # 假设 df 里有 raw_request 列和 label 列 parsed_list [parse_http_request(r) for r in df[raw_request]] feature_matrix np.array([extract_char_features(p) for p in parsed_list]) labels df[label].values # 标准化 scaler StandardScaler() feature_matrix scaler.fit_transform(feature_matrix) # 切分stratify 保证类别比例 X_train, X_test, y_train, y_test train_test_split( feature_matrix, labels, test_size0.3, random_state42, stratifylabels )这里StandardScaler的fit_transform只在训练集上做测试集要用训练集的均值方差来transform否则会数据泄露。random_state42是为了结果可复现stratifylabels保证正负样本比例一致。维度控制方面17 维不算多但如果后续加了 TF-IDF 或者 n-gram 特征维度可能上千。这时候要么用SelectKBest做特征选择要么用 PCA 降维。我一般先看特征重要性把贡献低的直接砍掉比 PCA 更直观。提示特征提取函数里用了raw.lower()做大小写归一因为攻击者经常用SeLeCt这种混合大小写绕过规则但模型对大小写不敏感反而更稳。4. 模型训练与评估从逻辑回归到集成学习4.1 基线模型选择和训练脚本特征准备好之后模型选择其实没有想象中那么玄学。Web 入侵检测的特征维度不高样本量几万条逻辑回归和随机森林都能跑出不错的效果。我一般先用逻辑回归做基线因为它训练快、可解释性强系数能看出哪些特征重要。from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, confusion_matrix, roc_auc_score # 逻辑回归基线 lr LogisticRegression(max_iter1000, class_weightbalanced, random_state42) lr.fit(X_train, y_train) lr_pred lr.predict(X_test) lr_prob lr.predict_proba(X_test)[:, 1] print(逻辑回归评估) print(classification_report(y_test, lr_pred, target_names[正常, 攻击])) print(AUC:, roc_auc_score(y_test, lr_prob)) # 随机森林对比 rf RandomForestClassifier(n_estimators100, max_depth15, random_state42, n_jobs-1) rf.fit(X_train, y_train) rf_pred rf.predict(X_test) print(随机森林评估) print(classification_report(y_test, rf_pred, target_names[正常, 攻击]))参数说明max_iter1000是因为逻辑回归默认迭代次数可能不够收敛class_weightbalanced在类别不均衡时自动调权重CSIC 数据集正常请求偏多这个参数很关键随机森林的n_estimators100是树的数量max_depth15控制过拟合n_jobs-1用满所有 CPU 核。评估指标不能只看准确率。如果正常请求占 70%全猜正常也有 70% 准确率但攻击一个都抓不到。所以重点看攻击类别的召回率和 F1以及 AUC。我一般要求攻击召回率在 0.95 以上误报率控制在 5% 以内。4.2 交叉验证和超参数调整单次切分的评估有随机性更稳的做法是做 5 折交叉验证。用cross_val_score可以快速看模型在不同数据划分下的表现from sklearn.model_selection import cross_val_score, GridSearchCV # 5 折交叉验证看 F1 的均值和方差 cv_scores cross_val_score(rf, X_train, y_train, cv5, scoringf1) print(fF1 均值: {cv_scores.mean():.4f}, 标准差: {cv_scores.std():.4f}) # 网格搜索调参 param_grid { n_estimators: [50, 100, 200], max_depth: [10, 15, 20, None], min_samples_split: [2, 5, 10] } grid GridSearchCV( RandomForestClassifier(random_state42, n_jobs-1), param_grid, cv3, scoringf1, n_jobs-1, verbose1 ) grid.fit(X_train, y_train) print(最佳参数:, grid.best_params_) print(最佳 F1:, grid.best_score_)cross_val_score的scoringf1直接优化 F1比准确率更贴合安全场景。GridSearchCV的cv3是内部交叉验证折数n_jobs-1并行搜索。注意网格搜索很吃时间参数组合多的时候先粗调再细调。如果逻辑回归和随机森林效果都不理想可以试试 XGBoost 或 LightGBM这两个在表格数据上通常更强。但毕设场景下随机森林的 F1 到 0.97 以上已经够用了没必要上太复杂的模型。注意训练完模型一定要用joblib.dump保存不然每次演示都要重新训练。保存时把 scaler 也一起存推理时要先做同样的标准化。5. 避坑与排查那些让我熬夜的翻车现场5.1 数据泄露导致评估虚高现象训练集准确率 99%测试集也有 98%但实际部署后误报一堆。原因特征提取时用了全局统计量比如用整个数据集的均值做填充或者标准化时在切分前就fit了全部数据。解决所有拟合操作只在训练集上做测试集用训练集的参数transform。用Pipeline把 scaler 和模型串起来能避免这个问题。5.2 URL 解码顺序搞反现象模型对%27这种编码的 SQL 注入完全检测不到。原因先做了关键字匹配再解码导致%27没被还原成单引号。解决解析请求时第一步就unquote把编码还原后再提取特征。但要注意解码两次可能引入新问题一般解一次就够。5.3 类别不均衡导致攻击召回率低现象模型把大部分请求都判为正常攻击召回率只有 0.6。原因正常样本远多于攻击样本模型偏向多数类。解决训练时加class_weightbalanced或者用 SMOTE 做过采样。我一般先试class_weight不行再上采样。5.4 特征维度爆炸拖慢训练现象加了 TF-IDF 后特征从 17 维涨到 5000 维训练时间从几秒变成几分钟效果还没提升。原因字符级 n-gram 产生大量稀疏特征很多是噪声。解决用SelectKBest选前 200 个特征或者限制 n-gram 范围在 2 到 3min_df5过滤低频词。5.5 推理时忘记加载 scaler现象训练时 F1 0.97部署后预测结果全乱。原因推理代码只加载了模型没加载 scaler特征没标准化就喂进去了。解决用joblib把 scaler 和模型打包成一个字典一起保存推理时先scaler.transform再model.predict。6. 把检测系统跑起来从脚本到可演示的完整流程6.1 用 Flask 包一个最小推理接口毕设答辩时老师通常想看到实际效果光有训练脚本不够。用 Flask 包一个 HTTP 接口输入一条请求文本返回是否攻击演示起来很直观。from flask import Flask, request, jsonify import joblib import numpy as np app Flask(__name__) # 加载训练好的 scaler 和模型 artifact joblib.load(ids_model.pkl) scaler artifact[scaler] model artifact[model] app.route(/detect, methods[POST]) def detect(): data request.get_json() raw data.get(request, ) parsed parse_http_request(raw) if parsed is None: return jsonify({error: 无法解析请求}), 400 features np.array([extract_char_features(parsed)]) features scaler.transform(features) pred model.predict(features)[0] prob model.predict_proba(features)[0][1] return jsonify({ is_attack: bool(pred), confidence: round(float(prob), 4) }) if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse)逻辑说明接口接收 JSON 格式的请求文本复用训练时的解析和特征提取函数保证线上线下一致。joblib.load加载的字典里同时有 scaler 和 model避免遗漏。返回结果包含是否攻击和置信度方便演示时展示。参数方面port5000是 Flask 默认端口debugFalse在生产演示时关掉调试模式。启动后用 curl 测一下curl -X POST http://127.0.0.1:5000/detect \ -H Content-Type: application/json \ -d {request: GET /search?q1%27%20OR%20%271%27%271 HTTP/1.1}如果返回is_attack: true说明整条链路通了。6.2 验证模型是否真的学到了攻击模式训练完不能只看指标还要做行为验证。我一般会构造几条典型攻击和正常请求看模型输出是否符合预期。下面这个测试脚本覆盖 SQL 注入、XSS、路径遍历和正常访问test_cases [ (GET /search?q1 OR 11 HTTP/1.1, True), (GET /page?namescriptalert(1)/script HTTP/1.1, True), (GET /../../etc/passwd HTTP/1.1, True), (GET /index.html HTTP/1.1, False), (POST /login useradminpass123456 HTTP/1.1, False), ] for raw, expected in test_cases: parsed parse_http_request(raw) feat scaler.transform(np.array([extract_char_features(parsed)])) pred model.predict(feat)[0] status 通过 if pred expected else 失败 print(f[{status}] 预期{expected} 实际{pred} | {raw[:50]})这个脚本的价值在于如果某条攻击没检测出来你能立刻定位是特征没覆盖还是模型没学好。比如路径遍历那条如果失败说明路径深度特征权重不够可以针对性加强。6.3 一个容易被忽略的技巧特征重要性反推攻击类型随机森林自带feature_importances_逻辑回归有coef_。把特征名和重要性对应起来能看出模型到底在关注什么。我一般会打印前 10 个重要特征如果发现某个特征权重异常高就去检查是不是数据泄露或者特征设计有问题。import pandas as pd feature_names [ 单引号数, 双引号数, 分号数, 左尖括号, 右尖括号, 左括号, 右括号, 等号, 百分号, 特殊字符占比, 路径深度, 参数长度, SQL关键字, XSS关键字, 数字占比, 字母占比 ] importances rf.feature_importances_ feat_imp pd.Series(importances, indexfeature_names).sort_values(ascendingFalse) print(feat_imp.head(10))如果「单引号数」和「SQL关键字」排在前列说明模型确实学到了 SQL 注入的模式这个结果是可信的。如果某个无关特征排第一比如「字母占比」那就要警惕过拟合。这套流程跑下来从数据解析到模型部署大概两三天能搞定剩下的时间可以打磨特征和调参。我自己的习惯是先把基线跑通再逐步加特征每加一组就记录 F1 变化这样能清楚知道哪个改动真正有用。希望帮到你。本文还有配套的精品资源点击获取