news 2026/10/6 16:18:37

机器学习检测恶意URL:SVM与n-gram特征工程实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
机器学习检测恶意URL:SVM与n-gram特征工程实战

简介:这是一份面向计算机相关专业课程设计、期末大作业与毕业设计的机器学习实践项目,聚焦恶意URL检测场景,包含改进后的完整源码与项目说明。压缩包共15个文件,主体为3个Python工程脚本(数据预处理、模型训练与检测调用),辅以7个文本说明/标签映射文件、1份README文档、1个可视化结果图、1个pcap网络流量样本及1个训练好的pickle模型,整体体积10.82MB,结构紧凑、便于按需查阅。已有64人学习下载,适合具备Python与机器学习基础、希望快速搭建恶意URL检测原型的学习者参考。资源提供可直接运行的改进版检测流程、已训练模型与配套数据,可帮助读者理解从特征提取、模型调参到结果可视化的完整链路,也能作为毕设或课设的实用起点,节省自行造轮子的时间。

1. 机器学习检测恶意URL:黑名单失效后,让SVM从URL字符串里找规律

黑白名单是URL防护的第一道墙,但面对每天新生成的短链接、随机子域和跳转域名,黑名单的召回率会迅速衰减。这个资源走的是另一条路:用机器学习检测恶意URL,把流量包里的URL转成n-gram特征,交给SVM训练二分类模型,再回到test.pcap上做实测。项目不大,但数据采集、特征工程、模型训练、推理一条链路完整,适合正在做课程设计、期末大作业或毕设的计算机相关专业学生,也适合想搞清楚"特征到底怎么从URL里抠出来"的从业者。三个入口脚本分工清晰——pcap.py管数据,model.py管训练,start.py管预测,跑通一遍基本就懂了整条pipeline。

2. 先拆文件再看代码:test.pcap、train目录和三个py的职责边界

拿到zip别急着跑,先按README把目录过一遍。这个项目最值得看的不是某个算法有多深,而是它的文件组织方式已经把流程暗示得很清楚。解压后的结构大致是:

project_code_0628/ ├── MaliciousUrls2.png # 检测流程示意图 ├── data/ │ ├── train/ │ │ ├── bad/ # 恶意URL样本,一行一条 │ │ └── good/ # 正常URL样本,一行一条 │ └── test.pcap # 待检测的流量包 ├── start.py # 入口脚本:加载模型做检测 ├── pcap.py # 从pcap中解析HTTP请求,提取URL ├── model.py # 读取训练数据,做特征工程,训练SVM ├── model/ │ └── k80.label/ │ └── SVM__n2_k80.pickle # 训练好的SVM模型及元数据 ├── requirements.txt └── README.md

各文件职责可以用一张表说清:

文件定位输入输出
pcap.py数据采集data/test.pcapURL文本列表
model.py训练data/train/bad、data/train/goodmodel/k80.label/SVM__n2_k80.pickle
start.py推理流量包或URL文本恶意/正常判定结果
SVM__n2_k80.pickle模型产物由model.py生成供start.py加载

注意README里通常还会写依赖版本和运行顺序,我建议先按requirements.txt装好scapy、scikit-learn,再跑start.py。顺序反了会踩一堆import报错,后面第5章专门说。

2.1 训练数据是如何组织的:bad与good目录里的奥妙

train/bad和train/good是两类纯文本URL集合,每行一条。别小看这个目录结构,它直接决定了后续特征工程的写法:读取时只需要遍历两个目录,把文件名当作标签。常见做法是给bad标1、good标0,然后所有文件拼成一个带标签的DataFrame或list。

样本内容上,恶意URL集合里通常能看到几类典型形态:包含随机数字字母的子域、路径里带base64或hex编码串、短链跳转、直接IP加路径。正常URL则多以常见域名和可读路径为主。这些差异正是bigram特征能抓住的信号——恶意样本的字符组合模式往往更集中,比如随机子域会产生大量重复的"数字+字母"二元组。

我自己拆这类项目的时候,会先统计两类样本的行数和平均长度。样本量不均衡会直接影响SVM训练,如果bad和good差了一个数量级,后面预测结果大概率全偏向多的一侧。这个项目的train目录里两类样本数量设计得比较均衡,但还是建议你打开数一下,有个心理预期。

2.2 为什么还要一个test.pcap:训练和推理的数据形态可以不一样

训练数据是纯文本URL,推理输入却是pcap包,这是整个项目里最容易被忽略的设计点。我在第一次跑的时候就想不通:既然训练用的都是文本,检测时为什么不直接喂URL文本,非要绕一道pcap?

实际原因是pcap才是真实环境里的原始形态。你在业务侧能拿到的东西往往不是一个干净的URL字符串,而是一段流量、一份抓包文件,得先从流量里把HTTP请求解析出来,再还原成URL。pcap.py就是干这件事的。它把test.pcap读进来,按HTTP层拆出Host和Path,拼接成完整URL,然后再走和训练时完全一样的特征函数。

这个设计的好处是:训练时用文本,部署时用抓包,两头都能跑;坏处是pcap解析成了额外的一道坎。后面第3章会细说scapy怎么提URL,第5章会讲解析失败时怎么排障。

2.3 数据流全景:抓包、特征、训练、检测四段链路

把整个项目连起来看,数据流是这样的:

test.pcap --pcap.py--> URL文本 --特征函数--> 特征向量 --start.py--> 预测标签 train/bad + train/good --model.py--> 特征矩阵+标签 --> SVM训练 --> pickle模型

model.py负责后半段:读训练数据、做特征化、训练SVM、把模型和词表一起存进pickle。start.py负责前半段的推理:读test.pcap或用户输入URL,用同一个特征函数转成同维度向量,加载pickle里的模型和词表,输出恶意或正常。两条链路在"特征函数"这个点上汇合,这也意味着训练和推理必须共用同一套特征代码,否则维度对不上,pickle加载后全是维度错误。第5章会讲这个坑的具体表现。

3. pcap.py解析流量:把pcap变成URL特征向量的关键一步

pcap.py在全项目里最容易被当成"辅助工具"跳过,实际它决定了上游数据质量。pcap里HTTP层解析不干净,后面所有特征和预测都是白做。

3.1 用scapy读pcap并提取HTTP请求URL

scapy是这类项目里最常用的pcap解析库,写法也直接。核心思路是遍历每个包,判断是否含有HTTPRequest层,有就把Host和Path拼起来。常见实现长这样:

from scapy.all import rdpcap from scapy.layers.http import HTTPRequest def extract_urls_from_pcap(pcap_path: str) -> list: packets = rdpcap(pcap_path) urls = [] for pkt in packets: if pkt.haslayer(HTTPRequest): host = pkt[HTTPRequest].Host.decode(errors="ignore") path = pkt[HTTPRequest].Path.decode(errors="ignore") if host and path: urls.append(f"http://{host}{path}") return urls

这段代码里两个细节要说明:HTTPRequest是scapy的HTTP解析层,需要单独import,直接from scapy.all import *不一定带得进来;decode(errors="ignore")是保命写法,pcap里的HTTP头部可能混入非UTF8字节,不加ignore会在解码时直接抛异常,整个脚本中断。

rdpcap会把整个pcap一次性载入内存,小文件没问题,但如果你换成一个几百MB的抓包文件,内存会直接爆掉。我一般会改成PcapReader流式读取,或者先用tcpdump过滤一遍再喂进来。这个项目里的test.pcap是精心裁剪过的,一次性读没问题。

3.2 特征命名拆解:n2与k80到底是什么意思

模型文件名叫SVM__n2_k80.pickle,这个命名不是随手起的,它把特征方案写在文件名里了。n2表示bigram,也就是把URL按相邻两个字符切分成二元组;k80表示最终保留80维特征。这是字符级n-gram加特征筛选的经典组合。

def url_to_bigrams(url: str) -> list: url = url.strip().lower() return [url[i:i+2] for i in range(len(url) - 1)] def url_to_vector(url: str, vocab: dict) -> list: vec = [0] * len(vocab) for bg in url_to_bigrams(url): if bg in vocab: vec[vocab[bg]] += 1 return vec

第一段代码把URL切成相邻字符对,比如ab12.com会切出ab、b1、12、2.、.c、co、om。第二段把bigram映射到词表下标,统计频次,得到一个和词表等长的稀疏向量。vocab是训练时从所有URL里统计bigram、按某种指标筛出80个高频词建成的字典;预测时直接用同一个字典,保证向量维度一致。

关于k80的取值,80维听起来很少,但对SVM来说完全够用。字符级bigram全集很大(字母数字符号组合能到几千个),但大量bigram在两类样本中出现的频次差异极小,属于噪声。用互信息或卡方筛选掉这些低频项,只保留区分度最高的几十维,反而能提升泛化能力。这也是为什么文件名里把n2和k80写死——它记录了一次特征实验的完整配置。

3.3 实际调参:抓包范围、过滤条件与特征维度怎么改

如果你打算在自己的环境里复现并改造,这三处参数是优先要动的:

第一是pcap解析范围。test.pcap是裁剪过的,几乎每个包都是HTTP请求;真实环境里包会混杂DNS、TLS握手、TCP重传,直接全量解析很浪费。我一般会先用tcpdump -r test.pcap -w filtered.pcap "tcp port 80"把HTTP流量单独筛出来,再交给scapy。这样解析速度能快一个数量级,也能减少误提。

第二是特征维度k。把k80改成k200不是只改一个数字的事:词表变了,pickle里的vocab要同步更新,start.py里加载模型后必须用新词表做特征化。最稳妥的做法是改完model.py重新训练一次,让pickle里的模型和词表一次性刷新。

第三是n的取值。n2(bigram)擅长抓字符组合模式,但对"URL长度异常""路径中含长数字串"这类全局特征无能为力。如果你想加入长度特征、数字占比特征,需要在url_to_vector里拼接额外维度,并且把k值同步调整。这类全局特征和bigram组合,通常能把准确率再往上拉几个点。

4. model.py训练SVM:从特征矩阵到SVM__n2_k80.pickle

model.py是核心训练脚本,但它本身不复杂。复杂的是特征函数和模型配置的配合关系,搞懂这一段,pickle文件就不再是黑匣子了。

4.1 为什么选SVM而不是深度学习

恶意URL检测这个任务有个特点:样本量不大,单条URL的特征维度也不高。在几百到几千条训练样本、几十到几百维特征的情况下,SVM是性价比最高的选择之一。RBF核的SVM能把特征映射到高维空间,恰好够表达"恶意URL的bigram模式与正常URL不同"这种非线性关系,又不会像神经网络那样动辄需要几万条样本才训得起来。

深度学习的坑在数据量:字符级bigram特征训练一个小型MLP,通常需要至少上万条标注URL,这个项目的数据规模撑不起。SVM在小样本上泛化能力明显更好,而且训练时间以秒计,迭代调参很快。做毕设答辩时,"为什么选SVM而不选深度学习"几乎是必问题,回答要点就是:样本量小、特征维度可控、SVM在小样本高维场景下更稳,且结果可解释性强。

4.2 训练主流程:数据集切分与模型保存

model.py的主流程一般长这样:

from sklearn.svm import SVC from sklearn.model_selection import train_test_split import pickle, os def build_features(): # 遍历train/bad与train/good,调用url_to_vector # 返回X(特征矩阵)与y(标签列表) pass X, y = build_features() X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, stratify=y, random_state=42 ) clf = SVC(kernel="rbf", C=1.0, gamma="scale", probability=True) clf.fit(X_train, y_train) print("测试集准确率:", clf.score(X_test, y_test)) os.makedirs("model/k80.label", exist_ok=True) with open("model/k80.label/SVM__n2_k80.pickle", "wb") as f: pickle.dump({"model": clf, "vocab": vocab}, f)

这段代码有三个关键点。第一是stratify=y,它保证切分后训练集和测试集里bad/good的比例和原始数据一致,避免运气不好把某一类全切进测试集。第二是gamma="scale",这是sklearn较新版本的默认值,会自动根据特征维度计算gamma,比写死gamma=0.1更稳。第三是pickle里不只存了clf,还存了vocab,这比只存模型更专业——推理的时候必须用同一个词表做特征化,词表丢了,模型就是废的。

模型保存路径里的k80.label这个目录名,是在延续特征命名的规范。你把词表改成200维,就应该建一个k200.label目录,pickle命名也同步改成SVM__n2_k200.pickle。这种命名习惯会让后期管理多个模型版本省很多事,强烈建议保留。

4.3 模型文件的命名规则与再训练方法

SVM__n2_k80.pickle的命名规则是:模型类型+特征方案+维度。换特征、换模型、换维度,都通过改名体现。我自己维护这类项目时,会在pickle里额外加一个字段记录训练时间、训练样本数、测试集准确率,免得三个月后回来看模型文件完全想不起来是怎么训出来的。

重新训练也简单:删掉旧的pickle,改model.py里的路径,重跑一遍。唯一要小心的是重训后start.py必须加载新pickle,如果你同时保留了n2_k80和n2_k200两个模型文件,记得检查入口脚本里写的是哪个路径。这类低错我犯过不止一次,每次都是预测结果看着不对,排查半天才发现加载的是旧模型。

5. 复现避坑:跑通这个源码最容易翻车的五个细节

这个项目整体不复杂,但跑起来的小问题不少。下面五条是我按发生率排序的真实踩坑记录。

5.1 scapy解析pcap报错:HTTP层依赖缺失

现象:from scapy.layers.http import HTTPRequest直接ImportError,或者rdpcap能读包但haslayer(HTTPRequest)永远返回False。原因:scapy的HTTP支持依赖scapy_http模块,或者scapy版本太老不带HTTP层。解决:先pip install scapy确认版本在2.4.5以上,再装scapy-http;如果还不行,检查requirements.txt里指定的scapy版本,按它的版本重装一遍环境。我当时是conda环境里scapy版本过旧,升级后立刻正常。

5.2 pickle加载报错:特征维度对不上

现象:start.py里pickle.load成功,但predict时报"dimension mismatch"或"feature names mismatch"。原因:模型是用80维bigram词表训练的,而当前的url_to_vector用了另一套词表,或者k值被改过。解决:回查model.py里build_features用的词表来源,确认训练和推理共用同一个vocab字典。最稳妥的做法是重训模型,让pickle里的vocab和推理代码保持同步。

5.3 预测结果全是一个标签:样本不均衡

现象:模型跑通了,准确率显示90%以上,但把test.pcap里的URL全部预测成"正常"或全部预测成"恶意"。原因:训练集里bad与good数量差距过大,SVM学到的是把样本全判给多数类。解决:看train/bad和train/good的文件行数,如果差得多,用class_weight="balanced"或者对少数类做上采样。改完后重新训练,看测试集上两类各自的recall,别只看整体准确率。

5.4 自己抓的pcap解析不出URL:流量里根本没有HTTP

现象:换了自己的抓包文件后,extract_urls_from_pcap返回空列表。原因:现在的流量大量走HTTPS/TLS,HTTP层被加密,scapy默认看不到明文Host和Path。解决:这种项目实验环境里,要么用tcpdump抓tcp port 80的明文流量,要么在本地起一个HTTP服务制造样本。别指望直接拿公司生产环境的抓包文件来跑,里面基本全是加密流量,解析出来也是空的。

5.5 中文路径和Windows环境下的路径坑

现象:pcap_path写的是data\test.pcap,Windows下直接跑能过,但换到Linux就崩;或者路径里有中文,rdpcap读不到文件。原因:不同系统路径分隔符不同,加上代码里用了相对路径拼接。解决:统一用os.path.join("data", "test.pcap"),模型路径同理。我在Windows上踩过一次中文目录名导致的编码报错,从那以后所有实验目录一律用纯英文路径。

6. 进阶验证:用交叉验证和混淆矩阵判断SVM有没有过拟合

跑通只是第一步,判断模型是不是真的能用在真实场景里,还得做交叉验证。sklearn里几行就能搞定:

from sklearn.model_selection import cross_val_score from sklearn.metrics import confusion_matrix scores = cross_val_score(clf, X, y, cv=5, scoring="f1") print("五折F1:", scores.mean(), "+/-", scores.std()) y_pred = clf.predict(X_test) tn, fp, fn, tp = confusion_matrix(y_test, y_pred).ravel() print(f"TP={tp} FP={fp} FN={fn} TN={tn}")

交叉验证的核心目的是看模型在不同数据划分下的表现是否稳定。五折F1的均值在0.9以上、标准差低于0.05,说明模型不是靠运气命中;如果均值高但标准差很大,多半是训练集分布不够均匀,或者特征里有极端离群样本。混淆矩阵则用来分辨"误杀"和"漏网"具体是哪一类:fp多说明正常URL被误判得厉害,适合线上误报率高的场景;fn多说明恶意样本漏检,安全场景不可接受,需要增加恶意样本或换特征。

我拿到这个资源后,习惯是把训练代码改造一遍,把交叉验证结果和混淆矩阵一起输出,再跑start.py测test.pcap。这也顺便给毕设论文提供了实验数据——分类报告、混淆矩阵、五折交叉验证三张图放上去,答辩的"实验验证"这一部分就扎实了。你可以先按原样跑通,再做这三个增强:加交叉验证、输出混淆矩阵、尝试把k从80改成200对比效果,每一步改动都把结果记录下来。从那以后我每次拿到模型文件,都强制走一遍"训练-交叉验证-混淆矩阵-样本检查"的流程,确认没问题才敢让它上线跑数据,希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 16:15:42

遥感图像识别四模型协同流水线:kNN+SVM+CNN+LSTM

简介:本资源是一套面向遥感图像识别初学者与算法实践者的机器学习与深度学习综合实验方案,聚焦kNN、SVM、CNN及LSTM四类主流模型在WHU-RS19遥感数据集上的系统性对比实现。资源共34个文件,涵盖6个核心Python脚本(含数据预处理、模…

作者头像 李华
网站建设 2026/10/6 16:11:50

python五大数据容器切片 基础内容

python五大数据容器 | 切片TOC 在啃Python五大数据容器的时候,我发现切片算是新手最容易混淆的知识点之一。很多同学刚学会下标取值,一碰到切片的起始、终止、步长就乱了,尤其反向切片很容易踩空。 切片本质上是有序容器专属的截取工具&…

作者头像 李华
网站建设 2026/10/6 16:11:49

字符设备驱动(一)

字符设备驱动(一)目录前言我们前两天提到了模块的编写,知道了模块是我们驱动代码的承载物,那么设备又是什么东西呢,顾名思义,它是我们在内核注册的对于硬件的一种抽象,如果把模块比作流水线&…

作者头像 李华
网站建设 2026/10/6 16:04:46

证书链不完整修复方法(进阶篇):原理、方案与优化

本文深入探讨证书链不完整修复方法(进阶篇),涵盖背景分析、原理剖析、实战步骤、配置示例、优化建议和避坑指南。随着业务规模增长,证书链不完整修复方法(进阶篇)的重要性日益凸显。无论你是刚入门还是资深…

作者头像 李华