不止是@所有人:用Python玩转企业微信机器人的Markdown、图文和文件消息

企业微信机器人早已超越了简单的文本通知时代。想象一下:凌晨三点服务器崩溃时,自动推送带高亮错误日志的Markdown报告;每日晨会前,系统准时发送整合了KPI图表和数据分析的图文简报;项目结项时,一键生成PDF文档并分发给所有相关成员——这些场景正在通过Python与企业微信机器人的深度结合变为现实。

对于已经掌握基础文本消息推送的开发者而言,解锁富媒体消息能力意味着将团队协作效率提升到全新维度。本文将深入解析企业微信机器人支持的四大高级消息类型,并通过可立即复用的Python代码示例,带您实现从监控报警到自动报告的全场景覆盖。

1. 环境准备与基础配置

在开始发送富媒体消息前,需要完成两个关键步骤:创建企业微信机器人和配置Python环境。不同于简单的文本消息,富媒体消息对数据格式和接口调用有更严格的要求。

首先登录企业微信管理后台,在「应用管理」中创建自定义机器人。获取webhook地址时需注意:

https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

这个包含32位字符的key是后续所有API调用的通行证。为安全起见,建议将其存储在环境变量中:

import os
WEBHOOK_KEY = os.getenv('WECHAT_WORK_BOT_KEY')
WEBHOOK_URL = f"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key={WEBHOOK_KEY}"

Python环境需要安装requests库处理HTTP请求,推荐使用2.28+版本以获得更稳定的文件上传支持:

pip install requests>=2.28.0

验证基础连接可用性时,可以发送一个简单的文本消息测试通道:

def test_connection():
    payload = {
        "msgtype": "text",
        "text": {"content": "通道测试", "mentioned_list": []}
    }
    response = requests.post(WEBHOOK_URL, json=payload)
    return response.status_code == 200

2. Markdown消息:专业报告的最佳载体

企业微信支持标准的Markdown语法(GFM风格),这让技术报告的呈现变得异常简单。以下是一个包含多级标题、代码块和表格的监控报警示例:

def send_markdown_report():
    content = """
# [紧急] 数据库主库CPU告警
> 发生时间: {timestamp}

## 当前指标
| 指标项 | 阈值 | 当前值 | 超出 |
|--------|------|--------|------|
| CPU使用率 | 80% | 92% | ✔ |
| 连接数 | 500 | 623 | ✔ |

## 最近错误日志
```sql
ERROR 1040 (HY000): Too many connections

建议操作

  1. 立即检查慢查询

  2. 考虑增加连接池大小

  3. 通知DBA团队 """.format(timestamp=datetime.now().strftime('%Y-%m-%d %H:%M:%S'))

    payload = { "msgtype": "markdown", "markdown": {"content": content} } requests.post(WEBHOOK_URL, json=payload)


实际效果中,Markdown消息会自动转换为适合移动端阅读的格式,并保留所有关键样式元素。几个实用技巧:

* 使用`<font color="warning">`标签实现彩色文本(支持red/green/blue等颜色值)
* 通过`> `引用块突出关键信息
* 表格列宽会自动调整,建议控制在5列以内

对于需要强调的消息,可以结合@提醒功能:

```python
payload["markdown"]["content"] += "\n<@%s>" % "userid1|userid2"

3. 图文与文件消息:自动化报告解决方案

当简单的Markdown无法满足需求时,图文消息(news)和文件消息(file)提供了更专业的解决方案。以下是发送日报图文卡片的完整实现:

def send_daily_report():
    # 先上传本地图片获取media_id
    image_url = "https://example.com/report_20230815.png"
    image_resp = requests.get(image_url)
    
    upload_url = WEBHOOK_URL.replace("send", "upload_media?type=image")
    files = {"media": ("report.png", image_resp.content)}
    media_info = requests.post(upload_url, files=files).json()
    
    # 构建图文消息
    articles = [{
        "title": "8月15日运营日报",
        "description": "核心指标趋势与关键事件汇总",
        "url": "https://bi.example.com/daily/20230815",
        "picurl": media_info['url']
    }]
    
    payload = {
        "msgtype": "news",
        "news": {"articles": articles}
    }
    requests.post(WEBHOOK_URL, json=payload)

文件消息的发送略有不同,需要先通过API上传文件获取media_id:

def send_excel_report(filepath):
    upload_url = WEBHOOK_URL.replace("send", "upload_media?type=file")
    
    with open(filepath, "rb") as f:
        files = {"media": (os.path.basename(filepath), f)}
        media_info = requests.post(upload_url, files=files).json()
    
    payload = {
        "msgtype": "file",
        "file": {"media_id": media_info['media_id']}
    }
    requests.post(WEBHOOK_URL, json=payload)

关键限制说明

  • 图片建议尺寸:1200x720像素,大小不超过2MB
  • 文件大小上限:20MB
  • media_id有效期:3天

4. 实战组合:构建智能通知系统

将各种消息类型与自动化场景结合,可以创造出强大的工作流。以下是几个典型用例的实现方案:

场景一:服务器监控看板(定时任务)

def server_monitor_dashboard():
    # 获取监控数据
    cpu_usage = get_cpu_usage()
    memory_usage = get_memory_usage()
    
    # 生成Markdown内容
    status = "正常" if cpu_usage < 80 else "警告"
    content = f"""
## 服务器资源监控 {status}
**更新时间**: {datetime.now().strftime('%H:%M')}

```bash
CPU:  {'█' * int(cpu_usage/10)} {cpu_usage}%
内存: {'█' * int(memory_usage/10)} {memory_usage}%

阈值提醒:CPU >80% 或 内存 >90% 时自动通知运维 """

# 添加截图附件
screenshot = generate_screenshot()
send_markdown(content)
send_image(screenshot)

**场景二:自动化测试报告(事件触发)**

```python
def handle_test_result(test_run):
    if test_run.failed_count > 0:
        content = f"""
# 测试失败告警 ❌
**构建编号**: #{test_run.build_number}

**失败用例**:
{'\n'.join(f'- {case.name}' for case in test_run.failed_cases)}

[查看详细报告]({test_run.report_url})
"""
        send_markdown(content)
        send_file(test_run.log_file)
    else:
        send_markdown(f"✅ 所有测试通过 (共{test_run.total_cases}个用例)")

场景三:项目进度日报(定时+人工触发)

def generate_project_report(project_id, manual_trigger=False):
    report = generate_project_metrics(project_id)
    chart = render_burndown_chart(project_id)
    
    articles = [{
        "title": f"{report['name']} 进度日报",
        "description": f"完成度: {report['progress']}% | 剩余: {report['remaining']}天",
        "url": report['detail_url'],
        "picurl": chart
    }]
    
    if manual_trigger:
        articles[0]["title"] += " (手动生成)"
    
    send_news(articles)

5. 高级技巧与异常处理

在实际企业环境中使用时,有几个关键问题需要注意:

消息频率限制

  • 相同内容每分钟最多发送20次
  • 机器人每分钟最多发送30条消息
  • 建议实现简单的消息队列控制:
from collections import deque
from time import sleep

message_queue = deque(maxlen=30)

def safe_send(payload):
    now = time.time()
    if len(message_queue) >= 30 and now - message_queue[0] < 60:
        sleep(60 - (now - message_queue[0]))
    
    response = requests.post(WEBHOOK_URL, json=payload)
    message_queue.append(time.time())
    return response

文件上传优化: 大文件上传时可能超时,建议实现分块上传:

def chunked_upload(filepath, chunk_size=5*1024*1024):
    upload_url = WEBHOOK_URL.replace("send", "upload_media?type=file")
    file_id = None
    
    with open(filepath, 'rb') as f:
        while True:
            chunk = f.read(chunk_size)
            if not chunk:
                break
                
            files = {'media': (os.path.basename(filepath), chunk)}
            params = {'file_id': file_id} if file_id else {}
            resp = requests.post(upload_url, files=files, params=params)
            file_id = resp.json().get('media_id')
    
    return file_id

错误处理最佳实践: 企业微信API可能返回各种错误代码,需要合理处理:

ERROR_CODES = {
    40001: "无效的Webhook地址",
    40002: "消息内容为空",
    40003: "消息内容超过限制",
    40004: "无效的媒体文件",
    40005: "文件类型不支持"
}

def robust_send(payload):
    try:
        response = requests.post(WEBHOOK_URL, json=payload, timeout=10)
        result = response.json()
        
        if result.get('errcode') != 0:
            msg = ERROR_CODES.get(result['errcode'], "未知错误")
            raise Exception(f"API错误 {result['errcode']}: {msg}")
            
        return True
    except requests.exceptions.RequestException as e:
        logging.error(f"网络错误: {str(e)}")
        return False

在企业级应用中,建议将消息发送封装为独立服务,并实现以下增强功能:

  • 消息模板管理
  • 发送频率监控
  • 失败自动重试机制
  • 消息到达率统计
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐