资讯动态

Python启发式特征与随机森林的钓鱼网站检测系统设计

发布时间:2026/10/3 3:51:35 来源:尧图企业网站定制
简介这是一份基于Python启发式特征的钓鱼网站检测系统毕业设计资料包属于带完整源码、数据集和文档的高分项目适合计算机科学与技术、软件工程、人工智能、网络空间安全等专业的在校学生用于毕设、课设或初期项目演示也适合自学机器学习实践的开发者参考。包内共5个文件核心是两个Python脚本——html_svm.py与url_svm.py分别实现基于网页HTML内容的特征提取和基于URL结构特征的启发式分析并结合SVM分类器完成钓鱼网站识别另含一个数据集压缩包、README.md详细项目说明和README.html可视化介绍整体仅16KB文件层次清晰省去了环境配置与数据搜集的烦恼。目前已有318人学习下载代码经过测试可正常运行读者可按文档快速复现从特征构造、模型训练到评估的完整流程也可在此基础上扩展特征维度或更换算法是高年级本科生和研究生完成相关课题的实用参考资料。1. 启发式特征检测正在替代黑名单这个系统的核心价值在哪要说钓鱼网站检测最常见的误读是「拿黑名单比对不就完了」。但真实情况是钓鱼站通常几个小时内就更换域名或改路径黑名单的滞后性导致拦截率不到四成。基于 Python 启发式特征的钓鱼网站检测不依赖任何历史情报直接从 URL 词法、域名结构和页面 HTML 源码里挖「看起来像钓鱼」的线索再交给随机森林做判定。这套系统最适合需要完整走通「特征提取-数据准备-模型训练-接口封装」链路、并能在答辩中讲清每个特征含义的毕业设计场景。下面按这条链路逐段展开最后补一个特征贡献度审计技巧用来证明你的模型不是黑匣子。2. 启发式特征怎么设计才算够用词汇、结构与内容三层共 18 个特征2.1 三层特征体系与选择理由设计启发式特征的第一步是明确分层。我一般把候选特征分成三层URL 词汇特征、URL 结构特征、页面内容特征。分层的好处是后续做消融实验时可以直接说「词汇特征贡献了多少、内容特征贡献了多少」这是答辩时最常被问到的问题。层次特征名计算方式为什么有效URL 词汇sensitive_token_hits品牌词与诱骗词按词边界匹配钓鱼 URL 爱堆 login、verify、secure、updateURL 词汇url_entropy对整条 URL 算 Shannon 熵程序化生成的 URL 字符分布更混乱URL 结构is_ip主机名是否为裸 IPv4 地址正规站点极少用 IP 直接提供服务URL 结构subdomain_depth去掉顶级域后的子域层级数攻击者爱用多层子域伪装品牌URL 结构special_char_rate、-、_、% 等字符占比 号能让用户误判真实域名页面内容external_form_count表单 action 指向外部域名的数量钓鱼的核心动作是让数据出站页面内容hidden_input_counttypehidden 的 input 数量不可见控件常用来偷采信息页面内容meta_refresh是否存在 meta refresh 跳转用户无感知跳转到仿冒页为什么不做成纯规则判断因为规则碰撞的误杀率兜不住。比如「URL 里有 login 就算钓鱼」会把 outlook 登录页和无数正常站点的登录入口误判掉。把线索量化成连续数值让模型去学组合关系才能把误杀压下来。2.2 用 Python 把特征计算写成一条流水线我习惯把特征计算封装成两个纯函数一个吃 URL 出词法和结构特征一个吃 HTML 出内容特征。这样训练时批量跑、在线预测时单条跑逻辑完全一致。import re import math from urllib.parse import urlparse, urldefrag # 敏感词典按钓鱼话术维护可随数据迭代增删 SENSITIVE_TOKENS [login, signin, verify, secure, update, confirm, account, bank, paypal, ebay, free] def url_lexical_features(url: str) - dict: 从 URL 词法和结构上提取启发式特征全部数值化。 url urldefrag(url)[0] # 去掉 #fragmentfragment 不会发给服务器 parsed urlparse(url) host parsed.netloc.lower() # 是否裸 IP is_ip bool(re.match(r^\d{1,3}(\.\d{1,3}){3}(:\d)?$, host)) # 敏感词按词边界匹配避免 loginpage 这类长词被重复计数 name parsed.hostname or token_hits sum(1 for t in SENSITIVE_TOKENS if re.search(rf\b{t}\b, name)) # 特殊字符密度 和 - 是重灾区 special_count sum(1 for c in url if c in -_~%) special_rate special_count / max(1, len(url)) # 子域层级约等于域名 label 数减 2顶级域按常见集合判断 labels name.split(.) if name else [] tlds {com, net, org, cn, xyz, top, icu, loan} depth len(labels) - 2 if labels and labels[-1] in tlds else len(labels) - 1 # URL 熵 freq {} for c in url: freq[c] freq.get(c, 0) 1 n len(url) entropy -sum((v / n) * math.log2(v / n) for v in freq.values()) return { is_ip: int(is_ip), url_length: len(url), host_length: len(name), subdomain_depth: max(0, depth), special_char_rate: round(special_rate, 4), sensitive_token_hits: token_hits, url_entropy: round(entropy, 4), }代码里的几个参数值得说清楚。urldefrag先去掉 fragment因为#后面内容不参与真实请求留着只会污染熵特征。\b{t}\b是词边界匹配\b在正则里表示单词边界避免loginpage把 sensitive_token_hits 加 2。max(0, depth)保证 hostname 为空时特征不会出现负值。熵的计算用math.log2而不是log10这样熵值落在 4~7 区间量纲与其他特征更接近训练时特征缩放的压力更小。2.3 页面内容特征与缺失值策略内容特征依赖 HTML抓取失败时这些字段只能留空。我的做法是返回 -1 而不是 0原因是 0 本身是有效值「没有外部表单」和「拿不到页面」是两种状态混在一起会让模型学到错误映射。def content_features(html: str, final_url: str) - dict: 从页面 HTML 源码中提取内容特征抓取失败时返回空字典。 if not html: return {} parsed urlparse(final_url) real_domain parsed.hostname or # 表单 action 指向外部域名才计数同域 action 属于常规操作 actions re.findall(rform[^]*action[\]([^\]), html, re.I) ext_form 0 for action in actions: m urlparse(action) if m.netloc and m.netloc.lower() ! real_domain: ext_form 1 hidden_inputs len( re.findall(rinput[^]*type[\]hidden[\], html, re.I)) title re.search(rtitle[^]*([^])/title, html, re.I) title_len len(title.group(1).strip()) if title else 0 has_refresh int(bool( re.search(rmeta[^]*http-equiv[\]refresh[\], html, re.I))) return { external_form_count: ext_form, hidden_input_count: hidden_inputs, title_len: title_len, meta_refresh: has_refresh, }抓取这一步需要注意requests.get的超时别设太长。训练集有几千条样本每条等 10 秒会导致整体进度无法接受。我一般设 timeout3并配合 allow_redirectsTrue 拿到最终落地 URL因为钓鱼链接的短跳转目标才是真正要分析的对象。回应码不是 200 的页面同样丢弃 HTML让 content_features 返回空字典。3. 组织打标数据集并训练模型随机森林参数与评估流程3.1 数据集怎么来、怎么标公开集与自采样本的对齐毕业设计的训练集通常由两块拼成。第一块是公开的钓鱼网站数据集比如 UCI 的 Phishing Websites Data Set它已经把 30 个特征和标签打包好适合做快速基线。第二块是自采数据从 PhishTank 导出当日新增的钓鱼 URL 作为正样本从 Alexa 热门站点列表随机抽一批正常站点作为负样本然后跑第 2 章的特征提取代码生成自己的表。自采数据时有三个细节不能省。一是正样本要尽快抓页面钓鱼站的平均存活时间很短晚一天抓到的基本是空响应。二是负样本要人工抽查Alexa 热门列表里偶尔混进已关停的域名这些残骸按正常站打标会把模型带偏。三是数据集文件按目录归档训练代码只访问固化后的 CSV。data/ raw/ phishing_urls_2023.csv # 正样本 URL 清单含抓取时间和 label1 legit_urls_2023.csv # 负样本 URL 清单经人工抽查label0 processed/ features.csv # 特征提取后的完整特征矩阵 train.csv test.csv抓取前先确认 Python 环境干净建议用 conda 建一个 py3.8 的独立环境再装依赖避免把系统 Python 弄乱。依赖集中在 requirements.txtpandas、scikit-learn、requests、flask、joblib、shap。3.2 训练脚本特征矩阵、随机森林与三个关键参数训练前把 URL 列剥离只留特征列和标签列。随机森林是这类任务最稳的默认选型原因有三个对数值型特征的量纲不敏感不需要做标准化自带特征重要性输出写论文时可以直接引用对噪声标签有韧性个别乱标样本不会导致模型崩掉。import pandas as pd from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import StratifiedKFold, cross_val_score df pd.read_csv(data/processed/features.csv) X df.drop(columns[label, url]) y df[label] # 三个关键参数的含义 # max_depth12限制单棵树深度防止模型对钓鱼样本背答案 # min_samples_split6分裂节点至少需要 6 个样本压制过拟合 # class_weightbalanced正负样本不均衡时保证召回率不崩 model RandomForestClassifier( n_estimators200, max_depth12, min_samples_split6, n_jobs-1, random_state42, class_weightbalanced ) skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) scores cross_val_score(model, X, y, cvskf, scoringf1) print(f5-Fold F1: {scores.mean():.4f} ± {scores.std():.4f}) model.fit(X, y)参数表里可以看出取舍逻辑。n_estimators 提到 200 就够了再往上收益递减只增加训练时间和模型体积。max_depth12 是一项防过拟合的关键设定钓鱼检测的特征维度不算高树太深会把训练集里的噪声组合当成规律存下来。min_samples_split6 配合 class_weightbalanced在正负样本 1:3 情况下小样本类别也能被照顾到。n_jobs-1让随机森林并行跑满 CPU四核机器上 200 棵树也就几十秒。3.3 评估不只盯准确率混淆矩阵和召回率怎么读钓鱼检测的代价是不对称的。漏掉一个钓鱼站用户可能直接损失账号和资金误杀一个正常站用户体验受损但可申诉。所以评估时我优先看召回率Recall和 F1而不是总体准确率。from sklearn.metrics import confusion_matrix, classification_report pred model.predict(X_test) cm confusion_matrix(y_test, pred) print(cm) # [[TN, FP], [FN, TP]] print(classification_report(y_test, pred, target_names[safe, phishing]))最常见的翻车是整体准确率 97%但钓鱼类召回率只有 70%。这说明模型把多数正常样本学得很好却牺牲了正类。此时去看混淆矩阵的 FN漏报本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑