Scrapling이란?
Scrapling은 개발자 D4Vinci가 만든 자동 적응형 Python 웹 크롤링 프레임워크입니다. 2026년 GitHub Trending에서 빠르게 인기를 끌며 57,000개 이상의 Star를 획득했고, 차세대 크롤링 도구의 대표 주자가 되었습니다.
왜 Scrapling이 필요한가요?
전통적인 크롤링 프레임워크(예: Scrapy, BeautifulSoup)는 세 가지 큰 문제점을 안고 있습니다.
- 웹사이트 구조 변경 시 코드 무용지물: CSS 선택기나 XPath는 페이지가 리뉴얼되면 전부 망가집니다.
- 안티크롤링 시스템 차단: Cloudflare Turnstile, Akamai 같은 보호 장치는 일반 요청을 바로 403으로 돌려보냅니다.
- 확장성 부족: 소규모 수집에서 대규모 동시 크롤링으로 넘어가려면 코드를 많이 다시 써야 합니다.
Scrapling의 핵심 설계 철학은 "One library, zero compromises"(하나의 라이브러리, 타협 없음)입니다.
- 자동 적응형 파서: 웹사이트 구조를 자동으로 학습하고, 페이지가 업데이트되면 요소를 다시 찾아줍니다.
- 내장 안티-안티크롤링: 별도 설정 없이 Cloudflare Turnstile 등 주요 보호 장치를 우회합니다.
- Scrapy와 유사한 API: Scrapy에 익숙한 개발자는 그대로 옮겨 쓸 수 있습니다.
- 단일 요청부터 전체 크롤링까지: 동일한 API로 간단한 수집과 대규모 동시 크롤링을 모두 지원합니다.
Scrapling vs 전통적인 크롤링 도구
| 기능 | BeautifulSoup | Scrapy | Scrapling |
|---|---|---|---|
| 학습 곡선 | 낮음 | 중간 | 중간 |
| 자동 적응형 파싱 | ❌ | ❌ | ✅ |
| Cloudflare 우회 | ❌ | 플러그인 필요 | ✅ 내장 |
| 동시 크롤링 | ❌ | ✅ | ✅ |
| 동적 페이지 지원 | ❌ | 미들웨어 필요 | ✅ 내장 Playwright |
| 일시정지/재개 | ❌ | 확장 필요 | ✅ 내장 |
| 스트리밍 출력 | ❌ | ❌ | ✅ |
Scrapling 설치하기
환경 요구사항
- Python 3.8+
- pip 패키지 매니저
빠른 설치
pip install scrapling
설치 확인
from scrapling.fetchers import Fetcher
# 기본 기능 테스트
p = Fetcher.fetch('https://example.com')
print(p.title) # 페이지 제목 출력
선택적 의존성
JavaScript 렌더링이 필요한 동적 페이지를 다루려면 Playwright를 설치해야 합니다.
pip install playwright
playwright install chromium
빠르게 시작하기: 첫 번째 크롤러
예제 1: 단순 페이지 수집
간단한 예제로 Hacker News의 헤드라인 뉴스를 가져와 보겠습니다.
from scrapling.fetchers import Fetcher
# HTTP 요청 보내기
page = Fetcher.fetch('https://news.ycombinator.com/')
# CSS 선택기로 데이터 추출
stories = page.css('.titleline > a')
for story in stories[:5]: # 앞의 5개만 가져오기
title = story.text
link = story.attrs.get('href', '')
print(f"제목: {title}")
print(f"링크: {link}")
print("-" * 40)
출력 예시:
제목: Show HN: 실시간 코드 협업 도구를 만들었습니다
링크: https://github.com/example/collab-tool
----------------------------------------
제목: Ask HN: 2026년 당신이 좋아하는 Python 라이브러리는?
링크: https://news.ycombinator.com/item?id=123456
----------------------------------------
예제 2: 자동 적응형 파싱 (Auto-save)
Scrapling의 핵심 기능은 자동 적응형 파싱입니다. 처음 데이터를 긁을 때 auto_save=True를 켜면, Scrapling이 페이지 구조를 학습하고 특징을 저장합니다. 웹사이트가 리뉴얼된 후에는 adaptive=True만 넘겨주면 자동으로 목표 요소를 찾아냅니다.
from scrapling.fetchers import Fetcher
page = Fetcher.fetch('https://quotes.toscrape.com/')
# 첫 번째 수집: auto_save 활성화
quotes = page.css('.quote', auto_save=True)
for quote in quotes[:3]:
text = quote.css('.text::text').get()
author = quote.css('.author::text').get()
print(f"{text} — {author}")
웹사이트 구조가 바뀌었다면:
# 이후 수집: adaptive=True 전달
page = Fetcher.fetch('https://quotes.toscrape.com/')
quotes = page.css('.quote', adaptive=True) # 새 구조에 자동 적응!
for quote in quotes[:3]:
text = quote.css('.text::text').get()
author = quote.css('.author::text').get()
print(f"{text} — {author}")
💡 동작 원리: Scrapling은 요소의 다양한 특징(태그 종류, 근처 텍스트, 속성 패턴 등)을 기록해 둡니다. CSS 클래스 이름이 바뀌어도 다른 특징들을 통해 요소를 다시 찾아냅니다.
고급 기능
1. Cloudflare Turnstile 우회
많은 웹사이트가 Cloudflare Turnstile이나 다른 안티크롤링 시스템을 사용합니다. Scrapling의 StealthyFetcher는 이런 보호 장치를 자동으로 처리합니다.
from scrapling.fetchers import StealthyFetcher
# 적응형 모드 활성화
StealthyFetcher.adaptive = True
# Cloudflare 자동 우회
page = StealthyFetcher.fetch(
'https://example-protected-site.com',
headless=True, # 헤드리스 브라우저 모드
network_idle=True # 네트워크 유휴 상태 대기
)
# 정상적으로 데이터 추출
products = page.css('.product-item')
for product in products:
name = product.css('.name::text').get()
price = product.css('.price::text').get()
print(f"{name}: {price}")
주요 파라미터 설명:
- headless=True: 헤드리스 브라우저를 사용해 실제 사용자 행동을 모방합니다.
- network_idle=True: 모든 네트워크 작업이 끝날 때까지 기다립니다(SPA 애플리케이션에 적합).
- adaptive=True: 자동 적응형 파싱을 활성화합니다.
2. 동적 페이지 렌더링 (Playwright)
JavaScript 렌더링이 필요한 사이트에는 DynamicFetcher를 사용합니다.
from scrapling.fetchers import DynamicFetcher
page = DynamicFetcher.fetch(
'https://spa-example.com',
wait_for='.content-loaded', # 특정 요소가 나타날 때까지 대기
timeout=30000 # 타임아웃 시간(밀리초)
)
# 동적으로 로드된 콘텐츠 추출
articles = page.css('article')
for article in articles:
title = article.css('h2::text').get()
summary = article.css('.summary::text').get()
print(f"{title}\n{summary}\n")
3. 비동기 동시 수집
AsyncFetcher를 사용해 높은 동시성으로 수집합니다.
import asyncio
from scrapling.fetchers import AsyncFetcher
async def fetch_multiple_pages():
urls = [
'https://example.com/page/1',
'https://example.com/page/2',
'https://example.com/page/3',
'https://example.com/page/4',
'https://example.com/page/5',
]
# 동시에 요청 보내기
pages = await AsyncFetcher.fetch_many(urls, concurrency=3)
for url, page in zip(urls, pages):
if page:
title = page.title
print(f"{url}: {title}")
else:
print(f"{url}: 요청 실패")
asyncio.run(fetch_multiple_pages())
Spider 프레임워크: 대규모 크롤링
Scrapling은 Scrapy와 유사한 Spider 프레임워크를 제공하며, 대규모 동시 크롤링을 지원합니다.
기본 Spider
from scrapling.spiders import Spider, Response
class QuoteSpider(Spider):
name = "quotes"
start_urls = ["https://quotes.toscrape.com/"]
async def parse(self, response: Response):
# 현재 페이지에서 명언 추출
for quote in response.css('.quote'):
yield {
"text": quote.css('.text::text').get(),
"author": quote.css('.author::text').get(),
"tags": quote.css('.tag::text').getall()
}
# 다음 페이지로 이동
next_page = response.css('.next a::attr(href)').get()
if next_page:
yield self.follow(next_page, callback=self.parse)
# 크롤러 시작
QuoteSpider().start()
동시성 설정
class MultiPageSpider(Spider):
name = "multi-page"
start_urls = [f"https://example.com/page/{i}" for i in range(1, 101)]
# 동시성 설정
custom_settings = {
"concurrency": 5, # 최대 동시 접속 수
"download_delay": 1, # 다운로드 지연(초)
"robots_txt_obey": True, # robots.txt 준수
}
async def parse(self, response: Response):
title = response.css('h1::text').get()
yield {"url": response.url, "title": title}
MultiPageSpider().start()
일시정지 및 재개
Scrapling은 checkpoint 기반 지속화를 지원합니다. Ctrl+C로 우아하게 종료한 후, 다음 실행 시 자동으로 복구됩니다.
class LongRunningSpider(Spider):
name = "long-crawl"
start_urls = ["https://large-site.com/"]
custom_settings = {
"checkpoint_dir": "./checkpoints", # 체크포인트 디렉터리
}
async def parse(self, response: Response):
# 데이터 추출...
yield {"data": "..."}
# 계속 크롤링
for link in response.css('a::attr(href)').getall():
yield self.follow(link, callback=self.parse)
LongRunningSpider().start()
실전 사례
사례 1: GitHub Trending 프로젝트 수집
from scrapling.fetchers import Fetcher
def scrape_github_trending():
page = Fetcher.fetch('https://github.com/trending')
repos = page.css('.Box-row')
trending = []
for repo in repos[:10]:
name = repo.css('h2 a::text').get('').strip()
description = repo.css('p.col-9::text').get('').strip()
stars = repo.css('[href$=stargazers] span::text').get('').strip()
language = repo.css('[itemprop=programmingLanguage]::text').get('').strip()
trending.append({
"name": name,
"description": description,
"stars": stars,
"language": language
})
return trending
if __name__ == "__main__":
results = scrape_github_trending()
for repo in results:
print(f"📦 {repo['name']}")
print(f" {repo['description'][:80]}...")
print(f" ⭐ {repo['stars']} | 📝 {repo['language']}")
print()
사례 2: 이커머스 제품 가격 모니터링
from scrapling.fetchers import StealthyFetcher
import json
from datetime import datetime
def monitor_prices():
urls = [
"https://amazon.com/dp/B08N5WRWNW",
"https://amazon.com/dp/B0BSHF7WHW",
"https://amazon.com/dp/B09G9FPHY6",
]
results = []
for url in urls:
page = StealthyFetcher.fetch(url, headless=True)
title = page.css('#productTitle::text').get('').strip()
price = page.css('.a-price .a-offscreen::text').get('').strip()
results.append({
"url": url,
"title": title,
"price": price,
"timestamp": datetime.now().isoformat()
})
print(f"✅ {title[:50]}... - {price}")
# JSON으로 저장
with open('price_monitor.json', 'w', encoding='utf-8') as f:
json.dump(results, f, ensure_ascii=False, indent=2)
print(f"\n📊 {len(results)}개의 가격 데이터를 price_monitor.json에 저장했습니다")
if __name__ == "__main__":
monitor_prices()
사례 3: 스트리밍 출력(실시간 처리)
오래 걸리는 크롤링 작업에서는 스트리밍 모드로 데이터를 실시간으로 처리할 수 있습니다.
from scrapling.spiders import Spider, Response
class StreamingSpider(Spider):
name = "streaming-demo"
start_urls = ["https://quotes.toscrape.com/"]
async def parse(self, response: Response):
for quote in response.css('.quote'):
item = {
"text": quote.css('.text::text').get(),
"author": quote.css('.author::text').get()
}
yield item # 모두 기다리지 않고 즉시产出
next_page = response.css('.next a::attr(href)').get()
if next_page:
yield self.follow(next_page, callback=self.parse)
# 스트리밍 소비
async def main():
spider = StreamingSpider()
async for item in spider.stream():
# 각 item을 실시간으로 처리
print(f"수신: {item['text'][:50]}... by {item['author']}")
# 데이터베이스에 즉시 저장하거나 메시지 큐로 전송 가능
import asyncio
asyncio.run(main())
고급 팁
1. 프록시 로테이션
from scrapling.spiders import Spider, Response
class ProxySpider(Spider):
name = "proxy-spider"
start_urls = ["https://httpbin.org/ip"]
custom_settings = {
"proxy_list": [
"http://proxy1.example.com:8080",
"http://proxy2.example.com:8080",
"http://proxy3.example.com:8080",
],
"proxy_rotation": "per_request", # 요청마다 프록시 교체
}
async def parse(self, response: Response):
ip = response.json().get('origin')
print(f"현재 IP: {ip}")
2. 커스텀 Export 파이프라인
from scrapling.spiders import Spider, Response
import csv
class CSVSpider(Spider):
name = "csv-export"
start_urls = ["https://quotes.toscrape.com/"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.csv_file = open('quotes.csv', 'w', newline='', encoding='utf-8')
self.writer = csv.writer(self.csv_file)
self.writer.writerow(['Text', 'Author', 'Tags'])
async def parse(self, response: Response):
for quote in response.css('.quote'):
text = quote.css('.text::text').get()
author = quote.css('.author::text').get()
tags = ', '.join(quote.css('.tag::text').getall())
self.writer.writerow([text, author, tags])
next_page = response.css('.next a::attr(href)').get()
if next_page:
yield self.follow(next_page, callback=self.parse)
def close(self, reason):
self.csv_file.close()
print(f"✅ 데이터가 quotes.csv에 저장되었습니다")
3. 개발 모드(응답 캐싱)
파싱 로직을 디버깅할 때 서버에 반복적으로 요청하지 않도록 합니다.
from scrapling.spiders import Spider, Response
class DevSpider(Spider):
name = "dev-mode"
start_urls = ["https://example.com"]
custom_settings = {
"dev_mode": True, # 개발 모드 활성화
"cache_dir": "./http_cache", # 캐시 디렉터리
}
async def parse(self, response: Response):
# 첫 실행: 응답을 디스크에 캐싱
# 이후 실행: 디스크에서 직접 읽어옴, 네트워크 요청 불필요
title = response.css('h1::text').get()
yield {"title": title}
자주 묻는 질문
Q1: Scrapling과 Scrapy의 차이점은 무엇인가요?
A: Scrapling은 Scrapy의 현대화된 강화판이라고 볼 수 있습니다. - 자동 적응형 파싱: Scrapy의 선택기는 페이지가 리뉴얼되면 작동하지 않지만, Scrapling은 자동으로 적응합니다. - 내장 안티-안티크롤링: Scrapy는 Cloudflare를 우회하려면 추가 미들웨어가 필요하지만, Scrapling은 바로 사용할 수 있습니다. - 더 간결한 API: Scrapling의 Fetcher API는 소규모 빠른 수집에 더 적합합니다.
Scrapy에 이미 익숙하다면 Scrapling으로 옮기는 데 거의 학습 비용이 들지 않습니다.
Q2: 로그인 후 페이지는 어떻게 처리하나요?
A: StealthyFetcher나 DynamicFetcher로 로그인을 시뮬레이션합니다.
from scrapling.fetchers import DynamicFetcher
page = DynamicFetcher.fetch(
'https://example.com/login',
headless=True,
wait_for='#dashboard' # 로그인 후 대시보드로 이동할 때까지 대기
)
# 로그인 작업 수행(Playwright 통해)
page.page.fill('#username', 'your_username')
page.page.fill('#password', 'your_password')
page.page.click('#login-button')
page.page.wait_for_selector('#dashboard')
# 이제 로그인 후 콘텐츠를 수집할 수 있습니다
data = page.css('.private-data::text').getall()
Q3: 차단되지 않도록 크롤링 속도를 제한하려면?
A: Spider에서 다운로드 지연과 동시성 제한을 설정합니다.
class PoliteSpider(Spider):
custom_settings = {
"concurrency": 2, # 동시성 낮추기
"download_delay": 2, # 요청 간 2초 간격
"robots_txt_obey": True, # robots.txt 준수
}
Q4: Scrapling은 어떤 선택자를 지원하나요?
A: CSS 선택자와 XPath를 모두 지원합니다.
# CSS 선택자
page.css('.class-name::text').get()
page.css('#id-name').getall()
# XPath
page.xpath('//div[@class="example"]/text()').get()
요약
Scrapling은 2026년 가장 주목할 만한 Python 크롤링 프레임워크입니다. 자동 적응형 파싱, 안티-안티크롤링 능력, Scrapy와 유사한 API를 완벽하게 결합해, 개발자가 최소한의 코드로 가장 안정적인 크롤러를 만들 수 있게 해줍니다.
핵심 장점 요약: - ✅ 자동 적응형 파서: 페이지가 리뉴얼되어도 요소를 자동으로 다시 찾아냄 - ✅ 내장 안티-안티크롤링: 별도 설정 없이 Cloudflare Turnstile 우회 - ✅ 단일 요청부터 전체 크롤링까지: 동일한 API로 모든 시나리오 커버 - ✅ 동시성, 일시정지/재개, 스트리밍 출력: 프로덕션급 기능 완비
리소스 링크: - GitHub 저장소 - 공식 문서 - Discord 커뮤니티
이 글이 도움이 되었다면 더 많은 개발자와 공유해 주세요!