手搓奇安信安全设备巡检机器人
写在前面的
本文主要提供一个日常工作中遇到的问题场景,讲解如何利用AI图片识别能力和游览器自动化控制能力,解决问题的思路和方法。代码为核心问题代码,并非全部代码,如果想跑起来,可以找作者要全部源码。
为什么会有这个需求?
- 作者公司有套gz网安的系统,会联动上级单位报警,但是这套系统没有出现告警发送邮件通知提醒功能,询问了厂商,需要将安全日志导入到自己的日志设备中,通过日志分析设备带的告警功能区实现。
- 作者单位有深信服的日志设备,配置syslog接收日志但是接收不到告警日志格式的,都是原始的syslog日志,显然就无用了,估计是厂商之间还是有些壁垒。
- 为了快速响应告警,避免出现告警事件,尤其是一些高等级事件,如失陷告警导致被上级单位通报,作者决定自己写个告警的小程序来解决这个需求。
功能实现的路径分析:
- Playwright 操作edge 游览器模拟登录
- 通过大模型识别验证码
- 分析告警页面信息,判断是否存在告警。
- 告警信息通过邮件发送到指定邮箱
- 告警信息通过企业微信机器人发送到指定群
具体的代码实现过程和需要解决的问题
一、初始化playwright,并访问奇安信设备的管理页面:
- 首先需要安装 Microsoft.Playwright 包
- 初始化playwright ,context, 打开登录页面
string loginUrl = "设备访问页面"; LogService.Initialize(); LogService.Logger.Info("应用程序启动"); var playwright = await Playwright.CreateAsync(); await using var context = await playwright.Chromium.LaunchPersistentContextAsync( @"C:\EdgeUserData", //自定义的用户数据存放目录 new BrowserTypeLaunchPersistentContextOptions { Headless = false, Channel = "msedge", Args = new[] { "--disable-gpu", "--no-sandbox", "--disable-dev-shm-usage", "--disable-blink-features=AutomationControlled" } }); var page = context.Pages[0]; await page.SetViewportSizeAsync(1920, 1080); await page.GotoAsync(q.loginUrl); page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
二、分析定位页面元素
1. 整个页面需要获取到用户名输入框,密码输入框,验证码输入框,验证码图片,立即登录按钮四个元素。
按F12 打开开发者工具可以查看页面结构,进入到元素的页面。

这里有个小技巧可以点住页面圆满,按ctrl+F 调出查找页面,这样能够快速定位元素

2. 页面结构分析的结果如下:
用户名: input[id='name']
密码: input[id='password']验证码框:input[id=\"captcha\"]
登录按钮:button[id='btn_submit']
验证码图片:img[id='captcha_img_id']
这里有个需要解决的问题,验证码的图片看下页面代码如下:
<img id="captcha_img_id" name="captcha_img" src="/verify.html" alt="CAPTCHA" onclick="change_captcha();">
验证码的图片是 src =‘/verify.html ’ , 这个页面每次访问图片都会变动的,所以必须调用playwright的截图功能,来获取二维码的图片。核心代码如下:
var checkCodeDiv = await page.QuerySelectorAsync("img[id='captcha_img_id']");
if (checkCodeDiv != null)
{
var box = await checkCodeDiv.BoundingBoxAsync();
if (box != null)
{
await page.ScreenshotAsync(new PageScreenshotOptions
{
Path = @".\png\code.png",
Clip = new Clip
{
X = box.X,
Y = box.Y,
Width = box.Width,
Height = box.Height
}
});
}
}
3. 拿到了验证码的图片,下一步就需要将验证码发送给大模型去做解析。
这一步有点复杂,作者是本地使用lmstudio, 跑了qwen3.5 35B的模型,注意关闭模型的思考,速度会很,秒响应。硬件是一块4090的显卡+ 4060ti,24G+16G=40G显存。对于这种跑验证码,不涉及的公司内部信息,也可以直接去接外部的大模型,虽然有些费用。提示词是“请识别验证码,直接返回文本,内容为4位数字或字母,没有其他字符”,lmstudio支持标准的openai接口的,拿到的返回代码,只需要里面的“content”部分内容。 这一部分具体的不细讲。

4. 获取到验证码,那么可以点击按钮进行登录。
这里需要考虑两个问题,一个是如何判断登录成功,也就是AI返回的验证码是否正确(默认用户名和密码是正确的),一个是需要有重试的机制。
a. 如何判断你验证码正确:
如果验证码输入错误,页面是不会调整,同时页面会出现一个tip提示,可以把页面是否有该元素作为验证码是否成功的标志。
b. 重试的机制
写一个函数,把整个页面用户名、密码、验证码分析以及按钮点击都放在里面,页面登录成功后返回验证码的字符串,失败返回空字符串。(作者想保留下来识别结果做他用,返回true或者false也都可以)
定义一个重试次数的变量,用while循环来判断是该变量超过阈值,如果页面登录失败,那么该变量自增,如果成功,那么跳出while循环。
public async Task<String> TaskLoginAction(IPage page) {
var userNameInput = await page.QuerySelectorAsync("input[id='name']");
await userNameInput.FillAsync(userName);
var passwordInput = await page.QuerySelectorAsync("input[id='password']");
await passwordInput.FillAsync(password);
var checkCodeDiv = await page.QuerySelectorAsync("img[id='captcha_img_id']");
if (checkCodeDiv != null)
{
var box = await checkCodeDiv.BoundingBoxAsync();
if (box != null)
{
await page.ScreenshotAsync(new PageScreenshotOptions
{
Path = @".\png\code.png",
Clip = new Clip
{
X = box.X,
Y = box.Y,
Width = box.Width,
Height = box.Height
}
});
}
}
if (File.Exists(@".\png\code.png"))
{
byte[] imageBytes = File.ReadAllBytes(@".\png\code.png");
string base64String = Convert.ToBase64String(imageBytes);
string url = $"data:image/png;base64,{base64String}";
string result = await client.RecognizeCaptchaFromBase64(url);
var checkCodeInput = await page.QuerySelectorAsync("input[id=\"captcha\"]");
await checkCodeInput.FillAsync(result);
var submitButton = await page.QuerySelectorAsync("button[id='btn_submit']");
await submitButton.ClickAsync();
Thread.Sleep(1000);
var tipSpan = await page.QuerySelectorAsync("span[class='tip']");
if (tipSpan != null)
{
return "";
}
else
{
return result;
}
}
else {
return "";
}
}
重试机制代码
string code = await TaskLoginAction(page);
int rerTryTimes = 1;
if (!string.IsNullOrEmpty(code))
{
File.Move(@".\png\code.png", $".\\png\\{code}.png");
LogService.Logger.Info("登录成功");
}
else {
while (rerTryTimes < 10 && string.IsNullOrEmpty(code)) {
page.ReloadAsync();
Thread.Sleep(1000);
code = await TaskLoginAction(page);
if (!string.IsNullOrEmpty(code))
{
File.Move(@".\png\code.png", $".\\png\\{code}.png");
LogService.Logger.Info("登录成功");
}
rerTryTimes++;
}
}
5. 登录成功后,点击数据中心,默认就是威胁日志,这个就是我们需要的内容。
分析页面,数据中心按钮对应 a标签的路径为: //a[text()='数据中心']

6. 分析日志, 关注类型是 攻击结果为 “失陷”; 威胁类型为: "恶意代码", "恶意邮件","漏洞攻击","入侵攻击"
两种方式可以获取日志,一种是在下来菜单做选择,一种是查询输入框中输入指令来查询,显然后者要简单些。分析页面,查询框、查询按钮、以及结果对应的元素如下:
查询框: input[id='tbar_monitor_logThreat_input']
查询按钮: div[id='tbar_monitor_logThreat_input-textfield-search']
查询结果: div[id*=\"monitor_logThreat_maingrid-rowdata\"]
查询结果是多行,我们使用 QuerySelectorAllAsync ,如果返回的结果元素多个表明存在该查询类型的日志,那么该类型需要告警。注意失陷对应的是结果 attack_status , 而其他的是 threat_type
失陷:((attack_status eq '失陷'))
入侵攻击: ((threat_type eq '入侵攻击'))
#region 事件检测
public async Task<JObject> CheckIfExitsAlert(IPage page, string alertTypeString, string type) {
JObject resObj = new JObject();
resObj.Add("alertTypeString", alertTypeString);
var inputText = await page.QuerySelectorAsync("input[id='tbar_monitor_logThreat_input']");
await inputText.FillAsync($"(({type} eq '{alertTypeString}'))");
var queryButton = await page.QuerySelectorAsync("div[id='tbar_monitor_logThreat_input-textfield-search']");
await queryButton.ClickAsync();
Thread.Sleep(2000);
var threatDiv = await page.QuerySelectorAllAsync("div[id*=\"monitor_logThreat_maingrid-rowdata\"]");
if (threatDiv.Count == 0) {
resObj.Add("ifAlert", false);
resObj.Add("snapPng", "");
LogService.Logger.Info($"没有{alertTypeString}");
}
else{
resObj.Add("ifAlert", true);
string pngName = $"{alertTypeString}-{DateTime.Now.ToString("yyyyMMddHHssmm")}.png";
await page.ScreenshotAsync(new PageScreenshotOptions
{
Path = $".\\风险截图\\{pngName}"
});
if (File.Exists($".\\风险截图\\{pngName}")) {
resObj.Add("snapPng", pngName);
}
LogService.Logger.Info($"存在{alertTypeString}");
}
return resObj;
}
#endregion
7. 拿到了日志告警的结果,那么发送邮件和企业微信告警都是简单的事情,这部分代码都可以交给AI自动去生成。如果要定时巡检,那么可以设置定时任务来执行程序。
写在最后:
在企业AI落地过程中,更多可以考虑在提高效率,减少重复性劳动,通过这个小程序,作者就不用每日都打开安全设备管理页面去查看,后续作者还计划将该功能生成copaw的技能,让copaw定时调用,输出报告到企业微信中。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)