// Gotowe workflow

YouTube RSS

▸ // rozdziały lekcji

YouTube RSS - Wolniś: „Nowe filmy bez scrollowania”

Cały workflow na kanwie po uruchomieniu, z liczbami items na połączeniach

Subskrypcje na YouTube przewija algorytm, nie ty. Śledzisz 20 kanałów z branży, a połowę nowych filmów zobaczysz za tydzień albo wcale. Oficjalne YouTube Data API wymaga projektu w Google Cloud, klucza i pilnowania limitów.

Ten workflow omija cały ten cyrk. Każdy kanał YouTube ma publiczny kanał RSS w formacie XML, dostępny bez logowania i bez klucza. n8n czyta go codziennie o 7:40 i dopisuje do tabeli tylko filmy, których jeszcze nie widział.

W teście kanał Google for Developers pokazał w RSS 15 filmów. Z ostatnich 14 dni było 5 i te trafiły do tabeli.

Co robi ten workflow

  1. Codziennie 7:40 - harmonogram.
  2. Przygotuj tabele - Data Table z operacją Create zakłada tabelę yt_filmy z kolumnami video_id, tytul, link, kanal, opublikowano, opis i dodano. Jeśli tabela już jest, używa istniejącej.
  3. Kanaly YouTube - Edit Fields z listą ID kanałów w polu kanaly i liczbą dni wstecz w dni_wstecz (14).
  4. Rozbij kanaly - Split Out, jeden item na kanał, ID w polu channel_id.
  5. Pobierz kanal RSS - HTTP Request na adres kanału RSS danego kanału.
  6. XML na JSON - węzeł XML zamienia odpowiedź na JSON.
  7. Lista filmow - Edit Fields wyciąga listę filmów i nazwę kanału.
  8. Rozbij filmy - Split Out, jeden item na film, z nazwą kanału dołączoną do każdego.
  9. Dane filmu - Edit Fields wybiera ID filmu, tytuł, link, kanał, datę i opis.
  10. Tylko swieze - Filter przepuszcza filmy z ostatnich dni_wstecz dni.
  11. Czy nowy film? - Row Not Exists po video_id.
  12. Zapisz film - Insert do tabeli.

Czego potrzebujesz

  • ID kanałów YouTube, które chcesz śledzić.

Tyle. Bez klucza API, bez konta Google, bez poświadczeń w n8n.

Skąd wziąć ID kanału

ID kanału zaczyna się od „UC", na przykład UC_x5XG1OV2P6uZZ5FSM9Ttw (Google for Developers, ten jest w pliku). Najprościej skopiujesz je z YouTube:

  1. Otwórz stronę kanału i kliknij ...więcej przy opisie kanału.
  2. W oknie przewiń do sekcji Więcej informacji i kliknij Udostępnij kanał.
  3. Wybierz Kopiuj identyfikator kanału.

Okno informacji o kanale Google for Developers na YouTube - przycisk Udostępnij kanał z opcją Kopiuj identyfikator kanału

Haczyk. W starszych poradnikach przeczytasz, żeby szukać channelId w źródle strony. W obecnym kodzie YouTube tego tekstu już nie ma. Jeśli wolisz źródło strony, szukaj channel_id= - stoi w linku do kanału RSS, zaraz przed ID.

Jak uruchomić

  1. Zaimportuj plik z dołu lekcji (jak importować workflow).
  2. W węźle Kanaly YouTube wpisz swoje ID w polu kanaly: tablica w nawiasach kwadratowych, każde ID w cudzysłowie, oddzielone przecinkami.
  3. Ustaw dni_wstecz, jeśli 14 dni ci nie pasuje.
  4. Kliknij Execute workflow.
  5. Zajrzyj do tabeli: Overview > zakładka Data tables > yt_filmy.
  6. Kliknij Publish, żeby harmonogram ruszył (interfejs i publikacja).

Kanał RSS i XML

Adres kanału RSS składa się z jednego parametru:

https://www.youtube.com/feeds/videos.xml?channel_id={{ $json.channel_id }}

YouTube oddaje XML. Węzeł XML na JSON zamienia go na strukturę, w której filmy siedzą w feed.entry, a nazwa kanału w feed.title.

W węźle Lista filmow stoi wyrażenie:

{{ [].concat($json.feed.entry || []) }}

Po co takie kombinacje? Węzeł XML przy jednym elemencie zwraca obiekt, a przy kilku listę. Kanał z jednym filmem dałby obiekt i Split Out nie miałby czego rozbić. [].concat(...) zawsze robi z tego listę: z obiektu listę jednoelementową, z listy tę samą listę. || [] łapie kanał bez żadnego filmu.

Haczyk. Kanał RSS YouTube pokazuje tylko ostatnie 15 filmów kanału. Jeśli kanał wrzuca kilka filmów dziennie, a workflow nie chodził przez tydzień, część filmów przepadnie. Przy codziennym uruchomieniu i zwykłym kanale ten limit nie przeszkadza.

Pola z dwukropkiem w nazwie

XML YouTube ma pola z przedrostkami, na przykład yt:videoId albo media:group. Zapis z kropką na nich nie działa, bo dwukropek nie może stać w nazwie po kropce. Dlatego wyrażenia w Dane filmu używają nawiasów kwadratowych:

{{ $json.film['yt:videoId'] }}
{{ ($json.film['media:group']['media:description'] || '').substring(0, 1950) }}

Opis filmu siedzi dwa poziomy niżej, w media:group > media:description, i jest przycinany do 1950 znaków. To limit z poprzedniej wersji tej lekcji, w której filmy trafiały do Notiona, a ten przyjmuje do 2000 znaków w polu tekstowym. Czy kolumna tekstowa w Data Table ma własny limit długości, nie sprawdzałem, dlatego przycięcie zostało. Możesz je podnieść albo usunąć na własną rękę.

Poprzednia wersja miała tu błąd: substring(1, 1950) ucinał pierwszą literę każdego opisu, bo liczenie znaków zaczyna się od zera. Teraz stoi substring(0, 1950).

Węzeł Dane filmu - pola video_id i opis z wyrażeniami w nawiasach kwadratowych i podglądem wyniku

Dwa sita, jak w czytniku RSS

Tylko swieze porównuje datę publikacji filmu z datą sprzed dni_wstecz dni. Czy nowy film? to Row Not Exists po kolumnie video_id: przepuszcza tylko filmy, których ID nie ma jeszcze w tabeli. Drugie uruchomienie nie dopisuje duplikatów.

Tabela zakłada się sama, bo węzeł Przygotuj tabele ma włączoną opcję Reuse Existing Tables. Bez niej drugie uruchomienie kończy się błędem, że tabela o tej nazwie już istnieje. Szczegóły są w lekcji o czytniku RSS.

Pro-tip. Węzeł Pobierz kanal RSS ma w ustawieniach On Error na Continue. Jedno złe ID na liście nie zatrzymuje reszty kanałów. Jeśli któryś kanał nic nie daje, sprawdź w historii wykonań, co zwrócił ten węzeł. Najczęstsza przyczyna to literówka w ID.

Kiedy tego nie używać

Kanał RSS daje tytuł, link, datę i opis. Nie daje liczby wyświetleń, komentarzy ani historii całego kanału. Do raportu ze statystykami potrzebujesz YouTube Data API.

Podsumowanie

Dwanaście węzłów, zero kluczy. Dopisujesz ID kanału do listy, a nowe filmy same trafiają do tabeli co rano o 7:40. Z tej lekcji zabierasz dwa patenty: [].concat(...) na XML, który raz zwraca obiekt, a raz listę, i nawiasy kwadratowe na pola z dwukropkiem.

W następnej lekcji, Monitoring trendów GitHub, n8n codziennie czyta stronę trendujących repozytoriów i zapisuje nowe pozycje, bez AI i bez kluczy.

Pokaż JSON do skopiowania - youtube-rss.json
{
  "name": "Nowe filmy z YouTube do Data Table",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 7,
              "triggerAtMinute": 40
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        0
      ],
      "id": "b1a1a663-625e-9258-6827-20f11e46f619",
      "name": "Codziennie 7:40"
    },
    {
      "parameters": {
        "resource": "table",
        "operation": "create",
        "tableName": "yt_filmy",
        "columns": {
          "column": [
            {
              "name": "video_id",
              "type": "string"
            },
            {
              "name": "tytul",
              "type": "string"
            },
            {
              "name": "link",
              "type": "string"
            },
            {
              "name": "kanal",
              "type": "string"
            },
            {
              "name": "opublikowano",
              "type": "date"
            },
            {
              "name": "opis",
              "type": "string"
            },
            {
              "name": "dodano",
              "type": "date"
            }
          ]
        },
        "options": {
          "createIfNotExists": true
        }
      },
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        220,
        0
      ],
      "id": "50e04acf-072a-3cab-fe86-01881a9048bd",
      "name": "Przygotuj tabele"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "acb80293-8d93-eac1-d363-e821ccf6164f",
              "name": "kanaly",
              "value": "=[\"UC_x5XG1OV2P6uZZ5FSM9Ttw\"]",
              "type": "array"
            },
            {
              "id": "fddbc31b-5a94-cb30-486d-3b8bf08e9470",
              "name": "dni_wstecz",
              "value": "14",
              "type": "number"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        440,
        0
      ],
      "id": "cb4ad1e7-35a8-1398-479f-86640b1780af",
      "name": "Kanaly YouTube"
    },
    {
      "parameters": {
        "fieldToSplitOut": "kanaly",
        "options": {
          "destinationFieldName": "channel_id"
        }
      },
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [
        660,
        0
      ],
      "id": "da5cfc29-ae5d-a5f8-34d9-c8c50bd62425",
      "name": "Rozbij kanaly"
    },
    {
      "parameters": {
        "url": "=https://www.youtube.com/feeds/videos.xml?channel_id={{ $json.channel_id }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        880,
        0
      ],
      "id": "1afafd00-68ed-ea84-a109-e7a72f5f6cff",
      "name": "Pobierz kanal RSS",
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "options": {}
      },
      "type": "n8n-nodes-base.xml",
      "typeVersion": 1,
      "position": [
        1100,
        0
      ],
      "id": "7700c8b7-3511-4597-52ce-7319a59a6598",
      "name": "XML na JSON"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "e6f8496c-6b12-c1ce-242b-329a8f94d97d",
              "name": "filmy",
              "value": "={{ [].concat($json.feed.entry || []) }}",
              "type": "array"
            },
            {
              "id": "39882812-7cbf-5910-c605-f6e6b0787016",
              "name": "kanal",
              "value": "={{ $json.feed.title }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1320,
        0
      ],
      "id": "1cab0270-a285-9b95-8d41-e4ca2d7eaa37",
      "name": "Lista filmow"
    },
    {
      "parameters": {
        "fieldToSplitOut": "filmy",
        "include": "selectedOtherFields",
        "fieldsToInclude": "kanal",
        "options": {
          "destinationFieldName": "film"
        }
      },
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [
        1540,
        0
      ],
      "id": "55f46ef3-455f-62a3-c9be-4f31a5437d88",
      "name": "Rozbij filmy"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "6c102c0b-b4af-3a80-182f-3bac2096dcae",
              "name": "video_id",
              "value": "={{ $json.film['yt:videoId'] }}",
              "type": "string"
            },
            {
              "id": "56515e7d-44da-0f92-3b57-ea36fb261e24",
              "name": "tytul",
              "value": "={{ $json.film.title }}",
              "type": "string"
            },
            {
              "id": "693e9b66-f812-f262-9a6a-e963d43ecac3",
              "name": "link",
              "value": "={{ $json.film.link.href }}",
              "type": "string"
            },
            {
              "id": "56deb56f-c3a2-64db-76f2-407b55af9796",
              "name": "kanal",
              "value": "={{ $json.kanal }}",
              "type": "string"
            },
            {
              "id": "63e62558-ea92-2ded-2b78-dc20f053ffa6",
              "name": "opublikowano",
              "value": "={{ $json.film.published }}",
              "type": "string"
            },
            {
              "id": "6fe25707-a99b-eb61-69c0-a8c363498807",
              "name": "opis",
              "value": "={{ ($json.film['media:group']['media:description'] || '').substring(0, 1950) }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1760,
        0
      ],
      "id": "da0e9711-a437-858c-35cb-13686cac2c71",
      "name": "Dane filmu"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "f1",
              "leftValue": "={{ $json.opublikowano }}",
              "rightValue": "={{ $now.minus({days: $('Kanaly YouTube').first().json.dni_wstecz}).toISO() }}",
              "operator": {
                "type": "dateTime",
                "operation": "after"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.2,
      "position": [
        1980,
        0
      ],
      "id": "5eae4886-fa73-68e5-646b-366385d3e3ac",
      "name": "Tylko swieze"
    },
    {
      "parameters": {
        "operation": "rowNotExists",
        "dataTableId": {
          "__rl": true,
          "value": "yt_filmy",
          "mode": "name"
        },
        "filters": {
          "conditions": [
            {
              "keyName": "video_id",
              "condition": "eq",
              "keyValue": "={{ $json.video_id }}"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        2200,
        0
      ],
      "id": "63f60264-b199-a964-de7b-c22adfc028a4",
      "name": "Czy nowy film?"
    },
    {
      "parameters": {
        "operation": "insert",
        "dataTableId": {
          "__rl": true,
          "value": "yt_filmy",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "video_id": "={{ $json.video_id }}",
            "tytul": "={{ $json.tytul }}",
            "link": "={{ $json.link }}",
            "kanal": "={{ $json.kanal }}",
            "opublikowano": "={{ $json.opublikowano }}",
            "opis": "={{ $json.opis }}",
            "dodano": "={{ $now.toISO() }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "video_id",
              "displayName": "video_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "tytul",
              "displayName": "tytul",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "link",
              "displayName": "link",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "kanal",
              "displayName": "kanal",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "opublikowano",
              "displayName": "opublikowano",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "date",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "opis",
              "displayName": "opis",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "dodano",
              "displayName": "dodano",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "date",
              "readOnly": false,
              "removed": false
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        2420,
        0
      ],
      "id": "2462448b-f95f-af94-7e77-73e99a6452aa",
      "name": "Zapisz film"
    }
  ],
  "connections": {
    "Codziennie 7:40": {
      "main": [
        [
          {
            "node": "Przygotuj tabele",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Przygotuj tabele": {
      "main": [
        [
          {
            "node": "Kanaly YouTube",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Kanaly YouTube": {
      "main": [
        [
          {
            "node": "Rozbij kanaly",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rozbij kanaly": {
      "main": [
        [
          {
            "node": "Pobierz kanal RSS",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pobierz kanal RSS": {
      "main": [
        [
          {
            "node": "XML na JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "XML na JSON": {
      "main": [
        [
          {
            "node": "Lista filmow",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Lista filmow": {
      "main": [
        [
          {
            "node": "Rozbij filmy",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rozbij filmy": {
      "main": [
        [
          {
            "node": "Dane filmu",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Dane filmu": {
      "main": [
        [
          {
            "node": "Tylko swieze",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Tylko swieze": {
      "main": [
        [
          {
            "node": "Czy nowy film?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Czy nowy film?": {
      "main": [
        [
          {
            "node": "Zapisz film",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "active": false
}