ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

钉钉机器人自动发送消息到群:Webhook、Python脚本与定时任务实战

钉钉机器人自动发送消息到群:Webhook、Python脚本与定时任务实战 简介面向Java开发者的钉钉机器人自动发送消息源码包完整演示了通过调用钉钉Webhook接口向群聊推送自定义文本信息的实现过程。项目核心包含AlarmService类封装了HTTP POST请求、JSON消息结构构建、返回状态判断等关键步骤并预留扩展点便于接入定时任务或告警场景。包体共161个文件压缩后仅382KB。文件以Java源码、XML配置、JS脚本、JSON数据、CSS样式和Maven工程文件为主其中XML多用于项目与Spring配置JS与CSS配合前端页面展示properties保存环境参数源代码可直接导入IDE运行调试。目前已有2296人学习下载。借助该源码可快速掌握钉钉机器人接入流程、HttpClient工具类的用法以及消息推送的通用设计思路。下载后能直接获得完整工程与配置文件既可用于团队通知、系统监控报警、任务进度播报等实际场景也可作为学习Java网络编程和第三方API调用的实用范例。1. 钉钉机器人自动发送自定义信息到钉钉群一条 Webhook 就能把重复通知变成定时任务每天早上把昨天的报表、报警记录、订单状态挨个打开看一眼再手动复制粘贴到群里这个动作一次两次无所谓天天做就成了纯消耗。钉钉机器人自动发送自定义信息到钉钉群就是把这一步从「人肉通知」变成「定时任务」的常见做法在群里建一个自定义机器人得到一个 Webhook 地址任何能发 HTTPS 请求的脚本都能以机器人身份把文本、Markdown 表格甚至带跳转链接的卡片推进群里。很多人搜钉钉机器人消息推送要的其实就是这个能力不需要开放平台权限也不需要一台独立服务。标题里的「对应源码」说明这套方案通常自带可直接改着用的脚本而不是只有原理。这篇按从一个空群到定时推送的完整路线写建机器人、读懂源码里的消息模型、写发送脚本、排掉最常见的坑照着做完半个小时能跑通第一版。2. 从建群到拿到 Webhook三步建好机器人并选对安全校验方式2.1 创建钉钉群机器人的完整路径群设置、自定义机器人、密钥先纠正一个普遍误解钉钉机器人自动发送这事机器人不是在钉钉开放平台创建的而是在群设置里加的。打开目标群点右上角“...”找到「群机器人」入口选「添加机器人」在类型列表里选「自定义」。这里很多人会选错成「企业内部机器人」那是给正经集成开发用的要申请 AppKey、走应用发布流程而「自定义」机器人建完直接给你一串 Webhook 地址脚本往这个地址发请求就算发消息。标题里说的对应源码基本都围绕这个自定义机器人方向写因为接入成本最低不需要管理员审批群主或群管理员在手机端和电脑端都能操作。具体路径按顺序走一遍群设置 → 群机器人 → 添加机器人 → 自定义 → 输入机器人名称 → 选择安全设置 → 完成。机器人名称建议带上环境标识比如「线上告警」「报表机器人」不然三五个机器人全叫「小助手」运维排错时根本分不清消息从哪来。头像可以默认也可以传团队 logo这一步不影响功能。完成后钉钉会展示一个 Webhook 地址形如https://oapi.dingtalk.com/robot/send?access_token一串字符这串字符就是群的身份凭证泄露给别人别人也能往群里发消息所以后续脚本里要把它当密码处理别直接写死在代码里到处传。还有一个边界要提前说清楚自定义机器人只能往它被创建的那个群发消息不能跨群发送也不能读取群消息更不会自动回复。它只有「发消息」这一项能力。看清这个边界后才好判断它能干什么——定时报表推送、监控告警、版本上线通知、运营活动播报这些单向通知场景都合适如果要和群成员的回复做交互那就不是这个方案能覆盖的范围了。另外记一个失效规则机器人被群管理员移除或群被解散后旧 Webhook 立即失效重新添加得到的是新的 access_token。所以脚本里的 Webhook 不要散落各处我会把它放在单独配置或环境变量里换群时只改一个地方。2.2 三种安全校验方式怎么选关键词、加签、IP白名单建机器人时「安全设置」这一步必须选钉钉默认不让你跳过。三种方式各有取舍先看对照安全方式原理适合场景自动化脚本的代价自定义关键词消息内容必须包含至少一个预设关键词人工测试、内容固定每条消息都要带关键词自由文本受限加签用 Secret 计算签名拼进请求 URL自动化推送、内容不固定每次发送要现算 timestamp 和 signIP 白名单只允许指定来源 IP 调用出口 IP 固定的服务器动态 IP 环境会频繁失效如果选了「自定义关键词」后续所有消息的内容里必须包含至少一个预设关键词否则钉钉在网关层直接拒绝返回类似「关键词不匹配」的错误。给团队演示时这个最省事因为内容里写上词就行但对自动化链路有点难受比如关键词设成「报表」那每一条自定义信息都得记得把「报表」两个字塞进去消息内容被绑住了。自动化推送我一般默认选「加签」。流程是钉钉给一个以SEC开头的密钥发送时把当前毫秒级时间戳和密钥拼成timestamp\nsecret用 HMAC-SHA256 计算、Base64 编码、再 URL 编码最后拼到 Webhook 的 query 参数上。钉钉服务端用同一份密钥做校验能对上就放行。这个模式不会约束消息内容正好匹配「自定义信息」随意变化的需求。IP 白名单适合服务器出口固定、多服务共用一个推群机器人的场景但小团队办公网和个人电脑基本都是动态出口 IP今天能发明天被拒排查一圈才发现是 IP 变了所以不建议把它当首选。实际部署时三种方式可以组合比如「关键词 加签」同时启用。我一般只开加签重点提醒一句加签用的SEC密钥只在创建时完整展示一次之后在群设置里只能看到 Webhook 地址看不到密钥内容。第一次拿到就保存好丢了只能删掉机器人重新建。2.3 先拿 curl 验证 Webhook 通不通最小可用请求在写 Python 脚本之前先用 curl 把链路打通确认 Webhook 没抄错。如果机器人选的是「自定义关键词」或没设安全设置直接发curl https://oapi.dingtalk.com/robot/send?access_token你的TOKEN \ -H Content-Type: application/json \ -d {msgtype:text,text:{content:钉钉机器人连通测试}}返回{errcode:0,errmsg:ok}测试群里出现这条消息说明 Webhook 可用。如果内容里没带预设关键词返回的 errcode 非 0也能顺手验证安全设置是否真的生效。如果建机器人时选了「加签」上面这个裸 curl 是发不出去的必须先算签名再拼 URL这一步放到第四章的脚本里处理。先验证再编码的好处是缩小排错范围链路不通时先怀疑 Webhook 本身链路通了再谈签名和消息体后面加变量时才不会一团乱麻。3. 看懂源码里的消息模型text、markdown、link 三种消息体的字段对照3.1 text 消息体最少字段也能发 指定人的正确写法打开对应源码包不管它用 Python、Java 还是 Go 写核心都在构造一段 JSON 消息体。钉钉机器人自动发送自定义信息本质就是把信息塞进固定结构的 JSON然后用 POST 丢给 Webhook。最基础的是 text 类型{ msgtype: text, text: { content: 巡检完成一切正常 }, at: { atMobiles: [13800138000], isAtAll: false } }顶层msgtype告诉钉钉这是文本消息text.content就是要展示的原文直接填字符串就行。at字段可选atMobiles接收一个手机号数组表示要提醒哪些群成员isAtAll设为 true 会 全员。日常定时通知如果没有紧急情况建议把isAtAll保持 false避免半夜一个成功通知打扰所有人。一个消息体常见误区有人会把13800138000拼进 content又把同一手机号放进 atMobiles结果群里出现一段带 符号的正文外加一条提醒看起来很啰嗦。正确做法是 content 里不写 提醒由 atMobiles 负责。另外 atMobiles 里的手机号必须是当前钉钉组织内已激活的账号写在普通外部联系人群里可能不提醒这是钉钉侧的限制脚本多写了也无解。3.2 markdown 消息体标题、文本、表格的渲染边界报表、告警摘要这类结构信息用 text 发一大段纯文本很难读这时用 markdown 类型{ msgtype: markdown, markdown: { title: 每日订单报表, text: ## 订单统计\n\n| 日期 | 订单数 |\n| --- | --- |\n| 2025-03-21 | 128 |\n\n 数据来自线上库 }, at: { isAtAll: false } }markdown.title显示在会话列表和通知栏作用类似邮件标题要起得一看就懂markdown.text才是正文写 Markdown 源文本。钉钉渲染的是 Markdown 子集不是 GitHub 完整风味标题、粗斜体、有序无序列表、表格、引用块基本都支持但不支持复杂 HTML、也不支持 base64 内嵌图片图片只能放外链 URL。表格是自动化推送里最常用也最容易摔跤的部分。钉钉要求表头、分隔行、数据行之间结构完整前后要有空行。text字段里的\n是真实的换行符源码里如果只是简单\n.join()大概率被渲染成一整段连续文本。我习惯在每行之间留一个空行也就是两个换行符像上面的示例一样。单元格内容里的竖线、换行必须清洗掉否则表格会被拦腰截断这一节的坑在第五章还会展开。3.3 link 消息体一条带跳转入口的通知自动推送到最后往往还要给人一个落脚点想看详情点哪里。link 类型就是干这个的{ msgtype: link, link: { text: 点击查看完整报表与昨日明细, title: 订单日报已生成, picUrl: , messageUrl: https://your-report.example.com/daily } }link.title是卡片主标题link.text是概要描述link.messageUrl是点击卡片后打开的地址必须是完整 http/https 链接picUrl是缩略图地址可留空。适合版本上线通知、跳转报表、跳转工单这类场景。源码里它常常配合 text 一起用先发一条 markdown 汇总再补一条 link 给人入口。看完三种消息体就会发现源码里发送函数往往像这样封装公共逻辑def send_payload(webhook: str, payload: dict) - dict: resp requests.post(webhook, jsonpayload, timeout5) return resp.json()消息体统一用 dict 构造再交给requests.post的json参数序列化而不是自己拿字符串拼 JSON。原因很朴素自定义信息里经常混着中文引号、双引号、换行、百分号手工拼字符串极易转义错误一个引号就让你排查半天。用 dict 构造让序列化库去处理转义是源码里最值得保留的习惯。4. 用 Python 把自定义信息推进钉钉群最小脚本、Excel 报表转 Markdown 与定时触发4.1 完整可用的发送脚本加签、命令行参数、文本消息三合一把第三章的消息模型落到脚本上第一版只做一件事接收命令行参数发一条 text 消息同时支持加签。代码可以收敛到一个文件import argparse import base64 import hashlib import hmac import time import urllib.parse import requests def build_signed_url(webhook: str, secret: str) - str: # 没有 secret 说明机器人没开加签直接返回原始链接 if not secret: return webhook timestamp str(round(time.time() * 1000)) string_to_sign f{timestamp}\n{secret} hmac_code hmac.new( secret.encode(utf-8), string_to_sign.encode(utf-8), digestmodhashlib.sha256, ).digest() sign urllib.parse.quote_plus(base64.b64encode(hmac_code).decode(utf-8)) return f{webhook}timestamp{timestamp}sign{sign} def send_text(webhook: str, secret: str, content: str, mobilesNone): url build_signed_url(webhook, secret) payload { msgtype: text, text: {content: content}, at: {atMobiles: mobiles or [], isAtAll: False}, } return requests.post(url, jsonpayload, timeout5).json() if __name__ __main__: parser argparse.ArgumentParser(description钉钉机器人自动发送自定义信息) parser.add_argument(--webhook, requiredTrue, help群设置里复制的完整 Webhook 地址) parser.add_argument(--secret, default, help加签模式下的 SEC 开头密钥没加签可留空) parser.add_argument(--content, requiredTrue, help要发送的自定义文本内容) parser.add_argument(--mobiles, nargs*, default[], help需要 的群成员手机号多个用空格分隔) args parser.parse_args() result send_text(args.webhook, args.secret, args.content, args.mobiles) print(result) if result.get(errcode) ! 0: raise SystemExit(1)传给函数的主要参数见下命令行参数含义与示例--webhook完整 Webhook 地址形如https://oapi.dingtalk.com/robot/send?access_tokenxxx--secret加签模式下的SEC密钥没开加签就不传--content推送到群里的自定义信息比如「巡检完成一切正常」--mobiles被 的手机号多个用空格分隔build_signed_url是加签的全部秘密时间戳必须用毫秒string_to_sign顺序是「时间戳 换行 Secret」算完 HMAC-SHA256 后 Base64 编码最后还要 URL 编码一次。sign里可能带、/、不 URL 编码直接拼进链接会被解析成空格或截断签名对不上。用dict构造 payload 后交给requests.post(url, jsonpayload)requests 会自动设置 Content-Type也省去手工json.dumps的中文转义麻烦。注意如果机器人创建时没选「加签」--secret留空即可脚本里if not secret会直接返回原始 Webhook。如果确认加了签却一直报验签失败先跳到 5.1 对一遍常见错误。4.2 用 Python 将 Excel 使用钉钉机器人推送到群聊天消息报表转 Markdown很多群里的日报、周报其实是把 Excel 内容复制进去。这个动作完全可以用标题里的方案替代用 pandas 读 Excel转成一条 markdown 表格一次推送进群。先装依赖pip install requests pandas openpyxlopenpyxl负责读.xlsx文件如果手上还有旧版.xls额外装一个xlrd。读取并转换成 markdown 的函数可以这样写import pandas as pd def build_markdown_from_excel(path: str, max_rows: int 20) - str: df pd.read_excel(path).head(max_rows).fillna() header | | .join(str(col) for col in df.columns) | divider | | .join([---] * len(df.columns)) | lines [header, divider] for _, row in df.iterrows(): cells [] for value in row: text str(value).replace(\r, ).replace(\n, ).replace(|, /) if len(text) 40: text text[:40] ... cells.append(text) lines.append(| | .join(cells) |) return f## 数据报表\n\n \n\n.join(lines)单元格清洗是这段的关键原始 Excel 单元格里的换行符会直接破坏表格结构竖线|会和 markdown 分隔符冲突超长文本在手机端会把排版撑乱所以分别替换成空格、斜杠、截断加省略号。最后用\n\n.join(lines)把每一行表格之间加一个空行这和第三章提到的钉钉 markdown 渲染要求一致。配合发送函数使用text build_markdown_from_excel(/data/report.xlsx, max_rows20) resp send_text(args.webhook, args.secret, text) if resp.get(errcode) 0: print(推送成功) else: print(推送失败, resp)这里直接用send_text没问题因为钉钉对消息类型的识别只看msgtype字段。要发 markdown把 payload 换成msgtype: markdown并补上title就行。行数超过 20 行的表建议只发汇总或提供 link 跳转完整报表行数越多单条消息越容易被限制详情见 5.3。4.3 定时触发Linux crontab 和 Windows 计划任务的最小写法脚本能手动跑通只是第一步真正解放人力的是定时执行。Linux 上最常用 crontab0 9 * * 1-5 cd /opt/dingtalk /usr/bin/python3 send_daily.py \ --webhook https://oapi.dingtalk.com/robot/send?access_tokenxxx \ --secret SECxxx \ --content 每日巡检完成 /var/log/dingtalk.log 21从左到右依次是分钟、小时、日期、月份、星期0 9是每天 9 点1-5是周一到周五。cd /opt/dingtalk保证脚本能读到相对路径文件 /var/log/dingtalk.log 21把输出和错误都落到日志排错时翻日志比盯屏幕有效。留意 shell 会把未加引号的解释成后台执行所以 Webhook 地址一定要用双引号包住这是 cron 里最容易翻车的一个细节。Windows 机器用计划任务schtasks /create /tn DingTalkReport \ /tr python D:\dingtalk\send_daily.py --webhook \你的URL\ --secret \你的SEC\ \ /sc daily /st 09:00 /f/sc daily表示每天/st 09:00指定开始时间/f强制覆盖同名任务。如果 Python 不在系统 PATH 里/tr里要写完整解释器路径比如C:\Python312\python.exe。任务创建后建议先手动运行一次再改计划时间不然「创建成功但到点没消息」很难判断是任务没触发还是脚本报错。5. 钉钉机器人自动发送避坑指南签名、限流、换行截断这几个坑我全踩过5.1 加签机器人报「验签失败」时间戳单位与拼接顺序错一个都不行现象脚本第一次跑返回{errcode:310000,errmsg:sign not match}同一个脚本换成没加签的 Webhook 就能发出去。原因验签失败最常见有三个来源。第一时间戳用了秒而不是毫秒time.time()取整后只有 10 位钉钉要求 13 位毫秒时间戳。第二签名串顺序写成secret \n timestamp而官方规定是timestamp \n secret两者算出的 HMAC 完全不一样。第三Base64 结果没做 URL 编码号在 query 里被当成空格服务端还原出来的签名对不上。解决发出前给脚本加三个断言比肉眼检查快得多assert len(timestamp) 13, timestamp must be milliseconds assert string_to_sign.startswith(timestamp), timestamp must come first assert % in sign or ( not in sign and / not in sign), sign must be urlencoded第三个断言不太好使因为 URL 编码后%一定出现而未编码的会被 URL 解析丢失。更直接的办法是把url打印出来人工核对一遍timestamp、sign是否都出现在链接里SEC是否完整。5.2 返回 ok 但群里没消息别把 errcode 当送达证明现象send_text返回{errcode:0,errmsg:ok}但盯着业务群就是看不到消息。原因这个 ok 只代表钉钉网关收下了请求不代表消息已经进群。最常见情况是 Webhook 从旧群复制过来旧机器人已被移除或群已解散网关对旧 token 有时只回 ok 不下发另一种是发送的超长文本被钉钉服务端处理时丢弃但响应仍是 ok。依靠返回值做「送达审计」会测不出问题。解决脚本里把发送时间、Webhook 尾部 6 位、errcode 写进日志测试时先手动 curl 当前 Webhook看群里到底能不能出消息再跑业务脚本。如果怀疑超长把 content 截到前 500 字分两条发送观察哪一段消失就能确认边界。5.3 Excel 推 100 行被限流把逐行发送改成单条 Markdown 表格现象脚本 for 循环读取 Excel 每一行逐行调用发送函数前十几条正常突然开始返回限流错误后面再也没有消息进群。原因自定义机器人对单个 Webhook 有每分钟消息数限制逐行推送等于短时间内打几十次调用必然会触顶。限流错误的具体码在不同版本钉钉上有差异但返回格式都是errcode非 0且错误码集中在限流相关区间。解决数据量小时用一条 markdown 表格打包发送把 20 行合并成一条消息这是最省配额的做法。数据量大时只发送汇总统计加抽样明细完整数据通过 link 消息跳转。如果业务真要求逐行发就在循环里加time.sleep(1.5)并记录已发送行号宁可慢一点也不要半夜被限流打断但一条 markdown 表格在手机端的阅读体验其实比几十条单行消息好得多我会优先改合并方案。5.4 markdown 表格只有第一行换行符和管道符的处理顺序现象同一个 markdown 文本在本地预览正常推送到群里只剩表头和第一行后面全变成普通文本串。原因钉钉要求表格的每一行之间有空行源码里如果只用\n换行而不留空行解析器会把多行归成一个段落表格结构断掉。另一个元凶是单元格里的竖线没做处理多一个|就多一列对齐错位后后面的行全被忽略。解决统一在表格行之间用\n\n.join(lines)拼接而不是\n.join单元格内容里的换行符替换成空格竖线替换成全角竖线或斜杠/。这正好对上了第四章build_markdown_from_excel里那两行 replace 的作用当初踩这个坑时我在源码里加了很长一段注释才记住优先级先清数据再拼行最后才进消息体。6. 收尾给自动发送加一层重试、日志和「预览」习惯别让机器人变黑匣子源码跑通不难难的是第二天没人管它也能跑通。我现在凡是上生产的推送脚本都先加一个最小重试模板请求超时后等 10 秒重试最多三次并且每次都把返回的错误码和发送时间落日志。for attempt in range(3): resp requests.post(url, jsonpayload, timeout5) data resp.json() if data.get(errcode) 0: break time.sleep(10)日志里不要打印完整 Webhook 和 sign里面带着 access_token 和签名信息打日志等于把密钥撒得到处都是。我一般只记录 errcode、errmsg、发送时间和 Webhook 尾号 4 位既能定位问题又不会把敏感信息散出去。另一个坚持下来的习惯是任何机器人第一次接入先在一个人少的管理测试群发一条完全相同的消息肉眼看过渲染效果再让脚本去切生产群。markdown 表格这种排版代码里和手机上看到的永远是两个世界。很多次我以为脚本没问题结果测试群一跑就露出 5.4 里说的表格断裂也正因为提前预览才没在业务群里当众翻车。半年下来我觉得这个方向最有价值的地方反而不是省了多少复制粘贴而是让数据能用一种固定格式、固定时间出现在该出现的地方其他人不用追问「今天报表发了没」。希望帮到你。本文还有配套的精品资源点击获取
返回列表