8
n8n 中文网amn8n.com

自动同步本地事件到Google日历

中级

这是一个AI领域的自动化工作流,包含 11 个节点。主要使用 Code, Html, HttpRequest, GoogleCalendar, ScheduleTrigger 等节点,结合人工智能技术实现智能自动化。 使用n8n自动同步本地事件到Google日历

前置要求
  • 可能需要目标 API 的认证凭证
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "y1ZOHX6Zq13C68dP",
  "meta": {
    "instanceId": "60046904b104f0f72b2629a9d88fe9f676be4035769f1f08dad1dd38a76b9480"
  },
  "name": "自动同步本地事件到Google日历",
  "tags": [],
  "nodes": [
    {
      "id": "433e4192-599f-4f3d-a251-651b81db4960",
      "name": "每日事件同步触发器",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -2740,
        220
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 8
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "94f20c26-9a90-43ba-8ab1-b1107c17345c",
      "name": "获取事件页面(Bright Data)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -2520,
        220
      ],
      "parameters": {
        "url": "https://api.brightdata.com/request",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "sendHeaders": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "zone",
              "value": "n8n_unblocker"
            },
            {
              "name": "url",
              "value": "https://www.nypl.org/events/calendar"
            },
            {
              "name": "country",
              "value": "us"
            },
            {
              "name": "format",
              "value": "raw"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer API_KEY"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "e55c8584-f673-4201-bda7-42cdbadf49bf",
      "name": "提取事件数据(HTML解析器)",
      "type": "n8n-nodes-base.html",
      "position": [
        -2220,
        220
      ],
      "parameters": {
        "options": {},
        "operation": "extractHtmlContent",
        "extractionValues": {
          "values": [
            {
              "key": "Title",
              "cssSelector": ".event-title",
              "returnArray": true
            },
            {
              "key": "Location",
              "cssSelector": ".event-location",
              "returnArray": true
            },
            {
              "key": "Audience",
              "cssSelector": ".event-audience",
              "returnArray": true
            },
            {
              "key": "Time",
              "cssSelector": ".event-time",
              "returnArray": true
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "48196549-0378-4cd9-8e1d-f289c4b50180",
      "name": "清理和格式化事件数据",
      "type": "n8n-nodes-base.code",
      "position": [
        -2000,
        220
      ],
      "parameters": {
        "jsCode": "const data = items[0].json;\n\n// Extract arrays\nconst titles = data.Title || [];\nconst locations = data.Location || [];\nconst audiences = data.Audience || [];\nconst rawTimes = data.Time || [];\n\n// Step 1: Remove invalid \"time\" placeholders\nconst invalidTimeLabels = [\"Date/Time\", \"Title/Description\", \"Location\", \"Audience\"];\nconst times = rawTimes.filter(time => !invalidTimeLabels.includes(time.trim()));\n\n// Step 2: Safely calculate number of valid events\nconst eventCount = Math.min(titles.length, locations.length, audiences.length, times.length);\n\n// Helper: Convert \"Today @ 10 AM\" → ISO string with timezone\nfunction parseTimeToISO(rawTime) {\n  const match = rawTime.match(/@ ([0-9]{1,2})(?::([0-9]{2}))?\\s?(AM|PM)/i);\n  const now = new Date();\n\n  if (!match) return null;\n\n  let hour = parseInt(match[1]);\n  const minute = match[2] ? parseInt(match[2]) : 0;\n  const meridian = match[3].toUpperCase();\n\n  if (meridian === \"PM\" && hour !== 12) hour += 12;\n  if (meridian === \"AM\" && hour === 12) hour = 0;\n\n  const start = new Date(now.getFullYear(), now.getMonth(), now.getDate(), hour, minute);\n  const end = new Date(start.getTime() + 60 * 60 * 1000); // 1-hour event\n\n  const offsetMinutes = start.getTimezoneOffset();\n  const offsetHours = Math.floor(Math.abs(offsetMinutes) / 60);\n  const offsetMins = Math.abs(offsetMinutes) % 60;\n  const offsetSign = offsetMinutes > 0 ? \"-\" : \"+\";\n  const offset = `${offsetSign}${String(offsetHours).padStart(2, \"0\")}:${String(offsetMins).padStart(2, \"0\")}`;\n\n  const toISOStringWithOffset = (d) => {\n    const pad = (n) => n.toString().padStart(2, \"0\");\n    return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:00${offset}`;\n  };\n\n  return {\n    start: toISOStringWithOffset(start),\n    end: toISOStringWithOffset(end),\n  };\n}\n\n// Step 3: Build cleaned and enriched event objects\nconst results = [];\n\nfor (let i = 0; i < eventCount; i++) {\n  const titleText = titles[i];\n  const timeText = times[i]?.trim();\n  const parsedTime = parseTimeToISO(timeText);\n\n  results.push({\n    json: {\n      title: titleText.split('\\n')[0]?.trim(),\n      description: titleText.trim(),\n      location: locations[i]?.trim(),\n      audience: audiences[i]?.trim(),\n      time: timeText,\n      start: { dateTime: parsedTime?.start || null },\n      end: { dateTime: parsedTime?.end || null },\n      sourceUrl: \"https://www.nypl.org\" + (titleText.match(/\\[([^\\]]+)\\]/)?.[1] || '')\n    }\n  });\n}\n\nreturn results;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "945e912a-609a-42e0-82a4-c3d36623a767",
      "name": "创建Google日历事件",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        -1700,
        220
      ],
      "parameters": {
        "end": "={{ $json.end.dateTime }}",
        "start": "={{ $json.start.dateTime }}",
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "f14d18c7802fe01f77d5200ada9658c96cbc69b3cb9ff7b2914dc63bf6f263e3@group.calendar.google.com",
          "cachedResultName": "Community Events"
        },
        "additionalFields": {
          "attendees": [],
          "description": "=Title: {{ $json.title }}\nDescription: {{ $json.description }}\nLocation: {{ $json.location }}\nAudience: {{ $json.audience }}\nTime: {{ $json.time }}"
        }
      },
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "ZiXaTJAXCcyzY5iy",
          "name": "Google Calendar account"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "6c5825a7-6a1e-485c-820b-ed4188c6b718",
      "name": "便签",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2800,
        -540
      ],
      "parameters": {
        "color": 5,
        "width": 440,
        "height": 980,
        "content": "## 🧭 **第一部分:事件数据获取器**"
      },
      "typeVersion": 1
    },
    {
      "id": "aa5624b4-6c2e-4d70-a05b-4e038ee82dfb",
      "name": "便签1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2300,
        -660
      ],
      "parameters": {
        "color": 3,
        "width": 440,
        "height": 1100,
        "content": "## 🧩 **第二部分:智能事件提取器**"
      },
      "typeVersion": 1
    },
    {
      "id": "b19c44ba-1abb-4d0b-a854-1d844163ffdf",
      "name": "便签2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1800,
        -240
      ],
      "parameters": {
        "color": 6,
        "width": 320,
        "height": 680,
        "content": "## 📅 **第三部分:自动日历创建器**"
      },
      "typeVersion": 1
    },
    {
      "id": "85488a2c-a51a-4561-8f7d-44901a3a04a1",
      "name": "便签9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4600,
        -500
      ],
      "parameters": {
        "color": 4,
        "width": 1300,
        "height": 320,
        "content": "======================================="
      },
      "typeVersion": 1
    },
    {
      "id": "2395bf91-efa9-4149-b411-38d18924c5c5",
      "name": "便签4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4600,
        -160
      ],
      "parameters": {
        "color": 4,
        "width": 1289,
        "height": 2298,
        "content": "# **📅 自动同步本地事件到Google日历**"
      },
      "typeVersion": 1
    },
    {
      "id": "f4286f93-9457-4042-9b4d-bbb8304eaac1",
      "name": "便签5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1420,
        -240
      ],
      "parameters": {
        "color": 7,
        "width": 380,
        "height": 240,
        "content": "## 如果您通过此链接加入Bright Data,我将获得少量佣金——感谢您支持更多免费内容!"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "69086b47-9272-4887-8844-db532999a0ba",
  "connections": {
    "Daily Event Sync Trigger": {
      "main": [
        [
          {
            "node": "Fetch Event Page (Bright Data)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clean & Format Event Data": {
      "main": [
        [
          {
            "node": "Create Google Calendar Events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Event Page (Bright Data)": {
      "main": [
        [
          {
            "node": "Extract Event Data (HTML Parser)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Event Data (HTML Parser)": {
      "main": [
        [
          {
            "node": "Clean & Format Event Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

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

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

中级 - 人工智能

需要付费吗?

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

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

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

作者
Yaron Been

Yaron Been

@yaron-nofluff

Building AI Agents and Automations | Growth Marketer | Entrepreneur | Book Author & Podcast Host If you need any help with Automations, feel free to reach out via linkedin: https://www.linkedin.com/in/yaronbeen/ And check out my Youtube channel: https://www.youtube.com/@YaronBeen/videos

外部链接
在 n8n.io 查看

分享此工作流