Scrapy 요청 및 응답: 완전 초보자 가이드 (문서가 알려주지 않는 비밀 포함)
Source: Dev.to
1. 기본
| Concept | What it means in Scrapy |
|---|---|
| Request | “이 URL을 방문하고 싶어요” 라고 말하는 객체. |
| Response | 웹사이트가 반환한 내용(HTML, JSON 등)을 포함하는 객체. |
Think of web scraping like a conversation:
Request: "Hey website, can you show me this page?"
Response: "Sure, here's the HTML!"
2. Scrapy가 내부에서 수행하는 작업
간단한 스파이더:
import scrapy
class MySpider(scrapy.Spider):
name = 'myspider'
start_urls = ['https://example.com']
def parse(self, response):
# Do something with response
pass
실제로 일어나는 일
def start_requests(self):
for url in self.start_urls:
yield scrapy.Request(url=url, callback=self.parse)
Scrapy는 start_urls에 있는 각 URL에 대해 자동으로 Request를 생성하고 이를 parse에 전달합니다.
3. 요청을 수동으로 만들기
요청을 직접 만들어 모든 세부 사항을 제어할 수 있습니다:
import scrapy
class MySpider(scrapy.Spider):
name = 'myspider'
def start_requests(self):
yield scrapy.Request(
url='https://example.com',
callback=self.parse,
method='GET',
headers={'User-Agent': 'My Custom Agent'},
cookies={'session': 'abc123'},
meta={'page_num': 1},
dont_filter=False,
priority=0,
)
def parse(self, response):
# Process response
pass
3.1 요청 매개변수 (빠른 참고)
| Parameter | Description |
|---|---|
url | 대상 URL (필수). |
callback | Response를 받을 함수. 기본값은 parse. |
method | HTTP 메서드(GET, POST, PUT, DELETE, …). 기본값: GET. |
body | 원시 요청 본문(POST, PUT에 유용). |
headers | 사용자 정의 요청 헤더. |
cookies | 요청과 함께 보낼 쿠키. |
meta | Response에 전달되는 임의의 dict(response.meta). 콜백 간 데이터 공유에 유용. |
dont_filter | True이면 Scrapy가 이 URL을 중복으로 필터링하지 않습니다. |
priority | 정수 우선순위; 값이 클수록 먼저 처리됨(기본값 = 0). |
예시
# 1️⃣ Simple URL
yield scrapy.Request(url='https://example.com/products')
# 2️⃣ Custom callback
yield scrapy.Request(
url='https://example.com/products',
callback=self.parse_products,
)
def parse_products(self, response):
# Handle response here
pass
# 3️⃣ POST request with JSON body
yield scrapy.Request(
url='https://example.com/api',
method='POST',
body='{"key": "value"}',
headers={'Content-Type': 'application/json'},
)
# 4️⃣ Custom headers
yield scrapy.Request(
url='https://example.com',
headers={
'User-Agent': 'Mozilla/5.0',
'Accept': 'text/html',
'Referer': 'https://google.com',
},
)
# 5️⃣ Cookies
yield scrapy.Request(
url='https://example.com',
cookies={'session_id': '12345', 'user': 'john'},
)
# 6️⃣ Passing data via meta
yield scrapy.Request(
url='https://example.com/details',
meta={'product_name': 'Widget', 'price': 29.99},
callback=self.parse_details,
)
def parse_details(self, response):
name = response.meta['product_name']
price = response.meta['price']
# Do something with name & price
# 7️⃣ Bypass duplicate filter
yield scrapy.Request(
url='https://example.com',
dont_filter=True,
)
# 8️⃣ Prioritise a request
yield scrapy.Request(
url='https://example.com/important',
priority=10, # processed before priority 0 requests
)
Source: …
4. Response 객체
요청이 완료되면 Scrapy는 콜백에 Response 객체를 전달합니다. 일반적으로 다음과 같은 속성을 사용합니다:
def parse(self, response):
# 기본 속성
url = response.url # 최종 URL (리다이렉트 후)
body = response.body # 원시 바이트
text = response.text # 디코딩된 문자열 (기본 UTF‑8)
status = response.status # HTTP 상태 코드 (200, 404, …)
headers = response.headers # 응답 헤더 (대소문자 구분 없는 dict)
# 요청과 연결된 정보
request = response.request # 원본 Request 객체
meta = response.meta # 요청에서 전달된 meta dict
4.1 데이터 선택
# CSS 선택자 (가장 가독성이 좋음)
titles = response.css('h1.title::text').getall()
first_title = response.css('h1.title::text').get()
# XPath 선택자 (보다 강력함)
titles = response.xpath('//h1[@class="title"]/text()').getall()
4.2 링크 따라가기
# 수동 방식 (冗長)
next_page = response.css('a.next::attr(href)').get()
if next_page:
full_url = response.urljoin(next_page)
yield scrapy.Request(full_url, callback=self.parse)
# 권장 방식 – `response.follow`
next_page = response.css('a.next::attr(href)').get()
if next_page:
yield response.follow(next_page, callback=self.parse)
# 선택자를 바로 전달할 수도 있습니다:
yield response.follow(
response.css('a.next::attr(href)').get(),
callback=self.parse,
)
# 혹은 모든 <a> 태그를 순회하면서:
for link in response.css('a'):
yield response.follow(link, callback=self.parse_page)
response.follow() 은 자동으로:
- 상대 URL을 처리합니다 (
urljoin내부적으로 사용). - 선택자를 전달하면
href속성을 추출합니다. Request객체를 생성해 줍니다 (기본callback포함).
5. 디버깅 및 내부 탐색
때때로 응답을 생성한 원본 요청을 살펴볼 필요가 있습니다(특히 리다이렉트가 발생한 경우).
def parse(self, response):
# Original request data
original_url = response.request.url
original_headers = response.request.headers
original_meta = response.request.meta
# Log useful info
self.logger.info(f'Requested: {original_url}')
self.logger.info(f'Got back: {response.url}') # May differ after redirects
TL;DR
- Requests는 완전히 구성 가능한 객체(
url,method,headers,cookies,meta,priority, …)입니다. - Responses는 데이터를 추출하는 데 필요한 모든 정보를 제공합니다(
url,body,text,status,headers, 그리고 원본 요청에 대한 역참조). - 깔끔하고 간결한 링크 추적 로직을 위해 **
response.follow()**를 사용하세요. - **
meta**를 활용해 콜백 간에 데이터를 전달하고, **priority/dont_filter**를 사용해 크롤링 순서와 중복 처리 방식을 제어하세요.
이러한 세부 사항을 숙지하면 기본을 넘어 견고하고 효율적인 Scrapy 스파이더를 작성할 수 있습니다—필요한 작업을 정확히 수행하고 숨은 놀라움은 없습니다. 즐거운 크롤링 되세요!
Source: …
Scrapy 빠른 참고 치트 시트
아래는 정리되고 구조화된 유용한 Scrapy 패턴 모음입니다. 원본 스니펫은 그대로 유지하되, 포맷만 개선했습니다.
1. Response 헤더 다루기
def parse(self, response):
# Get all headers
all_headers = response.headers
# Get a specific header
content_type = response.headers.get('Content-Type')
# Check cookies the server sent back
cookies = response.headers.getlist('Set-Cookie')
# Useful for debugging blocks
server = response.headers.get('Server')
self.logger.info(f'Server type: {server}')
2. 리다이렉트 시 meta 보존하기
def start_requests(self):
yield scrapy.Request(
'https://example.com/redirect',
meta={'important': 'data'}, # custom meta data
callback=self.parse
)
def parse(self, response):
# Even after a redirect, the meta dict is still there!
data = response.meta['important']
# The final URL may be different
self.logger.info(f'Ended up at: {response.url}')
3. priority 로 크롤링 순서 제어하기
def parse_listing(self, response):
# High priority for product pages (process first)
for product in response.css('.product'):
url = product.css('a::attr(href)').get()
yield response.follow(
url,
callback=self.parse_product,
priority=10 # higher number → earlier processing
)
# Low priority for pagination (process later)
next_page = response.css('.next::attr(href)').get()
if next_page:
yield response.follow(
next_page,
callback=self.parse_listing,
priority=0 # default priority
)
Tip: “반드시 필요” 페이지에는 높은 우선순위를, 페이지네이션이나 보조 콘텐츠에는 낮은 우선순위를 사용하세요.
4. 폼 제출 – FormRequest
a) 간단한 POST 요청
import scrapy
class LoginSpider(scrapy.Spider):
name = 'login'
def start_requests(self):
yield scrapy.FormRequest(
url='https://example.com/login',
formdata={
'username': 'myuser',
'password': 'mypass'
},
callback=self.after_login
)
def after_login(self, response):
if 'Welcome' in response.text:
self.logger.info('Login successful!')
else:
self.logger.error('Login failed!')
b) 페이지에서 자동으로 폼 채우기 (from_response)
class LoginSpider(scrapy.Spider):
name = 'login'
start_urls = ['https://example.com/login']
def parse(self, response):
# Automatically locate the form, keep hidden fields (e.g., CSRF)
# and submit the supplied data.
yield scrapy.FormRequest.from_response(
response,
formdata={
'username': 'myuser',
'password': 'mypass'
},
callback=self.after_login
)
def after_login(self, response):
# Now you're logged in – continue crawling.
yield response.follow('/dashboard', callback=self.parse_dashboard)
FormRequest.from_response() 가 수행하는 작업
- 첫 번째
<form>요소(또는formname/formid와 일치하는 요소)를 찾습니다. - 숨겨진 입력 필드(예: CSRF 토큰)를 포함한 모든 폼 필드를 추출합니다.
formdata에 제공한 필드만 덮어씁니다.- 요청을 전송합니다.
5. meta 로 페이지 번호 추적하며 페이지네이션하기
import scrapy
class ProductSpider(scrapy.Spider):
name = 'products'
def start_requests(self):
yield scrapy.Request(
'https://example.com/products?page=1',
meta={'page': 1},
callback=self.parse
)
def parse(self, response):
page = response.meta['page']
self.logger.info(f'Scrapin
6. 체이닝 요청 – 목록에서 상세 페이지로
import scrapy
class DetailSpider(scrapy.Spider):
name = 'details'
start_urls = ['https://example.com/products']
def parse(self, response):
"""Scrape product listings and queue detail pages."""
for product in response.css('.product'):
item = {
'name': product.css('h2::text').get(),
'price': product.css('.price::text').get()
}
detail_url = product.css('a::attr(href)').get()
yield response.follow(
detail_url,
callback=self.parse_detail,
meta={'item': item} # pass the partially‑filled item forward
)
def parse_detail(self, response):
"""Enrich the item with data from the detail page."""
item = response.meta['item']
item['description'] = response.css('.description::text').get()
item['rating'] = response.css('.rating::text').get()
item['reviews'] = response.css('.reviews::text').get()
yield item