▸ // rozdziały lekcji


Codzienne przeglądanie trendujących repozytoriów zjada 15-20 minut i kończy się tak samo: otwierasz te same projekty co wczoraj, bo nie pamiętasz, które już widziałeś.
Ten workflow robi to o 7:45, zanim usiądziesz do kawy. Czyta stronę github.com/trending, wyciąga nazwę, opis, język i gwiazdki każdego repozytorium i dopisuje do tabeli tylko te, których jeszcze w niej nie ma. W teście: 20 repozytoriów z trendu tygodniowego.
Poprzednia wersja tej lekcji pobierała stronę przez Jinę, a dane wyciągał agent AI z modelem Gemini. Wyrzuciłem to wszystko. Strona trendów ma stały układ HTML i te same dane da się wyciąć selektorami. Model dokładał tylko koszt, dwa klucze do pilnowania i ryzyko, że coś sobie dopowie.
Co robi ten workflow
- Codziennie 7:45 - harmonogram.
- Przygotuj tabele - Data Table z operacją Create zakłada tabelę github_trendy z kolumnami repo, link, opis, jezyk, gwiazdki, nowe_gwiazdki, zakres i znaleziono. Jeśli tabela już jest, używa istniejącej.
- Ustawienia - Edit Fields z dwoma polami:
zakres(weekly) ijezyk(pusty). - Pobierz trending - HTTP Request na stronę trendów, odpowiedź jako tekst.
- Wytnij repozytoria - węzeł HTML wycina każdy blok
article.Box-rowjako osobny kawałek HTML i zwraca je jako listę. - Rozbij repozytoria - Split Out, jeden item na repozytorium.
- Dane repozytorium - drugi węzeł HTML wyciąga z każdego bloku ścieżkę, opis, język, gwiazdki i nowe gwiazdki.
- Uporzadkuj - Edit Fields składa nazwę i link, czyści opis, zamienia liczby z tekstu na liczby.
- Czy nowe repo? - Row Not Exists po kolumnie
repo. - Zapisz repo - Insert do tabeli.
Czego potrzebujesz
Niczego poza n8n. Strona trendów jest publiczna, tabela zakłada się sama. Bez konta GitHub, bez tokenu, bez modelu AI.
Jak uruchomić
- Zaimportuj plik z dołu lekcji (jak importować workflow).
- W węźle Ustawienia wybierz
zakres: daily, weekly albo monthly. To te same zakresy co na stronie GitHuba. - W polu
jezykwpisz język, na przykład python. Puste pole znaczy wszystkie języki. - Kliknij Execute workflow i zajrzyj do tabeli: Overview > zakładka Data tables > github_trendy.
- Kliknij Publish, żeby harmonogram ruszył (interfejs i publikacja).
Adres z ustawień
Pobierz trending składa adres z dwóch pól. Język idzie do ścieżki, zakres do parametru since:
https://github.com/trending/{{ $json.jezyk }}?since={{ $json.zakres }}Przy pustym języku wychodzi github.com/trending/?since=weekly, przy python github.com/trending/python?since=weekly. Odpowiedź jest ustawiona na format tekstowy, więc cały HTML strony trafia do pola data.
Selektory CSS w dwóch krokach
Pierwszy węzeł HTML ma jeden selektor: article.Box-row. Na stronie trendów każde repozytorium to osobny blok article z klasą Box-row. Opcja zwracania tablicy jest włączona, więc węzeł oddaje jedną listę kawałków HTML, po jednym na repozytorium. Split Out robi z niej osobne items.
Drugi węzeł HTML pracuje już na jednym bloku i wyciąga pięć pól:
| Pole | Selektor | Co bierze |
|---|---|---|
| sciezka | h2 a | atrybut href, np. /wlasciciel/nazwa |
| opis | p | tekst opisu |
| jezyk | span[itemprop="programmingLanguage"] | tekst |
| gwiazdki | a[href$="/stargazers"] | link, którego adres kończy się na /stargazers |
| nowe | span.float-sm-right | tekst z liczbą gwiazdek zdobytych w wybranym zakresie |
Opcja Clean Up Text jest w tym węźle wyłączona. W moich testach sklejała słowa na łamaniach linii, z „architecture" i „code" wyszło „architecturecode". Spacje w opisie porządkuje wyrażenie w następnym węźle:
{{ ($json.opis || '').replace(/\s+/g, ' ').trim() }}Liczby przychodzą jako tekst z przecinkami. parseInt(($json.gwiazdki || '0').replace(/[^0-9]/g, '')) wyrzuca wszystko, co nie jest cyfrą, i zamienia wynik na liczbę.

Haczyk. To czytanie HTML strony, nie oficjalne API. Gdy GitHub zmieni wygląd strony trendów, selektory przestaną trafiać. Jeśli nie trafi
article.Box-row, workflow po cichu nic nie zapisze. Jeśli nie trafih2 a, wywali się na węźle Uporzadkuj, bo zabraknie ścieżki. Tabela przestała rosnąć albo przyszedł alert o błędzie? Zacznij od selektorów.
Jak znaleźć selektor, gdy przestanie działać
- Otwórz github.com/trending w Chrome.
- Kliknij prawym przyciskiem nazwę repozytorium i wybierz Zbadaj. Otworzą się narzędzia deweloperskie z podświetlonym elementem.
- Popatrz na znacznik i jego klasy. Idź w górę drzewa, aż trafisz na blok, który obejmuje całe repozytorium.
- W panelu Elements naciśnij Cmd+F i wpisz selektor, na przykład
article.Box-row. Chrome pokaże, ile elementów pasuje. Dla trendów powinno wyjść tyle, ile repozytoriów widzisz na stronie. - Poprawiony selektor wklej do węzła HTML i kliknij Execute step.
Pro-tip. Row Not Exists po
repozapisuje repozytorium tylko przy pierwszym pojawieniu się w trendach. Liczby gwiazdek w tabeli są więc z dnia, w którym repo trafiło do tabeli. Chcesz śledzić, jak rosną? Zamień Row Not Exists i Insert na jeden węzeł Data Table z operacją Upsert po kolumnierepo.
Tabela zakłada się sama, bo Przygotuj tabele ma włączoną opcję Reuse Existing Tables. Dlaczego to ważne, pokazuję w lekcji o czytniku RSS.
Podsumowanie
Dziesięć węzłów, zero kluczy, zero AI. Dwa węzły HTML z selektorami CSS robią to, co wcześniej robił model: wycinają bloki, a z nich pola. Row Not Exists pilnuje, żeby w tabeli były tylko repozytoria, których jeszcze nie widziałeś. Gdy GitHub zmieni stronę, poprawiasz selektor w pięć minut w narzędziach przeglądarki.
W następnej lekcji, Scraper ogłoszeń z powiadomieniem, ten sam pomysł idzie dalej: n8n co godzinę sprawdza wyszukiwanie mieszkań na Otodom i pisze na Telegram o nowych ogłoszeniach.
Pokaż JSON do skopiowania - monitoring-trendow-github.json
{
"name": "Monitoring trendujacych repozytoriow GitHub",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 7,
"triggerAtMinute": 45
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
0,
0
],
"id": "5700a8fc-ab0a-73a4-f8ff-f52d7031db1d",
"name": "Codziennie 7:45"
},
{
"parameters": {
"resource": "table",
"operation": "create",
"tableName": "github_trendy",
"columns": {
"column": [
{
"name": "repo",
"type": "string"
},
{
"name": "link",
"type": "string"
},
{
"name": "opis",
"type": "string"
},
{
"name": "jezyk",
"type": "string"
},
{
"name": "gwiazdki",
"type": "number"
},
{
"name": "nowe_gwiazdki",
"type": "number"
},
{
"name": "zakres",
"type": "string"
},
{
"name": "znaleziono",
"type": "date"
}
]
},
"options": {
"createIfNotExists": true
}
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
220,
0
],
"id": "f7888cd5-c2a1-8ffd-92a3-2cf3a731fbeb",
"name": "Przygotuj tabele"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "2a54f3da-17f1-2743-d43a-a9d5159ff51b",
"name": "zakres",
"value": "weekly",
"type": "string"
},
{
"id": "523616ca-7c19-eba2-3a32-adbc0aa9af55",
"name": "jezyk",
"value": "",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
440,
0
],
"id": "15a13955-753a-dbb2-7c08-39db2fdf3d44",
"name": "Ustawienia"
},
{
"parameters": {
"url": "=https://github.com/trending/{{ $json.jezyk }}",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "since",
"value": "={{ $json.zakres }}"
}
]
},
"options": {
"response": {
"response": {
"responseFormat": "text"
}
}
}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
660,
0
],
"id": "95b056d6-1283-84fe-6361-5387c7ab8f62",
"name": "Pobierz trending"
},
{
"parameters": {
"operation": "extractHtmlContent",
"dataPropertyName": "data",
"extractionValues": {
"values": [
{
"key": "repozytoria",
"cssSelector": "article.Box-row",
"returnValue": "html",
"returnArray": true
}
]
},
"options": {}
},
"type": "n8n-nodes-base.html",
"typeVersion": 1.2,
"position": [
880,
0
],
"id": "1f75ac3e-957e-a77c-22ba-64f84ad96938",
"name": "Wytnij repozytoria"
},
{
"parameters": {
"fieldToSplitOut": "repozytoria",
"options": {
"destinationFieldName": "html"
}
},
"type": "n8n-nodes-base.splitOut",
"typeVersion": 1,
"position": [
1100,
0
],
"id": "05477e69-1fe4-ad34-6561-356702b548a4",
"name": "Rozbij repozytoria"
},
{
"parameters": {
"operation": "extractHtmlContent",
"dataPropertyName": "html",
"extractionValues": {
"values": [
{
"key": "sciezka",
"cssSelector": "h2 a",
"returnValue": "attribute",
"attribute": "href"
},
{
"key": "opis",
"cssSelector": "p",
"returnValue": "text"
},
{
"key": "jezyk",
"cssSelector": "span[itemprop=\"programmingLanguage\"]",
"returnValue": "text"
},
{
"key": "gwiazdki",
"cssSelector": "a[href$=\"/stargazers\"]",
"returnValue": "text"
},
{
"key": "nowe",
"cssSelector": "span.float-sm-right",
"returnValue": "text"
}
]
},
"options": {
"trimValues": true,
"cleanUpText": false
}
},
"type": "n8n-nodes-base.html",
"typeVersion": 1.2,
"position": [
1320,
0
],
"id": "8c9e240f-7139-8f72-f109-abe2948983ea",
"name": "Dane repozytorium"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "5fa576f5-806b-e2dc-246d-c246046c10b5",
"name": "repo",
"value": "={{ $json.sciezka.replace(/^\\//, '') }}",
"type": "string"
},
{
"id": "2f2da79d-4c33-4605-9262-a1f337541c24",
"name": "link",
"value": "={{ 'https://github.com' + $json.sciezka }}",
"type": "string"
},
{
"id": "328494d8-30e7-a5ab-c366-277c0404fab9",
"name": "opis",
"value": "={{ ($json.opis || '').replace(/\\s+/g, ' ').trim() }}",
"type": "string"
},
{
"id": "fe1797e1-4090-0c7f-c6f9-d854bd87c2b7",
"name": "jezyk",
"value": "={{ $json.jezyk || '' }}",
"type": "string"
},
{
"id": "996e3a4f-4c72-3b1b-d1eb-13d5adb6815b",
"name": "gwiazdki",
"value": "={{ parseInt(($json.gwiazdki || '0').replace(/[^0-9]/g, '')) }}",
"type": "number"
},
{
"id": "13b8ccde-2524-0073-6621-abba0374b04b",
"name": "nowe_gwiazdki",
"value": "={{ parseInt(($json.nowe || '0').replace(/[^0-9]/g, '')) }}",
"type": "number"
},
{
"id": "f6b3afa6-6972-6b62-0fcf-45f7039559a7",
"name": "zakres",
"value": "={{ $('Ustawienia').first().json.zakres }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
1540,
0
],
"id": "1a557615-08de-977d-4c3c-759bf61fa01a",
"name": "Uporzadkuj"
},
{
"parameters": {
"operation": "rowNotExists",
"dataTableId": {
"__rl": true,
"value": "github_trendy",
"mode": "name"
},
"filters": {
"conditions": [
{
"keyName": "repo",
"condition": "eq",
"keyValue": "={{ $json.repo }}"
}
]
}
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
1760,
0
],
"id": "0b103b29-badc-ce04-3cc5-13d109f510e2",
"name": "Czy nowe repo?"
},
{
"parameters": {
"operation": "insert",
"dataTableId": {
"__rl": true,
"value": "github_trendy",
"mode": "name"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"repo": "={{ $json.repo }}",
"link": "={{ $json.link }}",
"opis": "={{ $json.opis }}",
"jezyk": "={{ $json.jezyk }}",
"gwiazdki": "={{ $json.gwiazdki }}",
"nowe_gwiazdki": "={{ $json.nowe_gwiazdki }}",
"zakres": "={{ $json.zakres }}",
"znaleziono": "={{ $now.toISO() }}"
},
"matchingColumns": [],
"schema": [
{
"id": "repo",
"displayName": "repo",
"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": "opis",
"displayName": "opis",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "jezyk",
"displayName": "jezyk",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "gwiazdki",
"displayName": "gwiazdki",
"required": false,
"defaultMatch": false,
"display": true,
"type": "number",
"readOnly": false,
"removed": false
},
{
"id": "nowe_gwiazdki",
"displayName": "nowe_gwiazdki",
"required": false,
"defaultMatch": false,
"display": true,
"type": "number",
"readOnly": false,
"removed": false
},
{
"id": "zakres",
"displayName": "zakres",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "znaleziono",
"displayName": "znaleziono",
"required": false,
"defaultMatch": false,
"display": true,
"type": "date",
"readOnly": false,
"removed": false
}
]
},
"options": {}
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
1980,
0
],
"id": "6d6013fd-70cf-f932-a574-4c47c07d5163",
"name": "Zapisz repo"
}
],
"connections": {
"Codziennie 7:45": {
"main": [
[
{
"node": "Przygotuj tabele",
"type": "main",
"index": 0
}
]
]
},
"Przygotuj tabele": {
"main": [
[
{
"node": "Ustawienia",
"type": "main",
"index": 0
}
]
]
},
"Ustawienia": {
"main": [
[
{
"node": "Pobierz trending",
"type": "main",
"index": 0
}
]
]
},
"Pobierz trending": {
"main": [
[
{
"node": "Wytnij repozytoria",
"type": "main",
"index": 0
}
]
]
},
"Wytnij repozytoria": {
"main": [
[
{
"node": "Rozbij repozytoria",
"type": "main",
"index": 0
}
]
]
},
"Rozbij repozytoria": {
"main": [
[
{
"node": "Dane repozytorium",
"type": "main",
"index": 0
}
]
]
},
"Dane repozytorium": {
"main": [
[
{
"node": "Uporzadkuj",
"type": "main",
"index": 0
}
]
]
},
"Uporzadkuj": {
"main": [
[
{
"node": "Czy nowe repo?",
"type": "main",
"index": 0
}
]
]
},
"Czy nowe repo?": {
"main": [
[
{
"node": "Zapisz repo",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"active": false
}