파이썬 웹 스크래핑: 403 Forbidden 오류 해결하기 (헤더 설정, API 우회, 속도 제한)
![]() |
| < 403 Forbidden 오류 > |
웹 스크래핑을 처음 시작했을 때, 403 Forbidden 오류는 누구나 한 번쯤 마주치는 장벽입니다. Reddit의 r/learnpython에서도 이 오류를 해결하려는 질문이 자주 올라오죠. "왜 내 코드는 403 에러를 뱉는 걸까?", "헤더를 추가했는데도 안 돼!", "속도 제한 때문에 차단된 걸까?" 같은 고민들 말이죠.
이 글에서는 파이썬으로 웹 스크래핑할 때 403 Forbidden 오류의 원인과 해결 방법을 초보자도 이해할 수 있도록 자세히 다룹니다. 헤더 설정, API 우회, 속도 제한 문제 해결법 등 실용적인 팁과 코드 예제를 포함했으니, 천천히 따라오세요!
403 Forbidden 오류란?
403 Forbidden은 서버가 요청을 거부했을 때 발생하는 HTTP 상태 코드입니다. 즉, "이 페이지에 접근할 권한이 없어요!"라는 뜻이죠. 웹 스크래핑 중 이 오류가 발생하는 주요 원인은 다음과 같습니다:
- 봇 감지: 웹사이트가 당신의 요청을 사람이 아닌 봇으로 인식.
- 헤더 누락: 브라우저처럼 보이는 요청 헤더가 없음.
- 속도 제한: 짧은 시간 내에 너무 많은 요청을 보냄.
- IP 차단: 특정 IP에서 비정상적인 트래픽 감지.
- API 제한: 스크래핑 대신 API 사용을 요구하는 사이트.
이제 각 원인별로 해결 방법을 하나씩 살펴보겠습니다.
해결법 1: 사용자 에이전트(User-Agent)와 헤더 설정
왜 헤더가 중요한가?
대부분의 웹사이트는 요청에 포함된 헤더를 확인해 요청이 브라우저에서 온 것인지, 봇에서 온 것인지 판단합니다. 특히 User-Agent는 요청이 어떤 브라우저(Chrome, Firefox 등)에서 왔는지 알려주는 핵심 정보입니다. 기본적으로 requests 라이브러리로 요청을 보내면 User-Agent가 python-requests/2.28.1처럼 설정되어 봇으로 간주되기 쉽습니다.
해결 방법
User-Agent를 실제 브라우저처럼 설정하고, 추가적인 헤더를 포함하세요. 예를 들어, Chrome 브라우저의 헤더를 모방할 수 있습니다.
코드 예제
import requests
# 사용자 에이전트와 헤더 설정
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Connection': 'keep-alive',
}
url = 'https://example.com'
response = requests.get(url, headers=headers)
if response.status_code == 200:
print("성공!")
print(response.text)
else:
print(f"실패: {response.status_code}")
- 다양한 User-Agent 사용:
fake-useragent라이브러리를 사용하면 랜덤한User-Agent를 쉽게 생성할 수 있습니다. - 브라우저 헤더 확인: Chrome 개발자 도구(F12) → Network 탭에서 실제 요청의 헤더를 복사해 사용하세요.
해결법 2: 속도 제한(Rate Limiting) 회피
![]() |
| < 데이터 흐름을 표현한 속도 제한 > |
속도 제한이란?
웹사이트는 짧은 시간 내에 너무 많은 요청이 들어오면 봇으로 간주하고 403 오류를 반환하거나 IP를 차단합니다. 예를 들어, 1초에 10번 요청을 보내는 스크립트는 쉽게 차단당할 수 있습니다.
해결 방법
- 요청 간 딜레이 추가:
time.sleep()을 사용해 요청 사이에 대기 시간을 둡니다. - 랜덤 딜레이: 고정된 딜레이 대신 랜덤 시간을 설정해 더 자연스럽게 보이게 합니다.
코드 예제
import requests
import time
import random
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
urls = ['https://example.com/page1', 'https://example.com/page2', 'https://example.com/page3']
for url in urls:
response = requests.get(url, headers=headers)
if response.status_code == 200:
print(f"성공: {url}")
else:
print(f"실패: {url}, 상태 코드: {response.status_code}")
# 1~3초 사이 랜덤 딜레이
time.sleep(random.uniform(1, 3))
- 사이트별 제한 확인: 웹사이트의 robots.txt 파일이나 이용 약관에서 요청 빈도 제한을 확인하세요.
- 프록시 사용: IP 차단을 피하려면 아래에서 설명할 프록시를 고려하세요.
해결법 3: API 우회 및 대체 접근
API가 필요한 경우
일부 웹사이트는 스크래핑을 방지하기 위해 데이터를 API를 통해서만 제공합니다. 예를 들어, Reddit, Twitter(X), YouTube 같은 사이트는 API를 사용하도록 유도합니다. 이 경우 HTML 스크래핑 대신 API를 호출하는 것이 더 효율적입니다.
해결 방법
- 공식 API 사용: 웹사이트의 공식 문서를 확인해 API 엔드포인트를 찾습니다.
- 비공식 API 탐색: 공식 API가 없으면
Postman이나 브라우저 개발자 도구를 사용해 비공식 API를 찾아볼 수 있습니다. - API 키 요청: API 사용에 키가 필요하다면, 개발자 포털에서 등록하세요.
코드 예제 (Reddit API)
Reddit API를 사용하려면 먼저 Reddit 개발자 포털에서 클라이언트 ID와 시크릿을 발급받아야 합니다.
import requests
# Reddit API 인증
client_id = 'YOUR_CLIENT_ID'
client_secret = 'YOUR_CLIENT_SECRET'
user_agent = 'my-scraper/0.1'
auth = requests.auth.HTTPBasicAuth(client_id, client_secret)
data = {
'grant_type': 'client_credentials'
}
headers = {'User-Agent': user_agent}
response = requests.post('https://www.reddit.com/api/v1/access_token', auth=auth, data=data, headers=headers)
if response.status_code == 200:
token = response.json()['access_token']
headers['Authorization'] = f'bearer {token}'
# 데이터 요청
response = requests.get('https://oauth.reddit.com/r/learnpython/hot', headers=headers)
print(response.json())
else:
print("인증 실패")
- API 사용 제한 확인: API는 호출 횟수 제한이 있을 수 있으니 문서를 꼼꼼히 읽으세요.
- 비공식 API 주의: 비공식 API는 갑자기 변경될 수 있으니 주기적으로 확인하세요.
해결법 4: 프록시 및 IP 회피
IP 차단 문제
속도 제한을 지켰음에도 403 오류가 계속 발생한다면, 서버가 당신의 IP를 차단했을 가능성이 있습니다. 특히 대량 스크래핑 시 자주 발생합니다.
해결 방법
- 프록시 사용: 요청을 다른 IP를 통해 보내도록 설정합니다.
- 프록시 풀: 여러 프록시를 순환 사용해 차단 위험을 줄입니다.
코드 예제
import requests
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
# 프록시 설정
proxies = {
'http': 'http://your-proxy-ip:port',
'https': 'http://your-proxy-ip:port'
}
url = 'https://example.com'
response = requests.get(url, headers=headers, proxies=proxies)
if response.status_code == 200:
print("성공!")
else:
print(f"실패: {response.status_code}")
프록시 서비스 추천
- 무료 프록시: Free Proxy List (품질이 낮을 수 있음)
- 유료 프록시: Bright Data, Smartproxy, Oxylabs (안정적이고 빠름)
- 프록시 품질 확인: 무료 프록시는 느리거나 차단될 가능성이 높으니 테스트 후 사용하세요.
- VPN 고려: 간단한 스크래핑이라면 NordVPN, Surfshark 같은 VPN도 유용합니다.
![]() |
| < 프록시 서버와 네트워크 다이어그램 > |
해결법 5: JavaScript 렌더링 문제 (Dynamic Content)
동적 콘텐츠 문제
일부 웹사이트는 JavaScript로 콘텐츠를 동적으로 로드합니다. requests는 JavaScript를 실행하지 않으므로 빈 페이지나 403 오류를 받을 수 있습니다.
해결 방법
- Selenium 사용: 실제 브라우저를 자동화해 JavaScript를 렌더링합니다.
- Playwright: Selenium보다 가볍고 빠른 대안.
코드 예제 (Selenium)
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
# 헤드리스 모드 설정
options = Options()
options.add_argument('--headless')
options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36')
driver = webdriver.Chrome(options=options)
driver.get('https://example.com')
# 페이지 소스 가져오기
print(driver.page_source)
driver.quit()
- 렌더링 시간 고려: JavaScript 로딩이 완료될 때까지 대기하도록
time.sleep()또는WebDriverWait를 사용하세요. - Playwright 추천: 최신 프로젝트라면 Playwright가 더 간편합니다.
추가 팁: 403 오류 예방하기
- robots.txt 확인: 웹사이트의
/robots.txt를 확인해 스크래핑이 허용된 페이지를 파악하세요. - 최소한의 데이터 요청: 필요한 데이터만 요청해 서버 부담을 줄이세요.
- 에러 처리 추가:
try-except로 403 오류를 감지하고 대처하세요.
try:
response = requests.get(url, headers=headers)
response.raise_for_status() # 4xx, 5xx 에러 감지
except requests.exceptions.HTTPError as e:
print(f"HTTP 에러 발생: {e}")
- 윤리적 스크래핑: 웹사이트의 이용 약관을 준수하고, 과도한 요청으로 서버에 부담을 주지 마세요.
마무리
파이썬으로 웹 스크래핑 중 403 Forbidden 오류는 다양한 원인으로 발생하지만, 올바른 방법으로 접근하면 대부분 해결할 수 있습니다. 헤더 설정, 속도 제한 관리, API 활용, 프록시 사용, JavaScript 렌더링 같은 기술을 조합해 문제를 극복해보세요. Reddit r/learnpython에서도 비슷한 고민을 하는 분들이 많으니, 이 글이 여러분의 스크래핑 여정에 도움이 되길 바랍니다!


