현대 Scrapy 개발자 가이드 (파트 1): 첫 번째 Spider 만들기
Source: Dev.to
Scrapy는 벅차 보일 수 있지만 – 반드시 그래야 하는 것은 아니다
거대한 강력한 프레임워크이며, 문서가 초보자에게는 압도적으로 느껴질 수 있습니다. 어디서부터 시작해야 할까요?
이 결정적인 가이드에서는 실제 다중 페이지 크롤링 스파이더를 단계별로 만드는 방법을 안내합니다. 빈 폴더에서 구조화된 JSON 파일까지 약 15 분 안에 완성할 수 있습니다. 최신 async/await 파이썬을 사용하고 다음 내용을 다룹니다:
- 프로젝트 설정
- 셀렉터 찾기
- 링크 따라가기(크롤링)
- 데이터 저장
우리는 books.toscrape.com의 “Fantasy” 카테고리를 크롤링하고, “Next” 버튼을 따라 해당 카테고리의 모든 페이지를 순회하며, 각 책의 링크를 따라가 name, price, URL을 스크랩하는 Scrapy 스파이더를 만들 것입니다. 결과는 깔끔한 books.json 파일에 저장됩니다.
최종 스파이더 구축
# tutorial/spiders/books.py
import scrapy
class BooksSpider(scrapy.Spider):
name = "books"
allowed_domains = ["toscrape.com"]
# Starting URL (first page of the Fantasy category)
start_urls = [
"https://books.toscrape.com/catalogue/category/books/fantasy_19/index.html"
]
# ------------------------------------------------------------------
# Async version of start_requests – Scrapy will call this automatically
# ------------------------------------------------------------------
async def start_requests(self):
for url in self.start_urls:
yield scrapy.Request(url, callback=self.parse_listpage)
# ------------------------------------------------------------------
# Parse a category list page, follow book links and pagination
# ------------------------------------------------------------------
async def parse_listpage(self, response):
# 1️⃣ Extract all book detail page URLs on the current list page
product_urls = response.css("article.product_pod h3 a::attr(href)").getall()
for url in product_urls:
# `response.follow` correctly joins relative URLs
yield response.follow(url, callback=self.parse_book)
# 2️⃣ Follow the “Next” button, if it exists
next_page_url = response.css("li.next a::attr(href)").get()
if next_page_url:
yield response.follow(next_page_url, callback=self.parse_listpage)
# ------------------------------------------------------------------
# Parse an individual book page and yield the scraped data
# ------------------------------------------------------------------
async def parse_book(self, response):
yield {
"name": response.css("h1::text").get(),
"price": response.css("p.price_color::text").get(),
"url": response.url,
}
1️⃣ 프로젝트 설정
Prerequisite: Python 3.x가 설치되어 있어야 합니다.
의존성을 격리하기 위해 가상 환경을 사용할 것입니다. 표준venv+pip워크플로우 또는 최신uv도구를 사용할 수 있습니다.
프로젝트 폴더 및 가상 환경 만들기
# Create a new folder
mkdir scrapy_project
cd scrapy_project
# Option 1: Standard venv + pip
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# Option 2: Using uv (fast, modern alternative)
uv init
Scrapy 설치
# Option 1: pip
pip install scrapy
# Option 2: uv
uv add scrapy
# (If you used uv to create the env, it’s already activated)
2️⃣ Scrapy 프로젝트 보일러플레이트 생성
# '.'은 Scrapy에게 현재 폴더에 프로젝트를 생성하도록 알려줍니다
scrapy startproject tutorial .
이제 tutorial/ 패키지와 scrapy.cfg 파일이 보일 것입니다. tutorial/ 폴더에는 프로젝트 로직이 모두 들어 있습니다.
첫 번째 스파이더 생성
# tutorial/spiders/books.py 파일을 생성합니다
scrapy genspider books toscrape.com
3️⃣ 프로젝트 설정 조정
tutorial/settings.py 파일을 열고 다음과 같이 변경합니다.
robots.txt 비활성화 (테스트 사이트 전용)
# By default Scrapy obeys robots.txt – turn it off for this demo site
ROBOTSTXT_OBEY = False
크롤링 속도 향상 (테스트 사이트 전용)
# Increase concurrency and remove download delay
CONCURRENT_REQUESTS = 16
DOWNLOAD_DELAY = 0
⚠️ Warning: 이 설정은 테스트 사이트
toscrape.com에 대해서만 안전합니다. 실제 웹사이트를 스크래핑할 때는 항상 대상 사이트의robots.txt를 준수하고, 적절한 동시성/지연 값을 사용하여 예의를 지키세요.
4️⃣ scrapy shell로 사이트 탐색하기
Scrapy 셸은 CSS 선택자를 찾기에 최적입니다.
Fantasy 카테고리 페이지에서 셸 열기
scrapy shell https://books.toscrape.com/catalogue/category/books/fantasy_19/index.html
이제 쿼리할 수 있는 response 객체가 있습니다.
모든 책 링크 찾기
>>> response.css("article.product_pod h3 a::attr(href)").getall()
[
'../../../../the-host_979/index.html',
'../../../../the-hunted_978/index.html',
# …
]
“다음” 페이지 링크 찾기 (페이지네이션)
>>> response.css("li.next a::attr(href)").get()
'page-2.html'
단일 책 페이지에서 셸을 열어 데이터 선택자 가져오기
scrapy shell https://books.toscrape.com/catalogue/the-host_979/index.html
>>> response.css("h1::text").get()
'The Host'
>>> response.css("p.price_color::text").get()
'£25.82'
이제 스파이더에 필요한 모든 선택자를 확보했습니다.
5️⃣ Write the Spider
tutorial/spiders/books.py에 있는 보일러플레이트 코드를 이 가이드 상단에 표시된 최종 스파이더 코드(비동기 버전)로 교체합니다. 파일을 저장합니다.
6️⃣ 스파이더 실행 및 JSON으로 내보내기
scrapy crawl books -o books.json
Scrapy는 판타지 카테고리의 모든 페이지를 크롤링하고, 각 책 링크를 따라가며 이름, 가격, URL을 추출한 뒤 결과를 books.json에 기록합니다.
이렇게 하면 48개의 항목이 들어 있는 깔끔한 JSON 파일이 생성됩니다. 예시:
[
{
"name": "The Host",
"price": "£25.82",
"url": "https://books.toscrape.com/catalogue/the-host_979/index.html"
},
{
"name": "The Hunted",
"price": "£23.45",
"url": "https://books.toscrape.com/catalogue/the-hunted_978/index.html"
}
// …
]
🎉 성공했습니다!
이제 완전하게 작동하는 비동기 Scrapy 스파이더가 있습니다:
- 카테고리 페이지에서 시작합니다
- 페이지네이션을 자동으로 따라갑니다
- 각 제품 페이지를 방문합니다
- 구조화된 데이터를 추출합니다
- 모든 데이터를 깔끔한 JSON 파일에 저장합니다
자유롭게 실험해 보세요—필드를 더 추가하거나, 데이터를 데이터베이스에 저장하거나, 다른 사이트에 맞게 스파이더를 조정해 보세요. 즐거운 크롤링 되세요!
Scrapy 스파이더 개요
아래는 책 카탈로그를 크롤링하고, 페이지네이션을 따라가며, 각 제품 페이지에서 기본 정보를 추출하는 최소한의 async Scrapy 스파이더입니다.
import scrapy
class BooksSpider(scrapy.Spider):
name = "books"
start_urls = ["http://books.toscrape.com/"]
# ------------------------------------------------------------------
# 1️⃣ Spider entry point – called once when the spider starts.
# ------------------------------------------------------------------
async def start(self):
# Yield the first request; its response will be handled by `parse_listpage`.
yield scrapy.Request(self.url, callback=self.parse_listpage)
# ------------------------------------------------------------------
# 2️⃣ Parse the *category* (list) page.
# ------------------------------------------------------------------
async def parse_listpage(self, response):
# 1️⃣ Get all product URLs from the current page.
product_urls = response.css("article.product_pod h3 a::attr(href)").getall()
# 2️⃣ Follow each product URL and send the response to `parse_book`.
for url in product_urls:
yield response.follow(url, callback=self.parse_book)
# 3️⃣ Locate the “Next” page link (if any).
next_page_url = response.css("li.next a::attr(href)").get()
# 4️⃣ If a next page exists, follow it and recurse back to this method.
if next_page_url:
yield response.follow(next_page_url, callback=self.parse_listpage)
# ------------------------------------------------------------------
# 3️⃣ Parse the *product* (book) page.
# ------------------------------------------------------------------
async def parse_book(self, response):
# Yield a dictionary containing the data we want to export.
yield {
"name": response.css("h1::text").get(),
"price": response.css("p.price_color::text").get(),
"url": response.url,
}
Note:
response.follow은 상대 URL(e.g.,page-2.html)을 자동으로 해결하므로 전체 URL을 직접 만들 필요가 없습니다.
스파이더 실행
- 프로젝트 루트에서 터미널을 엽니다.
- 스파이더를 실행합니다:
scrapy crawl books
Scrapy가 시작되는 것을 확인하고 로그에 48개 아이템이 모두 스크랩되는 것을 볼 수 있습니다.
데이터 내보내기
Scrapy의 내장 Feed Exporter는 결과 저장을 매우 간단하게 해줍니다. -o (output) 플래그를 사용하여 스크랩된 아이템을 파일에 기록합니다:
scrapy crawl books -o books.json
이 명령으로 스파이더를 실행하면 프로젝트 루트에 books.json 파일이 생성되고, 48개의 아이템이 깔끔하고 구조화된 JSON 형식으로 들어갑니다.
배운 내용
- 최신 async Scrapy 프로젝트를 설정했습니다.
- 필요한 데이터에 대한 CSS 선택자를 찾았습니다.
- 링크를 따라가고 페이지네이션을 자동으로 처리했습니다.
- 한 명령으로 스크랩한 데이터를 내보냈습니다.
이것은 시작에 불과합니다!
💬 TALK: 이 Scrapy 코드가 막히셨나요? 유지보수자와 Discord의 5천 명 이상의 개발자에게 물어보세요.
▶️ WATCH: 이 게시물은 우리 영상에 기반합니다—전체 walkthrough를 YouTube 채널에서 시청하세요.
📩 READ: 더 보고 싶나요? Part 2에서는 Scrapy Items와 Pipelines를 다룰 예정입니다. 놓치지 않으려면 Extract 뉴스레터를 구독하세요.