400 lines
12 KiB
Markdown
400 lines
12 KiB
Markdown
# n8n-nodes-scrapling
|
||
|
||
Community node dla [n8n](https://n8n.io) integrujący bibliotekę [Scrapling](https://github.com/D4Vinci/Scrapling) — szybki, adaptacyjny scraper stron internetowych z obsługą zwykłego HTTP, trybu stealth (TLS fingerprint impersonation) oraz pełnej przeglądarki Playwright.
|
||
|
||
---
|
||
|
||
## Architektura
|
||
|
||
```
|
||
n8n (Node.js / TypeScript)
|
||
│
|
||
│ HTTP POST /scrape
|
||
▼
|
||
Scrapling Service (Python / FastAPI)
|
||
│
|
||
├── Fetcher — szybkie żądania HTTP
|
||
├── StealthyFetcher — impersonacja TLS (curl-impersonate)
|
||
└── PlayWrightFetcher — pełna przeglądarka Chromium
|
||
```
|
||
|
||
n8n node komunikuje się z Scrapling Service przez HTTP. Serwis Python zarządza instancjami scraperów i zwraca ustrukturyzowane JSON.
|
||
|
||
---
|
||
|
||
## Wymagania
|
||
|
||
| Komponent | Wersja |
|
||
|-----------------|----------------|
|
||
| n8n | ≥ 1.0 |
|
||
| Node.js | ≥ 18 |
|
||
| Python | ≥ 3.11 |
|
||
| Docker | opcjonalnie |
|
||
|
||
---
|
||
|
||
## Instalacja
|
||
|
||
### Opcja A — Docker Compose (zalecana)
|
||
|
||
Uruchamia n8n oraz Scrapling Service w jednym poleceniu.
|
||
|
||
```bash
|
||
# Skopiuj i uzupełnij zmienne środowiskowe
|
||
cp .env.test.example .env
|
||
|
||
# Zbuduj i uruchom
|
||
docker compose up -d
|
||
```
|
||
|
||
n8n dostępne pod adresem: `http://localhost:5678`
|
||
Scrapling Service: `http://localhost:8765`
|
||
|
||
### Opcja B — Ręczna instalacja
|
||
|
||
**1. Zainstaluj zależności n8n node:**
|
||
|
||
```bash
|
||
npm install
|
||
npm run build
|
||
```
|
||
|
||
**2. Zainstaluj serwis Python:**
|
||
|
||
```bash
|
||
cd service
|
||
python3 -m venv .venv
|
||
.venv/bin/pip install -e ".[dev]"
|
||
.venv/bin/scrapling install # pobiera przeglądarki Playwright
|
||
```
|
||
|
||
**3. Uruchom serwis:**
|
||
|
||
```bash
|
||
.venv/bin/uvicorn service.main:app --host 0.0.0.0 --port 8765
|
||
```
|
||
|
||
**4. Zainstaluj node w n8n:**
|
||
|
||
Skopiuj katalog `dist/` do katalogu community nodes n8n:
|
||
|
||
```bash
|
||
# Domyślna ścieżka dla n8n instalowanego przez npm
|
||
cp -r dist ~/.n8n/nodes/node_modules/n8n-nodes-scrapling
|
||
```
|
||
|
||
Następnie zrestartuj n8n.
|
||
|
||
---
|
||
|
||
## Konfiguracja credentials
|
||
|
||
W n8n dodaj nowy credential typu **Scrapling API**:
|
||
|
||
| Pole | Opis | Domyślnie |
|
||
|-------------|---------------------------------------------------|------------------------|
|
||
| Service URL | URL Scrapling Service (bez trailing slash) | `http://localhost:8765` |
|
||
| API Key | Opcjonalny klucz autoryzacyjny (header `X-API-Key`) | _(puste)_ |
|
||
|
||
Jeśli `API_KEY` w serwisie jest pusty, autoryzacja jest wyłączona.
|
||
|
||
---
|
||
|
||
## Użycie node
|
||
|
||
### Resource: Page
|
||
|
||
Służy do pobierania całych stron. Zwraca URL, status HTTP, czas pobierania i (opcjonalnie) surowy HTML.
|
||
|
||
#### Operacje
|
||
|
||
| Operacja | Fetcher | Opis |
|
||
|----------------|--------------------|---------------------------------------------------------|
|
||
| Fetch | `Fetcher` | Szybkie żądanie HTTP. Najszybsza opcja dla statycznych stron. |
|
||
| Fetch Stealth | `StealthyFetcher` | Impersonacja TLS (curl-impersonate). Omija podstawowe anti-bot. |
|
||
| Fetch Dynamic | `PlayWrightFetcher`| Pełna przeglądarka Chromium (Playwright). Dla SPA i stron wymagających JS. |
|
||
|
||
**Przykładowy output:**
|
||
|
||
```json
|
||
{
|
||
"url": "https://example.com",
|
||
"status_code": 200,
|
||
"html": "<html>...</html>",
|
||
"data": {},
|
||
"fetcher_used": "http",
|
||
"elapsed_ms": 312.5
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Resource: Data
|
||
|
||
Służy do ekstrakcji ustrukturyzowanych danych z pobranych stron.
|
||
|
||
#### Operacje
|
||
|
||
| Operacja | Opis |
|
||
|-----------------|------------------------------------------------------------------|
|
||
| Extract | Pobiera stronę i wyciąga dane za pomocą selektorów CSS lub XPath. |
|
||
| Extract Tables | Pobiera stronę i zwraca wszystkie tabele HTML jako tablice JSON. |
|
||
|
||
#### Konfiguracja selektorów (Extract)
|
||
|
||
Każdy selektor definiuje pole w wyjściowym obiekcie `data`:
|
||
|
||
| Pole | Opis |
|
||
|------------------|-------------------------------------------------------------------|
|
||
| Field Name | Nazwa klucza w wyjściowym JSON |
|
||
| Selector | Wyrażenie CSS lub XPath, np. `h1.title` lub `//h1[@class="title"]` |
|
||
| Type | `css` lub `xpath` |
|
||
| Attribute | Atrybut HTML do pobrania (np. `href`, `src`). Puste = tekst. |
|
||
| Return Multiple | Zwróć tablicę wszystkich pasujących elementów |
|
||
|
||
**Przykładowy output dla Extract:**
|
||
|
||
```json
|
||
{
|
||
"url": "https://news.ycombinator.com",
|
||
"status_code": 200,
|
||
"data": {
|
||
"titles": ["Show HN: ...", "Ask HN: ...", "..."],
|
||
"top_link": "https://..."
|
||
},
|
||
"fetcher_used": "http",
|
||
"elapsed_ms": 187.2
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Opcje wspólne
|
||
|
||
| Opcja | Opis | Domyślnie |
|
||
|------------------------|------------------------------------------------------------------|-----------|
|
||
| Return Raw HTML | Dołącz surowy HTML do odpowiedzi | `false` |
|
||
| Timeout (ms) | Maksymalny czas żądania (1000–120000 ms) | `30000` |
|
||
| Proxy | URL proxy, np. `http://user:pass@proxy.example.com:8080` | _(puste)_ |
|
||
| Extra Headers | Dodatkowe nagłówki HTTP jako JSON, np. `{"Accept-Language": "pl"}` | `{}` |
|
||
| Wait for Selector | CSS selector, na który czeka Playwright przed ekstrakcją | _(puste)_ |
|
||
| Wait for Network Idle | Czekaj na zakończenie aktywności sieciowej (Playwright) | `false` |
|
||
| Headless Browser | Uruchom Playwright bez GUI | `true` |
|
||
|
||
> Opcje **Wait for Selector**, **Wait for Network Idle** i **Headless Browser** działają tylko z fetcherem `dynamic`.
|
||
|
||
---
|
||
|
||
## CLI Runner
|
||
|
||
Do testowania bez uruchamiania n8n:
|
||
|
||
```bash
|
||
# Podstawowe użycie
|
||
npx ts-node scripts/scrapling-run.ts https://example.com
|
||
|
||
# Tryb stealth
|
||
npx ts-node scripts/scrapling-run.ts https://example.com --fetcher stealth
|
||
|
||
# Ekstrakcja danych
|
||
npx ts-node scripts/scrapling-run.ts https://news.ycombinator.com \
|
||
--selector "titles:.titleline a" \
|
||
--fetcher http \
|
||
--format json
|
||
|
||
# Playwright — czekaj na element
|
||
npx ts-node scripts/scrapling-run.ts https://spa.example.com \
|
||
--fetcher dynamic \
|
||
--wait "#content" \
|
||
--html
|
||
|
||
# Lub przez Taskfile
|
||
task scrape URL=https://example.com FETCHER=stealth
|
||
task scrape:dynamic URL=https://spa.example.com
|
||
```
|
||
|
||
Credentials CLI pobiera z pliku `.env.test` lub zmiennych środowiskowych:
|
||
|
||
```bash
|
||
SCRAPLING_SERVICE_URL=http://localhost:8765
|
||
SCRAPLING_API_KEY= # opcjonalnie
|
||
```
|
||
|
||
---
|
||
|
||
## Taskfile
|
||
|
||
| Task | Opis |
|
||
|---------------------|---------------------------------------------------|
|
||
| `task setup` | Instalacja npm + kopiowanie `.env.test.example` |
|
||
| `task build` | Kompilacja TypeScript → `dist/` |
|
||
| `task dev` | Watch mode |
|
||
| `task test` | Uruchom testy jednostkowe |
|
||
| `task test:coverage`| Testy z raportem pokrycia |
|
||
| `task lint` | ESLint |
|
||
| `task check` | Lint + test (pre-push) |
|
||
| `task service:install` | Instalacja serwisu Python w `.venv` |
|
||
| `task service:start` | Start serwisu na porcie 8765 |
|
||
| `task service:health` | Sprawdź health endpoint |
|
||
| `task scrape` | CLI scraper `[URL=...] [FETCHER=...] [FORMAT=...]`|
|
||
| `task scrape:stealth` | Scrape w trybie stealth |
|
||
| `task scrape:dynamic` | Scrape z przeglądarką |
|
||
| `task docker:up` | Start Docker Compose (n8n + serwis) |
|
||
| `task docker:down` | Stop Docker Compose |
|
||
| `task docker:logs` | Tail logów |
|
||
|
||
---
|
||
|
||
## API Scrapling Service
|
||
|
||
### `POST /scrape`
|
||
|
||
Pobiera stronę i opcjonalnie ekstrahuje dane.
|
||
|
||
**Request body:**
|
||
|
||
```json
|
||
{
|
||
"url": "https://example.com",
|
||
"fetcher_type": "http",
|
||
"selectors": [
|
||
{
|
||
"name": "title",
|
||
"selector": "h1",
|
||
"selector_type": "css",
|
||
"attribute": null,
|
||
"multiple": false
|
||
}
|
||
],
|
||
"return_html": false,
|
||
"timeout": 30000,
|
||
"proxy": null,
|
||
"headers": {},
|
||
"wait_selector": null,
|
||
"network_idle": false,
|
||
"headless": true
|
||
}
|
||
```
|
||
|
||
**Response:**
|
||
|
||
```json
|
||
{
|
||
"url": "https://example.com",
|
||
"status_code": 200,
|
||
"html": null,
|
||
"data": {
|
||
"title": "Example Domain"
|
||
},
|
||
"fetcher_used": "http",
|
||
"elapsed_ms": 245.3,
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
W przypadku błędu serwis zwraca HTTP 200 z wypełnionym polem `error` (zamiast rzucać wyjątek) — pozwala to n8n obsłużyć błąd przez opcję **Continue On Fail**.
|
||
|
||
### `GET /health`
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"version": "0.1.0",
|
||
"dynamic_session_ready": true
|
||
}
|
||
```
|
||
|
||
Autoryzacja: jeśli zmienna `API_KEY` jest ustawiona w serwisie, każdy request do `/scrape` wymaga nagłówka `X-API-Key`.
|
||
|
||
---
|
||
|
||
## Zmienne środowiskowe
|
||
|
||
### Scrapling Service
|
||
|
||
| Zmienna | Opis | Domyślnie |
|
||
|--------------|---------------------------------------------------|-----------|
|
||
| `API_KEY` | Klucz API do autoryzacji requestów | _(puste — wyłączone)_ |
|
||
|
||
### Docker Compose
|
||
|
||
| Zmienna | Opis | Domyślnie |
|
||
|----------------------|-----------------------------------|-------------|
|
||
| `SCRAPLING_API_KEY` | API key serwisu (przekazywany) | _(puste)_ |
|
||
| `N8N_USER` | Login do n8n basic auth | `admin` |
|
||
| `N8N_PASSWORD` | Hasło do n8n basic auth | `changeme` |
|
||
|
||
---
|
||
|
||
## Struktura projektu
|
||
|
||
```
|
||
scrapling_n8n/
|
||
├── src/
|
||
│ ├── credentials/
|
||
│ │ └── ScraplingApi.credentials.ts # Credentials: serviceUrl + apiKey
|
||
│ └── nodes/Scrapling/
|
||
│ ├── Scrapling.node.ts # Główny node n8n
|
||
│ ├── helpers.ts # HTTP client + typy
|
||
│ ├── scrapling.svg # Ikona node
|
||
│ └── __tests__/
|
||
│ ├── helpers.test.ts # Testy helpera
|
||
│ └── Scrapling.node.test.ts # Testy node (z mock)
|
||
├── service/ # Python FastAPI microservice
|
||
│ ├── main.py # Entry point + autoryzacja
|
||
│ ├── routers/
|
||
│ │ ├── scrape.py # POST /scrape
|
||
│ │ └── health.py # GET /health
|
||
│ ├── scrapers/
|
||
│ │ ├── base.py # Abstrakcja + apply_selectors()
|
||
│ │ ├── fetcher.py # Wrapper Fetcher
|
||
│ │ ├── stealthy.py # Wrapper StealthyFetcher
|
||
│ │ └── dynamic.py # Wrapper PlayWrightFetcher
|
||
│ ├── models/
|
||
│ │ ├── request.py # Pydantic ScrapeRequest
|
||
│ │ └── response.py # Pydantic ScrapeResponse
|
||
│ ├── pyproject.toml # Zależności Python
|
||
│ └── Dockerfile
|
||
├── scripts/
|
||
│ └── scrapling-run.ts # CLI runner (bez n8n)
|
||
├── dist/ # Skompilowany JS (po npm run build)
|
||
├── docker-compose.yml
|
||
├── package.json
|
||
├── tsconfig.json
|
||
├── jest.config.js
|
||
├── Taskfile.yml
|
||
└── .env.test.example
|
||
```
|
||
|
||
---
|
||
|
||
## Rozwój
|
||
|
||
```bash
|
||
# Instalacja
|
||
task setup
|
||
|
||
# Tryb watch (TypeScript)
|
||
task dev
|
||
|
||
# Testy
|
||
task test
|
||
task test:coverage
|
||
|
||
# Lint
|
||
task lint
|
||
|
||
# Uruchomienie serwisu lokalnie
|
||
task service:install
|
||
task service:start
|
||
|
||
# Szybki test end-to-end
|
||
task scrape URL=https://httpbin.org/html
|
||
```
|
||
|
||
---
|
||
|
||
## Licencja
|
||
|
||
MIT
|