8
n8n 中文网amn8n.com

使用Python和AI的自定义天气邮件

中级

这是一个Personal Productivity, Multimodal AI领域的自动化工作流,包含 11 个节点。主要使用 Code, Gmail, FormTrigger, HttpRequest, Agent 等节点。 使用OpenWeatherMap、Python和GPT-4.1-mini生成个性化天气报告

前置要求
  • Google 账号和 Gmail API 凭证
  • 可能需要目标 API 的认证凭证
  • OpenAI API Key
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "BrYg4iQRGkBoypeu",
  "meta": {
    "instanceId": "bdc54da2c96840612a04bf3fd3a4cd97a7a7bd7c1152bbe41d5615f09311c097",
    "templateCredsSetupCompleted": true
  },
  "name": "使用 Python 和 AI 的自定义天气邮件",
  "tags": [],
  "nodes": [
    {
      "id": "822a0d12-4591-40d0-85f9-0c940372ed5f",
      "name": "获取天气数据",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -80,
        304
      ],
      "parameters": {
        "url": "https://api.openweathermap.org/data/2.5/weather",
        "options": {},
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "=q",
              "value": "={{ $json.City }}"
            },
            {
              "name": "appid",
              "value": "<your_API_key>"
            },
            {
              "name": "units",
              "value": "metric"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "99ddfbb3-c834-4017-9ec4-f32f870e1482",
      "name": "处理天气数据",
      "type": "n8n-nodes-base.code",
      "position": [
        32,
        496
      ],
      "parameters": {
        "language": "python",
        "pythonCode": "import json\nfrom datetime import datetime\nimport math\n\n# Get the weather data from the previous node\nweather_data = items[0]['json']\n\n# Extract relevant information\ncity = weather_data['name']\ncountry = weather_data['sys']['country']\ntemperature = weather_data['main']['temp']\nfeels_like = weather_data['main']['feels_like']\nhumidity = weather_data['main']['humidity']\npressure = weather_data['main']['pressure']\ndescription = weather_data['weather'][0]['description']\nwind_speed = weather_data['wind']['speed']\nvisibility = weather_data.get('visibility', 0) / 1000  # Convert to km\n\n# Calculate temperature in Fahrenheit\ntemp_fahrenheit = (temperature * 9/5) + 32\nfeels_like_fahrenheit = (feels_like * 9/5) + 32\n\n# Calculate wind speed in different units\nwind_speed_kmh = wind_speed * 3.6\nwind_speed_mph = wind_speed * 2.237\n\n# Determine comfort level based on temperature and humidity\ndef get_comfort_level(temp, humidity):\n    if temp < 0:\n        return \"Freezing\"\n    elif temp < 10:\n        return \"Cold\"\n    elif temp > 35:\n        return \"Very Hot\"\n    elif temp > 28:\n        return \"Hot\"\n    elif humidity > 80:\n        return \"Humid\"\n    elif 18 <= temp <= 24 and 40 <= humidity <= 60:\n        return \"Ideal\"\n    elif 15 <= temp <= 27 and humidity <= 70:\n        return \"Comfortable\"\n    else:\n        return \"Moderate\"\n\n# Get clothing suggestion\ndef get_clothing_suggestion(temp, wind, desc):\n    if \"rain\" in desc.lower() or \"drizzle\" in desc.lower():\n        clothing = \"Waterproof jacket and umbrella\"\n    elif \"snow\" in desc.lower():\n        clothing = \"Heavy winter coat, boots, gloves, and hat\"\n    elif temp < 0:\n        clothing = \"Heavy winter coat, thermal layers, gloves, and hat\"\n    elif temp < 10:\n        clothing = \"Warm jacket or heavy sweater\"\n    elif temp < 18:\n        clothing = \"Light jacket or cardigan\"\n    elif temp < 25:\n        clothing = \"Long sleeves or light sweater\"\n    elif temp < 30:\n        clothing = \"T-shirt and light pants/shorts\"\n    else:\n        clothing = \"Light, breathable clothing and sun protection\"\n    \n    if wind > 5:\n        clothing += \" (consider wind protection)\"\n    \n    return clothing\n\n# Get activity recommendation\ndef get_activity_recommendation(temp, desc, wind):\n    desc_lower = desc.lower()\n    \n    if \"thunderstorm\" in desc_lower or \"heavy rain\" in desc_lower:\n        return \"Stay indoors, perfect for indoor activities\"\n    elif \"rain\" in desc_lower or \"drizzle\" in desc_lower:\n        return \"Indoor activities recommended, or covered outdoor areas\"\n    elif \"snow\" in desc_lower:\n        return \"Winter sports, snowman building, or cozy indoor activities\"\n    elif temp < -5:\n        return \"Limit outdoor exposure, indoor activities recommended\"\n    elif temp < 5:\n        return \"Short outdoor activities, winter sports, or indoor activities\"\n    elif 15 <= temp <= 25 and wind < 7:\n        return \"Perfect for any outdoor activities - hiking, sports, picnics\"\n    elif temp > 30:\n        return \"Early morning or evening outdoor activities, stay hydrated\"\n    elif wind > 10:\n        return \"Be cautious with outdoor activities, secure loose items\"\n    else:\n        return \"Good for moderate outdoor activities\"\n\n# Determine weather emoji\ndef get_weather_emoji(desc):\n    desc_lower = desc.lower()\n    if \"clear\" in desc_lower:\n        return \"☀️\"\n    elif \"cloud\" in desc_lower:\n        return \"☁️\" if \"few\" not in desc_lower else \"⛅\"\n    elif \"rain\" in desc_lower:\n        return \"🌧️\"\n    elif \"thunderstorm\" in desc_lower:\n        return \"⛈️\"\n    elif \"snow\" in desc_lower:\n        return \"❄️\"\n    elif \"mist\" in desc_lower or \"fog\" in desc_lower:\n        return \"🌫️\"\n    else:\n        return \"🌤️\"\n\ncomfort_level = get_comfort_level(temperature, humidity)\nweather_emoji = get_weather_emoji(description)\n\n# Create a comprehensive weather report\nweather_report = {\n    \"location\": f\"{city}, {country}\",\n    \"timestamp\": datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\"),\n    \"weather_emoji\": weather_emoji,\n    \"current_conditions\": {\n        \"temperature_celsius\": round(temperature, 1),\n        \"temperature_fahrenheit\": round(temp_fahrenheit, 1),\n        \"feels_like_celsius\": round(feels_like, 1),\n        \"feels_like_fahrenheit\": round(feels_like_fahrenheit, 1),\n        \"description\": description.title(),\n        \"humidity\": humidity,\n        \"pressure\": pressure,\n        \"visibility_km\": round(visibility, 1),\n        \"wind_speed_ms\": round(wind_speed, 1),\n        \"wind_speed_kmh\": round(wind_speed_kmh, 1),\n        \"wind_speed_mph\": round(wind_speed_mph, 1)\n    },\n    \"analysis\": {\n        \"comfort_level\": comfort_level,\n        \"is_good_weather\": temperature >= 15 and temperature <= 25 and humidity <= 70 and \"rain\" not in description.lower(),\n        \"clothing_suggestion\": get_clothing_suggestion(temperature, wind_speed, description),\n        \"activity_recommendation\": get_activity_recommendation(temperature, description, wind_speed),\n        \"weather_quality_score\": min(100, max(0, \n            100 - abs(20 - temperature) * 3 - max(0, humidity - 60) - max(0, wind_speed - 5) * 5\n        ))\n    },\n    \"raw_data\": weather_data\n}\n\n# Return the processed data\nreturn [{\"json\": weather_report}]"
      },
      "typeVersion": 2
    },
    {
      "id": "0d850dd1-2930-407f-9f87-f418bc588f4a",
      "name": "生成邮件内容",
      "type": "n8n-nodes-base.code",
      "position": [
        160,
        320
      ],
      "parameters": {
        "language": "python",
        "pythonCode": "# Get the processed weather data\nweather_data = items[0]['json']\n\n# Create HTML email content\nhtml_content = f\"\"\"\n<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Weather Report</title>\n    <style>\n        body {{ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; padding: 20px; background-color: #f5f7fa; }}\n        .container {{ max-width: 600px; margin: 0 auto; background-color: white; border-radius: 15px; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.1); }}\n        .header {{ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; text-align: center; }}\n        .header h1 {{ margin: 0; font-size: 2em; }}\n        .location {{ font-size: 1.2em; margin-top: 10px; opacity: 0.9; }}\n        .section {{ padding: 25px; border-bottom: 1px solid #eee; }}\n        .section:last-child {{ border-bottom: none; }}\n        .section h2 {{ color: #2c3e50; margin-top: 0; display: flex; align-items: center; gap: 10px; }}\n        .temp-main {{ font-size: 3em; font-weight: bold; color: #e74c3c; text-align: center; margin: 20px 0; }}\n        .temp-feels {{ font-size: 1.2em; color: #7f8c8d; text-align: center; margin-bottom: 20px; }}\n        .conditions-grid {{ display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin: 20px 0; }}\n        .condition-item {{ background-color: #f8f9fa; padding: 15px; border-radius: 8px; text-align: center; }}\n        .condition-value {{ font-size: 1.4em; font-weight: bold; color: #2c3e50; }}\n        .condition-label {{ font-size: 0.9em; color: #7f8c8d; margin-top: 5px; }}\n        .comfort-badge {{ display: inline-block; padding: 8px 16px; border-radius: 20px; font-weight: bold; text-transform: uppercase; font-size: 0.9em; }}\n        .comfort-ideal {{ background-color: #d4edda; color: #155724; }}\n        .comfort-comfortable {{ background-color: #cce5ff; color: #004085; }}\n        .comfort-moderate {{ background-color: #fff3cd; color: #856404; }}\n        .comfort-hot {{ background-color: #f8d7da; color: #721c24; }}\n        .comfort-cold {{ background-color: #e2e3e5; color: #383d41; }}\n        .recommendation {{ background-color: #e8f5e8; padding: 20px; border-radius: 10px; margin: 15px 0; border-left: 4px solid #28a745; }}\n        .score {{ text-align: center; margin: 20px 0; }}\n        .score-circle {{ display: inline-block; width: 80px; height: 80px; border-radius: 50%; background: conic-gradient(#28a745 0deg {weather_data['analysis']['weather_quality_score'] * 3.6}deg, #e9ecef {weather_data['analysis']['weather_quality_score'] * 3.6}deg 360deg); position: relative; }}\n        .score-inner {{ position: absolute; top: 10px; left: 10px; width: 60px; height: 60px; border-radius: 50%; background-color: white; display: flex; align-items: center; justify-content: center; font-weight: bold; color: #2c3e50; }}\n        .footer {{ background-color: #f8f9fa; padding: 20px; text-align: center; font-size: 0.9em; color: #6c757d; }}\n    </style>\n</head>\n<body>\n    <div class=\"container\">\n        <div class=\"header\">\n            <h1>{weather_data['weather_emoji']} Weather Report</h1>\n            <div class=\"location\">{weather_data['location']}</div>\n            <div style=\"font-size: 0.9em; margin-top: 5px;\">{weather_data['timestamp']}</div>\n        </div>\n        \n        <div class=\"section\">\n            <div class=\"temp-main\">{weather_data['current_conditions']['temperature_celsius']}°C</div>\n            <div class=\"temp-feels\">Feels like {weather_data['current_conditions']['feels_like_celsius']}°C</div>\n            <div style=\"text-align: center; font-size: 1.3em; color: #495057; margin-bottom: 20px;\">\n                {weather_data['current_conditions']['description']}\n            </div>\n            \n            <div style=\"text-align: center; margin: 20px 0;\">\n                <span class=\"comfort-badge comfort-{weather_data['analysis']['comfort_level'].lower().replace(' ', '-')}\">\n                    {weather_data['analysis']['comfort_level']}\n                </span>\n            </div>\n        </div>\n        \n        <div class=\"section\">\n            <h2>📊 Current Conditions</h2>\n            <div class=\"conditions-grid\">\n                <div class=\"condition-item\">\n                    <div class=\"condition-value\">{weather_data['current_conditions']['humidity']}%</div>\n                    <div class=\"condition-label\">Humidity</div>\n                </div>\n                <div class=\"condition-item\">\n                    <div class=\"condition-value\">{weather_data['current_conditions']['pressure']}</div>\n                    <div class=\"condition-label\">Pressure (hPa)</div>\n                </div>\n                <div class=\"condition-item\">\n                    <div class=\"condition-value\">{weather_data['current_conditions']['wind_speed_kmh']}</div>\n                    <div class=\"condition-label\">Wind (km/h)</div>\n                </div>\n                <div class=\"condition-item\">\n                    <div class=\"condition-value\">{weather_data['current_conditions']['visibility_km']}</div>\n                    <div class=\"condition-label\">Visibility (km)</div>\n                </div>\n            </div>\n        </div>\n        \n        <div class=\"section\">\n            <h2>🎯 Recommendations</h2>\n            <div class=\"recommendation\">\n                <strong>👕 What to Wear:</strong><br>\n                {weather_data['analysis']['clothing_suggestion']}\n            </div>\n            <div class=\"recommendation\">\n                <strong>🏃 Activities:</strong><br>\n                {weather_data['analysis']['activity_recommendation']}\n            </div>\n        </div>\n        \n        <div class=\"section\">\n            <h2>⭐ Weather Quality Score</h2>\n            <div class=\"score\">\n                <div class=\"score-circle\">\n                    <div class=\"score-inner\">{int(weather_data['analysis']['weather_quality_score'])}</div>\n                </div>\n                <div style=\"margin-top: 10px; color: #6c757d;\">Out of 100</div>\n            </div>\n        </div>\n        \n        <div class=\"footer\">\n            <strong>🤖 Generated by n8n Weather Pipeline</strong><br>\n            Powered by Python • OpenWeatherMap API<br>\n            <em>Stay informed, stay prepared!</em>\n        </div>\n    </div>\n</body>\n</html>\n\"\"\"\n\n# Create plain text version\ntext_content = f\"\"\"\n🌤️ DAILY WEATHER REPORT\n\n📍 Location: {weather_data['location']}\n🕒 Time: {weather_data['timestamp']}\n\n🌡️ CURRENT CONDITIONS\n- Temperature: {weather_data['current_conditions']['temperature_celsius']}°C ({weather_data['current_conditions']['temperature_fahrenheit']}°F)\n- Feels Like: {weather_data['current_conditions']['feels_like_celsius']}°C ({weather_data['current_conditions']['feels_like_fahrenheit']}°F)\n- Condition: {weather_data['current_conditions']['description']}\n- Humidity: {weather_data['current_conditions']['humidity']}%\n- Wind Speed: {weather_data['current_conditions']['wind_speed_kmh']} km/h\n- Pressure: {weather_data['current_conditions']['pressure']} hPa\n- Visibility: {weather_data['current_conditions']['visibility_km']} km\n\n💡 ANALYSIS\n- Comfort Level: {weather_data['analysis']['comfort_level']}\n- Weather Quality Score: {int(weather_data['analysis']['weather_quality_score'])}/100\n\n👕 CLOTHING RECOMMENDATION\n{weather_data['analysis']['clothing_suggestion']}\n\n🏃 ACTIVITY RECOMMENDATION\n{weather_data['analysis']['activity_recommendation']}\n\n---\nGenerated by n8n Weather Pipeline\nPowered by Python & OpenWeatherMap API\n\"\"\"\n\n# Return the email content\nreturn [{\n    \"json\": {\n        \"subject\": f\"{weather_data['weather_emoji']} Weather Report - {weather_data['location']} ({weather_data['current_conditions']['temperature_celsius']}°C)\",\n        \"html_content\": html_content,\n        \"text_content\": text_content,\n        \"weather_data\": weather_data\n    }\n}]"
      },
      "typeVersion": 2
    },
    {
      "id": "22cbe6d0-2360-4039-a7b5-fc242c3061ff",
      "name": "发送消息",
      "type": "n8n-nodes-base.gmail",
      "position": [
        800,
        304
      ],
      "webhookId": "0fa8e721-11fd-4a08-b150-d6b1c58a59f3",
      "parameters": {
        "sendTo": "emailAddress",
        "message": "={{ $json.output }}",
        "options": {},
        "subject": "={{ $('Generate Email Content').item.json.subject }}"
      },
      "typeVersion": 2.1
    },
    {
      "id": "0c5c9ca9-ec89-4420-ad8c-0a96ccb2b5b5",
      "name": "AI 代理",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        384,
        304
      ],
      "parameters": {
        "text": "=You are an email drafting expert. Take the weather information from {{ $json.text_content }} and make it pretty and easy to read. Use proper spacing, use of paragraphs and bullet points.\n\nPrepare your email in html format.\n\nAdd a joke relevant to the weather if possible. ",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 2.1
    },
    {
      "id": "1557d3ed-d6fa-4542-ab01-961873998ca4",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        432,
        512
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "eccbbc05-da0c-40b9-8954-3f0fadb0640f",
      "name": "表单提交时",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        -320,
        304
      ],
      "webhookId": "28ccc6a1-25c4-4f3c-9358-4365285f7cdf",
      "parameters": {
        "options": {},
        "formTitle": "Enter a City",
        "formFields": {
          "values": [
            {
              "fieldLabel": "City",
              "placeholder": "New York"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "52859605-1f9d-4e87-a722-920ac296095b",
      "name": "便签",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -400,
        192
      ],
      "parameters": {
        "height": 304,
        "content": "在表单中输入城市名称"
      },
      "typeVersion": 1
    },
    {
      "id": "bcd1c3e0-ee2d-479d-9fc4-1d391d20c0e9",
      "name": "便签1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -144,
        176
      ],
      "parameters": {
        "width": 480,
        "height": 464,
        "content": "添加 OpenWeather API 密钥以获取城市的当前天气信息,将 <your_API_key> 替换为您的实际 API 密钥"
      },
      "typeVersion": 1
    },
    {
      "id": "49c4cdfb-13ea-41fa-90bc-7ced8147fc19",
      "name": "便签2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        352,
        176
      ],
      "parameters": {
        "width": 352,
        "height": 480,
        "content": "添加 OpenAI API 密钥。AI 代理将为您输入的城市添加一个关于天气的自定义笑话作为邮件内容的一部分"
      },
      "typeVersion": 1
    },
    {
      "id": "46ed9532-4693-4740-819f-81a1ddf51ca1",
      "name": "便签3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        720,
        176
      ],
      "parameters": {
        "height": 304,
        "content": "添加您的 Gmail 凭据并更新邮件标题和正文。节点将向收件人发送自定义邮件"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "83a69a4e-f4cd-472f-812e-1492a06ddbc5",
  "connections": {
    "AI Agent": {
      "main": [
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Weather Data": {
      "main": [
        [
          {
            "node": "Process Weather Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "On form submission": {
      "main": [
        [
          {
            "node": "Fetch Weather Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Weather Data": {
      "main": [
        [
          {
            "node": "Generate Email Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Email Content": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

中级 - 个人效率, 多模态 AI

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
中级
节点数量11
分类2
节点类型7
难度说明

适合有一定经验的用户,包含 6-15 个节点的中等复杂度工作流

外部链接
在 n8n.io 查看

分享此工作流