深度学习驱动的钓鱼页面检测系统设计与实践

发布时间:2026/10/13 15:02:10

深度学习驱动的钓鱼页面检测系统设计与实践 简介一套基于深度学习的钓鱼页面检测系统前后端架构面向Web安全学习者和Python/JavaScript开发者用于解决恶意URL识别与访问拦截问题。压缩包共187个文件、约42.55MB目录按后端与前端划分清晰后端为Django RESTful API TensorFlow模型包含25个Python源码、模型权重与checkpoint前端为Chrome浏览器插件主要有JavaScript脚本、样式及打包好的crx文件另含SQLite等存储文件。系统工作原理是插件在每次打开页面时把URL发送到后端后端提取域名注册时间、外链比例等页面特征并送入深度模型预测再将结果返回前端决定拦截或放行。学习者可以从中掌握完整的网络钓鱼检测流程、前后端API交互方式、特征提取思路和浏览器插件开发方法对搭建同类安全工具或完成课程设计都有直接参考价值。目前已有283人学习下载。1. 检测链路先立住为什么必须看渲染后的页面而不是只看 URL做安全风控的人多少都有过这种体验某公司安全组连续两周收到员工反馈说“你们拦截平台一点用都没有我今天又点进了一个仿冒登录页”。点开记录一看URL 黑名单确实在跑但攻击者用了“一次性域名 短链跳转”的做法黑名单还没来得及收录页面已经下线。webPish_detect 就是针对这个场景设计的它不只查 URL而是把页面渲染出来的 HTML 结构、表单行为、URL 特征、视觉指纹一并喂给深度学习模型由后端给出“是/否/不确定”的置信度再由前端把结果落成可管理的检测任务。适合做风控平台、安全运营、想把单脚本升级成服务化检测能力的人。这套方案的出发点是钓鱼页面的本质是“伪装”伪装一定会在页面内容里漏出痕迹只看 URL 是抓不住伪装的。2. 特征怎么选从轻量规则到深度学习模型的分层方案2.1 黑名单策略失效的两个典型场景黑名单失效通常是两种形态。第一种是一次性域名攻击者买几十个低价域名每个域名只活几小时只服务少数目标黑名单收录速度永远追不上域名更换速度。第二种是空壳页面域名固定但后续请求返回的 HTML 内容每次都不同页面文字、表单字段、CSS 类名都在变黑名单能封住域名却封不住不断换皮的内容。这两种形态的共同点是URL 层面的证据非常稀疏可页面内容层面的证据非常密集。钓鱼页面总要把用户引到“输入账号密码”这一步把密码提交到攻击者可控的服务器这一步在 HTML 里表现为表单 action 指向可疑域、表单字段包含 password、登录按钮的文案与真实品牌不一致。这些信号是归纳式的适合让模型去学而不是靠枚举黑名单。2.2 最小可用的特征抽取与判定脚本在进入深度模型之前我会先让团队把“特征驱动”这件事跑通。最小版本不依赖任何深度学习框架只靠 Python 标准库和正则就能抽出一组可解释特征。下面这段脚本是我在一期模拟项目 X 里最先落地的版本功能是把一条 URL 和它的页面 HTML 压成 5 维特征。# extract_features.py # 目标把一条 URL 和它的 HTML 变成 5 维特征供规则或浅层模型使用 import re import math from urllib.parse import urlparse from collections import Counter def url_entropy(s: str) - float: URL 字符熵衡量字符串随机程度。 钓鱼子域名常带随机串熵值会比正常品牌域名高。 freq Counter(s) n len(s) if n 0: return 0.0 return -sum((c / n) * math.log2(c / n) for c in freq.values()) def extract_features(url: str, html: str) - list: p urlparse(url) # 1) 是否启用 https钓鱼页不少仍用 http is_https 1 if p.scheme https else 0 # 2) 主域名熵子域名里带随机串会明显拉高熵 entropy round(url_entropy(p.netloc), 4) # 3) 是否用 IP 直连这类页面风险极高 ip_host 1 if re.match(r\d\.\d\.\d\.\d, p.netloc) else 0 # 4) 表单 action 是否指向 http 协议 forms re.findall(rform[^]*action[\]([^\])[\], html, re.I) form_http 1 if any(f.startswith(http://) for f in forms) else 0 # 5) 页面前 2000 字符里是否出现登录相关关键词 title_match 1 if login in html[:2000].lower() else 0 return [is_https, entropy, ip_host, form_http, title_match]这段脚本的定位是把链路跑通不是终版方案。逻辑说明前三维做 URL 结构分析后两维做内容行为分析组合起来能覆盖最基础的仿冒登录页。参数说明url_entropy里n 0的兜底是为了避免空字符串触发除零熵值超过 3.8 时就需要警惕正常品牌域名的熵一般在 3.2 到 3.6 之间title_match只做粗匹配正式版本我会换成一张品牌关键词表逐个品牌检查页面标题是否与域名主体一致。这组特征可以直接丢给后续的浅层模型也可以作为深度模型输入的一部分。2.3 从浅层特征到深度模型三类网络怎么选浅层特征容易解释但攻击者知道规则后会刻意污染这些特征比如用合法域名做跳板、把表单 action 改写成相对路径绕开检测。所以要上深度学习。常见做法是把检测拆成三个特征维度每类维度配一种网络结构如下表。特征来源推荐网络适用场景单次推理耗时量级URL 字符序列TextCNN 或 FastText快速过滤一次性域名、随机子域名毫秒级页面文本语义小型 Transformer 或 TextCNN判断页面是否伪装成某个品牌十毫秒级渲染后截图视觉ResNet 系列识别页面版式、品牌 Logo 仿冒百毫秒级以上最终我通常会采用“轻量模型做前筛 深度模型做精判”的分层结构。前筛用 TextCNN 处理 URL秒级以内排除掉大量正常流量被前筛判定为“可疑”的请求才进入页面渲染和视觉模型精判这样既控制成本又保留深度模型的泛化能力。视觉模型单次推理在单卡 GPU 上也需要百毫秒量级无头浏览器渲染一个页面还要额外花一两秒不可能每一条流量都走完整链路分层是落地时最现实的选择。3. 前后端架构拆分一个检测请求如何在系统里流转3.1 模块划分采集器、检测服务、结果库、管理前端webPish_detect 的前后端架构不复杂但模块边界必须清楚。通常分成四个部分前端管理后台负责提交 URL、展示判定证据后端 API 负责接收任务、调度状态页面采集器负责用无头浏览器抓取页面内容检测服务负责特征抽取、模型推理、结果落库。存储层用 MySQL 保存任务与结果用 Redis 缓存高频查询结果。webPish_detect/ ├── frontend/ # 管理后台Vue 或 React ├── collector/ # 页面渲染与资源拉取独立进程 ├── detector/ # 特征抽取 模型推理 ├── api/ # FastAPI 入口负责任务调度 └── storage/ # MySQL 任务表 Redis 缓存这里有一个容易踩坑的设计点采集器必须独立于 API 进程。无头浏览器是重量级进程如果 API 进程内直接调用渲染并发一高整个服务会被拖垮。我一般会让 API 只做两件事把任务写入 MySQL把消息推给任务队列采集器和检测服务各自订阅任务处理完再回调 API 回写结果。3.2 后端接口与任务状态机后端接口的设计核心是异步任务状态机。因为一次完整检测要经过入队、渲染、特征抽取、模型推理四个阶段耗时短则一两秒长则十几秒同步等待会导致前端连接大量堆积。任务表里维护四个状态pending、running、done、failed前端轮询任务结果。# api.py # FastAPI 入口只做任务接收与状态查询不参与检测逻辑 from fastapi import FastAPI, HTTPException from pydantic import BaseModel import uuid app FastAPI() class DetectRequest(BaseModel): url: str callback: str | None None # 可选回调地址处理完主动通知 app.post(/api/detect) async def create_detect_task(req: DetectRequest): 提交检测任务立即返回 task_id。 task_id str(uuid.uuid4()) # 真实实现里这里写入 MySQL并推入任务队列 # queue.push({task_id: task_id, url: req.url, callback: req.callback}) return {task_id: task_id, status: pending} app.get(/api/tasks/{task_id}) async def get_task(task_id: str): 查询任务当前状态。 # 真实实现里查 MySQL 并返回状态 task {id: task_id, status: done, score: 0.93} if task is None: raise HTTPException(status_code404, detailtask not found) return task逻辑说明POST /api/detect只负责登记任务返回task_id后立刻释放连接后续所有进展通过查询接口获取。GET /api/tasks/{task_id}返回状态和最终分数前端在任务status done时读取结果。参数说明callback字段是可选的适合做大规模批量检测时由服务端主动通知减少前端轮询压力。常用的超时配置是渲染阶段 15 秒超时、模型推理 5 秒超时超过直接标记为failed避免任务卡死占用资源。3.3 前端如何把“置信度”解释给运营看前端展示不是一个简单的进度条而是要把判定过程拆给运营看懂。模型输出一个 0 到 1 的分数但运营真正需要的是“为什么判它是钓鱼”。所以在结果卡片里我一般会同时展示四块内容置信度分区条、命中规则标签列表、页面缩略图、URL 结构拆解。// result-view.js // 前端轮询任务状态并用 fetch 拉取结果 async function fetchResult(taskId) { const resp await fetch(/api/tasks/${taskId}); const data await resp.json(); if (data.status done) { renderScore(data.score); // 置信度条 renderTags(data.hit_rules); // 命中的规则/特征标签 renderThumbnail(data.snapshot); // 页面缩略图 renderUrlBreakdown(data.url); // URL 拆解 } else { setTimeout(() fetchResult(taskId), 2000); } }参数说明data.hit_rules是检测服务回填的证据标签列表比如“表单指向 http 协议”“主域名熵偏高”“页面标题包含多个品牌词”每个标签都对应特征层面的一个判定依据。分数阈值建议做成前端配置项而不是写死在页面代码里这样运营可以在 0.6 到 0.8 之间实时调整适应不同时期的攻击水位。4. 样本、训练与阈值从能跑通到抗绕过4.1 训练数据从哪来公开样本清洗加自建复标深度学习模型最需要的不是模型本身而是干净且新鲜的样本。钓鱼页面的特点是存活时间极短平均只有几十小时这意味着训练数据的时效性直接决定模型上线后的召回率。常见做法分三步先从社区众包的钓鱼 URL 库拉取原始链接再用无头浏览器批量抓取页面内容最后做去重与人工复标。# prepare_data.sh # 拉取样本后做基础清洗去重、去死链、记录抓取时间 python fetch_pages.py --input raw_urls.txt --output pages/ python dedup_pages.py --input pages/ --output clean_pages/ --min-diff 0.85 python relabel.py --input clean_pages/ --output labeled.json --human-ratio 0.1这段脚本的三个步骤对应数据管线里最脏的三件事。fetch_pages.py负责用无头浏览器渲染输出 HTML 与截图dedup_pages.py按内容相似度去重min-diff 0.85的含义是页面文本相似度超过 85% 就合并避免同一套模板反复进样本集relabel.py会抽 10% 的样本做人工复核用来校对公开样本库自带的标签是否过期。我踩过最大的一次坑是某批公开样本库里的标签是三个月前打的模型上线第一天召回率只有一半复标之后才发现有大量样本对应的页面已经下线。4.2 训练脚本与关键参数训练部分最直接的落地方式是拿 URL 字符级序列做输入用一个小型网络完成分类。下面的模型结构是在模拟项目 X 里跑通的深度版本输入是 URL 的字符 id 序列输出是钓鱼概率。# train.py # 小型字符级神经网络输入 URL 的 token 序列输出钓鱼概率 import torch import torch.nn as nn from torch.nn import functional as F class PhishNet(nn.Module): def __init__(self, vocab_size256, embed_dim64, hidden_dim128): super().__init__() self.embed nn.Embedding(vocab_size, embed_dim) self.fc1 nn.Linear(embed_dim * 8, hidden_dim) self.out nn.Linear(hidden_dim, 1) def forward(self, x): # x: (batch, seq_len8)seq_len 是截断后的字符 token 长度 e self.embed(x).view(x.size(0), -1) h F.relu(self.fc1(e)) return torch.sigmoid(self.out(h))逻辑说明字符级输入的好处是能捕捉到子域名随机串、数字字母混合这类肉眼可见的异常不需要依赖分词器也不存在词表外的罕见 token。seq_len8是刻意设置的短序列因为 URL 的可疑信号集中在主域名和路径前段序列拉太长反而会引入大量噪声。右侧的fc1把 512 维的展平向量压到 128 维全连接层最后输出一个 0 到 1 的概率。训练时的几个关键参数学习率lr设在2e-3批次大小batch_size设 128训练轮次不超过 20同时用早停机制监控验证集损失连续 3 轮不下降就停止。损失函数用BCEWithLogitsLoss比在forward里手动加 sigmoid 再算 BCE 数值更稳定。4.3 召回率与误报率的取舍阈值不是拍脑袋定的二分类模型落地时最难的不是训练而是确定判定阈值。阈值调低模型会放过正常流量但会把活动页、营销落地页大量误判成钓鱼阈值调高误报减少但真实钓鱼页面的漏报也会上升。这个权衡没有标准答案取决于你的业务要“拦截优先”还是“体验优先”。我一般会准备一份验证集里面正常页面和钓鱼页面比例控制在 10:1 左右模拟真实流量分布然后按 0.05 的步长从 0.5 到 0.9 扫描阈值画出一条召回率和误报率的变化曲线。业务方如果只能接受每天最多 50 条误报就反推这个约束下的最低阈值。阈值必须做成后端接口的可配置参数让运营在攻击爆发期能临时调低阈值提高拦截率等攻击水位下降再调回来。5. 部署与并发里的高频避坑4 条实测翻车记录5.1 渲染器同步调用导致服务雪崩现象模拟项目 X 第一次压测时单次接口从 0.8 秒一路恶化到 5 秒以上CPU 打满后续请求全部超时。原因API 进程里直接调用了无头浏览器做页面渲染渲染是重量级操作并发到了几十路内存和 CPU 直接被吃光。解决把渲染拆成独立进程用进程信号量限制并发数为 4同时给渲染加 15 秒超时熔断。核心转变是 API 只负责调度渲染永远在后台异步执行。5.2 URL 带跟踪参数破坏了特征分布现象一个正常营销链接被模型判成钓鱼运营反馈误报。原因这条链接带了utm_source、sid、device_id等长查询参数URL 熵被这些随机参数拉高模型把“高熵”当成了“随机子域名”。解决提交任务前在入口做 URL 归一化去掉utm_*、sid、device_id、session_id这类跟踪参数只保留业务路径。这个归一化步骤必须放在特征抽取之前不然模型学到的全是噪声。5.3 正样本集过期导致召回率腰斩现象模型上线前验证集召回率 82%上线第三天掉到 50%。原因拉来的钓鱼样本库里有大量页面已经下线标签还是“钓鱼”模型看到的是一个不存在的页面特征。解决样本入库的时候强制记录抓取时间戳数据管线只保留最近 72 小时的样本同时每小时增量拉新样本每 24 小时触发一次增量重训。这个教训让我意识到钓鱼检测模型的维护成本主要在数据保鲜不在训练。5.4 全局单一阈值把正常活动页批量误杀现象某电商大促期间大量活动落地页被判定为钓鱼业务方当场要求下线检测功能。原因检测服务对所有站点使用同一个阈值 0.65而活动页的短链、低品牌词覆盖度、临时页面结构与钓鱼页面高度相似。解决改成按域名分组配置阈值品牌域名和活动域名使用白名单加降级阈值陌生域名才走完整严格判定。注意误报在安全类系统里造成的业务损失比漏报更直观因为漏报不可见误报直接挡业务。阈值分层配置是上线前必须完成的事。6. 让规则引擎站在模型前面一个降误报的实用技巧如果你已经把模型跑进了生产环境下一步最值得做的不是换更大的模型而是在模型前面加一道规则前置。具体做法是三类分流第一类是白名单品牌域名、已知可信站点直接放行第二类是高风险硬规则比如 IP 直连且页面包含密码表单直接拦截不需要模型判断第三类才是交给模型去精判的灰色样本。# gate.py # 规则前置逻辑白名单放行高危规则拦截其余走模型 def decision(url: str, page: dict): if is_in_whitelist(url): return pass if high_risk_rule_hit(page): return block score model.predict(url, page) return block if score current_threshold() else pass这套逻辑最大的价值是把模型从“所有请求都判”变成“只判没把握的请求”。白名单挡住了品牌域名的正常流量高危规则挡住了特征极其明显的攻击模型剩余的判定压力小很多误报率会肉眼可见地降下来。我在模拟项目 X 里最开始把模型结果直接暴露给业务侧结果某个大型平台登录页被模型误报了一次功能被业务方直接下线。后来加了白名单和规则前置同类误报再没出现过。这算是我在钓鱼检测系统上最大的实战教训深度学习模型是核心能力但生产环境里决定成败的往往是它前后那两层不起眼的规则。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/13 15:02:10

人工智能归结原理源码包:一阶逻辑自动定理证明实战

简介:这份资源面向人工智能初学者与具备一定编程经验的开发者,聚焦一阶逻辑中的归结原理,提供完整源码与配套文档,帮助读者从理论到代码理解自动推理的实现机制。压缩包共10个文件,约102KB,以java源码与cla…

2026/10/13 15:02:10

中文复合事件抽取:条件因果顺承反转四类关系与事理图谱构建

简介:这份资源面向自然语言处理初学者与进阶开发者,聚焦中文复合事件抽取这一细分任务,帮助解决条件、因果、顺承、反转等事件类型的识别与关系建模问题。包内共11个文件,以6个xml配置、2张png示意图、1个md说明、1个py脚本及1个i…

2026/10/13 15:02:10

Python网络舆情分析系统实战:从爬虫采集到情感分析的完整闭环

简介:一份基于Python与MySQL实现的网络舆情分析系统毕业设计论文包,面向高校相关专业学生、毕业设计开发者以及网络舆情管理方向的技术人员。内容围绕系统完整设计与实现展开,包含摘要、绪论、需求分析、系统设计、功能模块说明(言…

2026/10/13 16:13:06

上下文工程实战:从设计到优化,解决大模型答非所问

1. 从一个真实困境说起:为什么模型总是“答非所问”我最早接触大语言模型应用开发的时候,踩过一个特别典型的坑。当时我在做一个智能客服原型的问答模块,用户问“我上周买的那个订单什么时候到”,模型给出的回答是“请提供您的订单…

2026/10/13 16:13:06

名企AI面试100题:高频考点与实战解析

简介:《名企AI面试100题》是面向求职互联网大厂AI岗位的笔试面试备考资料,源自七月在线近4000道题库的精选题集,适合计算机相关专业学生及转行求职者系统复习。本资源为PDF格式,共1个文件,压缩包约49.22MB,…

2026/10/13 16:13:06

TCN时间序列预测实战:因果卷积与膨胀卷积详解

简介:本资源是一份面向初级及以上数据科学学习者与时间序列分析实践者的完整TCN项目教程,聚焦利用Python与TensorFlow实现时间卷积神经网络进行高精度时序预测,特别以外汇市场趋势建模为落地场景,有效解决RNN在长序列中梯度消失、…

2026/10/13 16:13:06

LiteLLM实战:用OpenAI风格接口统一调度多种大模型API

1. 这不是一个“模型”,而是一个“总闸”——用一分钟认识 LiteLLM如果你和我一样,过去一年里被各种大模型 API 折腾到头秃,你应该能秒懂我在说什么:OpenAI 的接口长一个样子,Anthropic 的接口是另一个样子&#xff0c…

2026/10/13 16:08:06

CS+ for CC编译器设置记录

瑞萨编译器在创建新工程后必须设置以下参数用以让工程顺利进行 1,设置选项字节: 不同与其他MCU,瑞萨单片机的时钟与看门狗是在用户选项字节中设置的,选项字节供4个Byte。 其一位置在:CC-RL(Build Tool)->Link Optio…

2026/10/13 5:19:27

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/12 12:01:26

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/12 12:54:07

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/13 0:04:41

基于Triton部署YOLO目标检测:从训练脚本到线上服务

简介:这份资源面向希望将目标检测模型落地到生产环境的深度学习开发者与算法工程师,聚焦于用 Triton Inference Server 部署 YOLOv9 的完整实战方案,解决从训练权重到线上推理服务之间的工程化难题。压缩包共 16 个文件,约 886KB&…

2026/10/13 0:04:41

手把手教你云端部署Qwen-Image-2.1:从GPU选型到在线文生图服务

Qwen-Image-2.1 这个开源的图像生成模型,最近在内容创作和 AI 应用圈子里热度相当高。它属于通义 Qwen 系列里的视觉生成分支,让我眼前一亮的是它对中文长文本的理解能力——以前用那些老方案生成带中文文字的图,十个里九个是“乱码印刷体”&…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑