시리즈 요약: 이전 글에서 N8N + Playwright + FastAPI로 뉴스 스크래핑 자동화를 구축하고, Google Sheets 연동까지 완료했어요. 이번 편에서는 보안(JWT), 속도(병렬 처리), 데이터 품질(PostgreSQL 중복 방지) 세 가지를 개선하면서, 동시에 아키텍처를 재설계해서 각 컴포넌트의 역할을 명확하게 분리할 거예요!
이전 글에서 N8N과 Playwright, FastAPI, Google Sheets를 연동해 뉴스 스크래핑 자동화 파이프라인을 구축했어요.
docker-compose와 lifespan을 사용해 이미 고속화된 API 서버를 구축했지만, 시스템에는 여전히 세 가지 아쉬운 점이 있었어요.
- 보안성 부재: FastAPI 엔드포인트가 외부에 그대로 노출되어 누구나 호출할 수 있었어요.
- 순차 처리: N8N 워크플로우가 제공된 URL을 순차적으로 요청하여, 아무리 lifespan이 빨라도 10개 URL 처리에 20~30초가 걸렸어요.
- 데이터 중복: 스크래핑 대상 URL에 대해 중복을 체크하지 않아 같은 기사가 Google Sheets에 계속 쌓일 수 있어요.
이번 글에서는 이 세 가지 문제를 한 번에 해결해볼게요. 그리고 더 나아가, FastAPI와 N8N의 역할을 명확하게 분리해서 유지보수하기 쉬운 시스템으로 개선할 거예요!
📑 목차
개선 목표
lifespan 아키텍처는 그대로 유지하면서, 보안과 효율성, 데이터 품질을 높이는 것이 목표예요.
| 개선 항목 | 기존 방식 | 개선 방식 |
|---|---|---|
| 기본 속도 | lifespan으로 빠름 (요청당 1~2초) | FastAPI lifespan (유지) |
| 보안 | 누구나 API 호출 가능 | JWT 토큰 인증 필요 🔒 |
| 효율성 | 순차 처리 (10개 = 20초) | 병렬 처리 (10개 = 4초) ⚡ |
| 데이터 품질 | 중복 데이터 저장됨 | PostgreSQL로 체크 후 저장 🗄️ |
📦 필요한 패키지:
GitHub의 requirements.txt에서 전체 목록을 확인할 수 있어요!
# FastAPI & Server
fastapi==0.121.1
uvicorn[standard]==0.38.0
# Browser Automation
playwright==1.56.0
# Data Validation
pydantic==2.9.2
# JWT Authentication
python-jose[cryptography]==3.5.0
# Password Hashing
passlib[bcrypt]==1.7.4
bcrypt==4.0.1
# Form Data
python-multipart==0.0.20
1단계: JWT 인증으로 보안 강화 (상세)
JWT(JSON Web Token)는 서버에 세션을 저장하지 않고도 사용자를 인증할 수 있는 토큰 방식이에요. 기존에는 누구나 FastAPI를 호출할 수 있었지만, 이제는 유효한 토큰이 있어야만 스크래핑을 실행할 수 있어요.
JWT가 필요한 이유
| 인증 방식 | 기존 방식 (없음) | JWT 방식 |
|---|---|---|
| 보안 | 누구나 호출 가능 ⚠️ | 토큰 있어야 호출 ✅ |
| 상태 관리 | - | Stateless (서버 부담 없음) |
| 만료 시간 | 없음 | 30분 후 자동 만료 |
| N8N 통합 | URL만 호출 | 토큰 발급 → 헤더에 포함 |
JWT 토큰의 구조
JWT 토큰은 3개 부분으로 나뉘어져 있고, 점(.)으로 구분돼요:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJuOG5fdXNlciIsImV4cCI6MTczMjAwMDAwMH0.signature
│ Header │ Payload │ Signature │
│ {"alg": "HS256", "typ": "JWT"} │ {"sub": "n8n_user", "exp":...} │ 암호화된 서명 │
- Header: 토큰 타입과 암호화 알고리즘 정보
- Payload: 사용자 정보 (username, 만료 시간 등)
- Signature: Header + Payload를 SECRET_KEY로 암호화한 값
JWT 인증 흐름 (전체 과정)
1. N8N → FastAPI: POST /login
Body: {"username": "n8n_user", "password": "secure_password_123"}
↓
2. FastAPI: 비밀번호 해싱 후 검증
- passlib.CryptContext로 bcrypt 해싱 비교
- 일치하면 다음 단계, 불일치하면 401 에러
↓
3. FastAPI: JWT 토큰 생성
- 사용자명(sub)과 만료 시간(exp)을 Payload에 포함
- SECRET_KEY로 서명 생성
↓
4. FastAPI → N8N: 토큰 반환
{"access_token": "eyJhbGc...", "token_type": "bearer", "expires_in": 1800}
↓
5. N8N: 토큰을 변수에 저장 (30분간 유효)
↓
6. N8N → FastAPI: POST /scrape/batch
Header: Authorization: Bearer eyJhbGc...
↓
7. FastAPI: 토큰 검증
- Bearer에서 토큰 추출
- SECRET_KEY로 서명 검증
- Payload에서 사용자명 추출
- 만료 시간 확인
↓
8. FastAPI: 스크래핑 실행 ✅
JWT 코드 구현
GitHub의 main_enhanced.py에서 전체 코드를 확인할 수 있어요!
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta
# JWT 설정
SECRET_KEY = os.getenv("SECRET_KEY", "your-secret-key-change-this")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "30"))
# 비밀번호 해싱
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# 간단한 사용자 DB (실제로는 PostgreSQL 등 사용)
FAKE_USERS_DB = {
"n8n_user": {
"username": "n8n_user",
"hashed_password": "$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewY5GyYzpLaOALem"
}
}
def verify_password(plain_password: str, hashed_password: str) -> bool:
"""비밀번호 검증"""
return pwd_context.verify(plain_password, hashed_password)
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
"""JWT 액세스 토큰 생성"""
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
@app.post("/login", response_model=Token)
async def login(request: LoginRequest):
"""JWT 토큰 발급"""
user = FAKE_USERS_DB.get(request.username)
if not user or not verify_password(request.password, user["hashed_password"]):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="사용자명 또는 비밀번호가 잘못되었습니다."
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user["username"]},
expires_delta=access_token_expires
)
return Token(
access_token=access_token,
token_type="bearer",
expires_in=ACCESS_TOKEN_EXPIRE_MINUTES * 60
)
💡 코드 핵심 포인트:
- 🛡️ 비밀번호 해싱 (Bcrypt): DB에 비밀번호를 평문으로 저장하는 것은 매우 위험해요. passlib을 사용해 비밀번호를 암호화(해싱)하여 저장하고 검증합니다.
- ⏱️ 만료 시간 설정 (Expiration): 탈취된 토큰이 악용되는 것을 막기 위해 유효 기간(exp)을 설정해요. 여기선 30분으로 설정되어 있어, 30분이 지나면 토큰이 자동 만료됩니다.
- 🔑 환경 변수 사용 (Security): SECRET_KEY는 토큰의 위조를 막는 핵심 열쇠예요. 코드에 직접 적지 않고 os.getenv로 환경 변수에서 불러와 보안을 유지합니다.
🚧 참고: 실제 운영 환경에서는?
현재 코드의 FAKE_USERS_DB는 예시를 위해 하드코딩된 딕셔너리 형태입니다. 실제 서비스에서는 이 부분을 PostgreSQL이나 MySQL 같은 데이터베이스와 연동해야 합니다. 사용자가 로그인할 때 DB에서 해당 사용자의 해시된 비밀번호를 조회하여 검증하도록 코드를 수정하면, 동적으로 회원 관리가 가능해집니다.
2단계: 병렬 스크래핑으로 속도 개선 (상세)
Python의 asyncio를 활용하면 여러 URL을 동시에 스크래핑할 수 있어요!
병렬 처리가 필요한 이유
| 처리 방식 | 순차 처리 | 병렬 처리 (5개) | 개선율 |
|---|---|---|---|
| 10개 URL | 약 20초 | 약 4초 | 5배 ⚡ |
| 50개 URL | 약 100초 | 약 20초 | 5배 ⚡ |
| 100개 URL | 약 200초 | 약 40초 | 5배 ⚡ |
병렬 스크래핑 핵심 코드
@app.post("/scrape/batch", response_model=List[ScrapeResponse])
async def batch_scrape(
request: BatchScrapeRequest,
current_user: str = Depends(get_current_user)
):
"""병렬 스크래핑 (여러 URL 동시 처리)"""
logger.info(f"📥 병렬 스크래핑 요청: {len(request.urls)}개 URL")
# Semaphore로 동시 실행 제한
semaphore = asyncio.Semaphore(request.max_concurrent)
async def scrape_with_semaphore(url: HttpUrl):
async with semaphore:
return await scrape_single_url(
url=str(url),
wait_for=request.wait_for,
timeout=request.timeout,
stealth_mode=request.stealth_mode
)
# 병렬 실행
tasks = [scrape_with_semaphore(url) for url in request.urls]
results = await asyncio.gather(*tasks)
success_count = sum(1 for r in results if r.success)
logger.info(f"✅ 병렬 스크래핑 완료: {success_count}/{len(results)} 성공")
return results
💡 코드 핵심 포인트:
- 🚦 세마포어 (Semaphore): asyncio.Semaphore(request.max_concurrent)는 동시에 실행될 수 있는 작업의 수를 제한합니다. 예를 들어 URL이 100개라도 세마포어가 5라면, 동시에 5개의 브라우저 탭만 열리고 나머지는 대기합니다. 이는 서버 리소스 고갈을 막는 핵심 장치예요.
- ⚡ Gather (동시 실행): asyncio.gather(*tasks)는 준비된 모든 스크래핑 작업(Task)을 한 번에 실행하고, 모든 작업이 끝날 때까지 기다린 후 결과를 리스트로 반환합니다. 순차적으로 하나씩 기다리는 것보다 훨씬 효율적입니다.
완성된 FastAPI 코드
전체 코드는 GitHub에서 확인하세요: main_enhanced.py
💡 핵심 특징:
- ✅ JWT 인증: 보안 강화
- ✅ 병렬 처리: asyncio.gather() + Semaphore
- ✅ Lifespan: 브라우저 연결 재사용
- ✅ Stealth 모드: 봇 탐지 우회
- ✅ 에러 처리: 타임아웃 및 예외 핸들링
3단계: Docker Compose 설정
GitHub의 docker-compose.yml을 확인하세요.
예전에 작성했던 .yml 파일과 조금 달라졌어요. 설정 값을 이제는 .env 라는 환경변수 파일에서 읽어와요. 그래서 fastapi의 설정을 보면 { ... } 사이에 있는 값을 .env 파일에서 읽어오는 것을 볼 수 있어요.
1. docker-compose.yml
version: '3.8'
services:
# Playwright 브라우저 서버
playwright:
image: [mcr.microsoft.com/playwright:v1.56.0-jammy](https://mcr.microsoft.com/playwright:v1.56.0-jammy)
container_name: playwright_server
command: npx -y playwright@1.56.0 run-server --host 0.0.0.0 --port 3000
ports:
- "3000:3000"
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000"]
interval: 10s
timeout: 5s
retries: 5
networks:
- n8n_network
restart: unless-stopped
# FastAPI 애플리케이션 (순수 스크래핑만)
fastapi:
build:
context: .
dockerfile: Dockerfile
container_name: fastapi_scraper
env_file:
- .env
environment:
- PLAYWRIGHT_SERVER_URL=${PLAYWRIGHT_SERVER_URL:-ws://playwright:3000}
- SECRET_KEY=${SECRET_KEY}
- ACCESS_TOKEN_EXPIRE_MINUTES=${ACCESS_TOKEN_EXPIRE_MINUTES:-30}
ports:
- "8000:8000"
depends_on:
playwright:
condition: service_healthy
volumes:
- ./main_enhanced.py:/app/main.py
networks:
- n8n_network
restart: unless-stopped
# N8N과 같은 네트워크 사용 (외부 네트워크)
networks:
n8n_network:
external: true
name: ${NETWORK_NAME:-n8n_network}
💡 주요 변경점 설명:
- Playwright 서비스 분리: 브라우저 엔진만 별도 컨테이너로 띄워 관리해요. 이렇게 하면 FastAPI가 가벼워지고, 브라우저 충돌 시 브라우저 컨테이너만 재시작하면 됩니다.
- depends_on: FastAPI 컨테이너가 시작될 때 Playwright 브라우저가 준비될 때까지 기다리도록 설정했어요. (condition: service_healthy)
- n8n_network: N8N과 FastAPI가 서로 통신할 수 있도록 같은 Docker 네트워크로 묶어주었습니다. 이 설정이 없으면 N8N에서 FastAPI를 호출할 수 없어요!
2. Dockerfile
docker 파일은 이전에 비해 시스템 의존성으로 gcc 패키지를 설치해요.
GitHub: Dockerfile
FROM python:3.11-slim
WORKDIR /app
# 시스템 의존성 설치 (최소화)
RUN apt-get update && apt-get install -y \
gcc \
&& rm -rf /var/lib/apt/lists/*
# Python 패키지 설치
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 애플리케이션 코드 복사
COPY main_enhanced.py main.py
# 포트 노출
EXPOSE 8000
# 애플리케이션 실행
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
💡 왜 'slim'과 'gcc'를 쓰나요?
- python:3.11-slim: 기본 파이썬 이미지는 용량이 크기 때문에, 불필요한 도구가 제거된 'slim' 버전을 사용하여 이미지 크기를 1/5 수준으로 줄였습니다.
- gcc 설치: python-jose(암호화)나 일부 파이썬 패키지는 설치 과정에서 C 컴파일러(gcc)를 필요로 합니다. slim 버전에는 이게 없어서 수동으로 설치해줘야 해요.
3. 환경 변수 설정 (.env)
GitHub의 env.example을 복사해서 사용해요.
env.example -> .env 로 변경해야해요.
# JWT 인증 설정
SECRET_KEY=your-secret-key-change-this-in-production-2024-min-32-characters
ACCESS_TOKEN_EXPIRE_MINUTES=30
# Playwright 설정
PLAYWRIGHT_SERVER_URL=ws://playwright:3000
# Docker 네트워크 설정
NETWORK_NAME=n8n_network
🛡️ 보안 팁:
SECRET_KEY는 JWT 토큰의 서명을 만드는 중요한 열쇠예요. 절대 GitHub에 그대로 올리지 말고, 로컬의 .env 파일에서만 관리하세요. 실제 운영 시에는 길고 복잡한 무작위 문자열을 사용해야 안전합니다.
4. 실행 방법
# 1. N8N 네트워크 확인
docker network ls | grep n8n
# 2. 환경 파일 생성 및 수정
cp env.example .env
nano .env # SECRET_KEY, NETWORK_NAME 수정
# 3. 서비스 시작
docker compose up -d
# 4. 확인
curl http://localhost:8000/health
4단계: N8N이 PostgreSQL 관리
중복 체크와 데이터 저장은 모두 N8N에서 PostgreSQL을 통해 관리해요!
⚠️ 중요: FastAPI에는 PostgreSQL 코드가 없어요! 모든 DB 작업은 N8N에서 처리합니다.
1. 테이블 생성
## Docker로 동작중인 PostgreSQL 컨테이너에 Shell 연결
knockknows@instance:~/n8n_domain$ docker compose exec postgres /bin/bash
## PSQL로 PostgreSQL 접속
root@7c1b0c0ed470:/# psql -U <PostgreSQL 관리자 계정> -d <DB 이름>
## 새로운 Database 및 DB 생성
n8n=# create database <생성할 DB 이름>;
CREATE DATABASE
## N8N PostgreSQL 노드 (Execute Query)
n8n_workflow_db=# CREATE TABLE IF NOT EXISTS processed_urls (
id SERIAL PRIMARY KEY,
url TEXT UNIQUE NOT NULL,
title TEXT,
success BOOLEAN DEFAULT true,
processed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
## 인덱스 생성 (검색 속도 10~100배 향상)
n8n_workflow_db=# CREATE INDEX IF NOT EXISTS idx_url ON processed_urls(url);
💡 SQL 포인트: UNIQUE NOT NULL 제약 조건을 걸어두면 DB 레벨에서 중복 저장을 원천 차단할 수 있습니다. 나중에 같은 URL이 들어오면 DB가 알아서 거부하거나 업데이트해요.
2. URL 추출 및 배열 변환 (Code 노드)
RSS 피드는 여러 개의 아이템(객체)을 배열 형태로 제공해요. 하지만 PostgreSQL에서 중복 체크를 하려면 URL로만 구성된 배열이 필요하죠. 그래서 Code 노드를 추가해서 RSS 아이템에서 link만 쏙 뽑아내야 합니다.
// 이전 노드(RSS Read)의 모든 아이템에서 'link' 속성만 추출
const urls = $input.all().map(item => item.json.link);
// 배열을 하나의 긴 문자열로 합칩니다. 구분자는 URL에 절대 쓰이지 않는 문자열(예: '|||')을 사용합니다.
// 예: "url1|||url2|||url3"
const urlString = urls.join('|||');
// 다음 노드(PostgreSQL)로 전송
return { json: { urls: urlString } };
3. 중복 URL 체크 (PostgreSQL)
이제 DB에서 중복을 확인할 차례예요. 여기서 중요한 점은 "성공적으로 처리된(success=true)" URL만 중복으로 간주해야 한다는 점이에요. 만약 이전에 실패했다면 다시 시도해야 하니까요!
-- 성공 이력이 있는 URL만 중복으로 체크
SELECT url FROM processed_urls
WHERE url = ANY($1::text[])
AND success = true;
-- 파라미터: $1 = {{ $json.urls }} (위의 Code 노드에서 만든 배열)
4. 중복 제외 필터링 (Code 노드)
// 1. PostgreSQL에서 조회한 '이미 처리된(성공한)' URL 목록
const processedUrls = $('중복 체크').all()
.map(item => item.json.url);
// 2. 원본 RSS 데이터 (모든 정보 포함)
const allItems = $('RSS Read').all();
// 3. 중복이 아닌(DB에 없는) URL만 필터링하여 배열로 생성
const newUrls = allItems
.filter(item => !processedUrls.includes(item.json.link)) // DB에 없는 것만 통과
.map(item => item.json.link); // 통과된 아이템에서 'link'만 뽑아서 문자열 배열 생성
// 4. HTTP Request (Batch) 노드가 한 번에 받을 수 있는 형태로 반환
// 결과 예시: [{ json: { urls: ["http://...", "http://..."] } }]
return {
json: {
urls: newUrls
}
};
5. 처리 완료 URL 저장
-- N8N PostgreSQL 노드 (Execute Query)
INSERT INTO processed_urls (url, title, success)
VALUES ($1, $2, true) -- 여기서 success를 true로 명시적 지정
ON CONFLICT (url) DO UPDATE SET
title = EXCLUDED.title,
success = true, -- 업데이트 시에도 true로 설정
processed_at = CURRENT_TIMESTAMP;
💡 UPSERT (ON CONFLICT) 란?
이미 존재하는 URL이 들어오면 에러를 내는 대신(INSERT 실패), 기존 정보를 최신 정보로 업데이트(UPDATE)하는 방식이에요. 이를 통해 스크래핑 재시도 시 정보를 갱신할 수 있어 데이터 무결성이 보장됩니다.
5단계: 완전한 N8N 워크플로우 설정
이제 모든 것을 통합한 완전한 워크플로우예요! 각 단계별 핵심 설정값을 확인해보세요.

💡 워크플로우 논리 구조:
- 선(先) 필터링 (단계 3~6): 무거운 스크래핑 작업을 하기 전에 DB에서 중복 여부를 먼저 확인합니다. URL을 문자열로 변환하여 PostgreSQL에서 효율적으로 조회하고, 중복이 아닌 것만 배열로 만들어 새 URL이 있을 때만 다음 단계로 진행합니다. 이렇게 해야 불필요한 API 호출을 줄이고 속도를 높일 수 있어요.
- 병렬 스크래핑 (단계 7~9): JWT 토큰을 발급받고, FastAPI에 URL 배열을 전송하여 병렬로 스크래핑합니다. 실패한 요청을 제외하기 위해 Filter로 success=true인 것만 통과시킵니다.
- 데이터 추출 및 정규화 (단계 10~12): Loop Over Items로 각 아이템을 순회하면서, WebpageContentExtractor로 HTML에서 텍스트를 추출하고(단계 11), Google Sheets 헤더 형식에 맞게 데이터를 정규화합니다.
- 후(後) 저장 (단계 13~14): 스크래핑과 구글 시트 저장이 모두 성공했을 때만 DB에 '처리 완료'라고 기록합니다. 만약 중간에 에러가 나면 DB에 기록되지 않으므로, 다음 실행 때 다시 시도하게 되어 누락을 방지합니다.
1. HTTP Request (JWT 토큰 발급)
FastAPI에 로그인하여 인증 토큰을 받아오는 단계입니다.
| 설정 항목 | 값 (Value) |
|---|---|
| Method | POST |
| URL | http://fastapi:8000/login (Docker 내부망 주소) |
| Body Content Type | JSON |
| Body Parameters |
|
2. HTTP Request (Batch 스크래핑)
발급받은 토큰을 헤더에 싣고, 필터링된 URL 목록을 전송하여 병렬 스크래핑을 요청합니다.
| 설정 항목 | 값 (Value) |
|---|---|
| Method | POST |
| URL | http://fastapi:8000/scrape/batch |
| Authentication | Header Auth |
| Header Parameters | Name: Authorization Value: Bearer {{ $json.access_token }} |
| Body Parameters |
(이전 Code 노드에서 만든 URL 배열을 넣습니다) |
3. Filter (성공 여부 필터링)
FastAPI는 실패한 요청도 에러를 내지 않고 success: false 응답을 줍니다. 따라서 저장하기 전에 성공한 건들만 걸러내야 합니다.
| 설정 항목 | 값 (Value) |
|---|---|
| Condition | Boolean |
| Value 1 | {{ $json.success }} |
| Operation | is true |
⚠️ 중요: 컨테이너명 사용!
N8N에서 FastAPI를 호출할 때는 localhost가 아닌 컨테이너명을 사용해야 해요!
- ❌ 잘못된 예: http://localhost:8000/scrape
- ✅ 올바른 예: http://fastapi:8000/scrape 또는 http://fastapi_scraper:8000/scrape
트러블슈팅 (Q&A)
Q1: JWT 토큰이 계속 만료돼요
A: JWT 토큰은 기본 30분 동안만 유효해요. 워크플로우 맨 앞에 JWT 토큰 발급 노드를 배치하면 매번 새 토큰을 받아요.
Q2: N8N에서 FastAPI 연결이 안 돼요
A: localhost 대신 컨테이너명을 사용하세요! N8N과 FastAPI가 같은 Docker 네트워크에 있어야 해요.
Q3: 병렬 처리가 느려요
A: max_concurrent 값을 늘려보세요. 하지만 너무 많이 늘리면 메모리가 부족하거나 봇으로 감지될 수 있어요.
정리
이전 글에서 만든 시스템을 이번 글에서 완전히 개선했어요!
| 항목 | 이전 글 | 현재 글 (완성) | 개선 |
|---|---|---|---|
| 보안 | 없음 | JWT 토큰 | ✅ |
| 속도 | 20초 (10개 URL) | 4초 (병렬 처리) | 5배 ⚡ |
| 중복 방지 | 없음 | PostgreSQL | ✅ |
| 아키텍처 | 혼재 | 역할 분리 | ✅ |
🎉 최종 시스템의 특징:
- 보안: JWT 토큰으로 인증된 요청만 처리
- 속도: 병렬 처리로 5배 빠른 스크래핑
- 품질: PostgreSQL로 중복 데이터 완벽 차단
- 구조: FastAPI는 스크래핑, N8N은 데이터 관리
- 확장성: FastAPI를 여러 개 띄워도 문제없음
📦 전체 코드 다운로드:
🔗 GitHub: https://github.com/knockknows/Blog/tree/main/code
상세한 설정 가이드는 README.md를 참고하세요!
'N8N' 카테고리의 다른 글
| N8N - 웹 스크래핑 데이터 Google Sheet 저장 (0) | 2025.11.15 |
|---|---|
| N8N - Playwright를 통한 뉴스 스크래핑 (0) | 2025.11.13 |
| N8N 사용법 - RSS 피드 본문 추출 후 엑셀 저장하기 (0) | 2025.09.18 |
| N8N Guide - 커뮤니티 노드(Community Node) 설치 (0) | 2025.09.10 |
| N8N 사용법 - HTTP Request 노드 및 HTTP 메소드 이해 (0) | 2025.09.08 |