使用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)可能需要您自行付费。
相关工作流推荐
每日生日庆祝(代码节点最终版本)
基于NASA图片、GPT-4、Gmail和Slack的自动化太空主题生日邮件
If
Code
Gmail
+7
17 节点Yanagi Chinatsu
个人效率
{模板} ATS优化简历和求职信生成器
使用Gemini AI和PDF.co优化简历并生成求职信
If
Set
Code
+10
23 节点Paul Abraham
个人效率
使用Airtop、GPT-4 Mini和Gmail分析网站UX和SEO质量
使用Airtop、GPT-4 Mini和Gmail分析网站UX和SEO质量
Set
Code
Html
+10
33 节点LukaszB
内容创作
Apollo 数据抓取与触达流程 1 ✅
使用 Apollo、AI 解析和定时邮件跟进自动生成潜在客户
If
Code
Wait
+13
39 节点Deniz
内容创作
基于RapidAPI、Hunter.io、GPT和Gmail的自动化B2B潜在客户挖掘
基于RapidAPI、Hunter.io、GPT和Gmail的自动化B2B潜在客户挖掘
If
Set
Code
+10
21 节点Jimmy Gay
潜在客户开发
使用Gmail、GPT-4和向量知识库的自动化客户支持系统
使用Gmail、GPT-4和向量知识库的自动化客户支持系统
If
Set
Code
+15
32 节点Khair Ahammed
AI RAG 检索增强