
简介基于深度学习的钓鱼页面检测系统完整提供前后端架构与可运行代码。资源面向安全方向学习者、Web 开发者及对反钓鱼检测感兴趣的读者适用于研究 URL 特征工程、模型部署和浏览器插件联动等场景。项目主体包含 BackEnd_django_restful_api 后台服务与 FrontEnd_browser_plug_in 前端插件后端基于 Django RESTful 框架和 TensorFlow 构建负责从 URL 与页面特征如连接数量、外链比例、域名注册时间中提取信息并送入模型预测前端插件在每次打开页面时将 URL 发送到后台根据返回结果进行拦截或放行整体形成完整的检测闭环。压缩包共 187 个文件约 42.55MB以 Python 源码、模型权重data/checkpoint/model和前端插件资源js/css/html/crx为主附带 SQLite 数据库及说明文档目录结构清晰便于直接运行和二次开发。已有 282 人学习适合用作毕业设计、课程项目或企业安全工具的基线参考。 咱们搞安全的都知道钓鱼页面这玩意儿永远打不完。今天换个新思路聊聊这个开源点的东西 webPish_detect一套基于深度学习的钓鱼页面检测系统。这名字有点朴素但背后的路子挺值钱不再纯靠黑名单和规则硬扛而是让模型去看页面长什么样、写了什么字从视觉语义上判断这到底是不是个“李鬼”。我会把整套前后端架构怎么拆、模型怎么选、数据从哪来、埋了哪些坑一次说清楚。这套系统解决的实际问题很明确传统反钓鱼手段静态黑名单、域名信誉对短命钓鱼站基本束手无策。攻击者可以短时间内换几百个域名黑名单根本来不及更新。而深度学习模型的优势在于它不依赖精确匹配而是学习“钓鱼页面长什么样”具备一定泛化能力遇到从没见过的钓鱼域名只要页面风格、布局、文案逻辑像就能被识别出来。适合谁看呢如果你是做安全产品研发的、搞反欺诈风控的或者正在琢磨怎么把深度学习模型落地到实际业务系统里的这篇文章可以直接当参考架构用。我会把从数据标注、模型训练、服务封装到前端展示的完整链路全部拆开配合实际踩坑记录给你一条可以直接抄作业的路。1. 架构设计为什么深度学习检测系统要拆成三块1.1 整体架构思路前端展示、后端编排、算法推理各干各的我先说结论检测系统的架构核心不在于“AI多牛逼”而在于让AI能力稳定地嵌进业务流程里。webPish_detect 的架构严格分成三块前端负责交互与结果展示、后端负责业务逻辑和数据编排、算法层负责模型推理和结果返回。为什么要这么拆一个核心原因是模型训练和线上推理的环境完全不同。训练时你用 GPU 大显存 各种 Python 深度学习库而线上服务大概率是 CPU 或者轻量级 GPU 环境的容器。这两者的依赖、性能调优方式、甚至 Python 版本要求,都不是一套配置能兼顾的。拆开之后模型被封装成独立的 REST API 服务后端只需要发 HTTP 请求拿到结果各自升级互不影响。再从工程角度说钓鱼检测本身是一个多步骤串联的任务后面细说它不只是跑一次模型那么简单。后端负责把这些步骤编排起来把“脏活累活”——比如 URL 访问、页面下载、超时重试、数据入库——都接住算法服务只做纯推理接口职责单一方便做并发扩容。前端就更纯粹了只做查询入口和可视化展示。1.2 技术栈选型Python 打底、FastAPI 做胶水、前端轻量化技术层面整个系统是 Python 全家桶。深度学习算法部分用的是 PyTorch模型结构用的是目标检测 OCR 文本识别的组合具体模型后面会展开。后端服务用 FastAPI选择它的原因很简单自带异步支持、自动生成 OpenAPI 文档、性能在 Python 框架里是第一梯队而且 Pydantic 做数据校验在对接算法层返回结果时特别方便。前端我没有上 Vue/React 这类重型框架而是用了简单的服务端渲染加少量原生 JavaScript。为什么因为这类内部安全工具的使用者就那几种角色页面核心功能就一个提交 URL看检测结果。做成单页应用反而要维护接口鉴权、跨域配置、打包构建好一堆事情。服务端渲染配上模板引擎后端返回结果时直接渲染表格和风险标签简单直接部署时也不用单独起一个 Nginx 节点来托管前端静态资源。数据库方面用了 PostgreSQL主要存储检测历史记录和 URL 特征。之所以不用 MySQL是因为检测结果里有很多 JSON 类型的字段模型返回的坐标、置信度、OCR 文本等PostgreSQL 对 JSON 的支持比 MySQL 用得顺手而且将来要跑地理位置和时间序列分析PG 的扩展能力更稳。1.3 通信设计模型服务与后端之间的异步调用这里有个很多人忽视的坑。如果你在接口里同步调用模型推理哪怕模型推理只要 500 毫秒用户的请求也会卡在那里 500 毫秒。钓鱼页面检测有个特点要真实访问目标 URL、等页面加载完、截图、再跑模型整个链路耗时可能达到 5~10 秒甚至更长。今天用户根本等不了这么久。所以我在后端和模型服务之间加了一层异步任务队列。用户提交 URL 后接口立即返回一个任务 ID前端轮询任务状态后端把检测任务丢进 Celery配合 Redis 做 Broker消费端再把任务拆解分成多个子任务页面抓取、截图渲染、模型推理、结果汇总。这样做了三件事一是用户不需要干等体验好二是算法服务不再被 HTTP 请求阻塞压力可控三是任务失败可以重试不会整个链路崩掉。这一套设计我后面还会专门讲怎么落地。2. 检测核心逻辑视觉语义理解是钓鱼检测的命门2.1 为什么文本检测不够用钓鱼页面的伪装逻辑传统思路里检测钓鱼页面最直接的办法是看 URL 和页面源码中的关键词。比如页面里面出现了“login”、“bank”、“password”这些词配合域名看起来不像官方就判定为可疑。这套逻辑对付早期钓鱼站还行但现在攻击者也聪明了页面源码里不再直接写敏感词而是用图片代替文字、用 JavaScript 动态渲染静态分析很难抓到。还有一个更根本的问题钓鱼页面的本质是“高度模仿某个正规站点的页面”从视觉上骗人。这种视觉相似性用文本特征根本表达不出来。就好比你看一个人的脸认出他是谁靠的不是他衣服上写了什么字而是五官比例和轮廓——钓鱼页面检测也该这么干。2.2 视觉特征提取从截图到结构化特征webPish_detect 的特征提取链路分四步走浏览器内核渲染用 Playwright 驱动无头浏览器访问目标 URL等待页面加载完成后截取全屏截图。这里的关键是设置好等待策略既要等页面稳定渲染又不能等到页面弹窗卡死。目标检测定位关键区域用训练好的 YOLO 模型在截图里框出 Logo、登录表单、输入框、按钮这类关键 UI 元素。这一步是为了回答“页面里面有哪些像敏感组件的元素”。OCR 提取文本内容对框出的区域做 OCR用 PaddleOCR识别出里面的文字。比如 Logo 区域的文字可能是某个知名银行的名字按钮区域的文字可能是“登录”或“Sign In”。视觉嵌入比对把整张截图交给一个用图像分类任务预训练好的卷积神经网络ResNet 或 EfficientNet提取出高维视觉特征向量。这一步的核心作用是把“页面长什么样”变成一组数字向量方便后续做相似度计算。这四个步骤的结果会汇总成一个 JSON 结构传给检测模型做判定。很多人以为深度学习检测就是“一个模型输入图片、输出是不是钓鱼”那太理想化了。工业界真正跑得通的方案都是多模型级联或者模型加规则混合的。2.3 模型选型与判定逻辑相似度比对启发式规则我的判定逻辑可以概括为两步走。第一步视觉指纹相似度比对。把待检测页面的特征向量和历史钓鱼样本库中的特征向量做余弦相似度计算。如果相似度超过阈值直接判定为高风险的“克隆页”。这里有个细节阈值不能一刀切比如输入框、登录按钮这些特定区域权重需要更高因为钓鱼者往往会改页面背景和文案来绕过整图相似度检测但不敢动输入框的位置和大小——动了就容易露出破绽访问者立刻会起疑。第二步启发式规则兜底。很多新出现的钓鱼页面视觉上相似度不高但具备明显的“钓鱼行为特征”比如域名刚注册不到 30 天、页面只有一个输入表单没有其他链接、表单的提交地址是外域 IP、页面没有任何备案信息、访问来源是即时通讯软件分享链接带着 suspicious 的 Referer。这些特征不是深度学习能学的而是安全经验沉淀出来的规则。模型判分和规则判分最终加权汇总得出 0 到 100 的风险分值。模型比较我列一下我测试过的情况方便大家选型模型名称 | 类型 | 推理速度CPU | 准确率 | 适合场景 YOLOv5s | 目标检测 | 约 80ms | 单体页面区域识别稳定 | 定位 UI 组件和关键区域 YOLOv8n | 目标检测 | 约 60ms | 小目标检测效果略输 YOLOv5 | 需要快速处理大批量截图 ResNet50 | 图像分类/嵌入 | 约 50ms | 整页特征鲁棒性良好 | 全页面视觉嵌入提取 EfficientNet-B0 | 图像分类/嵌入 | 约 70ms | 准确率与 ResNet 相近 | 当 ResNet 误报率高时做替代 PaddleOCR | OCR 文本识别 | 约 150ms | 中英文混排稳定 | 提取页面文本和商标区域文字实际上用下来YOLOv5s 虽然年代早一些但生态成熟部署时踩坑少。ResNet50 作为视觉嵌入主干网络虽然不算最先进但胜在开源预训练模型多而且特征维度不高2048 维相似度计算开销小。2.4 为什么需要持续更新样本库钓鱼背后的对抗性演化我特意把更新机制单独拎出来写一段因为这是很多深度学习检测项目上线之后才发现的隐性需求。钓鱼页面的演化速度极快攻击者会在被拦截后迅速微调页面外观比如改配色、换字体、调整按钮的大小和位置。原有的视觉特征库会在一两周内失效。所以 webPish_detect 不只做检测它还做了“反馈闭环”每次用户上报的疑似钓鱼页面都会进入人工审核队列审核确认后自动加入钓鱼样本库定期触发视觉特征库重新聚类和模型微调用 PyTorch 做增量训练。这个闭环听着简单恰恰是让模型效果不掉线的最关键设计。忘了说一句这一整套闭环流程的调度也是由后端业务逻辑模块负责算法服务本身不感知“样本库”“人工审核”这些东西。3. 前后端实现细节从训练模型到可用服务要过的几道坎3.1 模型训练与数据准备样本从哪里来、如何标注我估计有人要问模型训练的数据从哪来这里说实话真正完全公开的钓鱼页面大型数据集基本没有安全数据通常比较敏感。我的做法是混合三路数据源第一路公开的恶意 URL 数据集如 PhishTank、OpenPhish 这类平台但这类平台数据更新有延迟而且失效链接很多需要自己写爬虫定期抓取验证。第二路内部蜜罐系统收集的真实钓鱼样本这部分质量最高因为攻击者会上传全新的钓鱼模板大概率不会被公开引擎收录。第三路模拟生成的“半导体钓鱼页面”。这个思路很有趣也是我推荐大家试试的用正规知名网站的模板基于计算机视觉库对截图做修改换掉品牌名和 Logo改变部分布局生成一批“合成钓鱼样本”。这样做的好处是标注成本极低而且能够控制特征分布的多样性让模型针对特定攻击方式的识别能力增强。标注这块是一个关键人力投入点千万不要随便外包给不懂行的人做。钓鱼页面的精巧伪装、诱导文案、品牌仿冒逻辑如果没有安全背景很难判断“到底算不算钓鱼”。我采用的方式是做一个简单的内部标注平台标注界面把页面截图和目标 URL 并列展示标注人员只需要选择“钓鱼/正常/无法访问”三个标签不确定的页面标记为“存疑”后续统一人工复核。3.2 后端接口设计任务提交、状态查询、结果反馈FastAPI 这块代码不多我把核心部分贴出来感兴趣的可以直接照着改from fastapi import FastAPI, HTTPException, BackgroundTasks from pydantic import BaseModel from celery.result import AsyncResult from task_queue import analyze_url_task app FastAPI() class URLRequest(BaseModel): url: str source: str manual class TaskResponse(BaseModel): task_id: str status: str app.post(/api/detect, response_modelTaskResponse) async def detect_url(req: URLRequest, background_tasks: BackgroundTasks): # 简单校验 URL 格式过滤掉伪协议 if not req.url.startswith((http://, https://)): raise HTTPException(status_code400, detailURL 格式不正确) task analyze_url_task.delay(req.url, req.source) return TaskResponse(task_idtask.id, statuspending) app.get(/api/result/{task_id}) async def get_result(task_id: str): res AsyncResult(task_id) if res.state PENDING: return {status: pending} if res.state FAILURE: return {status: failed, error: str(res.info)} return {status: success, data: res.info}注意我在 URL 校验那里做了一个很轻量的过滤只允许 HTTP/HTTPS。实际项目里下面的过滤更多比如内网地址127.0.0.1、192.168 网段、伪装的短链接要解析跳转、带认证信息的 URL 等。这些如果不做恶意用户会拿这个接口当跳板去打内网这属于安全系统自身的安全设计问题务必要重视。3.3 前端展示设计让检测结论可解释而不是黑盒刚做完模型的人很容易把检测结果做成“safe”或“phishing”两个字。这在前端交互上是灾难级设计——安全运营人员不敢信一个二分类结果因为模型会误报。所以我要求前端结果页必须展示完整证据链。具体来说结果页分了四块区域第一块整体风险评分用一个大数字和颜色标识出来0~100 分。第二块截图命中区域的可视化就是那张请求页面的截图模型检测出的登录框、Logo 区域、输入框用不同颜色的框画出来。用户一眼就能看到模型分析的是页面哪些位置。第三块相似度匹配结果展示“当前页面和哪个已知钓鱼模板相似度达到多少”如果是克隆页面这里会列出被仿冒的官方站点名人工判定时可以省下大量时间。第四块规则命中列表逐条列出命中的启发式规则比如“域名注册时间不足 30 天”“表单提交流向 IP 地址”等并解释每条规则为什么可疑。这种“证据链展示”设计能让使用者对系统能力建立真实认知——既包括系统能看出来的也包括系统看不出来的。比如某些页面截图不完整前端也会标注“本次检测截图不完整结果可信度降低”这些都是我们内部打磨很久才加上的细节。3.4 服务部署与性能调优CPU 推理也能扛住中等规模并发模型推理服务我默认跑在 CPU 上这看起来有点反直觉——都深度学习系统了不用 GPU 不太行吧其实不然。钓鱼检测的业务特征是单次请求量大但并发量相对低安全分析场景而且推理链路中目标检测模型对这种算力要求并不算高经过 INT8 量化后YOLOv5s 在 CPU 上推理单张图只需要 80ms 左右完全能接受。具体部署方式模型服务用 Triton Inference Server 或者单纯的 TorchServe 打包成 Docker 容器通过 gRPC 与主后端通信。这里我用的是 TorchServe轻量易上手PyTorch 生态自带集成 YOLO 和 ResNet 都顺手。如果你对性能要求更极端想多实例并行推理就上 Triton但配置复杂度会显著提升这个就按团队实际情况取舍吧。量化这块有个经验值得分享用 PyTorch 自带的量化工具做 INT8 量化时最好先用少量校准数据跑一跑确认精度损失控制在 1% 以内再上线。我实测 YOLOv5s 量化后精度掉了大约 0.8%但推理速度提升了接近 3 倍这个性价比相当划算。OCR 部分我不建议量化文本识别对精度更敏感而且 PaddleOCR 本身已经做过剪枝优化了。4. 踩过的坑与排查技巧实录4.1 检测结果全是误报问题竟出在无头浏览器的渲染差异这个坑特别有意思。系统上线第一天我拿了一批正常网站测试结果误报率高达 40%。排查了很久最后发现根源在无头浏览器的渲染默认视口尺寸。用 Playwright 默认的 800x600 小视口截图很多正常网站的页面布局会乱掉弹窗会盖住核心内容导致视觉特征和训练样本差异巨大。解决办法是设置固定的视口大小和等待策略用 1920x1080 的桌面视口强制等待网络空闲事件并且对滚动后截长图做了统一处理。这里特别提醒如果你的训练数据也是用同一套浏览器环境生成的那训练时和推理时的截图参数必须严格一致包括视口大小、等待时间、DPI 缩放。改任何一项模型效果都会波动。4.2 模型判断有偏差但就是排查不出原因看看你的数据集有一次我发现模型对某品牌的钓鱼页面识别率很低。起初我怀疑是模型结构不够复杂想换更大的网络但后来分析历史检测日志才意识到训练数据里这个品牌的钓鱼样本太少只有不到 30 张模型根本没见够这个类别的特征。如果你也遇到某类样本准确率上不去第一步要做的是统计训练集中各类别的样本数量分布而不是盲目调模型。我后来专门针对这批稀缺样本做了数据增强和定向爬取用该品牌最近半年的钓鱼报告提取 URL重新抓取存活页面同时也从正规站点抓相同品牌的正常页面作为负样本。数据均衡后准确率直接提升了 8 个百分点。模型调试的时候数据往往是瓶颈而不是模型本身。4.3 多进程并发导致的 GPU 显存泄漏这个属于比较典型的部署坑。我在服务端把 PyTorch 模型加载写成了全局变量用 gunicorn 起了多个 worker 进程。按理说每个进程独立加载一份模型没啥问题但实际运行几天后显存占用不断爬升直到 OOM。查下来原因有两个一是每个 worker 进程都加载了一份完整的模型权重两个模型实例同时占显存二是经过 TorchServe 调用时每次推理后的计算图没有被完全释放导致累积显存碎片。解决思路单 worker 加载模型对外用队列接收请求另外就是显式调用torch.cuda.empty_cache()释放未使用的缓存显存并设置torch.no_grad()阻断梯度追踪。这些纯属工程经验走一遍就能记一辈子。4.4 常见问题速查表问题现象可能原因处理办法所有页面都判为钓鱼模型或规则缺陷训练数据被污染检查评分权重检查样本集有没有把“正常页面”误标成“钓鱼”特定品牌钓鱼页检测率极低训练数据中该品牌样本不足定向补充样本做数据增强接口响应超时页面抓取阶段被卡住设置全局超时和重试机制限制页面大小过滤无响应站点高并发下排队严重模型推理慢Worker 数太少模型量化或起多实例算法服务OCR 识别中文乱码模型语言包不全换 PaddleOCR 中文模型保证页面字体渲染正确相似度误判页面相似但不钓鱼相似度阈值设置过低分析相似度分布动态调整阈值或引入二级判定规则4.5 系统上线后的持续观测你不去盯它它就会悄悄变差最后这点是我个人体会最深、也最想提醒大家的一件事深度学习检测系统不是“训练完、部署好、放着不管”就能一直用的。上线后要持续观测两个指标一是每日检测结果的置信度分布如果越来越集中在中低分段说明模型对当前新样本的区分度在下降二是用户申诉率也就是用户觉得判断错了点“重新检测”或“申诉”的比例这个指标能直接反映模型对真实世界的适应程度。我给自己定了一个例行巡检的节奏每两天看一次检测日志的异常分布每两周跑一次影子评估拿新采集的样本库评估旧模型看性能是否衰退每个月做一次增量训练并灰度发布模型版本。这套节奏不算重但对保持系统长期可用非常关键。有时候模型老旧不是因为算法不行就是因为你偷懒没去更新它。这套用视觉语义来做钓鱼检测的路线我跑下来整体效果是明显优于传统规则方案而且越用越“聪明”。前面说的那些工程落地细节代码量都不算大但每一条都是实打实踩出来的经验。你如果也要做类似的深度学习检测系统建议先别急着调模型把架构的每一层职责理清楚、把数据闭环跑通系统自然就会稳定起来。动手试试有问题再来交流。本文还有配套的精品资源点击获取