手把手用 Doubao-Seed-Evolving 写一个网站监控脚本:完整代码与两处踩坑

手把手用 Doubao-Seed-Evolving 写一个网站监控脚本:完整代码与两处踩坑

一、为什么让模型来写监控

先说背景。手上有几个对外服务在跑,半夜悄悄挂了没人知道是最憋屈的事。市面上的监控 SaaS 免费额度紧、付费按年收,对只盯三五个站的人来说偏重。于是我想验证一件事:能不能直接让大模型从一个自然语言需求,产出「能跑、能告警、能调度」的真实工具。

这次用的模型是 Doubao-Seed-Evolving,字节跳动面向复杂工程任务推出的模型,8 月 3 日刚完成第二次升级,重点强化了 Coding 工程能力、Agent 检索能力和幻觉控制能力。本文聚焦它 Coding 工程能力的实测。

二、完整 monitor.py(可直接运行)

下面是我让模型生成、并经两处踩坑修正后的最终版,Python 3.10+ 依赖仅 requests,复制即可跑。

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""HTTP 目标监控脚本:定期监控配置的 URL,异常时通过飞书机器人发送告警"""
import json
import sys
import time
from datetime import datetime
from pathlib import Path
from typing import Any

import requests


def load_json_file(file_path: Path) -> Any:
    with open(file_path, "r", encoding="utf-8") as f:
        return json.load(f)


def get_feishu_webhook(base_dir: Path) -> str | None:
    # 优先从同目录 config.json 读取 feishu_webhook,缺失则返回 None
    config_path = base_dir / "config.json"
    if not config_path.exists():
        return None
    try:
        config = load_json_file(config_path)
        webhook = config.get("feishu_webhook", "").strip()
        return webhook if webhook else None
    except (json.JSONDecodeError, OSError) as e:
        print(f"[警告] 读取 config.json 失败: {e}")
        return None


def load_targets(base_dir: Path) -> list[dict]:
    targets_path = base_dir / "targets.json"
    if not targets_path.exists():
        print(f"[错误] 未找到配置文件: {targets_path}")
        sys.exit(1)
    try:
        targets = load_json_file(targets_path)
        if not isinstance(targets, list):
            print("[错误] targets.json 必须是 JSON 数组格式")
            sys.exit(1)
        return targets
    except json.JSONDecodeError as e:
        print(f"[错误] 解析 targets.json 失败: {e}")
        sys.exit(1)


def check_target(target: dict) -> dict:
    name = target.get("name", "未命名")
    url = target.get("url", "")
    timeout = target.get("timeout", 5)
    result = {
        "name": name, "url": url, "timeout": timeout,
        "is_reachable": False, "status_code": None,
        "elapsed": 0.0, "is_error": False, "error_reason": "",
    }
    start_time = time.time()
    try:
        response = requests.get(
            url, timeout=timeout, allow_redirects=True,
            headers={"User-Agent": "MonitorScript/1.0"})
        result["elapsed"] = round(time.time() - start_time, 3)
        result["status_code"] = response.status_code
        result["is_reachable"] = True
        if response.status_code >= 400:
            result["is_error"] = True
            result["error_reason"] = f"HTTP状态码异常: {response.status_code}"
        elif result["elapsed"] > timeout:
            result["is_error"] = True
            result["error_reason"] = f"响应超时: 耗时{result['elapsed']}s > 阈值{timeout}s"
    except requests.exceptions.Timeout:
        result["elapsed"] = round(time.time() - start_time, 3)
        result["is_error"] = True
        result["error_reason"] = f"请求超时: 超过{timeout}秒未响应"
    except requests.exceptions.ConnectionError:
        result["elapsed"] = round(time.time() - start_time, 3)
        result["is_error"] = True
        result["error_reason"] = "连接失败: 无法建立网络连接"
    except requests.exceptions.RequestException as e:
        result["elapsed"] = round(time.time() - start_time, 3)
        result["is_error"] = True
        result["error_reason"] = f"请求异常: {str(e)}"
    return result


def send_feishu_alert(webhook: str, result: dict) -> bool:
    # 飞书机器人对 markdown 类型关键词校验异常,改用 text 类型 + 关键词"监控"
    current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    text_content = (
        f"监控:服务异常通知\n---\n"
        f"服务名称: {result['name']}\n访问地址: {result['url']}\n"
        f"错误原因: {result['error_reason']}\n状态码: {result['status_code'] or 'N/A'}\n"
        f"响应耗时: {result['elapsed']}s\n超时阈值: {result['timeout']}s\n"
        f"触发时间: {current_time}\n")
    payload = {"msg_type": "text", "content": {"text": text_content}}
    try:
        response = requests.post(webhook, json=payload, timeout=10,
                                 headers={"Content-Type": "application/json"})
        response.raise_for_status()
        resp_data = response.json()
        if resp_data.get("code", 0) != 0:
            print(f"[飞书告警发送失败] {resp_data.get('msg', '未知错误')}")
            return False
        return True
    except requests.exceptions.RequestException as e:
        print(f"[飞书告警发送异常] {e}")
        return False


def print_log(result: dict) -> None:
    current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    status = "[异常]" if result["is_error"] else "[正常]"
    log_line = (f"[{current_time}] {status} | 名称: {result['name']} | "
                f"URL: {result['url']} | 状态码: {result['status_code'] or 'N/A'} | "
                f"耗时: {result['elapsed']}s")
    if result["is_error"]:
        log_line += f" | 原因: {result['error_reason']}"
    print(log_line)


def main():
    base_dir = Path(__file__).resolve().parent
    print("=" * 60)
    print(f"HTTP监控脚本启动 - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    print("=" * 60)
    feishu_webhook = get_feishu_webhook(base_dir)
    print("[信息] 飞书告警已启用" if feishu_webhook else "[信息] 飞书告警未配置,仅打印控制台日志")
    targets = load_targets(base_dir)
    print(f"[信息] 成功加载 {len(targets)} 个监控目标")
    print("-" * 60)
    error_count = 0
    for target in targets:
        if "name" not in target or "url" not in target:
            print(f"[跳过] 配置项缺少 name 或 url 字段: {target}")
            continue
        result = check_target(target)
        print_log(result)
        if result["is_error"]:
            error_count += 1
            if feishu_webhook and send_feishu_alert(feishu_webhook, result):
                print(f"  └─ 飞书告警已发送: {result['name']}")
    print("-" * 60)
    print(f"监控完成: 共检查 {len(targets)} 个目标, 异常 {error_count} 个")
    print("=" * 60)
    sys.exit(1 if error_count > 0 else 0)


if __name__ == "__main__":
    main()

配套两个配置文件,targets.json 是必需项,config.json 可选。

json 复制代码
// targets.json 示例
[
  {"name": "示例站点A", "url": "https://www.baidu.com", "timeout": 5},
  {"name": "示例站点B", "url": "https://github.com", "timeout": 10}
]
json 复制代码
// config.json 示例(可选,存放飞书 webhook)
{"feishu_webhook": "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxx"}

三、关键实现解析

异常分支分全,是这段代码最稳的一点。 check_target 没有只写 happy path,而是把网络失败分成了三类:Timeout(超时)、ConnectionError(连不上)、通用 RequestException(其他)。真实网络环境里站点挂掉的方式不止一种,分清楚失败类型,告警信息才有用。比如同样是「打不开」,超时和连不上对应的处理动作完全不同:超时可能是后端慢或链路抖动,连不上大概率是 DNS、端口或服务进程挂了,告警里写清楚类别,值班的人一眼就知道往哪查。

请求参数也做了取舍。 requests.get 带了 timeout=timeoutallow_redirects=True 和自定义 User-Agenttimeout 是关键,不设这个参数,请求可能无限挂起,监控脚本自己先卡死;allow_redirects 让 301/302 跳转后的最终状态码参与判定,避免把「跳转到正常页」误判成异常;User-Agent 则防止个别站点对空 UA 直接拒绝。这些细节模型一次就带上了,说明它理解的是真实请求而不是教学示例。

配置和代码分离,决定了它能不能长期用。 监控目标放 targets.json、飞书地址放 config.json,脚本本身不写死任何业务数据。后面加站点、换告警通道,都不用改逻辑。更关键的是 get_feishu_webhook 做了「文件不存在 / JSON 解析失败 / 字段为空」三种降级,拿不到 webhook 就退回纯控制台日志,不会因为配置问题直接崩。

飞书告警选择 text 类型是有原因的。 前面提过 markdown 类型在这台机器人上关键词校验不稳定,所以 send_feishu_alert 直接用 msg_type: text,正文第一行固定带「监控」关键词。文本结构用换行分隔字段(名称、地址、原因、状态码、耗时、阈值、时间),在飞书里可读性足够,也绕开了富文本渲染的兼容坑。

退出码也考虑了。 main 末尾 sys.exit(1 if error_count > 0 else 0),异常时返回非 0,方便被 crontab 或计划任务接住做二次处理。比如 crontab 里可以再套一层,非 0 时才触发额外的通知或上报,把「探测」和「处置」解耦。

四、踩坑一:飞书关键词校验拦了告警

第一版我用的是 markdown 类型消息,正文含「告警」二字。逻辑跑通了,但飞书一条没收到,后台报错「Key Words Not Found」。先试着把「告警」挪到正文第一行,还是失败。根因是这台飞书机器人对 markdown 类型的关键词校验不稳定,换成 text 类型、关键词改成「监控」后立刻成功。

(图 1:飞书群里收到两条告警消息,分别是 httpbin 超时和不存在域名连接失败)

这里有个值得记下的点:模型严格按提示词写了 markdown + 关键词,代码逻辑没错,却没踩中第三方平台的安全规则。定位根因、决定改类型的,还是人。

五、踩坑二:Windows 计划任务编码崩

手动在终端能跑,挂进 Windows 计划任务每五分钟跑一次就报 gbk codec can't encode。根因是计划任务调起的控制台默认 GBK 编码,而早期版本日志里用了 emoji 图标,一进计划任务就崩。修法是 bat 入口加 chcp 65001 强制 UTF-8,并把 emoji 换成 [正常] / [异常] 纯文字。

(图 2:Windows 任务计划程序里 SeedMonitor 的属性页,触发器为每 5 分钟重复一次)

改完这两处,定时任务才真正稳定。顺手贴一张稳定运行后的日志,三个目标全部正常、异常 0 个,说明它已经老实在替你盯着了。

(图 3:monitor.log 内容,三个目标全部「正常」、异常 0 个,证明定时任务稳定运行)

六、从脚本到接口:能力还能再往前走一步

脚本在本地跑着只是起点。把 check_target 的探测逻辑抽象成一个 HTTP 健康检查接口,参数就是 url + timeout + expected_status,返回 status_codeelapsed_msis_error,再叠告警规则、调度、历史可用率,它就成了一个轻量监控能力。这样一个原本只能在本机跑的网站监控脚本,就变成了可被其他系统调用的接口监控服务,前端大屏、运维平台都能直接拉数据。

想让脚本对外提供服务,也可以封装成接口托管到 YesApi Pro 这类 API 开放平台,省去自己搭服务、接计费、做权限的功夫,是否采用看各自基础设施情况。

这不是把简单的事搞复杂,而是当监控目标从三五个涨到几十个、告警要从飞书扩到企微钉钉时,平台化的收益才会显现。

需要说清的适用边界

这套方案不是银弹,它最适合「盯几个对外站点、要个及时告警」的场景。如果要做真正的企业级可用性监控,至少要补三件事:多节点探测(一台机器一个网络出口会误报)、SSL 证书到期检查、以及把探测和告警通道解耦。另外它只检查「首页能不能打开」,不验证页面内容和接口深层逻辑,深度巡检得另写用例。把边界讲清楚,比把它吹成万能工具更有价值。

七、小结

这次实测里,Doubao-Seed-Evolving 真正的强项不是一次写对,而是能接住自然语言需求、产出完整可运行工程,还能跟着多轮修正不丢上下文。人负责的是需求边界、踩坑定位和部署判断。两者配合,才到得了「真在跑、真在告警」这一步。

#AI编程#大模型#Python#开发工具#后端#程序员#网站监控#自动化#接口测试#技术分享

相关推荐
ZZHow10241 小时前
PyTorch深度学习入门笔记(小土堆)P7-14
人工智能·pytorch·笔记·python·深度学习
李剑一1 小时前
AI到底是在降本增效还是「降本增笑」?员工成本降下去了,AI反而成为最大的成本来源了!
aigc·ai编程
GuWenyue1 小时前
大模型幻觉无解?7步搭建Milvus+LangChain电子书RAG,私有小说精准问答零编造
人工智能·langchain·ai编程
很楠爱上2 小时前
AI项目------赛博负熵:拆解一个 Codex 生成的英语背单词全栈工程(附源码文件免费)
人工智能·python·codex
小挪号底迪滴2 小时前
Docker 多阶段构建实战:让 Python 镜像更小、更快、更安全
python·安全·docker
Postkarte不想说话2 小时前
Hugging Face模型转为GGUF格式
ai编程
学者猫头鹰2 小时前
基于Spring AI 1.1.x 私有知识库RAG系统
ai编程
WangChenGe2 小时前
Windows 10/11 上 Pyenv-win 完整安装教程
python
想会飞的蒲公英2 小时前
PyTorch中SGD 与 Momentum 从零理解:给最朴素的优化器加上“惯性“
人工智能·pytorch·python·深度学习·机器学习