Scrapy HTTP Cache: 완전 초보자용 가이드 (웹사이트를 과도하게 요청하지 않기)
Source: Dev.to
죄송하지만, 현재 저는 외부 웹사이트의 내용을 직접 가져올 수 없습니다. 번역이 필요한 텍스트를 여기 채팅에 그대로 붙여 주시면, 요청하신 대로 소스 링크는 그대로 두고 한국어로 번역해 드리겠습니다.
내가 처음 스파이더를 만들기 시작했을 때
나는 스파이더를 반복해서 실행하면서 테스트하곤 했습니다.
셀렉터를 수정할 때마다 스파이더를 다시 실행하고, 웹사이트에 다시 접속해 같은 페이지를 다시 다운로드했습니다.
하루에 50번째 실행을 마친 뒤, 나는 뭔가를 깨달았습니다: 나는 끔찍한 인터넷 시민이었어요. CSS 셀렉터를 제대로 작성하지 못해서 수백 개의 요청을 보내며 가엾은 웹사이트를 마구 두드리고 있었습니다.
그때 나는 Scrapy의 HTTP 캐시를 발견했어요 – 게임 체인저였습니다. 이제 스파이더를 테스트할 때 페이지를 한 번만 가져오고 캐시된 응답을 재사용합니다. 테스트가 빨라지고, 죄책감도 없으며, 차단당할 일도 없습니다.
이제 캐시를 올바르게 사용하는 방법을 보여드리겠습니다.
HTTP 캐시란?
HTTP 캐시를 웹페이지의 복사기처럼 생각하세요.
캐시 없이
- 스파이더 실행 → 페이지 다운로드
- 선택자 수정 → 다시 실행 → 같은 페이지를 다시 다운로드
- 다른 항목 수정 → 다시 실행 → 같은 페이지를 다시 다운로드
같은 페이지를 여러 번 다운로드하고 있습니다. 비효율적이고 느리며 웹사이트에 부담을 줍니다.
캐시 사용 시
- 스파이더 실행 → 페이지 다운로드 → 복사본 저장
- 선택자 수정 → 다시 실행 → 저장된 복사본 사용 (다운로드 없음!)
- 다른 항목 수정 → 다시 실행 → 여전히 저장된 복사본 사용
한 번만 다운로드하고 무한히 테스트합니다. 웹사이트는 한 번의 요청만 받습니다.
캐시 활성화 (한 줄)
settings.py에 다음을 추가하세요:
HTTPCACHE_ENABLED = True
그게 전부입니다. 이제 Scrapy가 모든 것을 캐시합니다.
스파이더를 실행하세요:
scrapy crawl myspider
첫 번째 실행: 페이지를 정상적으로 다운로드합니다. 프로젝트 폴더를 확인하면 캐시된 페이지가 저장된 새로운 .scrapy/httpcache/myspider/ 디렉터리를 볼 수 있습니다.
다시 실행:
scrapy crawl myspider
이번에는 번개처럼 빠릅니다. 실제 HTTP 요청이 없으며 모든 것이 캐시에서 제공됩니다.
캐시 작동 방식 (간단 설명)
- 첫 번째 요청 – 스파이더가 URL을 요청합니다.
- 캐시 확인 – “이미 이 페이지가 있나요?”
- 캐시 미스 – 없어요.
- 다운로드 – 웹사이트에서 가져옵니다.
- 저장 – 응답을 캐시에 저장합니다.
- 반환 – 스파이더에게 응답을 전달합니다.
다음에 같은 URL을 요청하면:
- 요청 – 스파이더가 같은 URL을 요청합니다.
- 캐시 확인 – “이 페이지가 있나요?”
- 캐시 히트 – 네!
- 반환 – 캐시된 응답을 전달합니다 (다운로드 없음!).
간단함. 빠름. 효율적.
기본 캐시 설정
캐시 보관 기간
기본적으로 캐시는 만료되지 않습니다. 만료 시간을 설정할 수 있습니다:
# settings.py
HTTPCACHE_ENABLED = True
HTTPCACHE_EXPIRATION_SECS = 86400 # 24 hours
24시간이 지나면 캐시된 페이지가 다시 다운로드됩니다.
만료를 사용할 때
- 뉴스 사이트 스크래핑 (콘텐츠가 매일 변경)
- 제품 가격 (자주 변동)
- 모든 동적 콘텐츠
만료를 사용하지 않을 때
- 개발 (페이지를 캐시된 상태로 유지하고 싶을 때)
- 정적 콘텐츠 스크래핑
- 변하지 않는 과거 데이터
캐시 저장 위치
기본 위치: .scrapy/httpcache/. 다음과 같이 변경할 수 있습니다:
# settings.py
HTTPCACHE_ENABLED = True
HTTPCACHE_DIR = 'my_custom_cache'
이제 캐시는 my_custom_cache/에 저장됩니다.
특정 상태 코드 무시
오류 페이지를 캐시하지 않음:
# settings.py
HTTPCACHE_ENABLED = True
HTTPCACHE_IGNORE_HTTP_CODES = [404, 500, 502, 503]
404와 500 오류는 캐시되지 않습니다 – 깨진 페이지를 캐시하고 싶지 않을 때입니다.
캐시 정책 (두 가지 종류)
Scrapy는 두 가지 캐시 정책을 제공합니다: DummyPolicy와 RFC2616Policy.
DummyPolicy (간단한 버전)
# settings.py
HTTPCACHE_ENABLED = True
HTTPCACHE_POLICY = 'scrapy.extensions.httpcache.DummyPolicy'
- 모든 것을 캐시합니다.
- 캐시가 최신인지 절대 확인하지 않습니다.
- 재검증을 전혀 하지 않습니다.
사용 상황: 테스트, 오프라인 개발, 혹은 스크래핑을 정확히 “재생”하고 싶을 때.
RFC2616Policy (똑똑한 버전)
# settings.py
HTTPCACHE_ENABLED = True
HTTPCACHE_POLICY = 'scrapy.extensions.httpcache.RFC2616Policy'
- HTTP 캐시 헤더(
Cache‑Control,max‑age등)를 준수합니다. - 필요할 때 재검증을 수행합니다.
사용 상황: 프로덕션 스크래퍼를 운영할 때, 웹사이트의 캐시 규칙을 따를 때, 최신 데이터를 필요로 할 때, 혹은 좋은 인터넷 시민이 되고 싶을 때.
실제 예시: 개발 vs. 프로덕션
개발 설정 (모두 캐시)
# settings.py
# Development: cache everything forever
HTTPCACHE_ENABLED = True
HTTPCACHE_POLICY = 'scrapy.extensions.httpcache.DummyPolicy'
HTTPCACHE_DIR = '.dev_cache'
HTTPCACHE_EXPIRATION_SECS = 0 # Never expire
테스트에 완벽합니다 – 한 번 다운로드하고 영원히 테스트합니다.
프로덕션 설정 (스마트 캐싱)
# settings.py
# Production: respect HTTP caching rules
HTTPCACHE_ENABLED = True
HTTPCACHE_POLICY = 'scrapy.extensions.httpcache.RFC2616Policy'
HTTPCACHE_DIR = '.prod_cache'
HTTPCACHE_EXPIRATION_SECS = 3600 # 1 hour
HTTPCACHE_IGNORE_HTTP_CODES = [404, 500, 502, 503]
웹사이트 규칙을 준수하고 필요할 때 업데이트합니다.
Source: …
실용적인 워크플로우
단계 1: 개발용 캐시 활성화
# settings.py
HTTPCACHE_ENABLED = True
여기서 워크플로우에 맞게 다른 옵션(정책, 디렉터리, 만료 등)을 조정할 수 있습니다. 즐거운 크롤링 되세요!
단계 2: 첫 실행 (캐시 채우기)
scrapy crawl myspider
이 명령은 모든 페이지를 다운로드하고 캐시에 저장합니다.
단계 3: 캐시를 이용한 개발
이제 웹사이트에 직접 접근하지 않고 스파이더를 수백 번 실행할 수 있습니다:
# 다시 실행
scrapy crawl myspider
# 셀렉터 수정
# 다시 실행
scrapy crawl myspider
# 또 다른 부분 수정
# 다시 실행
scrapy crawl myspider
모든 실행이 즉시 이루어지며 캐시에서 제공됩니다.
단계 4: 필요할 때 캐시 삭제
웹사이트 구조가 변경되었거나 최신 데이터가 필요할 때:
rm -rf .scrapy/httpcache/
그런 다음 스파이더를 다시 실행하여 최신 페이지로 캐시를 재채우세요.
요청별 캐시 제어
특정 요청에 대해 캐시를 비활성화할 수 있습니다:
def parse(self, response):
# This request won't be cached
yield scrapy.Request(
'https://example.com/dynamic',
callback=self.parse_dynamic,
meta={'dont_cache': True}
)
일부 페이지는 최신 상태를 유지해야 하고 다른 페이지는 캐시해도 될 때 유용합니다.
고급: 스토리지 백엔드
Scrapy는 HTTP 캐시를 위한 두 가지 스토리지 백엔드를 제공합니다: Filesystem(기본) 및 DBM.
Filesystem (Default)
# settings.py
HTTPCACHE_ENABLED = True
HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
장점
- 쉽게 확인 가능 (파일을 열기만 하면 됨)
- 어디서든 작동
- 간단함
단점
- 작은 파일이 많이 생성
- 수천 페이지에서는 느림
- 디스크 공간을 더 많이 차지
DBM (Database)
# settings.py
HTTPCACHE_ENABLED = True
HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.DbmCacheStorage'
장점
- 많은 페이지에서 더 빠름
- 파일 수가 적음
- 더 효율적
단점
- 확인하기 어려움
- 데이터베이스별 이슈
- 더 복잡함
팁: 대부분의 프로젝트에서는 파일시스템 백엔드를 사용하세요; 더 간단합니다.
캐시 디버깅
캐시된 내용 확인
ls -R .scrapy/httpcache/
각 요청마다 폴더가 표시됩니다. 각 폴더 안에는 다음과 같은 파일이 있습니다:
request_body– 수행된 요청request_headers– 전송된 헤더response_body– 받은 HTMLresponse_headers– 응답 헤더meta– 메타데이터
요청이 캐시되었는지 확인
Scrapy는 캐시 히트를 로그에 기록합니다:
[scrapy.core.engine] DEBUG: Crawled (200) (referer: None) ['cached']
['cached'] 접미사는 캐시 히트를 의미합니다.
캐시 히트가 없을 경우 로그는 다음과 같습니다:
[scrapy.core.engine] DEBUG: Crawled (200) (referer: None)
일반적인 함정
함정 #1: 캐시가 스파이더 실행 사이에 유지됨
캐시는 실행 간에 유지됩니다. 최신 데이터를 강제로 얻으려면 수동으로 삭제하세요:
rm -rf .scrapy/httpcache/
또는 만료 시간을 설정하세요(위 참고).
함정 #2: 서로 다른 스파이더가 동일한 캐시 디렉터리를 공유함
하나의 프로젝트에 여러 스파이더가 있으면 .scrapy/httpcache/를 공유하지만, 각 스파이더마다 자체 하위 폴더가 생성됩니다:
.scrapy/httpcache/
spider1/
spider2/
spider3/
함정 #3: POST 요청은 기본적으로 캐시되지 않음
GET 요청만 캐시됩니다. POST 요청(예: 폼 제출)은 캐시를 우회합니다:
# This won't be cached
yield scrapy.FormRequest(
'https://example.com/search',
formdata={'query': 'test'}
)
이는 POST 요청이 보통 멱등성이 없기 때문에 의도된 동작입니다.
함정 #4: 리다이렉트도 캐시됨
URL이 리다이렉트되면 그 리다이렉트도 캐시됩니다. 이후 실행에서는 다시 리다이렉트를 따라가지 않고 캐시된 최종 페이지를 반환합니다:
https://example.com → https://www.example.com
실제 시나리오
시나리오 1: 선택자 테스트
# settings.py
HTTPCACHE_ENABLED = True
HTTPCACHE_EXPIRATION_SECS = 0 # 절대 만료되지 않음
한 번 실행해 캐시를 채운 뒤, 사이트에 다시 접속하지 않고 선택자를 하루 종일 조정할 수 있습니다.
시나리오 2: 과거 데이터 스크래핑
# settings.py
HTTPCACHE_ENABLED = True
HTTPCACHE_POLICY = 'scrapy.extensions.httpcache.DummyPolicy'
HTTPCACHE_EXPIRATION_SECS = 0 # 영구 보관
절대 변하지 않는 데이터(예: 오래된 기사)에 적합합니다.
시나리오 3: 프로덕션 스크래퍼
# settings.py
HTTPCACHE_ENABLED = True
HTTPCACHE_POLICY = 'scrapy.extensions.httpcache.RFC2616Policy'
HTTPCACHE_EXPIRATION_SECS = 1800 # 30분
HTTP 캐싱 규칙을 따르며 30 분 후에 새로 고침합니다—균형 잡힌 접근 방식입니다.
시나리오 4: 오프라인 개발
# settings.py
HTTPCACHE_ENABLED = True
HTTPCACHE_IGNORE_MISSING = False # 페이지가 캐시되지 않으면 실패
스파이더가 캐시된 페이지만 사용합니다. 페이지가 없으면 다운로드를 시도하지 않고 실패하므로 비행기 안에서 작업하기에 이상적입니다.
아무도 알려주지 않는 팁
- 버전 관리로 캐시(또는 일부)를 협업 시에 사용하여 모두가 동일한 데이터를 사용하도록 합니다.
- 정책 결합: 정적 페이지에는
DummyPolicy를, 동적 페이지에는RFC2616Policy를 사용하고, 스파이더별로HTTPCACHE_POLICY를 재정의합니다. - 캐시 크기 모니터링: 주기적으로
du -sh .scrapy/httpcache/명령을 실행하고 오래된 항목을 정리하여 디스크 용량이 늘어나는 것을 방지합니다. HTTPCACHE_IGNORE_HTTP_CODES사용: 오류 페이지(예: 404, 500)의 캐시를 방지합니다.
팁 #1: CI/CD에 캐시 사용
연속 통합에서는 실제 웹사이트에 접근하고 싶지 않습니다. Scrapy HTTP 캐시를 사용하세요:
# settings.py for CI/CD
HTTPCACHE_ENABLED = True
HTTPCACHE_IGNORE_MISSING = False # Tests fail if page not cached
리포지토리에서 캐시를 미리 채워두세요. 테스트는 캐시된 페이지를 대상으로 실행되므로 빠르고 신뢰할 수 있습니다.
Tip #2: 개발자 간 캐시 공유
캐시 폴더를 버전 관리에 커밋합니다:
git add .scrapy/httpcache/
git commit -m "Add test cache"
이제 팀의 모든 사람이 테스트를 위해 동일한 캐시된 페이지를 사용하므로 일관된 결과를 얻을 수 있습니다.
팁 #3: 환경별 다른 캐시
# settings.py
import os
HTTPCACHE_ENABLED = True
if os.getenv('ENV') == 'production':
HTTPCACHE_DIR = '.prod_cache'
HTTPCACHE_POLICY = 'scrapy.extensions.httpcache.RFC2616Policy'
else:
HTTPCACHE_DIR = '.dev_cache'
HTTPCACHE_POLICY = 'scrapy.extensions.httpcache.DummyPolicy'
개발 환경과 프로덕션 환경을 위한 별도 캐시 – 양쪽의 장점을 모두 누릴 수 있습니다.
팁 #4: 캐시 압축으로 공간 절약
# settings.py
HTTPCACHE_ENABLED = True
HTTPCACHE_GZIP = True # Compress cached responses
특히 큰 페이지의 경우 디스크 공간을 크게 절약합니다.
전체 예제 스파이더
스마트 캐싱이 적용된 프로덕션 레디 스파이더:
# spider.py
import scrapy
class SmartCacheSpider(scrapy.Spider):
name = 'smartcache'
start_urls = ['https://example.com/products']
custom_settings = {
'HTTPCACHE_ENABLED': True,
'HTTPCACHE_POLICY': 'scrapy.extensions.httpcache.RFC2616Policy',
'HTTPCACHE_EXPIRATION_SECS': 3600,
'HTTPCACHE_IGNORE_HTTP_CODES': [404, 500, 502, 503],
'HTTPCACHE_GZIP': True,
'HTTPCACHE_DIR': '.product'
}
def parse(self, response):
# parsing logic here
pass