selectolax 실측 비교: BeautifulSoup보다 빠르고 lxml과는 접전을 벌이는 초고속 HTML 파서

최종 업데이트: July 17, 2026
selectolax 실측 비교: BeautifulSoup보다 빠르고 lxml과는 접전을 벌이는 초고속 HTML 파서
AI 요약
이 selectolax 리뷰는 페이지 크기별 성능, 선택자 지원 범위, 메모리 사용량, 특수 HTML 처리까지 lxml, BeautifulSoup, parsel, 그리고 각 백엔드를 비교해 파서를 실측합니다. 결과적으로 selectolax가 BeautifulSoup보다 훨씬 빠르고, 전체 파싱+질의 작업에서는 lxml과 경쟁력이 뛰어나지만, 순수 파싱만 놓고 보면 이 테스트 환경에서는 lxml이 더 빨랐다는 점도 보여줍니다. 또한 Lexbor와 Modest 엔진의 차이, CSS 선택자 공백, `<template>` 처리 문제, 메모리 절감 효과, 라이선스 정보, 그리고 느린 Python 파싱 워크플로를 대체하기에 selectolax가 적합한 경우를 설명합니다.

“가장 빠른 Python HTML 파서”를 다룬 글을 보면 결국 selectolax가 등장하고, 결론도 늘 “BeautifulSoup보다 훨씬 빠르다”에서 끝납니다. 그 말은 맞습니다. 하지만 누구도 끝까지 풀어내지 않는 질문이 하나 있습니다. selectolax를 lxml 옆에 놓으면 무슨 일이 벌어질까? 그 순간부터 “가장 빠르다”라는 표현에는 별표가 붙기 시작합니다.

그래서 제대로 벤치마크해봤습니다. selectolax의 두 백엔드 모두를 lxml, html.parserlxml을 사용하는 BeautifulSoup, 그리고 parsel과 비교했습니다. 페이지 크기는 1 KB부터 10 MB까지 다섯 구간으로 나눴고, 각 측정값은 서로 다른 세 번의 프로세스 실행값의 중앙값으로 잡았습니다. 결과는 분명했습니다. selectolax는 BeautifulSoup를 압도했고, 순수한 lxml과는 동급이었으며, HTML 파싱만 떼어놓고 보면 lxml에 밀렸습니다. 아래 수치는 모두 한 대의 머신(macOS arm64, Python 3.14.2)에서 나온 예비 결과입니다. 스크립트는 커밋해두었으니, 인용하기 전에 꼭 본인 환경에서 다시 돌려보세요.

selectolax는 정확히 무엇이고, 무엇이 아닌가

selectolax는 HTML5를 파싱하고 CSS 선택자로 질의할 수 있게 해주는 두 개의 C 엔진—Modest와 Lexbor—에 대한 Python 바인딩입니다. 크롤러도 아니고, 브라우저도 아니고, 버튼 하나 눌러서 되는 의미의 “스크래퍼”도 아닙니다. 이미 가져와 둔 HTML 덩어리를 넘겨주면 그걸 해석해주는 도구입니다. 유지관리자의 한 줄 정의는 이렇습니다. “Cython으로 작성되었고 Modest와 Lexbor 엔진을 사용하는, CSS 선택자를 지원하는 빠른 HTML5 파서.”

백엔드는 두 가지이며, 문서보다 이 차이가 훨씬 중요합니다.

  • LexborHTMLParser (Lexbor 엔진) — 2024년 기준 README에서 권장하는 쪽입니다.
  • HTMLParser (Modest 엔진) — 원조 백엔드이며, README에 따르면 기반 C 라이브러리는 “더 이상 유지관리되지 않습니다.”

속도 이야기를 하기 전에 알아둘 점도 있습니다. 2026-07-10에 찍은 repo snapshot 기준으로 selectolax는 별점 1,653개, 최신 릴리스는 v0.4.10(2026년 5월)입니다. PyPI에서는 Python >=3.9,<3.15를 지원합니다. 설치는 이 리뷰에서 가장 덜 드라마틱한 부분이었습니다. pip install selectolax만으로 2.3 MB짜리 사전 빌드 cp314 wheel이 바로 내려받아졌고, Python 3.14에서 즉시 동작했습니다. 브라우저 다운로드도 없고, doctor 단계도 없고, 컴파일도 필요 없었습니다. 브라우저 의존 도구와 달리 순수 파서가 가진 조용한 장점이 바로 이것입니다. 그냥 import해서 바로 실행됩니다.

라이선스도 한 가지 짚고 넘어가야 합니다. Python 바인딩 자체는 MIT지만, wheel 안에는 엔진이 함께 번들되어 있고 그 엔진들은 각자 라이선스가 따로 있습니다. Modest는 LGPL-2.1, Lexbor는 Apache-2.0입니다. 즉 “selectolax는 MIT다”라는 말은 Python 코드에 대해서는 맞지만, 실제 배포되는 바이너리 전체를 설명하기에는 부족합니다. 법무팀이 재배포 컴포넌트를 신경 쓴다면, 이 구분은 꼭 표시해둘 만합니다.

속도 질문에 대한 실제 숫자

제가 측정한 작업은 이렇습니다. HTML 문자열을 파싱한 뒤 <h3 class="title"> 텍스트를 모두 뽑고, <a>의 href를 모두 추출합니다. 지표는 밀리초 단위 지연 시간이며, 서로 다른 세 번의 프로세스 실행값 가운데 중앙값을 사용했습니다. C 기반 파서들의 실행 간 편차는 대부분의 크기에서 약 5% 이내였습니다. 각 셀을 재기 전에 파서의 결과를 콘텐츠 해시로 변환해, 몰래 일을 덜 하는 파서가 있으면 걸러내도록 했습니다. 이 페이지들에서는 여섯 개 구현이 모든 크기에서 완전히 일치했으므로, 비교는 진짜로 공정한 1:1 비교입니다. 전체 데이터는 커밋된 bench_parse.json에 있습니다.

selectolax is 12-17x faster than BeautifulSoup across page sizes

페이지selectolax (Lexbor)selectolax (Modest)lxmlparselBS (lxml)BS (html.parser)
1 KB0.0270.0290.0360.0440.2810.323
10 KB0.1600.1710.1660.2281.7052.049
100 KB1.4641.5651.4232.02016.520.6
1 MB14.90116.914.17720.9181.9232.6
10 MB159.9247.9172.9231.92261.62788.7

BeautifulSoup와 비교하면: 대략 12~17배, 그리고 통념은 과소평가하고 있다

이 수치를 비율로 바꾸면 selectolax-Lexbor는 1 KB 페이지에서 BeautifulSoup(html.parser)보다 약 12배 빠르고, 10 MB에서는 약 17배 빠르며, 같은 범위에서 BeautifulSoup(lxml)보다도 약 10~14배 빠릅니다. 온라인에서 흔히 도는 “selectolax는 BeautifulSoup보다 4~5배 빠르다”는 말은 html.parser 기준으로는 너무 낮고, lxml 기반 BeautifulSoup와 비교했을 때만 대체로 맞습니다. 진짜 배수는 어떤 BeautifulSoup를 말하는지, 그리고 페이지당 얼마나 많이 추출하느냐에 따라 달라집니다.

이건 README의 자체 벤치마크와도 들어맞습니다. 거기서는 BeautifulSoup(html.parser) 대비 25.5배 우위를 시사합니다. 어느 쪽도 틀린 건 아닙니다. README의 작업(작은 홈페이지만 대상으로 title, 링크, 스크립트, meta를 가져오는 방식)은 작은 페이지에서 BeautifulSoup의 파싱 오버헤드를 더 크게 드러내기 때문입니다. 범위를 제한해서 보면 결론은 이렇습니다. selectolax는 현실적인 파싱+추출 작업에서 BeautifulSoup보다 대체로 10~15배 빠르고, 작은 페이지이거나 추출량이 가벼울수록 더 큰 격차가 납니다.

현재 병목이 BeautifulSoup 코드가 페이지를 줄기차게 처리하느라 생기는 것이라면, 이건 교체만으로도 투자 대비 효과가 나오는 마이그레이션입니다. 이건 논쟁거리가 아닙니다. 다음 이야기가 문제입니다.

lxml과 비교하면: 동급이다 — 그리고 lxml이 모두가 놓치는 부분에서 앞선다

100 KB와 1 MB 행을 다시 보세요. Lexbor와 lxml은 서로 약 5% 이내에 있고, 실행별 범위도 겹칩니다. 제 기준에서는 이건 동점입니다. 승자도 없고, “더 빠르다”라고 말할 수도 없습니다. selectolax가 실제로 앞서는 구간은 10 MB 페이지뿐입니다(159.9 ms vs 172.9 ms, 구간이 겹치지 않는 8.1% 차이). 즉 전체 작업 기준으로는 selectolax가 lxml과 비슷하고, 아주 큰 문서에서만 살짝 앞섭니다.

selectolax ties lxml on the full task but lxml leads pure parsing by 33-34 percent

그다음엔 CSS 질의를 떼어내고 트리 구성만 따로 재봤는데, 여기서 결과가 많은 글과 정반대로 뒤집혔습니다. 순수 파싱만 놓고 보면, 질의가 전혀 없는 상태에서 lxml이 이 머신에서 selectolax-Lexbor보다 일관되게 약 33~34% 더 빨랐습니다 — 10 MB 페이지에서는 77.9 ms 대 116.6 ms였습니다. 전체 작업에서는 어차피 둘이 수렴합니다. 제 가설은 이렇습니다(attribution 실험으로 입증한 건 아닙니다). 이 페이지들에서는 CSS 질의가 총 시간에서 차지하는 비중이 작아서, lxml의 파싱 단계 우위가 희석되다가 결국 총합에서 만나게 된다는 것입니다.

이건 이 리뷰에서 가장 공격받기 쉬운 주장입니다. 이유도 분명합니다. 흔히 알려진 상식과 반대로 나오고, 순수 파싱만 분리한 공개 벤치마크 중 제가 찾은 aows.jpt.sh는 정반대로 selectolax가 약 4배 빠르다고 보고합니다. 그래서 안전장치를 뒀습니다. 이 결과는 단일 플랫폼(macOS arm64, Python 3.14, 사전 빌드 cp314 wheel — Linux x86_64나 소스 빌드는 미검증)에서 나온 것이고, 네 가지 페이지 크기에서 모두 같은 경향이 재현되었으며, 서로 다른 두 개의 lxml API로도 다시 확인해 API 산물 가능성을 배제했습니다. 두 lxml API 모두 모든 크기에서 selectolax-Lexbor를 앞섰습니다. 저는 “lxml이 파싱이 더 빠르다”를 정설처럼 말하는 게 아니라, 제 벤치에서 그렇게 나왔다는 사실을, 공개된 다수의 수치와 대조해 스크립트와 함께 제시하는 것입니다. 본인 장비에서 직접 돌려보세요.

추가로 하나 더 보면, 평평한 페이지에서 <a> 100,000개를 조회했을 때 lxml과 selectolax-Modest는 동점이었습니다(33.30 ms vs 34.19 ms, 범위가 겹침). 반면 selectolax-Lexbor는 둘보다 약 15% 느렸습니다. 세 C 엔진이 공통으로 보여주는 강점은 parsel이나 BeautifulSoup보다 대량 선택에서 5~7배 빠르다는 점입니다. 노드마다 Python 객체를 만들어야 하는 구조가 진짜 발목을 잡기 때문입니다. 따라서 “selectolax가 대량 CSS 선택에서 가장 빠르다”는 말도 성립하지 않습니다. Modest는 lxml과 비기고, Lexbor는 그보다 느립니다.

제가 실제로 감수할 수 있는 결론은 이겁니다. selectolax가 lxml보다 압도적으로 빠른 폭넓은 우위는 없습니다. 전체 작업에서는 동급이고, 진짜 앞서는 구간은 가장 큰 페이지뿐입니다. selectolax의 강점은 다른 데 있습니다. API의 편의성, 이상한 입력에서의 동작, 그리고 현대적인 CSS 지원이 그 자리입니다. 이제부터가 진짜 이야기입니다.

메모리와 콜드 스타트: 프로파일러가 아니라 RSS로 순위를 매겨라

메모리 부분은 제 예전 수치를 바로잡아야 했고, 그 수정 자체가 핵심입니다. tracemalloc을 끈 상태에서 10 MB 페이지의 RSS 증가량으로 측정하면 BeautifulSoup는 selectolax나 lxml보다 약 1.5~1.8배 더 많은 메모리를 사용합니다. 범위는 1.51배(BS-lxml, 218.4 MB vs Lexbor, 144.6 MB)에서 상단 기준 1.75배까지입니다. selectolax와 lxml은 같은 가벼운 구간에 있고, RSS 기준으로는 lxml이 가장 가볍습니다.

selectolax memory tier measured by RSS with profiler off

예전엔 “약 3배”라고 적었는데, 그 수치는 잘못이었습니다. 이유도 교육적이었습니다. tracemalloc을 켠 상태에서 측정했기 때문입니다. tracemalloc은 할당마다 bookkeeping을 하므로, 할당량이 큰 파서의 겉보기 RSS를 대략 두 배로 부풀립니다. 그래서 파서 메모리를 벤치마크할 때는 꼭 기억해야 합니다. 프로파일러를 끈 상태에서 RSS로 순위를 매기세요. tracemalloc peak로 파서를 정렬하면 특히 C 기반 파서의 순서가 왜곡됩니다. 실제 RSS에선 서로 비슷한 selectolax-Lexbor와 Modest가, tracemalloc이 켜지면 Lexbor가 더 무거워 보이기까지 했습니다. 여기서 진짜 무거운 건 BeautifulSoup가 맞습니다. 다만 contaminated instrument가 보여준 것처럼 3배 차이는 아닙니다.

콜드 스타트도 작지만 실제로 차이가 있습니다. selectolax import는 약 14 ms 정도로, lxml과 비슷하고 bs4나 parsel보다 약 2.3배 빠릅니다. CLI 도구나 서버리스 함수처럼 import 시간이 매 호출마다 비용으로 들어가는 환경이라면, 무시할 수 없는 차이입니다.

CSS 선택자 지원 범위: 꽤 강하지만, 실제 구멍도 몇 개 있다

CSS 지원 범위는 41개 항목으로 검사했습니다. 각 선택자는 정답이 이미 알려진 픽스처와 대조했고, Lexbor 엔진을 깨뜨리려는 고의적 실패 테스트도 따로 넣었습니다. 모든 케이스는 각기 별도 subprocess에서 돌렸습니다. 안 그러면 하나가 인터프리터 전체를 죽여버리기 때문입니다. 결과는 다음과 같습니다.

CSS selector coverage comparison: soupsieve 41/41, Lexbor 39/41

엔진PASSWRONGUNSUPPORTEDPROCESS_ABORT
soupsieve41000
selectolax Lexbor39020
lxml (cssselect)37130
parsel (cssselect)37130
selectolax Modest35321

적대적인 선택자까지 넣고 보면 Lexbor가 무조건 1등은 아닙니다. 오히려 soupsieve가 41/41로 가장 깔끔했고, Lexbor는 39/41이었습니다. Lexbor가 놓친 두 항목은 :lang(en):dir(rtl)인데, 둘 다 파싱 에러로 거부했습니다. 그 외에는 :has(), :is(), :where(), 대소문자 무시 속성까지 모두 완벽했습니다.

Lexbor가 특히 빛나는 부분은 cssselect 계열과 비교할 때입니다. README의 대표 선택자 — div > :nth-child(2n+1):not(:has(a)) — 는 selectolax 양쪽 엔진과 soupsieve에서는 올바른 집합을 반환하지만, lxml과 parsel에서는 에러 없이 틀린 집합을 반환했습니다. Scrapy나 parsel에 저 선택자를 그대로 복사한 스크래퍼는 조용히 잘못된 결과를 얻게 됩니다. 정확히 말하면, cssselect는 1.2.0(2022)부터 :has()를 파싱할 수 있고, 제가 테스트한 버전은 1.4.0이었습니다. 따라서 이건 “미지원”이 아니라 “지원하지만 복합식 평가가 잘못됨”입니다. 이 특정 복합식에서의 무음 오류는 cssselect tracker에 기록된 :has() 한계처럼 명시된 에러 형태가 아닙니다. Lexbor는 또한 cssselect가 아예 거부하는 대소문자 무시 속성 플래그 [data-role="LEAD" i]도 처리합니다.

다만 마이그레이션을 가를 두 가지 큰 공백이 있습니다. selectolax는 XPath를 전혀 지원하지 않습니다. 어느 백엔드도 xpath()를 내보내지 않으며, ::text / ::attr() 같은 pseudo-element도 없습니다. 그건 parsel/Scrapy의 확장 기능이지 실제 CSS가 아니기 때문입니다. 기존 스크래퍼가 XPath에 크게 의존한다면, 가장 큰 장벽은 이 부분입니다. 라이브러리만 바꾸는 게 아니라 선택자 자체를 다시 써야 합니다. 반대로 Lexbor는 다른 도구들이 제공하지 않는 대소문자 무시 텍스트 매칭용 :lexbor-contains("text" i) pseudo-class를 제공하고, 문서 설명대로 잘 동작합니다.

거친 HTML에 대한 내구성: selectolax가 값을 증명하는 지점

실제 스크래핑은 파서에 쓰레기 같은 입력을 넣고도 안 넘어지길 바라는 일입니다. 18개의 적대적 입력을 돌려봤는데, 여기서 selectolax가 lxml보다 강한 모습을 보인 범주가 가장 많았습니다.

lxml.html.fromstring에 빈 문자열이나 공백만 넘기면 ParserError("Document is empty")를 던집니다. 반면 selectolax의 두 엔진은 모두 유효한 빈 트리를 반환합니다. 응답이 비어 돌아오는 URL 목록을 돌리는 스크래퍼라면, 전체를 감싸는 try/except를 하나 덜 써도 됩니다. selectolax는 100,000개 요소도 스택 오버플로 없이 처리했습니다.

가장 극명한 차이는 중첩 깊이에서 나왔습니다. 1,000단계와 5,000단계로 중첩된 <div>를 넣었을 때, lxml은 가장 깊은 내용을 조용히 버리지만 selectolax는 끝까지 보존했습니다. libxml2는 파싱 깊이를 대략 256 레벨로 제한하고 에러 없이 트리를 잘라버리기 때문에, 가장 깊은 텍스트는 아예 도달할 수 없습니다. 반면 selectolax 양쪽 엔진은 전체 트리를 그대로 반환합니다. 앞에서 나올 <template> 함정과는 거울상입니다. 그쪽에선 Lexbor가 다른 쪽이 유지하는 내용을 버리고, 여기서는 lxml이 selectolax가 유지하는 내용을 버립니다.

모든 칸이 다 승리는 아니었습니다. Modest 백엔드는 :dir()을 만나면 예외를 던지는 수준이 아니라 Python 인터프리터 전체를 SIGABRT로 종료시켰습니다. 잡을 수 있는 예외가 아니라 프로세스 자체가 죽는 것입니다. 아직 레거시 백엔드를 쓰는 사람이라면, 이건 분명한 내구성 경고입니다. 그리고 이런 문제는 새벽 3시에 운영 작업이 갑자기 죽기 전까지는 잘 보이지 않습니다.

배포 전에 꼭 알아야 할 두 가지 무음 데이터 손실 함정

이 둘은 새 발견은 아닙니다. upstream에 문서화되어 있습니다. 하지만 둘 다 실제 데이터를 조용히 잃게 만들고, README에서는 존재감이 너무 약합니다.

Lexbor는 <template> 안의 <a>를 놓친다

제가 테스트한 MDN 실페이지에서 selectolax-Lexbor는 링크 497개를 찾았고, lxml과 두 BeautifulSoup 백엔드, 그리고 selectolax의 Modest 백엔드는 모두 508개를 찾았습니다. 빠진 11개는 언어 전환기와 discussions 링크였는데, 모두 <template> 요소 안에 있었습니다(해당 페이지는 Lit 웹 컴포넌트를 사용합니다).

selectolax Lexbor template trap: 497 links versus 508 links

원인은 정당합니다. HTML5 명세상 <template> 콘텐츠는 일반 DOM이 아니라 별도의 inert fragment로 파싱되어야 하며, Lexbor는 이를 엄격하게 따릅니다. 그래서 tree.css("a")는 template 내부로 내려가지 않습니다. 반면 lxml, 두 BeautifulSoup 백엔드, 그리고 Modest는 template 내용을 메인 트리로 평탄화하므로 해당 링크를 찾습니다. 이건 문서화된 오픈 이슈(selectolax#146, 엔진 근본 원인은 lexbor#170)입니다. 두 해석 모두 변호할 수 있습니다. Lexbor가 오히려 명세에 더 충실하다고 볼 수도 있습니다. 하지만 권장 백엔드를 쓰는 개발자는 아무 경고 없이 그 데이터를 조용히 놓칩니다. 반대로 다른 파서들은 브라우저가 실제로 렌더링하지 않는 inert template 콘텐츠까지 보이게 하므로, 사용자에게는 보이지 않는 유령 데이터를 손에 쥐게 할 수도 있습니다. 이 페이지에서는 Modest 백엔드나 다른 라이브러리가 안전한 탈출구입니다.

UTF-8이 아닌 바이트는 .text()를 조용히 망가뜨린다

selectolax에 유효하지 않은 UTF-8 바이트를 넘기면 파싱은 성공합니다. 문제는 나중에, 그것도 더 나쁜 방식으로 드러납니다. "<p>café éè</p>".encode("latin-1")를 넣었을 때, Lexbor의 .text()는 대체 문자로 바뀐 문자열을 반환하고, Modest의 .text()는 문제 바이트를 조용히 날려버립니다. 그리고 두 엔진 모두 .html에 접근할 때서야 UnicodeDecodeError를 던집니다. 바인딩은 파싱 시점이 아니라 읽어올 때 엄격한 UTF-8로 디코딩합니다. 이건 인코딩/디코딩 엄격성과 관련된 알려진 selectolax 이슈와 맞닿아 있습니다.

해결은 한 줄이면 됩니다. 바이트를 먼저 직접 디코딩하세요 — LexborHTMLParser(resp.content.decode("latin-1")) — 그러면 두 엔진 모두 'café éè'를 올바르게 반환합니다. 실무에서는 raw 비UTF-8 bytes를 넘기지 말고, 항상 str만 넘기는 습관을 들이세요. README에는 이 점이 명시적으로 적혀 있지 않습니다.

운영 관점의 결과들(단일 관측치이므로 방향성 참고용)

아래 결과는 세 번 반복 측정한 것이 아니라 한 번만 측정한 것입니다. 그래서 확정 수치가 아니라 신호로 봐야 합니다.

가장 흥미로운 건 스레드 확장성입니다. 1 MB 페이지를 4개 스레드에서 48번 파싱했을 때, selectolax는 벽시계 기준 약 3.5~3.9배 속도 향상을 보였습니다. 이는 C 파싱 중 GIL을 놓아주는 라이브러리의 전형적인 흔적입니다. 반면 BeautifulSoup(lxml)은 스레드에서 몇 배나 더 느려졌는데, 이는 작업이 GIL에 직렬화되는 패턴과 같습니다. lxml은 그 중간쯤이었고 결론은 불분명했습니다. Python이 진입하는 free-threading 시대를 생각하면, BeautifulSoup와 달리 스레드 간 병렬화가 되는 selectolax 파싱은 잠정적이지만 분명한 장점입니다. 다만 이건 한 페이지 크기, 한 스레드 수에 대한 결과이고, 메커니즘도 C 코드 계측으로 확인한 게 아니라 가설입니다.

메모리 누수 쪽에서는, 1 MB 페이지에서 2,000번의 parse-extract-drop 반복 후에도 세 파서 모두 누수라면 보여야 할 선형 RSS 상승을 보이지 않았습니다. 각자 일정한 작업 집합 범위 안에 수렴했습니다. 이 결과는 특히 신뢰할 만한데, 같은 도구로 이미 누수가 알려진 대조군을 돌렸더니 의도한 대로 +198 MB까지 올라갔기 때문입니다. 즉 도구는 누수를 볼 수 있었고, 다만 파서에서는 찾지 못한 것입니다. 또한 소유 트리가 범위를 벗어난 뒤에도 노드 핸들을 계속 살려두었을 때, 세그폴트 없이 정상 사용이 가능했습니다. 모두 단일 관측치이며, 장시간 soak 테스트는 아닙니다.

selectolax가 들어맞는 자리와, 바통을 넘겨야 할 자리

위의 모든 내용은 이미 가지고 있는 HTML을 빠르게 구조화된 데이터로 바꾸는 한 가지 작업에 관한 것입니다. selectolax는 그 일을 아주 잘합니다. 하지만 의도적으로 하지 않는 일도 분명합니다. 페이지를 가져오지 않고, JavaScript를 렌더링하지 않고, 프록시를 돌리지 않고, CAPTCHA를 풀지 않고, 어떤 요소를 원하는지도 알아서 판단하지 않습니다. 그건 여전히 여러분의 코드가 해야 할 일입니다. selectolax는 파싱 계층일 뿐, 그 이상인 척하지 않습니다.

이 지점 위에는 파서를 대체하는 대신 파서 위에 얹히는 관리형 추출 서비스가 있습니다. 직접 fetch-render-anti-bot-extract 스택을 만들고 유지하고 싶지 않다면, Thunderbit는 이를 API, MCP 서버, CLI로 제공합니다. POST /distill은 페이지를 깔끔한 Markdown으로 바꾸고, POST /extract는 스키마에 맞는 구조화 JSON을 반환합니다. JS 렌더링과 안티봇 처리는 Thunderbit가 맡습니다. 문제의 층위가 다릅니다. 이미 HTML을 확보해 두었고 자신의 통제 하에 원시 파싱 속도가 필요하면 selectolax를 쓰면 됩니다. 반대로 가져오기와 추출을 맡기고 결과 데이터만 받고 싶다면 Thunderbit의 API, MCP 서버, 또는 CLI를 쓰면 됩니다. 서로 대체재가 아니라, 같은 스택의 다른 높이입니다.

웹 데이터 추출용 Thunderbit 체험하기

장단점, 그리고 실제로 누가 써야 하나

selectolax가 강한 점:

  • 현실적인 파싱+추출 작업에서 BeautifulSoup보다 약 12~17배 빠르며, 페이지 크기 3자릿수 범위에서도 안정적입니다.
  • 메모리가 가볍습니다(lxml 계열 수준이며, BeautifulSoup보다 약 1.5~1.8배 덜 먹음). import도 약 14 ms로 빠릅니다.
  • lxml이 약한 빈 입력, 공백 입력, 비정상적으로 깊은 중첩에 강합니다.
  • :has(), :is(), :where(), 대소문자 무시 속성, Lexbor 전용 :lexbor-contains() 등 현대적인 CSS를 지원합니다.
  • None-safe한 읽기/쓰기 DOM입니다. 없는 요소는 예외 대신 None이나 []로 반환하고, 트리 수정 후 재직렬화도 가능합니다.
  • 현재도 적극적으로 유지관리되고(v0.4.10, 2026년 중반), 설치도 아주 간단합니다.

selectolax가 약한 점:

  • lxml보다 전반적으로 빠르지 않습니다. 전체 작업에서는 동급이고, 제 벤치에서는 순수 파싱 단계에서 밀렸습니다.
  • XPath가 없고 ::text/::attr()도 없습니다. XPath 기반 스크래퍼에게는 큰 마이그레이션 벽입니다.
  • 두 가지 무음 데이터 손실 함정이 있습니다. Lexbor의 <template> 내용과, .text()를 통한 비UTF-8 바이트 처리입니다.
  • Modest 백엔드는 레거시이며 :dir()에서 SIGABRT를 일으킬 수 있습니다.
  • 여기의 수치는 모두 단일 플랫폼(macOS arm64, Python 3.14)에서 나온 예비값입니다.

그렇다면 selectolax를 써야 할까요? 네, lxml급 파싱 속도에 더 친절하고 None-safe한 API, 그리고 빈 입력과 깨진 입력에서 훨씬 나은 동작이 필요하고, CSS 전용 환경으로 살아갈 수 있다면 추천할 만합니다. 코드베이스가 XPath를 중심으로 짜여 있다면, 재작성 비용은 현실적이므로 솔직하게 따져봐야 합니다. 그리고 “세상에서 가장 빠른 단일 파서”를 찾는다면, 이 벤치에서 나온 정확한 답은 selectolax와 lxml이 사실상 붙어 있으므로 승부는 속도가 아니라 사용성, 내구성, 그리고 유지보수성에서 난다는 것입니다. 도구를 고를 때 더 좋은 기준입니다.

웹 데이터 추출용 Thunderbit 체험하기 Get Started Free

자주 묻는 질문

selectolax가 BeautifulSoup보다 빠른가요? 네, 분명합니다. 현실적인 파싱+추출 작업에서 BeautifulSoup(html.parser)보다 대략 1217배 빠르고, BeautifulSoup(lxml)보다도 1014배 빠릅니다. 1 KB부터 10 MB까지 같은 경향이 유지됩니다(macOS arm64, Python 3.14). 흔히 말하는 “4~5배”는 html.parser와의 격차를 너무 낮게 잡은 값입니다.

selectolax가 lxml보다 빠른가요? 전반적으로는 아닙니다. 파싱+추출 전체 작업에서는 100 KB와 1 MB에서 동급이고, selectolax가 이긴 건 10 MB 페이지뿐이었습니다. 순수 파싱만 놓고 보면 제 머신에서는 lxml이 약 33~34% 더 빨랐습니다. 이건 단일 플랫폼 결과로 가둬둔 반(半) 상식 반전 결과이므로, 여러분의 하드웨어에서도 꼭 확인해보세요.

Lexbor와 Modest 중 무엇을 써야 하나요? 거의 모든 경우에 Lexbor입니다. README가 권장하는 유지관리 중인 완성형 엔진이고, CSS 지원도 더 좋습니다. 예외는 <template> 안에 콘텐츠를 숨기는 페이지인데, 이 경우 Lexbor는 명세대로 그 내용을 놓치고 Modest는 우연히 유지합니다. 다만 Modest는 :dir()에서 인터프리터를 강제 종료시키는 등 날카로운 단점도 있습니다.

selectolax는 XPath를 지원하나요? 아니요. 어느 백엔드도 xpath() 메서드를 제공하지 않습니다. selectolax는 CSS 전용입니다. 스크래퍼가 XPath에 의존한다면, 마이그레이션은 선택자를 다시 쓰는 작업이 되며, 그게 lxml이나 parsel 기반 스택에서 넘어올 때 가장 큰 비용입니다.

selectolax 출력이 깨지거나 일부 요소가 사라지는 이유는 무엇인가요? 보통 두 가지입니다. 텍스트가 대체 문자로 나오거나 악센트가 사라진다면, raw 비UTF-8 바이트를 넘겼을 가능성이 큽니다. 파싱 전에 resp.content.decode("latin-1")처럼 str로 먼저 바꾸세요. 현대적인 사이트에서 링크나 요소가 빠졌다면, Lexbor 백엔드가 내려가지 않는 <template> 태그 안에 있을 수 있습니다. 그 페이지에서는 Modest나 다른 파서를 쓰세요.

Ke
Ke
Thunderbit CTO | 시니어 데이터 사이언티스트 & ML 전문가 머신러닝과 데이터 과학 분야에서 약 10년에 가까운 경험을 쌓아온 Ke Shen은 컬럼비아 대학교 출신이며, 전 Walmart Labs의 시니어 데이터 사이언티스트였습니다. Python, R, Java, 통계 분야에서 동료들에게도 인정받는 깊은 전문성을 바탕으로, 복잡한 AI 알고리즘을 이론에서 실제 운영 수준의 아키텍처로 전환하는 데 필요한 실전 인사이트를 공유합니다.
목차
Thunderbit · AI 웹 데이터 에이전트

원클릭 내 모든 페이지에서 데이터 추출

25만 명 이상의 사용자에게 신뢰받는
무료 플랜 이용 가능
AI를 사용하여 데이터 추출
Google Sheets, Airtable 또는 Notion으로 데이터를 쉽게 전송하세요
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week