ARTICLE DETAIL

资讯详情

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

用Python实现钉钉群文件自动发送:定时推送完整代码

用Python实现钉钉群文件自动发送:定时推送完整代码 干办公自动化这行这么多年我越来越觉得日常最烦的不是写代码而是“把文件准时送到该送的人手里”。日报、周报、对账单、发酵报告每到点就得四五个群轮着拖一遍文件偶尔一忙还发错群、忘发回头被提醒又得补一句“刚忘了马上补”。后来我把“钉钉群文件自动发送”这活儿用 Python 整个做成定时脚本配置一次之后每天到点自动推送群里瞬间出现消息和文件领导体验好了我也终于不用守着电脑盯发送按钮了。今天把整个流程和完整代码直接开源出来你照着走一遍5 分钟能把脚本跑起来。这套方案适合谁三类人最有必要看一是天天手动在钉钉群发日报、销售数据、运营截图的运营和行政二是每天要给客户或内部项目群推日志、安装包、测试结果的研发和运维三是想把“生成文件—发送文件”串成完整自动化的个人开发者。整篇内容不挑 Python 基础哪怕你只会面向搜索引擎编程只要会装依赖、会改配置就能跑通。1. 需求拆解所谓“发个文件”背后其实是三个问题1.1 稳定送达比发送动作本身更重要很多人第一反应是这不就是往群里丢个文件吗我自己手动拖一下也就几秒钟何必写脚本。但如果你负责的是每天固定时间推送、漏一次就会被人追问“今天的报表呢”的活儿手动操作最大的问题不是慢而是不稳定。你上午开会、中午吃饭、下午跑系统只要任何一环打岔文件就漏了。自动化脚本的核心价值是“到点必达”。我这边实际跑的是每天早上 8 点 55 分生成前一天的销售汇总表9 点整通过钉钉机器人推到管理群里同时给运营群发一份精简版给技术群发一份含调试日志的完整版。整个过程全部由 Python 脚本控制人在不在电脑前都无所谓。定时任务挂在 Linux 服务器上即使我休假群里照样天天收到文件。1.2 可追溯和权限控制也是隐藏需求除了稳定还有两个需求很容易被忽略一是“谁在什么时候发了什么”自动发送通过脚本日志可以完整沉淀出了问题能回溯二是“不要误发到无关群”脚本里用配置文件维护群 webhook 列表推送到哪些群完全可控比手动操作时手滑发错群要安全得多。再往深处说这类自动发送还隐含着文件去重、失败重试、大文件处理等细节。比如同一份文件如果内容和上一次完全一样就跳过不发送避免刷屏再比如接口偶发超时脚本要自动重试两次而不是发一次失败就结束。这些都是手动操作时根本不会去想的点但恰恰是自动化的价值所在。1.3 典型应用场景速查为了让你更快判断这套代码是否适配自己的场景我整理了一个速查表场景文件类型推荐发送方式关键点每日销售/运营日报Excel、PDFMarkdown 卡片 文件链接需要固定时间推送测试报告/日志推送txt、zip上传临时素材后发文件消息文件不宜过大安装包/客户端分发exe、dmg先传到 OSS再发下载链接卡片大文件走链接更稳运维告警现场截图png、jpgMarkdown 内嵌图片链接告警消息要包含关键词对账单/合同归档PDF上传文件消息注意临时素材有效期看完这个表你会发现纯粹的“文件消息”是有限制的并不是所有场景都硬要发文件本体。所以我下面的方案设计里会明确区分“发文件链接”和“发文件消息”两条路线。2. 方案选型钉钉里的文件自动发送到底有哪几条路2.1 两条主路线按需选择钉钉群文件自动发送实际能走通的一共就两条路。第一条是自己创建“自定义机器人”拿到一个 webhook 地址用 Python 往这个地址 POST JSON 消息。这种方式最简单5 分钟能跑通但自定义机器人官方支持的消息类型是文本、链接、Markdown、ActionCard、FeedCard并不直接支持“文件消息”类型。第二条路是走钉钉开放平台的企业内部应用先创建应用拿到 appKey 和 appSecret通过 API 获取 access_token再把文件通过 media/upload 接口上传为临时素材拿到 media_id 后发送文件类型消息。这条路能真正把“文件本体”送进群但配置稍多需要企业管理员权限整体下来大概多花 10 分钟。选择逻辑很简单如果是自己团队、日常群通知附带文件链接直接用自定义机器人如果业务方明确要求“群里能直接看到并下载文件”或者接收方不方便打开外链就走企业内部应用机器人发文件消息。2.2 选型时要避开的几个思维误区第一个误区是“文件消息一定比链接好”。实际上媒体上传接口返回的 media_id 是有有效期限制的在钉钉开放平台的实际策略里这类临时素材保留时间比较短通常不适合做长期归档。如果文件需要长期反复查看我更推荐把文件放到对象存储或内网文件服务再通过 Markdown 消息发一个固定的下载链接。第二个误区是“大文件也硬塞进机器人消息”。钉钉媒体上传对文件大小和格式有约束超大文件很容易在接口层被拒。我的习惯是超过 20MB 的文件一律先放到 OSS 或者公司内部网盘脚本只负责发链接这样不会撞上文件大小限制也更方便后续多人下载。第三个误区是忽略安全设置。机器人 webhook 一旦泄露任何人都能往你群里推垃圾消息。所以我所有生产脚本都要求使用“加签”模式代码里自动计算时间戳和签名webhook 地址即使被截获没有密钥也无法正常发送。这一点在后面代码部分会完整体现。2.3 为什么不用邮件或企业微信替代可能有人会问为什么不直接用邮件定时发送或者换公众平台的企业微信机器人。邮件的问题是到达率和时效性都不如群消息而且很多人没有及时看邮件的习惯。企业微信机器人虽然也很方便但有些人所在公司的主沟通工具就是钉钉再为报表专门切一套消息工具学习成本和迁移成本都不值得。与其重新推一套协作工具不如在钉钉这个已经全员在线的环境里做自动推送。3. 环境准备Python 环境与钉钉机器人配置一次讲清3.1 安装 Python 和依赖库如果本地还没装 Python官方一贯的建议是直接从 Python 官网下载 3.8 以上版本安装时务必勾选“Add Python to PATH”否则后面命令行里执行python会提示找不到命令。装好后打开终端或命令行窗口执行下面两条指令确认环境正常python --version pip --version如果pip不存在可能是安装时没勾选配套工具可以用python -m ensurepip --upgrade补齐。接着安装这次要用的依赖库requests 负责发 HTTP 请求APScheduler 负责定时调度pip install requests apscheduler国内网络环境下如果下载慢可以临时指定清华镜像pip install -i https://pypi.tuna.tsinghua.edu.cn/simple requests apscheduler3.2 创建自定义机器人并获取 webhook打开钉钉 PC 客户端进入目标群聊点击右上角“群设置”往下找到“智能群助手”进入后点“添加机器人”选择“自定义”。机器人名称可以按实际用途填比如“每日报表推送助手”。安全设置这里我强烈建议选“加签”方式钉钉会给你一串 secret 密钥后面代码里签名要用。当然也可以选“自定义关键词”比如设置“日报”为关键词那样发送的文本里必须包含“日报”两个字否则会被钉钉拦截。继续操作后钉钉会生成一个 webhook 地址格式类似https://oapi.dingtalk.com/robot/send?access_tokenxxxxxxxx这个地址里access_token就是机器人的身份令牌。把它和前面设置的加签 secret 一起保存到本地配置文件里即可不要提交到公开代码仓库否则别人拿到后也能往群里发消息。3.3 进阶准备企业内部应用机器人如果你打算走“发文件消息”路线还得在钉钉开放平台创建一个企业内部应用。登录钉钉开发者后台进入“企业内部应用”点“创建应用”填好应用名称和描述后在应用详情里找到机器人能力添加机器人并绑定到目标群。此时你需要的核心信息是应用的 appKey 和 appSecret。另外需要在应用权限管理里给应用授予文件上传和机器人发送消息相关的权限这样才能调用后续的 media/upload 接口和机器人消息接口。这一步不同后台版本的菜单名称略有差异但总思路就是“创建应用 — 添加机器人 — 授权文件能力”。如果企业管理员不在场这一步会卡住这也是我为什么把“自定义机器人”方案放在最前面的原因——它不需要企业管理员审批一个群主就能完成。4. 完整代码一套可以直接抄的发送脚本4.1 先封装签名计算与基础请求自定义机器人选择“加签”安全模式后POST 请求的 URL 需要带上timestamp和sign两个参数。签名的计算逻辑是钉钉定死的把时间戳和密钥拼接成字符串再用 HMAC-SHA256 加密最后 Base64 编码并做 URL 编码。下面这段封装可以直接用import time import hmac import hashlib import base64 import urllib.parse import requests def gen_signed_url(webhook: str, secret: str) - str: 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)) return f{webhook}timestamp{timestamp}sign{sign}这里有几个容易翻车的小细节时间戳必须是毫秒级字符串不能直接用秒级加密前要把密钥和待签字符串都编码成 UTF-8最终得到的签名必须做 URL 编码否则拼接进 URL 后可能因为、/、被服务端解析错误。我在第一次实现时没做quote_plus结果签名一直提示不匹配排查了半小时才发现是这个原因。4.2 发送 Markdown 卡片文件链接对于大多数办公场景把文件生成后推到对象存储或内网文件服务再把链接用 Markdown 卡片发到钉钉群里是最简单稳妥的做法。消息体结构如下def send_markdown(webhook: str, secret: str, title: str, text: str): url gen_signed_url(webhook, secret) payload { msgtype: markdown, markdown: { title: title, text: text, }, } resp requests.post(url, jsonpayload, timeout10) result resp.json() if result.get(errcode) ! 0: raise RuntimeError(f钉钉消息发送失败: {result}) return result实际调用时可以这样组织一份日报文件推送if __name__ __main__: ding_webhook 替换成你的webhook地址 ding_secret 替换成你的加签secret today time.strftime(%Y-%m-%d) file_name f销售日报_{today}.xlsx file_url fhttps://your-internal-file-server.com/reports/{file_name} markdown_text ( f### 销售日报自动推送\n\n f- 日期{today}\n f- 文件[点击下载 {file_name}]({file_url})\n f- 生成时间{time.strftime(%H:%M:%S)}\n\n f 本消息由 Python 自动化脚本定时发送 ) send_markdown(ding_webhook, ding_secret, 销售日报, markdown_text)这条路的代码量就这么点核心就两个函数签名函数和发送函数。文件生成、链接上传这些准备工作完全可以根据你的实际业务在调用前扩展。4.3 上传文件并发送文件消息如果你确实要把“文件本体”直接发到群里就需要走企业内部应用机器人的接口组合。第一步是获取 access_tokendef get_access_token(app_key: str, app_secret: str) - str: resp requests.get( https://oapi.dingtalk.com/gettoken, params{appkey: app_key, appsecret: app_secret}, timeout10, ) data resp.json() if data.get(errcode) 0: return data[access_token] raise RuntimeError(f获取access_token失败: {data})第二步是上传本地文件拿到 media_iddef upload_media(access_token: str, file_path: str) - str: file_name file_path.replace(\\, /).split(/)[-1] with open(file_path, rb) as fp: resp requests.post( https://oapi.dingtalk.com/media/upload, params{access_token: access_token, type: file}, files{media: (file_name, fp)}, timeout30, ) data resp.json() if data.get(errcode) 0: return data[media_id] raise RuntimeError(f上传失败: {data})第三步是把 media_id 放进文件消息里发出去def send_file_message(access_token: str, media_id: str): url https://oapi.dingtalk.com/robot/send payload { msgtype: file, file: {media_id: media_id}, } resp requests.post( url, params{access_token: access_token}, jsonpayload, timeout10, ) data resp.json() if data.get(errcode) ! 0: raise RuntimeError(f文件消息发送失败: {data}) return data这里再强调一次media_id 对应的临时素材是有限期的如果你需要一个长期可下载的文件不要只依赖这条路把文件同时归档到对象存储更稳妥。另外media/upload 接口对文件大小有约束几十 GB 的安装包装包不要去挑战接口上限直接走文件链接方案就好。4.4 定时发送让脚本每天早上自动执行发送逻辑写好后定时问题的常见做法有两种。第一种是纯 Python 脚本直接用 APScheduler适合服务器上只跑一个 Python 进程的场景第二种是用系统自带定时任务Windows 用任务计划程序Linux 用 crontab。对我来说最省心的是 APScheduler因为脚本内部就能控制调度还方便统一日志。from apscheduler.schedulers.blocking import BlockingScheduler def job(): print(f{time.strftime(%Y-%m-%d %H:%M:%S)} 开始推送报表) # 这里调用你整理好的发送函数 send_markdown(ding_webhook, ding_secret, 销售日报, markdown_text) if __name__ __main__: scheduler BlockingScheduler() scheduler.add_job(job, cron, hour9, minute0) scheduler.start()如果使用 Linux 服务器我更推荐 crontab简单直接还能被系统自动拉起日志。编辑定时任务的命令是crontab -e加入一行表示每天 9 点执行一次0 9 * * * cd /opt/dingtalk-sender /usr/bin/python3 send_report.py logs/run.log 21 logs/run.log 21是我必加的部分它能把脚本输出和错误日志都追加到文件里排查问题时非常有用。没有这行日志脚本半夜崩了你都不知道。4.5 多群发送和文件去重实际场景中很少只推一个群。我的做法是把每个群的 webhook 和 secret 放在一个groups.json配置里脚本循环发送{ management_group: { webhook: https://oapi.dingtalk.com/robot/send?access_tokenxxxx, secret: secxxxx }, operation_group: { webhook: https://oapi.dingtalk.com/robot/send?access_tokenyyyy, secret: secyyyy } }发送时遍历配置不同群发不同文案和附件灵活性很高。为了避免同一份文件重复推送刷屏我额外加了一个文件指纹记录用 MD5 计算文件哈希存到本地 JSON 文件里如果发现哈希没变直接跳过。对日报这种每天生成相同内容的场景这个去重能避免很多无用消息。import hashlib import json import os def file_md5(file_path: str) - str: h hashlib.md5() with open(file_path, rb) as f: for chunk in iter(lambda: f.read(8192), b): h.update(chunk) return h.hexdigest() def should_send(file_path: str, record_file: str last_hash.json) - bool: digest file_md5(file_path) records {} if os.path.exists(record_file): with open(record_file, r) as fp: records json.load(fp) if records.get(file_path) digest: return False records[file_path] digest with open(record_file, w) as fp: json.dump(records, fp, ensure_asciiFalse, indent2) return True这个方法对自动生成的报表特别友好。比如脚本每天生成一个文件但如果数据没更新导致文件内容完全一样就不会重复打扰群成员。5. 排坑手册我踩过的报错和安全注意事项5.1 高频错误与解决方法无论你是第一次用钉钉机器人还是已经跑了一段时间下面这些报错大概率会碰到。我整理了一份速查表错误码或提示出现原因解决办法errcode 310000keywords not in content机器人安全设置选了“自定义关键词”但消息文本不包含该关键词在文本中加上关键词或改为加签模式errcode 310000sign not match签名计算错误常见是时间戳用秒、未做URL编码、secret不对用本文的签名函数确保使用毫秒时间戳errcode 310000ip not in whitelist机器人安全设置限制了IP白名单当前服务器IP不在名单里添加服务器出口IP到白名单或删除IP限制errcode 163001企业内部应用权限不足或凭证无效检查 appKey/appSecret并在开发者后台补齐权限errcode 40035消息体参数格式错误严格参照 JSON 字段确认 msgtype 与对应内容结构匹配发送文件接口返回 media upload 失败文件过大、格式不支持或上传并发超限压缩文件、减小体积或改用文件链接方式最常见的坑集中在 310000 这个错误码。很多人把 secret 和 webhook 里自带的 access_token 搞混签名时用错了密钥导致一直签名失败。我建议把所有密钥都放到环境变量或本地配置文件里脚本里只引用变量不写死明文。5.2 关于“钉盘容量不足”和临时素材期限有朋友用钉钉手动拖文件到群里提示“钉盘容量不足”这是因为普通群文件默认会占用群对应的钉盘空间空间满后无法继续上传。换成机器人自动发送后通过 media/upload 接口上传的是临时素材一般不会受个人钉盘空间限制但临时素材有有效期限过期后下载不了。所以如果是需要长期留存的正式文件我强烈建议同时把文件放到对象存储或公司共享盘钉钉群里发送下载链接即可。如果文件大小超过接口限制更合理的方式是先压缩再发送。我处理几百兆文件时的套路是用 Python 的 zipfile 打成压缩包再推链接既不卡接口也方便接收方一次拉取全部文件。5.3 机器人安全建议机器人的本质是一个“谁拿到地址谁就能发消息”的入口所以安全措施不是可选项而是必选项。我给自己的脚本定了几条规则webhook 和 secret 从不写进公共代码仓库用本地配置文件或环境变量管理生产环境全部使用加签模式并且机器人管理后台开启服务器 IP 白名单群内提醒语写明“本消息由系统自动发送如有问题联系管理员”避免同事们以为出了 bug 反复私聊。另外脚本每次发送后要把返回值打印到日志里。钉钉接口返回非零 errcode 时说明消息没有发送成功光靠群成员“怎么没收到”的反馈来发现问题那就太被动了。日志里记录请求时间、目标群、文件名、接口返回这些信息在出问题的时候能省下大量沟通成本。6. 从“发文件”到“文件自动化流水线”的进一步扩展跑通钉钉群文件自动发送之后你会发现整套逻辑完全可以延伸到更复杂的自动化场景。最典型的扩展是“文件生成 自动发送”的整体串联。比如每天晚上从数据库读取当天的销售明细用 pandas 进行汇总计算生成 Excel 和 PDF 两种格式的报表再调用这个脚本推到钉钉群。整个流程不需要任何人干预数据源更新后结果会自动出现在群里。更进一步还能把“监控文件夹 新文件自动推送”结合起来。用 watchdog 监听一个指定目录只要有新文件生成就自动发送到钉钉群。这个模式在测试环境特别有用测试人员把新构建的安装包放进目录脚本自动把安装包发送给测试群省掉手动上传的步骤。如果你想把脚本对接到更多企业协作工具还可以把发送函数重新封装成通知服务在业务系统生成对账单、告警日志、风险报告时统一调用让钉钉群成为所有自动化任务的消息出口。我个人在实际使用中的体会是这套方案最大的意义不是省下那几秒钟的手动发送而是把“文件分发”从一个人盯着的重复劳动变成一套有日志、有重试、有去重、有安全检查的稳定流程。机器人第一次在群里发出日报的时候我甚至有种“请了个不领工资的行政助理”的错觉。最后再分享一个实用技巧不要只盯着“发送成功”的返回。钉钉接口返回 errcode 为 0只代表消息被服务端接收了不代表群成员一定看到了。如果你的文件自动发送对时效要求很高建议在脚本里额外加一个“下班前检查今日日志”的小任务扫一下当天有没有发送失败的记录有就补发没有就安静结束。机器和人一样都会偶尔出问题但有日志和兜底机制就不会让意外变成事故。
返回列表