Scrapy는 JavaScript를 실행하지 못한다는 이유로 흔히 "현대적인 웹사이트를 못 다룬다"는 평가를 받습니다. 그런데 그 인식은 사실 반대에 가깝습니다. 페이지를 렌더링하지 않는 것 자체가 설계의 핵심이고, 이 원리를 제대로 이해하고 나면 단점이라기보다 의도된 전략으로 보이기 시작합니다.
저도 직접 한 번 테스트해 보면서 이걸 확인했습니다. JavaScript로 렌더링되는 카탈로그를 만들어 두고, Scrapy로 브라우저가 보여줄 페이지를 지정해 보니 상품 카드가 0개만 잡혔습니다. 그런데 같은 페이지가 백그라운드에서 조용히 호출하던 JSON 엔드포인트를 같은 스파이더로 겨냥하자 8/8개 아이템이 깔끔하게 돌아왔습니다. 같은 도구, 같은 세션, 완전히 다른 결과였죠. 그리고 이 두 숫자의 차이가 바로 이 리뷰의 핵심입니다.
Scrapy는 실제로 무엇이고, 무엇이 아닌가

Scrapy는 웹사이트를 크롤링하고 구조화된 데이터를 추출하기 위한 Python 프레임워크입니다. 유지보수팀이 개요 문서에서 직접 이렇게 설명하고 있는데, 직접 써 보니 과장이 아니라 딱 맞는 설명이었습니다. Python 개발자가 진지하게 데이터 수집을 할 때 무엇을 쓰냐고 물으면, 오래전부터 거의 자동으로 나오는 이름이기도 합니다. 저장소 지표도 이를 뒷받침합니다. 2026-07-07 기준으로 약 62,981개의 GitHub 스타(scrapy/scrapy), 11,773개 포크, 590개의 오픈 이슈를 보유하고 있습니다. BSD-3-Clause 라이선스, Python 3.10 이상 지원, 그리고 제가 직접 시험한 버전은 2.17.0이었습니다. 마침 테스트를 진행한 당일 아침에 이 버전이 배포됐기 때문에, 이번엔 버전 태그를 따로 붙일 필요도 없었습니다.
새로운 세대의 AI 크롤러들과 구분되는 핵심 한 줄은 이겁니다. Scrapy는 기본적으로 HTTP 전용입니다. 브라우저도, 렌더링 엔진도 없습니다. 네트워크로 HTML을 받아 파서에 넘기고, CSS 선택자나 XPath로 필요한 필드를 뽑아냅니다. 이걸 한계라고 부르는 말은 절반만 맞고, 설계 의도는 놓친 표현입니다. Scrapy의 전제는 일반적인 수집 작업에서 헤드리스 Chrome을 띄우는 게 대개 최선이 아니라는 점입니다. 더 똑똑한 방법은 페이지가 이미 호출하고 있는 데이터 요청을 찾아서 그 요청을 바로 치는 것입니다.
이건 제가 도구에 의미를 덧붙인 게 아닙니다. 공식 동적 콘텐츠 문서에도 분명하게 적혀 있습니다. 먼저 내부 데이터 요청을 찾아 재현하고, 그게 현실적으로 어렵다면 마지막 수단으로 헤드리스 브라우저를 쓰라는 식입니다. 대부분의 스크래퍼는 브라우저를 먼저 열고 API는 아예 생각하지 않지만, Scrapy는 기본값을 거꾸로 세워 둡니다.
핵심 기능과, 그 기능마다 담긴 설계 판단
내부 구조를 보면 Scrapy는 사용자를 딱 하나로 가정합니다. 클릭 몇 번으로 끝내려는 사람이 아니라, 제어권을 쥐고 싶은 개발자라는 점입니다.
Spiders. 클래스를 작성하고 시작 URL을 넘긴 뒤, 아이템을 반환하거나 다음 링크를 따라가는 parse 콜백을 정의합니다. 노코드 추출 도구보다 손이 더 가지만, 그 대신 어떤 데이터를 어디까지 수집할지 정확하게 통제할 수 있습니다.
Selectors. 파싱은 parsel 위에서 돌아가고, 그 아래에는 lxml이 있습니다. CSS와 XPath 모두 일급 지원입니다. lxml 기반이라 선택 속도가 빠르고, 추출 코드도 문자열 조각 모음처럼 보이지 않고 의도가 읽히는 형태가 됩니다.
Feed exports. 스파이더를 파일에 연결하면 Scrapy가 아이템을 JSON, JSON Lines, CSV, XML로 별도 작업 없이 직렬화합니다. 제 테스트에서도 정적 카탈로그 스파이더 하나가 export 코드를 한 줄도 쓰지 않고 JSON과 CSV를 모두 출력했습니다. feed export는 실제로 잘 작동합니다.
AutoThrottle와 크롤링 제어. 요청은 Twisted 위에서 비동기적으로 스케줄링되며, 동시성 제한, 다운로드 지연, 깊이 제한, 적응형 속도 제한을 위한 AutoThrottle, robots.txt 준수 기능을 제공합니다. 넓은 범위를 크롤링할 때 서버에 과부하를 주지 않도록 도와주는 장치들입니다.
HTTP 전용이라는 점을 기능으로 다시 보기. 브라우저가 없다는 건, 원하는 데이터가 일반 HTTP로 접근 가능하다면 메모리 사용량이 낮고 처리량이 높으며 렌더링 엔진을 관리할 필요도 없다는 뜻입니다. 그리고 브라우저 우선 진영이 생각하는 것보다, 실제로는 그 조건을 만족하는 경우가 훨씬 많습니다.
설치: 아무도 스크린샷에 안 담는 의존성 스택

설치는 전혀 문제 없었습니다. 이 정도 규모의 프레임워크라면 오히려 그 점을 분명히 말할 가치가 있습니다. macOS arm64의 새 가상환경에서 pip install Scrapy==2.17.0는 바이너리 wheel을 받아 조용히 끝났고, 컴파일하다가 벽에 부딪히는 일도 없었습니다. 드라마는 없었지만, 그게 바로 포인트입니다.
다만 무엇이 깔렸는지는 봐야 합니다. scrapy version -v 결과에는 lxml 6.1.1, Twisted 26.4.0, pyOpenSSL 26.3.0, cryptography 49.0.0 위에서 Scrapy 2.17.0이 동작하고 있었고, 여기에 parsel, cssselect, tldextract가 더해져 있었습니다. 이건 단순한 HTML 파서가 아니라, 진짜 크롤링 프레임워크 한 세트 분량의 의존성입니다. 제 머신에서는 모든 항목의 wheel이 준비돼 있어서 설치가 아주 수월했습니다. 하지만 다른 환경에서는 공식 문서가 여전히 플랫폼별 의존성 충돌 가능성을 경고하고 있으며, 역사적으로는 cryptography와 Twisted 쪽이 특히 민감했습니다. 특이한 환경을 쓰고 있다면 그 점까지 감안하는 편이 좋습니다. 설치 자체는 매끄러웠지만, 무엇이 설치되는지의 규모는 미리 알아둘 필요가 있습니다. 프레임워크를 받는 만큼, 프레임워크다운 무게도 함께 가져오기 때문입니다.
실전 테스트: 무엇이 잘 버텼나

설치 후에는 정적 경로가 아주 깔끔했습니다. 전부 정확히 수집됐고, 누락도 없었습니다.
| 테스트 | 결과 | 실행 시간 |
|---|---|---|
| 로컬 정적 카탈로그 + 페이지네이션 | 제품 12/12개 | 0.557초 |
| 정적 카탈로그 CSV export | 12행 기록 | (동일 실행) |
| 기사 추출 | 제목 + 본문 문단 3/3개 | 0.416초 |
| 크롤 그래프, DEPTH_LIMIT=2 | 깊이 0/1/2에서 11페이지 | 0.904초 |
| 로컬 500 페이지 | status 500 포착, 크래시 없음 | 0.424초 |
| Books to Scrape (공개) | 제품 20개 | 2.053초 |
| Quotes to Scrape 스파이더 (공개) | 인용문 12개 | 3.465초 |
정적 카탈로그 스파이더는 1페이지에서 2페이지까지 페이지네이션을 따라가며 기대한 레코드 12/12개를 모두 수집했고, 같은 실행에서 JSON과 CSV로도 내보냈습니다. 가장 눈여겨볼 만한 건 기사 추출 테스트입니다. Scrapy는 페이지를 자동으로 정리해서 보기 좋은 Markdown으로 바꾸려 하지 않았습니다. 대신 article 필드를 명시적 선택자로 집어내고, 네비게이션과 푸터 텍스트는 별도 필드로 분리해 뒀습니다. 덕분에 본문 3/3개 문단을 얻으면서도 공통 문구는 출력물에 섞이지 않았습니다. 바로 이게 트레이드오프입니다. 선택자를 직접 쓰는 대신, 원하는 것만 정확히 얻고 원하지 않는 것은 그대로 걸러낼 수 있습니다.
크롤링 제어도 소규모 테스트에서는 잘 작동했습니다. DEPTH_LIMIT=2, 짧은 다운로드 지연, 도메인별 동시성 제한, robots.txt 준수 설정을 적용하자 크롤 그래프는 깊이 0, 1, 2에서 11페이지를 탐색했고, 깊이 카운팅도 정상적으로 맞았습니다. 실패 처리도 담담했습니다. 의도적으로 만든 500 페이지는 handle_httpstatus_list를 통해 status 500이 노출된 구조화된 아이템으로 반환됐고, 예외도, 전체 중단도 없었습니다. Scrapy는 오류 상태를 크롤을 무너뜨리는 돌발 상황이 아니라, 스파이더 로직 안에서 다룰 수 있는 데이터로 취급합니다.
실전 테스트: JavaScript 벽과 그 옆의 문

이제 이 리뷰의 핵심 결과입니다.
Scrapy의 HTTP fetcher를 JavaScript 렌더링 카탈로그에 연결했습니다. 소스 HTML은 내려받았지만 .product-card 노드는 0개였고, 그대로 넘어갔습니다. 왜냐하면 그 카드를 그리는 스크립트는 실행하지 않기 때문입니다. 공개 Quotes to Scrape JS 페이지도 같은 결과를 보였습니다. 렌더된 인용문 노드는 0개였습니다. 여기서 테스트를 멈췄다면, Scrapy를 오늘날 기준의 사이트에는 부적합한 도구라고 판단했을 겁니다.
하지만 거기서 멈추면 안 됩니다. 그 JS 카탈로그는 대부분의 경우처럼 백그라운드 JSON API가 내용을 채우고 있었습니다. 같은 Scrapy 스파이더를 그 엔드포인트에 보내자 브라우저도, 렌더링도 없이 0.416초 만에 8/8개 제품이 돌아왔습니다. 페이지가 이미 호출하고 있던 URL에 요청을 보내고, 응답으로 돌아온 JSON만 파싱한 것입니다.
이 나란한 결과가 바로 "요청을 재현하라"는 철학의 축소판입니다. 렌더된 페이지는 미끼일 뿐이고, 데이터는 처음부터 API 뒤에 있었습니다. Scrapy의 설계는 헤드리스 브라우저에 비용을 쓰며 페이지가 조립되는 과정을 지켜보는 대신, 그 API를 직접 치도록 유도합니다. 더 빠르고, 더 가볍고, 덜 깨집니다. API 계약은 클라이언트 측 DOM보다 의존하기 안정적이니까요. 다만 이 과정은 수동입니다. 네트워크 탭을 열고 요청을 찾아낸 뒤, 헤더와 파라미터를 직접 재현해야 합니다. Scrapy가 API를 찾아주지는 않습니다. 대신 한 번 찾아두면 그 API를 치는 일은 아주 쉽게 만들어 줍니다.
경계를 두 가지로 분명히 하겠습니다. 진짜로 재현할 수 있는 내부 요청이 없다면, 즉 클라이언트 측 렌더링으로 데이터가 만들어지지만 그 뒤에 API가 없는 경우라면, Scrapy는 직접 연결한 헤드리스 브라우저 통합이 필요합니다. 이번 테스트에서는 그 경로를 다루지 않았습니다. 또한 위의 모든 실험은 작은 fixture와 공개 데모 페이지 기준이었습니다. 100페이지에서 1,000페이지 규모의 크롤은 돌리지 않았기 때문에, 메모리, 처리량, 재시도 동작에 대해 규모 기준의 결론을 내릴 수는 없습니다. 비동기 코어와 크롤 제어는 분명 좋은 신호지만, 신호가 곧 측정치는 아닙니다.
장점과 단점
장점:
- HTTP 전용 설계라 빠르고 가볍습니다. 정적 데이터 12/12개를 약 0.5초 만에, JSON API에서는 0.416초에 8/8개를 처리했으며 브라우저 오버헤드가 없습니다.
- 요청 재현 방식이 실제로 잘 먹힙니다. JS 페이지에서 0개였던 데이터가 백엔드 API를 통해 8개 전부 살아났습니다.
lxml기반의 CSS 및 XPath 선택자는 추출 코드를 읽기 쉽고 빠르게 만듭니다.- 별도 export 코드를 쓰지 않아도 JSON/CSV/XML로 바로 내보낼 수 있습니다.
- 오류 처리 방식이 명확합니다. 500 응답도 크래시가 아니라 처리 가능한 상태로 돌아옵니다.
- 동시성, 지연, 깊이 제한, AutoThrottle, robots.txt 같은 성숙한 크롤 제어 기능을 제공합니다.
- BSD-3-Clause 라이선스로 상업적 사용에 유리하며, 현재 머신에서는 설치도 깔끔했습니다.
단점:
- 기본적으로 JavaScript를 렌더링하지 않습니다. 클라이언트 렌더링 페이지에서는 API를 직접 찾기 전까지 노드가 0개로 나옵니다.
- 내부 요청을 찾는 작업은 수동입니다. Scrapy가 엔드포인트를 대신 찾아주지는 않습니다.
- 의존성 스택이 큽니다(Twisted, lxml, cryptography, pyOpenSSL, parsel, tldextract). 이 환경에서는 문제 없었지만, 특이한 플랫폼에서는 예전부터 마찰 지점이었습니다.
- 노코드나 자동 추출 도구보다 코드가 더 많이 필요합니다. 스파이더 작성과 유지보수는 사용자가 직접 맡아야 합니다.
- 이번 테스트는 소규모 fixture와 데모 사이트 기준이었고, 대규모 크롤에서의 안정성은 이번 패스에서 검증하지 못했습니다.
누구에게 적합하고, 누구는 지나쳐도 되는가

Scrapy는 페이지보다 요청 단위로 생각하고, 코드 수준의 통제권을 원하는 개발자를 위한 도구입니다. 느린 JavaScript 사이트를 보면 바로 "이 안쪽에 API가 있을 것 같은데"라고 떠올리는 사람이라면, 이 도구는 정확히 그 감각을 위해 만들어졌다고 봐도 좋습니다. 선택자를 작성하고, 네트워크 탭을 읽고, 추출 로직 전반을 직접 책임질 수 있는 사람에게 잘 맞습니다. 정적 사이트, 페이지네이션이 있는 카탈로그, 그리고 찾을 수 있는 JSON 엔드포인트가 있는 대상이라면 속도도 빠르고 정확도도 좋습니다.
반대로 스파이더 코드를 작성하고 유지보수하는 데 시간을 쓰고 싶지 않거나, 대상 사이트가 데이터를 오로지 클라이언트 측에서만 렌더링해서 재현 가능한 요청이 없고 헤드리스 브라우저를 직접 붙이기도 싫다면, 다른 도구와 함께 쓰거나 아예 건너뛰는 편이 낫습니다. URL만 넣으면 추출 규칙 작성 없이 깔끔한 구조화 출력을 얻고 싶었다면, 애초에 그건 Scrapy의 역할이 아니었습니다. 그리고 Scrapy도 그 점을 숨기지 않습니다.
대안과, Thunderbit이 들어가는 자리
웹 데이터 추출을 위해 Thunderbit 사용해 보기
먼저 무엇을 감수하는지부터 봐야 합니다. Scrapy는 무료 오픈소스 프레임워크이고, 직접 운영하고 유지보수해야 합니다. 스파이더도, 의존성 스택도, 각 사이트의 데이터 요청을 찾아내는 작업도 모두 사용자의 몫입니다. 대신 요청당 비용은 없고, 모든 것을 내부에서 관리할 수 있으며, 세밀한 통제권을 얻습니다. 많은 팀에게는 이것이 정답이며, 이 리뷰는 그 선택을 말리려는 글이 아닙니다.
문제는 렌더링과 페이지 변화에 있습니다. Scrapy의 대답은 사용자가 그 문제를 직접 푸는 것입니다. API를 찾고, 요청을 재현하고, API가 없는 경우에는 브라우저를 직접 붙여야 합니다. 반면 관리형 AI 스크래핑 API는 그 레이어를 통째로 넘겨받습니다. 기술 사용자에게 Thunderbit의 개발자 스택은 바로 그 자리에 있습니다. 브라우저 확장 프로그램이 아니라, AI 스크래핑 API + MCP 서버 + CLI입니다. POST /distill은 페이지를 LLM이 바로 쓸 수 있는 깔끔한 Markdown으로 바꾸고, POST /extract는 사용자가 정의한 스키마에 맞춰 구조화된 JSON을 반환합니다. 둘 다 JavaScript 렌더링, anti-bot 대응, 동적 콘텐츠 처리를 서버 측에서 처리합니다. Scrapy가 브라우저를 찾아보라고 요구하는 클라이언트 렌더링 사례도 포함됩니다. AI 에이전트와 코딩 어시스턴트를 위한 MCP 서버도 있으며, 페이지 스코핑을 먼저 해볼 수 있도록 무료 thunderbit_suggest_fields도 제공합니다. 터미널, CI, cron 작업을 위한 CLI는 npx @thunderbit/thunderbit-cli로 사용할 수 있습니다.
차이는 품질이 아니라 소유권입니다. Scrapy는 명시적인 엔지니어링 프레임워크입니다. 스파이더, 파이프라인, JavaScript 전략을 직접 관리하는 대신 호출당 추가 비용 없이 완전한 통제권을 가집니다. Thunderbit의 스택은 렌더링과 추출 레이어를 관리형 서비스로 넘겨주기 때문에, 네트워크 탭을 뒤지는 수고를 덜고 호출당 비용을 지불하는 대신 시간을 절약할 수 있습니다. 소규모이고 코드 중심이며, 모든 단계를 직접 소유하고 싶다면 Scrapy가 더 잘 맞습니다. 반대로 수백 개 사이트로 확장해야 하고 사이트마다 요청을 하나씩 손으로 재현하고 싶지 않다면, 관리형 방식이 그 전체 작업 범주를 없애줍니다.
더 넓은 비교가 궁금하다면 이 벤치마크 글들도 참고할 만합니다. 오픈소스 스크래퍼 전체 비교, Colly의 브라우저 없는 Go 크롤러 리뷰, Scrapling의 적응형 셀렉터 리뷰입니다.
결론
Scrapy를 써야 할까요? 네, 개발자이고 제어권을 원하며 "페이지를 렌더링하지 말고 그 뒤의 요청을 찾아라"라는 관점을 받아들일 수 있다면 그렇습니다. 테스트 결과, 이 철학은 문서에서 말한 그대로 정확히 효과를 냈습니다. JavaScript 카탈로그는 HTTP fetcher에 0개의 카드를 내줬지만, 그 뒤의 JSON API는 같은 스파이더에 8개 전부를 반환했습니다. 정적 추출은 12/12개를 맞췄고, 기사 선택자는 본문 3/3개 문단을 공통 문구 없이 깔끔하게 뽑아냈으며, 크롤 그래프는 11페이지에 걸쳐 깊이 제한을 지켰고, 500 응답도 크래시가 아니라 처리된 상태로 돌아왔습니다.
다만 기대치는 정확히 잡아야 합니다. Scrapy는 JavaScript를 렌더링하지 않으며, API도 알아서 찾아주지 않습니다. 그 직감은 사용자가 직접 만들어야 합니다. 의존성 스택은 프레임워크답게 크고, 특이한 플랫폼에서는 여기저기서 걸릴 수 있습니다. 또한 이번에는 fixture와 데모 페이지를 테스트했을 뿐, 수천 페이지 크롤은 아니었기 때문에 대규모 안정성은 아직 검증되지 않았습니다. 그 범위 안에서는, Scrapy가 조용히 급진적인 하나의 생각을 가장 잘 밀고 가는 도구입니다. 웹페이지를 통과하는 가장 빠른 길은, 의외로 웹페이지 자체를 통과하는 길이 아닐 때가 많다는 생각입니다.
웹 데이터 추출을 위해 Thunderbit 사용해 보기 Get Started Free
자주 묻는 질문
Scrapy로 JavaScript 렌더링 페이지를 수집할 수 있나요? 기본 HTTP fetcher만으로는 불가능합니다. JS fixture와 공개 Quotes JS 페이지 모두에서 HTML만 내려받고 브라우저를 실행하지 않기 때문에 노드가 0개였습니다. 의도된 방식은 페이지가 호출하는 내부 데이터 요청을 찾아 직접 치는 것입니다. 제 테스트에서는 JS 카탈로그 뒤의 JSON API가 8개 항목 전부를 돌려줬습니다. 재현 가능한 요청이 전혀 없는 페이지라면, 헤드리스 브라우저를 직접 연결해야 합니다.
“요청을 재현한다”는 말이 정확히 무슨 뜻인가요? 대부분의 동적 페이지는 백그라운드에서 JSON API로 데이터를 받아온 뒤, 클라이언트 측에서 렌더링합니다. 이 과정을 브라우저로 지켜보는 대신, 네트워크 탭을 열어 그 API 호출을 찾고 Scrapy가 그 요청을 직접 보내게 하면 됩니다. 렌더링보다 빠르고 안정적입니다. API 계약은 DOM보다 덜 자주 깨지니까요. 하지만 이건 수동 작업이며, Scrapy가 엔드포인트를 대신 찾아주지는 않습니다.
Scrapy 설치는 어렵나요?
제 환경에서는 깔끔했습니다. macOS의 새 venv에서 pip install Scrapy==2.17.0는 컴파일 오류 없이 끝났고, 바이너리 wheel도 잘 내려왔습니다. 다만 Twisted, lxml, cryptography, pyOpenSSL, parsel, tldextract 같은 큰 스택을 함께 가져오며, 공식 문서도 일부 시스템에서는 플랫폼별 의존성 마찰을 경고하고 있습니다. 특이한 환경이라면 그 부분을 감안해야 합니다.
Scrapy는 어떤 출력 형식을 지원하나요? Feed export로 JSON, JSON Lines, CSV, XML을 기본 지원합니다. 스파이더를 파일에 연결하면 추가 코드 없이 아이템을 직렬화합니다. 제 테스트에서는 한 번의 실행으로 JSON과 CSV를 모두 생성했습니다. 단, 선택한 필드만 내보내며 페이지를 Markdown으로 자동 정리해 주지는 않습니다.
Scrapy는 상업적으로 무료로 사용할 수 있나요? BSD-3-Clause 라이선스라서 허용 범위가 넓고 상업적으로도 친화적입니다. 그래도 실제로 사용하기 전에 항상 저장소에서 최신 라이선스를 다시 확인하세요. 또한 user-agent, proxy, rate limit 설정은 책임 있게 운영해야 합니다. 기능이 있다고 해서 무조건 허용되는 것은 아닙니다.


