督學習的Web入侵檢測畢設(shè)源碼:從數(shù)據(jù)到模型全流程解析)
簡介這份資源是面向計算機相關(guān)專業(yè)學生與從業(yè)者的高分畢業(yè)設(shè)計源碼包主題為基于監(jiān)督學習的Web入侵檢測系統(tǒng)采用Python實現(xiàn)評審得分達97分可直接用于畢業(yè)設(shè)計、期末課程設(shè)計或課程大作業(yè)等場景。壓縮包共60個文件約2.25MB以18個ipynb實驗筆記、14個txt樣本與詞表、14個html頁面樣本、8個py腳本為主另含pkl與pickle模型文件、md說明文檔等覆蓋數(shù)據(jù)采集、字符處理、去重、特征提取與模型保存等完整流程。內(nèi)容預(yù)覽顯示項目包含爬蟲腳本、正常與惡意請求樣本、關(guān)鍵詞表及多份Jupyter實驗記錄便于理解監(jiān)督學習在Web入侵檢測中的特征工程與建模思路。目前已有188人學習下載適合需要完整項目方案、可運行代碼與排錯參考的讀者借鑒使用。1. 從一份 97 分畢設(shè)源碼說起監(jiān)督學習做 Web 入侵檢測到底靠不靠譜很多同學做畢設(shè)時都會卡在同一個地方題目選好了方向也定了但真正動手時發(fā)現(xiàn)要么數(shù)據(jù)找不到要么模型跑不通要么代碼寫完自己都不知道對不對。這份基于監(jiān)督學習的 Web 入侵檢測系統(tǒng) Python 源碼就是沖著這個痛點來的——它是一套完整可運行的畢設(shè)級項目從 HTTP 請求日志的采集、payload 參數(shù)提取、字符級特征處理到監(jiān)督學習模型訓(xùn)練和檢測整條鏈路都有對應(yīng)的腳本和 notebook。它解決的核心問題是把原始 Web 訪問日志變成模型能吃的特征向量再用監(jiān)督學習算法判斷某條請求是正常訪問還是惡意攻擊。適合計算機相關(guān)專業(yè)的本科生做畢業(yè)設(shè)計、期末大作業(yè)也適合剛?cè)胄邪踩较?、想動手跑一遍入侵檢測流程的從業(yè)者。源碼里包含了爬蟲腳本、字符處理腳本、去重腳本、參數(shù)提取 notebook、模型核心代碼以及多個 pickle/joblib 序列化文件結(jié)構(gòu)上覆蓋了從數(shù)據(jù)到模型的全流程。下面我按實際拆包的順序把這份資源怎么用、參數(shù)怎么調(diào)、哪里容易翻車講清楚。2. 拆開源碼包reptile.py 到 core.py 的數(shù)據(jù)鏈路與模塊職責2.1 從 reptile.py 到 payload.txt原始請求怎么變成可訓(xùn)練數(shù)據(jù)拿到源碼包后第一件事不是急著跑模型而是先理清數(shù)據(jù)從哪來、經(jīng)過哪些腳本、最終變成什么格式。這份資源的根目錄下有幾個關(guān)鍵文件reptile.py負責爬取或采集原始 HTTP 請求數(shù)據(jù)wordProcess.py做字符級處理removeRepeat.py去重makeChar.py生成字符集core.py是模型訓(xùn)練和檢測的核心邏輯。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是安全關(guān)鍵詞白名單。整個數(shù)據(jù)鏈路可以概括為原始日志 → payload 提取 → 字符處理 → 去重 → 特征向量 → 模型訓(xùn)練。我一般會先打開README.md確認作者標注的運行順序然后用count.py統(tǒng)計一下各類樣本的數(shù)量分布。這一步很關(guān)鍵因為監(jiān)督學習最怕的就是正負樣本嚴重不均衡。如果惡意樣本只有幾十條而正常樣本有幾千條后面訓(xùn)練出來的模型大概率會偏向多數(shù)類檢測效果會很難看。# 先看目錄結(jié)構(gòu)和文件數(shù)量心里有個底 ls -la find . -name *.py | head -20 find . -name *.txt | wc -l find . -name *.html | wc -l # 統(tǒng)計關(guān)鍵樣本文件的行數(shù)判斷正負樣本比例 wc -l src/payload.txt wc -l src/normal_require.txt wc -l src/normal-use1.txt wc -l src/safe_key.txt上面這幾條命令的作用是快速摸清數(shù)據(jù)規(guī)模。find . -name *.py列出所有 Python 腳本確認核心文件是否齊全wc -l統(tǒng)計樣本行數(shù)如果payload.txt只有幾十行而normal_require.txt有上千行那后面就必須做樣本均衡處理否則模型會嚴重偏斜。常見做法是用欠采樣把正常樣本降到和惡意樣本同一量級或者用 SMOTE 做過采樣但這份源碼里沒有內(nèi)置均衡邏輯需要自己補。2.2 wordProcess.py 與 removeRepeat.py字符級特征處理的兩個關(guān)鍵腳本wordProcess.py是整個特征工程的核心入口。它的作用是把原始請求字符串拆成字符序列再做歸一化處理。Web 入侵檢測和自然語言處理有個相似點請求 payload 本身就是一串字符SQL 注入、XSS、命令注入這些攻擊在字符層面有比較明顯的模式比如大量單引號、尖括號、分號、union select、../等。所以用字符級特征做監(jiān)督學習是合理的不一定非要上詞向量。removeRepeat.py負責去重避免同一條 payload 在訓(xùn)練集里反復(fù)出現(xiàn)導(dǎo)致模型過擬合。這個腳本通常會讀取payload.txt和normal_require.txt對每一行做哈?;蜃址葘敵鋈ブ睾蟮奈募akeChar.py則是生成字符集字典把所有出現(xiàn)過的字符映射成整數(shù)索引供模型輸入使用。# wordProcess.py 的典型處理邏輯根據(jù)源碼結(jié)構(gòu)還原 import re def normalize_payload(payload: str) - str: 對原始 payload 做歸一化轉(zhuǎn)小寫、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) # 把連續(xù)空白壓成一個空格 payload re.sub(r\s, , payload) return payload def char_tokenize(payload: str, char2idx: dict, max_len: int 200): 把字符串轉(zhuǎn)成整數(shù)索引序列超長截斷不足補零 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 解碼因為攻擊者經(jīng)常用編碼繞過簡單規(guī)則char_tokenize把字符映射成整數(shù)max_len控制序列長度太短會丟信息太長會增加計算量。參數(shù)方面max_len200是一個經(jīng)驗值Web 請求的 payload 通常不會超過這個長度如果數(shù)據(jù)集里有很多超長請求可以調(diào)到 500 甚至 1000但要注意顯存或內(nèi)存占用。UNK是未知字符的兜底索引防止測試時遇到訓(xùn)練集沒出現(xiàn)過的字符直接報錯。提示wordProcess.py和wordProcess.pyc同時存在說明作者可能用py_compile編譯過。直接跑.py就行.pyc是緩存不用管。2.3 core.py 與 pickle/joblib 文件模型訓(xùn)練與持久化的實際流程core.py是模型訓(xùn)練和推理的主入口。從目錄里的save_with_pickle.ipynb、save_with_joblib.ipynb、test.pickle、jpblib.pkl、joblib.pkl可以看出作者用了 pickle 和 joblib 兩種方式做模型持久化。core.ipynb和core.py內(nèi)容應(yīng)該基本一致notebook 方便調(diào)試.py方便直接運行。監(jiān)督學習部分常見做法是用 scikit-learn 里的分類器比如樸素貝葉斯、邏輯回歸、隨機森林或 SVM??紤]到字符級特征維度較高樸素貝葉斯和邏輯回歸比較輕量隨機森林效果通常更穩(wěn)但訓(xùn)練慢一些。源碼里沒有明確寫用哪個算法但從core.py的命名和getNormalPara.py、getPayloadPrarmaters.ipynb這些文件來看作者應(yīng)該是先提取正常請求和惡意請求的參數(shù)特征再喂給分類器。# core.py 的典型訓(xùn)練流程根據(jù)源碼結(jié)構(gòu)還原 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 # 假設(shè) X 是字符索引矩陣y 是標簽0 正常1 惡意 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) clf RandomForestClassifier( n_estimators100, # 樹的數(shù)量越多越穩(wěn)但越慢 max_depth20, # 控制過擬合太深會記住訓(xùn)練集 min_samples_leaf2, # 葉子節(jié)點最少樣本數(shù)防止噪聲影響 random_state42, 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)參數(shù)說明n_estimators100是隨機森林的默認起點如果訓(xùn)練集很大可以加到 200 或 300max_depth20是為了防止樹長得太深導(dǎo)致過擬合實際調(diào)參時可以用交叉驗證選stratifyy保證訓(xùn)練集和測試集里正負樣本比例一致這個在樣本不均衡時特別重要。評估時不要只看 accuracy因為如果惡意樣本只占 5%全預(yù)測成正常也有 95% 的準確率但實際毫無用處。重點看 malicious 類的 recall 和 F1-score。save_with_pickle.ipynb和save_with_joblib.ipynb的區(qū)別在于序列化方式。pickle 是 Python 標準庫通用但跨版本兼容性差joblib 對 numpy 數(shù)組優(yōu)化更好文件更小、加載更快。我一般優(yōu)先用 joblib尤其是模型里包含大量數(shù)組參數(shù)時。3. 把源碼跑起來環(huán)境配置、notebook 執(zhí)行順序與參數(shù)調(diào)整3.1 Python 環(huán)境與依賴安裝numpy、scikit-learn、joblib 一個都不能少這份源碼是純 Python 項目沒有復(fù)雜的深度學習框架依賴所以環(huán)境配置不算麻煩。但有幾個庫是必須的numpy做數(shù)值計算scikit-learn做模型訓(xùn)練和評估joblib做模型持久化pandas可能在 notebook 里用于數(shù)據(jù)查看。如果reptile.py里用了 requests 或 scrapy還需要裝對應(yīng)的爬蟲庫。# 建議用虛擬環(huán)境避免污染系統(tǒng) 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 版本差異導(dǎo)致模型加載失敗 python -c import sklearn; print(sklearn.__version__) python -c import numpy; print(numpy.__version__)這里有個血淚經(jīng)驗scikit-learn 不同版本之間pickle/joblib 文件的兼容性可能有問題。如果加載test.pickle時報AttributeError或ModuleNotFoundError大概率是版本不匹配。解決辦法是看README.md里有沒有標注版本或者用pip install scikit-learn1.x.x裝一個接近的版本。如果實在加載不了就重新跑一遍core.py訓(xùn)練不要死磕舊模型文件。注意jpblib.pkl這個文件名看起來像是joblib.pkl的拼寫變體加載時先確認實際文件名不要直接照抄代碼里的路徑。3.2 notebook 執(zhí)行順序從 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 不是隨便放的它們反映了作者的開發(fā)順序。我建議按以下順序執(zhí)行第一步打開firstTry.ipynb看看作者最初怎么讀取數(shù)據(jù)、怎么定義標簽。這個文件通常比較粗糙但能幫你快速理解數(shù)據(jù)格式。第二步跑getPayloadPrarmaters.ipynb和getNormalParamaters.ipynb這兩個負責從惡意和正常樣本中提取參數(shù)特征。第三步跑wordProcess.py和removeRepeat.py生成去重后的字符序列。第四步跑core.ipynb或core.py訓(xùn)練模型并輸出評估結(jié)果。最后用save_with_joblib.ipynb保存模型方便后續(xù)檢測調(diào)用。# 在 notebook 里檢查數(shù)據(jù)加載是否正常 import pandas as pd # 讀取 payload 和正常請求注意編碼問題 with open(src/payload.txt, r, encodingutf-8, errorsignore) as f: malicious [line.strip() for line in f if line.strip()] with open(src/normal_require.txt, r, encodingutf-8, errorsignore) as f: normal [line.strip() for line in f if line.strip()] print(f惡意樣本數(shù): {len(malicious)}) print(f正常樣本數(shù): {len(normal)}) print(f正負比: 1:{len(normal)/max(len(malicious),1):.1f}) # 看幾條樣本確認數(shù)據(jù)質(zhì)量 for s in malicious[:3]: print(MAL:, s[:120]) for s in normal[:3]: print(NOR:, s[:120])這段代碼的關(guān)鍵是errorsignore因為原始日志里可能有非 UTF-8 字符不加這個參數(shù)會直接報UnicodeDecodeError。打印正負比是為了判斷要不要做樣本均衡。如果比例超過 1:10建議先欠采樣正常樣本再訓(xùn)練。查看前幾條樣本可以直觀感受數(shù)據(jù)質(zhì)量如果惡意樣本里混了大量正常請求說明數(shù)據(jù)標注有問題需要手動清洗。3.3 關(guān)鍵參數(shù)怎么調(diào)max_len、n_estimators 與 test_size 的取舍這份源碼里沒有集中的配置文件參數(shù)散落在各個腳本和 notebook 里。根據(jù)我的經(jīng)驗有三個參數(shù)對結(jié)果影響最大字符序列長度max_len、隨機森林的n_estimators、以及訓(xùn)練集劃分的test_size。max_len決定每條請求保留多少字符。設(shè)得太小比如 50長 payload 的關(guān)鍵攻擊特征會被截掉設(shè)得太大比如 1000特征維度爆炸訓(xùn)練慢且容易過擬合。Web 請求的 payload 大部分在 100 到 300 字符之間所以max_len200到300是比較穩(wěn)的選擇??梢栽?notebook 里畫一下 payload 長度分布選覆蓋 90% 樣本的長度作為max_len。n_estimators是隨機森林的樹數(shù)量。樹越多模型越穩(wěn)定但訓(xùn)練和推理時間線性增長。100 棵樹在幾千條樣本上通常夠用如果樣本上萬可以加到 200。但不要盲目加到 1000收益遞減很明顯而且模型文件會變大。test_size一般設(shè) 0.2 到 0.3。如果樣本量很小比如總共只有幾百條可以設(shè) 0.3 讓測試集更有代表性如果樣本上萬0.2 就夠了。關(guān)鍵是加stratifyy保證正負樣本在訓(xùn)練集和測試集里比例一致。# 畫 payload 長度分布輔助選擇 max_len import matplotlib.pyplot as plt lengths [len(s) for s in malicious normal] plt.hist(lengths, bins50) plt.xlabel(payload length) plt.ylabel(count) plt.title(Payload Length Distribution) plt.axvline(x200, colorr, linestyle--, labelmax_len200) plt.legend() plt.show() # 輸出 90 分位數(shù)作為 max_len 參考 import numpy as np print(f90% 分位數(shù): {np.percentile(lengths, 90):.0f}) print(f95% 分位數(shù): {np.percentile(lengths, 95):.0f})這段代碼幫你用數(shù)據(jù)說話而不是拍腦袋定max_len。如果 90 分位數(shù)是 180那max_len200就能覆蓋絕大多數(shù)樣本如果 90 分位數(shù)是 500那就要考慮調(diào)大或者對長 payload 做特殊處理。plt.axvline畫出參考線直觀看到截斷位置。4. 避坑與排查這份源碼跑不起來時先查這五件事4.1 現(xiàn)象加載 pickle/joblib 文件報版本不兼容 → 原因scikit-learn 版本差異 → 解決重訓(xùn)或降版本這是最常見的問題。test.pickle、jpblib.pkl、joblib.pkl這些文件是在作者的環(huán)境里序列化的如果你本地的 scikit-learn 版本和作者不一致加載時就會報AttributeError: module sklearn.ensemble has no attribute ...或者ValueError: node array from the pickle has an incompatible dtype。解決辦法有兩個一是看README.md里有沒有寫版本號裝一個接近的版本二是直接刪掉舊模型文件重新跑core.py訓(xùn)練一遍。我一般選第二種因為重訓(xùn)雖然花幾分鐘但能保證環(huán)境和代碼完全匹配后面調(diào)試也省心。4.2 現(xiàn)象notebook 里中文注釋亂碼或讀取文件報 UnicodeDecodeError → 原因編碼不一致 → 解決統(tǒng)一用 utf-8 errorsignore原始日志和 HTML 文件可能來自不同系統(tǒng)編碼格式不統(tǒng)一。Windows 下可能是 GBKLinux 下可能是 UTF-8混在一起就會報UnicodeDecodeError。解決辦法是在所有open()調(diào)用里加encodingutf-8, errorsignore。如果確認是 GBK 文件就單獨用encodinggbk讀。notebook 里如果中文注釋顯示亂碼檢查文件本身的編碼用 VS Code 或 Notepad 轉(zhuǎn)成 UTF-8。4.3 現(xiàn)象模型評估 accuracy 很高但實際檢測效果差 → 原因樣本不均衡導(dǎo)致模型偏向多數(shù)類 → 解決看 recall 和 F1做欠采樣或過采樣如果正常樣本有 5000 條惡意樣本只有 200 條模型全預(yù)測成正常就能拿到 96% 的 accuracy但惡意請求一條都抓不到。這是監(jiān)督學習在入侵檢測里最典型的翻車場景。解決辦法是不要只看 accuracy重點看classification_report里 malicious 類的 recall 和 F1-score。如果 recall 低于 0.8就要做樣本均衡欠采樣正常樣本到和惡意樣本同一量級或者用 SMOTE 生成合成惡意樣本。這份源碼里沒有內(nèi)置均衡邏輯需要自己在core.py里加。4.4 現(xiàn)象reptile.py 跑不動或爬取超時 → 原因目標站點反爬或網(wǎng)絡(luò)環(huán)境變化 → 解決跳過爬蟲直接用 src 目錄里的現(xiàn)成數(shù)據(jù)reptile.py是爬蟲腳本依賴外部站點的可用性。如果目標站點改了結(jié)構(gòu)、加了反爬或者你的網(wǎng)絡(luò)環(huán)境訪問不了這個腳本就跑不通。但好消息是src目錄里已經(jīng)包含了大量現(xiàn)成的ff_*.html、ff_*.txt和access_log_payload*.txt完全可以直接用這些數(shù)據(jù)做訓(xùn)練跳過爬蟲步驟。我一般會先檢查src目錄的數(shù)據(jù)量如果夠用就不跑reptile.py省得折騰。4.5 現(xiàn)象joblib.dump 保存的模型文件特別大 → 原因隨機森林樹太多或 max_depth 太大 → 解決限制樹數(shù)量和深度或改用邏輯回歸隨機森林的模型大小和樹的數(shù)量、深度成正比。如果n_estimators500且max_depthNone模型文件可能幾百 MB加載慢、傳輸也不方便。解決辦法是把n_estimators降到 100 到 200max_depth限制在 20 到 30。如果對模型大小有嚴格要求可以改用邏輯回歸或樸素貝葉斯模型文件通常只有幾 KB 到幾十 KB雖然效果可能略差但部署方便很多。5. 從能跑到好用用交叉驗證和混淆矩陣把檢測效果壓榨出來5.1 用交叉驗證替代單次 train_test_split結(jié)果更可信單次train_test_split的結(jié)果受隨機種子影響很大換一個random_state可能 F1 差好幾個百分點。更穩(wěn)的做法是做 5 折或 10 折交叉驗證看平均 F1 和標準差。如果標準差很大說明模型不穩(wěn)定需要調(diào)參或增加數(shù)據(jù)。from sklearn.model_selection import cross_val_score from sklearn.ensemble import RandomForestClassifier clf RandomForestClassifier(n_estimators100, max_depth20, random_state42, n_jobs-1) scores cross_val_score(clf, X, y, cv5, scoringf1) print(f5 折 F1: {scores}) print(f平均 F1: {scores.mean():.4f} (/- {scores.std():.4f}))cv5表示 5 折交叉驗證scoringf1直接優(yōu)化 F1 而不是 accuracy。如果平均 F1 在 0.9 以上且標準差小于 0.03說明模型比較穩(wěn)如果標準差超過 0.05就要檢查數(shù)據(jù)里是不是有噪聲樣本或者分布不均。5.2 混淆矩陣告訴你模型到底錯在哪classification_report給的是匯總指標混淆矩陣能讓你看到具體有多少正常請求被誤判成惡意、多少惡意請求被漏掉。在入侵檢測場景里漏報惡意判正常通常比誤報正常判惡意更嚴重所以要根據(jù)業(yè)務(wù)需求調(diào)整分類閾值。from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay import matplotlib.pyplot as plt cm confusion_matrix(y_test, y_pred) disp ConfusionMatrixDisplay(confusion_matrixcm, display_labels[normal, malicious]) disp.plot(cmapBlues) plt.title(Confusion Matrix) plt.show() # 手動計算漏報率和誤報率 tn, fp, fn, tp cm.ravel() print(f漏報率惡意判正常: {fn/(fntp):.4f}) print(f誤報率正常判惡意: {fp/(fptn):.4f})cm.ravel()按[tn, fp, fn, tp]順序展開。漏報率是fn/(fntp)誤報率是fp/(fptn)。如果漏報率太高可以降低判定為惡意的閾值犧牲一點誤報率來換取更低的漏報。如果誤報率太高就提高閾值。這個權(quán)衡沒有標準答案取決于你的系統(tǒng)能承受多少誤報。5.3 一個實用技巧把模型預(yù)測概率存下來方便后續(xù)調(diào)閾值默認的clf.predict()直接輸出 0 或 1但很多時候我們需要根據(jù)業(yè)務(wù)調(diào)整閾值。用clf.predict_proba()拿到惡意類的概率存成 CSV后面想改閾值不用重新訓(xùn)練。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, indexFalse) # 試幾個閾值看漏報和誤報怎么變 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/(fntp):.4f}, 誤報率{fp/(fptn):.4f})這段代碼把預(yù)測概率和真實標簽一起存成 CSV后面不管是用 Excel 分析還是寫腳本調(diào)閾值都很方便。for thresh in [...]循環(huán)試了五個閾值輸出對應(yīng)的漏報率和誤報率幫你找到業(yè)務(wù)上最能接受的平衡點。我一般會把這個 CSV 留著答辯時直接展示不同閾值下的效果對比比只報一個 accuracy 有說服力得多。從那以后我每次跑完入侵檢測模型都會強制走一遍混淆矩陣和閾值掃描不看到漏報率和誤報率的具體數(shù)字心里就不踏實。希望這份源碼和上面的步驟能幫你少走點彎路順利把畢設(shè)跑通。本文還有配套的精品資源點擊獲取