블로그로 돌아가기

뉴스 사이트를 차단 없이 크롤링하는 방법: 프록시 설정 및 보안 우회

뉴스 사이트 파싱 설정에 대한 완벽한 가이드: 프록시 유형 선택, 안티봇 시스템 우회, IP 회전 설정 및 Python 코드 예제.

📅2026년 3월 7일
```html

뉴스 사이트는 인터넷에서 가장 보호받는 자원 중 하나입니다. Cloudflare, 속도 제한, IP 차단 등은 뉴스 파싱을 심각한 기술적 도전으로 만듭니다. 이 가이드에서는 뉴스 포털에서 안정적으로 데이터를 수집하기 위한 프록시 설정 방법, 다양한 작업을 위한 프록시 유형 선택 및 최신 보호 시스템을 우회하는 방법을 살펴보겠습니다.

뉴스 사이트가 파서를 차단하는 이유

뉴스 포털은 여러 가지 이유로 자동 데이터 수집에 특히 민감합니다. 첫째, 콘텐츠는 그들의 주요 자산으로, 광고와 구독을 통해 수익을 창출합니다. 대량 파싱은 경쟁자가 자료를 복사할 수 있게 하여 고유 방문자를 감소시킵니다. 둘째, 봇으로 인한 높은 부하는 서버 및 CDN 비용을 증가시킵니다.

현대 뉴스 사이트는 다층 보호를 사용합니다:

  • Cloudflare 및 유사 서비스 — JavaScript, TLS 브라우저 지문, 행동 패턴을 검사합니다.
  • 속도 제한 — 하나의 IP에서 요청 수를 제한합니다 (보통 분당 10-50 요청).
  • User-Agent 차단 — 표준 라이브러리의 헤더를 차단합니다 (Python-requests, curl).
  • CAPTCHA — 의심스러운 활동 시 표시됩니다.
  • 지리적 차단 — 일부 뉴스 포털은 특정 국가에서만 접근 가능합니다.

뉴스 사이트가 파서를 감지하는 일반적인 징후: 동일한 IP가 연속으로 많은 요청을 보냄, JavaScript 없음, 비표준 HTTP 헤더 순서, 너무 빠른 요청 속도 (사람이 초당 10페이지를 열 수 없음), 쿠키 및 참조자 없음.

중요: 뉴스 사이트 파싱은 회색 지대에 있습니다. 항상 대상 리소스의 robots.txt 및 서비스 약관을 확인하세요. 상업적 데이터 사용을 위해서는 공식 API를 사용하거나 파트너십 계약을 체결하는 것이 좋습니다.

뉴스 파싱을 위한 프록시 유형 선택

프록시 유형 선택은 작업의 규모, 예산 및 대상 사이트의 보호 수준에 따라 다릅니다. 뉴스 파싱에 적합한 세 가지 기본 옵션과 그 적용 가능성을 살펴보겠습니다.

프록시 유형 속도 비용 언제 사용해야 하는가
데이터 센터 프록시 높음 (50-100 ms) 낮음 Cloudflare가 없는 사이트, 대량 데이터, 테스트
주거용 프록시 중간 (200-500 ms) 높음 Cloudflare가 있는 사이트, 엄격한 보호, 지리적 타겟팅
모바일 프록시 중간 (300-600 ms) 매우 높음 최대 보호, 뉴스 사이트의 모바일 버전

뉴스 파싱을 위한 데이터 센터 프록시

심각한 보호가 없는 뉴스 사이트 파싱에 적합합니다: 지역 신문, 블로그, 작은 정보 포털. 장점: 높은 속도 (수백 개의 출처를 파싱할 때 중요), 낮은 비용 (50-100 IP 풀을 임대할 수 있음), 안정적인 연결.

단점: ASN에 따라 쉽게 감지됨 (데이터 센터 소속), 종종 대형 사이트의 블랙리스트에 올라 있으며, 70%의 경우 Cloudflare Challenge를 통과하지 못합니다. 데이터 센터 프록시는 RSS 피드, sitemap.xml, API 엔드포인트의 대량 파싱 또는 메타데이터(제목, 게시 날짜) 수집에 사용하세요.

주거용 프록시 — 황금 기준

주거용 프록시는 실제 가정 사용자의 IP 주소로, 인터넷 서비스 제공업체에 의해 제공됩니다. 뉴스 사이트에 대해서는 일반 방문자로 보이므로 보호된 리소스 작업 시 매우 중요합니다.

주거용 프록시가 필수인 경우: 대형 뉴스 포털(CNN, BBC, Reuters, RBC, Коммерсантъ) 파싱, Cloudflare 또는 유사한 보호가 있는 사이트, 특정 국가에서 데이터 수집(지리적 타겟팅), 인증이 필요한 장기 세션. 주거용 프록시는 Cloudflare의 JavaScript 검사를 통과하며, IP의 깨끗한 평판을 가지고 있고, 스티키 세션(10-30분 동안 IP 고정)을 지원합니다.

실용적인 조언: 요청에 따라 IP를 변경하는 대신 시간에 따라 회전하는 주거용 프록시를 사용하세요 (스티키 세션). 예를 들어, 하나의 IP가 10분 동안 작동하여 20-30개의 기사를 수집한 후 변경됩니다. 이는 요청마다 IP를 변경하는 것보다 더 자연스럽게 보입니다.

특별한 경우를 위한 모바일 프록시

모바일 프록시는 모바일 통신사의 IP(MTS, Beeline, Tele2)를 사용합니다. 수백만 명이 뉴스 읽기를 위해 모바일 인터넷을 사용하므로 최대 신뢰를 얻습니다. 모바일 버전의 뉴스 사이트(종종 간소화된 보호가 있음), 극도로 엄격한 보호가 있는 사이트, Google News의 AMP 페이지 파싱에 사용하세요.

모바일 프록시의 특징: IP가 자동으로 자주 변경됨 (모바일 통신사는 CGNAT를 사용함), 하나의 IP가 수백 명의 사용자와 동시에 공유될 수 있어 차단이 무의미해집니다. 단점은 높은 가격이므로, 가장 보호된 목표에 대해서만 선택적으로 사용하세요.

Cloudflare 및 기타 안티봇 시스템 우회

Cloudflare는 뉴스 사이트 파서의 주 적입니다. 약 40%의 대형 뉴스 포털이 봇으로부터 보호하기 위해 Cloudflare를 사용합니다. 표준 라이브러리(requests, urllib)는 검사를 통과하지 못합니다. Cloudflare는 TLS 지문, JavaScript 실행, HTTP 헤더 순서, 행동 패턴을 분석합니다.

Cloudflare 우회 전략

1. 헤드리스 브라우저 (Selenium, Playwright, Puppeteer)

JavaScript를 실행하여 실제 브라우저를 에뮬레이트합니다. Cloudflare는 Chrome/Firefox의 올바른 TLS 지문을 확인하고 요청을 통과시킵니다. 단점: 느림 (페이지당 2-5초 소요), 많은 자원 필요 (RAM, CPU).

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

# Selenium을 위한 프록시 설정
chrome_options = Options()
chrome_options.add_argument('--proxy-server=http://username:password@proxy.example.com:8080')
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-blink-features=AutomationControlled')

driver = webdriver.Chrome(options=chrome_options)
driver.get('https://news-site.com/article')

# JavaScript 로딩 대기
driver.implicitly_wait(10)
html = driver.page_source
driver.quit()

2. TLS 지문을 가진 라이브러리 (curl_cffi, tls-client)

헤드리스 브라우저를 실행하지 않고 실제 브라우저의 TLS 지문을 모방합니다. Selenium보다 10-20배 빠르게 작동하지만 JavaScript를 실행하지 않습니다. 기본 Cloudflare 검사가 있는 사이트에 적합합니다 (JS-challenge 없음).

from curl_cffi import requests

proxies = {
    'http': 'http://username:password@proxy.example.com:8080',
    'https': 'http://username:password@proxy.example.com:8080'
}

response = requests.get(
    'https://news-site.com/article',
    proxies=proxies,
    impersonate='chrome110'  # Chrome 110의 TLS 지문 모방
)

print(response.text)

3. Cloudflare 우회 서비스 (scraperapi, scrapingbee)

Cloudflare를 자동으로 우회하는 유료 API입니다. URL을 보내면 준비된 HTML을 반환합니다. 장점: 기술적 세부사항을 이해할 필요 없음, 자동 프록시 회전, CAPTCHA 처리. 단점: 대량 요청 시 비쌉니다 (100K 요청당 월 $50부터).

올바른 HTTP 헤더

프록시를 사용할 때도 올바른 헤더를 보내는 것이 중요합니다. 그렇지 않으면 사이트가 비표준 User-Agent나 Accept-Language의 부재로 봇을 감지할 수 있습니다.

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
    'Accept-Language': 'ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7',
    'Accept-Encoding': 'gzip, deflate, br',
    'DNT': '1',
    'Connection': 'keep-alive',
    'Upgrade-Insecure-Requests': '1',
    'Sec-Fetch-Dest': 'document',
    'Sec-Fetch-Mode': 'navigate',
    'Sec-Fetch-Site': 'none',
    'Cache-Control': 'max-age=0'
}

주기적으로 User-Agent를 업데이트하세요 — 최신 브라우저 버전을 사용하세요. 자신의 지문을 확인하려면 whoer.net 또는 browserleaks.com을 방문하세요.

IP 회전 설정 및 요청 관리

올바른 프록시 회전은 차단 없이 안정적인 파싱의 핵심입니다. 뉴스 사이트는 하나의 IP에서 요청 빈도를 추적하며, 한도를 초과하면 일시적 또는 영구적인 차단이 발생합니다.

프록시 회전 유형

요청에 따른 회전 — 각 요청이 새로운 IP를 통해 전송됩니다. 많은 다양한 사이트를 빠르게 파싱하는 데 적합하며 요청 빈도로 인한 차단 위험을 최소화합니다. 단점: 세션이 있는 사이트(쿠키, 인증)에는 적합하지 않으며, 일부 보호 시스템에는 의심스럽게 보일 수 있습니다.

시간에 따른 회전 (스티키 세션) — 하나의 IP가 고정된 시간(5-30분) 동안 사용된 후 변경됩니다. 여러 페이지가 있는 하나의 뉴스 포털을 파싱하는 데 적합하며, 쿠키와 세션을 유지하고 실제 사용자 행동처럼 보입니다. 대부분의 뉴스 파싱 작업에 권장됩니다.

지리적 회전 — 다양한 국가/도시의 IP로 변경됩니다. 지리적으로 의존하는 콘텐츠(지역 뉴스) 파싱 및 지리적 차단 우회에 사용됩니다.

최적의 요청 빈도

프록시 회전을 사용하더라도 요청을 너무 자주 하면 안 됩니다. 다양한 유형의 사이트에 대한 안전한 간격:

  • 대형 뉴스 포털 (RBC, Коммерсантъ, Ведомости) — 하나의 IP에서 요청 간 2-5초
  • 중간 사이트 — 1-3초
  • 작은 블로그 및 지역 신문 — 0.5-1초

요청 패턴이 자연스럽게 보이도록 무작위 지연(randomization)을 추가하세요:

import time
import random

def fetch_article(url, proxies):
    response = requests.get(url, proxies=proxies, headers=headers)
    
    # 2초에서 5초 사이의 무작위 지연
    delay = random.uniform(2, 5)
    time.sleep(delay)
    
    return response.text

프록시 풀에서의 회전 예제

프록시 목록이 있는 경우 수동으로 간단한 회전을 구현할 수 있습니다:

import itertools
import requests

# 프록시 풀
proxy_list = [
    'http://user:pass@proxy1.example.com:8080',
    'http://user:pass@proxy2.example.com:8080',
    'http://user:pass@proxy3.example.com:8080',
]

# 무한 반복자 생성
proxy_pool = itertools.cycle(proxy_list)

def get_next_proxy():
    proxy = next(proxy_pool)
    return {'http': proxy, 'https': proxy}

# 사용 예
urls = ['https://news1.com/article', 'https://news2.com/article']

for url in urls:
    proxies = get_next_proxy()
    response = requests.get(url, proxies=proxies, headers=headers)
    print(f'Fetched {url} via {proxies["http"]}')

코드 예제: Python + Scrapy + 프록시

Scrapy는 프록시, 미들웨어, 회전 및 오류 처리를 기본적으로 지원하는 전문 파싱 프레임워크입니다. 프록시 회전이 포함된 뉴스 사이트 파서의 전체 예제를 살펴보겠습니다.

의존성 설치

pip install scrapy scrapy-rotating-proxies

프록시와 함께 Scrapy 설정 (settings.py)

# settings.py

# 프록시 회전을 위한 미들웨어 활성화
ROTATING_PROXY_LIST = [
    'http://user:pass@proxy1.example.com:8080',
    'http://user:pass@proxy2.example.com:8080',
    'http://user:pass@proxy3.example.com:8080',
]

DOWNLOADER_MIDDLEWARES = {
    'rotating_proxies.middlewares.RotatingProxyMiddleware': 610,
    'rotating_proxies.middlewares.BanDetectionMiddleware': 620,
}

# 차단 우회를 위한 설정
CONCURRENT_REQUESTS = 8  # 최대 8개의 동시 요청
DOWNLOAD_DELAY = 2  # 요청 간 2초 지연
RANDOMIZE_DOWNLOAD_DELAY = True  # 무작위 지연

# User-Agent
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'

# 오류 발생 시 재시도
RETRY_TIMES = 3
RETRY_HTTP_CODES = [500, 502, 503, 504, 408, 429]

뉴스 파싱을 위한 스파이더

# news_spider.py

import scrapy
from datetime import datetime

class NewsSpider(scrapy.Spider):
    name = 'news_parser'
    
    # 파싱할 뉴스 사이트 목록
    start_urls = [
        'https://example-news.com/latest',
    ]
    
    def parse(self, response):
        # 메인 페이지의 기사 목록 파싱
        articles = response.css('article.news-item')
        
        for article in articles:
            article_url = article.css('a.title::attr(href)').get()
            
            if article_url:
                # 기사 페이지로 이동
                yield response.follow(article_url, callback=self.parse_article)
    
    def parse_article(self, response):
        # 기사 데이터 추출
        yield {
            'url': response.url,
            'title': response.css('h1.article-title::text').get(),
            'date': response.css('time.published::attr(datetime)').get(),
            'author': response.css('span.author::text').get(),
            'text': ' '.join(response.css('div.article-body p::text').getall()),
            'tags': response.css('a.tag::text').getall(),
            'scraped_at': datetime.now().isoformat(),
        }

파서 실행

# JSON으로 저장
scrapy crawl news_parser -o news_data.json

# CSV로 저장
scrapy crawl news_parser -o news_data.csv

requests + BeautifulSoup를 이용한 간단한 파서

복잡한 로직이 필요하지 않다면 requests + BeautifulSoup 조합을 사용할 수 있습니다:

import requests
from bs4 import BeautifulSoup
import time
import random

# 프록시 설정
proxies = {
    'http': 'http://user:pass@proxy.example.com:8080',
    'https': 'http://user:pass@proxy.example.com:8080'
}

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}

def parse_news_article(url):
    try:
        response = requests.get(url, proxies=proxies, headers=headers, timeout=10)
        response.raise_for_status()
        
        soup = BeautifulSoup(response.text, 'html.parser')
        
        # 데이터 추출 (선택자는 사이트에 따라 다름)
        title = soup.find('h1', class_='article-title').text.strip()
        date = soup.find('time', class_='published')['datetime']
        text = ' '.join([p.text for p in soup.find_all('p', class_='article-text')])
        
        return {
            'url': url,
            'title': title,
            'date': date,
            'text': text
        }
    
    except Exception as e:
        print(f'Error parsing {url}: {e}')
        return None

# 기사 목록 파싱
urls = [
    'https://news-site.com/article-1',
    'https://news-site.com/article-2',
]

for url in urls:
    article_data = parse_news_article(url)
    if article_data:
        print(article_data)
    
    # 요청 간 지연
    time.sleep(random.uniform(2, 4))

뉴스 파싱 시 일반적인 오류

올바른 프록시 설정에도 불구하고 파서는 기술적 오류로 인해 차단되는 경우가 많습니다. 가장 흔한 문제와 그 해결책을 살펴보겠습니다.

오류 1: 너무 높은 요청 빈도

증상: HTTP 429 (Too Many Requests), IP의 일시적 차단, CAPTCHA. 원인: 파서가 하나의 IP에서 초당 10-50 요청을 보냅니다. 해결책: 지연을 추가하세요 (time.sleep()), Scrapy에서 DOWNLOAD_DELAY를 사용하세요, CONCURRENT_REQUESTS를 제한하세요.

오류 2: 모든 요청에 하나의 프록시 사용

증상: 프록시가 빠르게 차단됨, 지연을 추가해도 효과 없음. 원인: 하나의 IP가 하나의 사이트에 수백 개의 요청을 보냅니다. 해결책: 회전하는 프록시 풀을 사용하세요. 대형 사이트의 경우 최소 10-20개의 프록시를 사용하고, 스티키 세션의 경우 10-15분마다 IP를 변경하세요.

오류 3: 쿠키 무시

많은 뉴스 사이트는 첫 방문 시 쿠키를 설정하고 이후 요청에서 쿠키의 존재를 확인합니다. 쿠키가 없으면 봇으로 인식됩니다. 해결책: requests.Session()를 사용하여 쿠키를 자동으로 저장하고, Scrapy에서 COOKIES_ENABLED = True를 활성화하세요.

import requests

session = requests.Session()
session.proxies = {'http': 'http://proxy.com:8080', 'https': 'http://proxy.com:8080'}

# 첫 요청 — 쿠키를 받습니다
response1 = session.get('https://news-site.com')

# 이후 요청은 자동으로 쿠키를 보냅니다
response2 = session.get('https://news-site.com/article')

오류 4: 리디렉션 처리 오류

뉴스 사이트는 종종 모바일 버전, 지역 하위 도메인, AMP 페이지를 위해 리디렉션(301, 302)을 사용합니다. 파서가 리디렉션을 따르지 않으면 빈 페이지를 받습니다. 해결책: requests에서는 기본적으로 활성화되어 있습니다 (allow_redirects=True), 최종 URL을 response.url로 확인하세요.

오류 5: JavaScript 없이 동적 콘텐츠 파싱

많은 현대 뉴스 사이트는 JavaScript(React, Vue)를 통해 콘텐츠를 로드합니다. requests 라이브러리는 빈 HTML 뼈대만 받습니다. 해결책: JavaScript를 실행하기 위해 Selenium/Playwright를 사용하고, DevTools의 Network를 확인하세요 — 데이터가 API를 통해 로드되는 경우가 많습니다 (JSON을 직접 파싱할 수 있습니다).

확장성: 수백 개의 출처 파싱

하나의 뉴스 사이트가 아니라 수백 개의 출처를 동시에 파싱해야 할 때 확장 가능한 아키텍처가 필요합니다.

Scrapy Cloud를 통한 분산 파싱

Scrapy Cloud(스크래피 제작자 제공)는 클라우드에서 파서를 실행하고 자동으로 확장할 수 있게 해줍니다. 장점: 자체 서버가 필요 없고, 자동 프록시 회전, 모니터링 및 로그가 제공됩니다. 비용: 기본 요금제는 월 $9부터 시작합니다.

작업 큐 (Celery + Redis)

자체 배포를 위해 Celery — 분산 작업 시스템을 사용하세요. 아키텍처: Redis는 파싱할 URL의 큐를 저장하고, 여러 워커(서버)가 큐에서 작업을 가져와 병렬로 파싱하며, 각 워커는 자신의 프록시 풀을 사용합니다.

# tasks.py

from celery import Celery
import requests

app = Celery('news_parser', broker='redis://localhost:6379/0')

@app.task
def parse_article(url, proxy):
    proxies = {'http': proxy, 'https': proxy}
    response = requests.get(url, proxies=proxies, timeout=10)
    # 파싱 및 데이터 저장
    return response.text

# 작업을 큐에 추가
urls = ['https://news1.com/article', 'https://news2.com/article']
proxies = ['http://proxy1.com:8080', 'http://proxy2.com:8080']

for url in urls:
    proxy = random.choice(proxies)
    parse_article.delay(url, proxy)  # 비동기 실행

모니터링 및 오류 처리

대규모 파싱 시 모니터링이 매우 중요합니다: 얼마나 많은 URL가 처리되었는지, 얼마나 많은 오류가 발생했는지, 어떤 프록시가 차단되었는지. Python 오류 추적을 위해 Sentry를 사용하고, 메트릭(초당 요청, 응답 시간)을 위해 Grafana + Prometheus를 사용하며, ELK Stack(Elasticsearch, Logstash, Kibana)에서 로깅하세요.

조언: 프록시 자동 검사 시스템을 만드세요. 매 5-10분마다 각 프록시를 통해 whoer.net 또는 httpbin.org에 테스트 요청을 보내세요. 프록시가 응답하지 않거나 차단된 경우 해당 프록시를 풀에서 제외하고 새 프록시를 추가하세요.

프록시 비용 최적화

수백 개의 출처를 파싱할 때 프록시 비용이 매달 수천 달러에 이를 수 있습니다. 최적화 전략: 간단한 사이트(RSS, API)에는 데이터 센터 프록시를 사용하고, 보호된 사이트에는 주거용 프록시를 사용하며, 데이터를 캐시하여 동일한 기사를 두 번 파싱하지 않도록 하고, 비피크 시간대(밤)에 파싱하여 사이트에 대한 부하를 줄이고 차단 위험을 최소화하세요.

예: 500개의 뉴스 사이트를 파싱하기 위해 80%의 데이터 센터 프록시(간단한 사이트 및 RSS용)와 20%의 주거용 프록시(상위 100개의 보호된 포털용)를 사용할 수 있습니다. 이는 비용을 3-5배 줄일 수 있습니다.

결론

뉴스 사이트 파싱은 기술적으로 복잡한 작업으로, 올바른 프록시 선택, 회전 설정 및 안티봇 시스템 우회가 필요합니다. 이 글의 주요 결론: 보호된 뉴스 포털(Cloudflare, 엄격한 속도 제한)에는 스티키 세션을 가진 주거용 프록시를 사용하고, 수백 개의 출처를 대량으로 파싱할 때는 빠른 회전이 가능한 데이터 센터 프록시를 사용하며, 요청 간 지연(2-5초)과 올바른 HTTP 헤더를 반드시 추가하세요. Cloudflare를 우회하기 위해 헤드리스 브라우저(Selenium, Playwright) 또는 TLS 지문을 가진 라이브러리를 사용하세요.

확장 시 분산 시스템(Celery, Scrapy Cloud)과 오류 모니터링을 사용하세요. 파싱은 윤리적이어야 하며, robots.txt를 준수하고 서버에 과도한 부하를 주지 않으며, 콘텐츠에 대한 저작권을 존중하세요.

Cloudflare 보호가 있는 대형 뉴스 포털을 파싱할 계획이라면 주거용 프록시를 사용하는 것이 좋습니다 — 높은 신뢰 수준과 최소한의 차단 위험을 보장합니다. 속도와 데이터 양이 중요한 작업(뉴스 RSS, API 엔드포인트 파싱)에는 데이터 센터 프록시가 적합합니다.

```