DOM과 싸우지 마세요. 선택자 우선 사고가 스크래퍼를 구합니다.
출처: Dev.to
내가 보는 대부분의 깨진 스크래퍼는 같은 형태를 가지고 있다: 누군가가 먼저 추출 로직을 작성하고 그 뒤에 선택자를 만든다. 선택자는 사후 생각에 불과했다—새벽 2시 DevTools에서 동작한 것이 무엇이든. 그건 역행하는 방식이다. 선택자는 코드와 페이지 사이의 계약이다. 선택자를 잘못 잡으면 나머지 스크래퍼는 의미가 없다. 선택자 우선 사고란: 추출 코드를 한 줄도 작성하기 전에 데이터가 어떻게 식별되는지를 결정한다는 뜻이다. “가격을 어떻게 가져올까?”가 아니라 “페이지가 프로그래밍적으로 가격이라는 것을 어떻게 알려주는가?”를 묻는 것이다.
선호 순위에 따른 세 가지 답변
- 시맨틱 —
getByRole,getByLabel,getByText. 접근성 트리가 노출하는 것을 그대로 사용한다. 디자인이 바뀌어도 살아남는다. - 데이터 속성 —
data-testid,data-product-id,itemprop. 개발자들이 자체 테스트용으로 자주 추가한다; 우리는 이를 그대로 활용한다. - 구조화된 데이터 — JSON‑LD, 마이크로데이터, OpenGraph. 페이지가 이미 Google에 가격이 무엇인지 알려주고 있다; 우리도 그것을 활용하자.
CSS 클래스는 최후의 수단이다. 클래스 이름은 스타일링을 위한 것이며 정체성을 나타내지 않는다. 디자인이 바뀌면 바뀐다. “위에서 세 번째 버튼”을 찾는 것과 같은데, 누군가 메뉴를 재배열하면 작동하지 않는다.
선택자를 작성하기 전에
- DevTools에서 접근성 트리를 연다 (Chrome: Elements → Accessibility 탭). 데이터에 역할(role)과 접근 가능한 이름이 있다면
getByRole을 사용한다. - 페이지 소스에서
application/ld+json을 검색한다. 존재하고 필요한 필드가 포함돼 있다면 직접 파싱한다. DOM을 탐색할 필요가 없다. - 데이터 근처에
data-*속성이 있는지 확인한다. 개발자들은 테스트 훅을 여기저기 남겨둔다. 그들을 활용한다. - 위 방법들이 모두 통하지 않을 경우에만 CSS나 XPath로 돌아간다. 이때도 안정적인 기준에 앵커한다—부모 랜드마크,
aria-label,data-속성 등—단순 클래스 체인만 사용하지 않는다.
아래는 내가 모든 새로운 액터에서 사용하는 우선순위다:
async function extractPrice(page) {
// 1. Structured data first.
const ld = await page.locator('script[type="application/ld+json"]')
.first().textContent();
const data = JSON.parse(ld ?? '{}');
if (data?.offers?.price) return data.offers.price;
// 2. Semantic selectors.
const priceByLabel = page.getByLabel(/^price$/i);
if (await priceByLabel.count()) return priceByLabel.textContent();
// 3. Data attributes.
const priceByData = page.locator('[data-testid="price"]');
if (await priceByData.count()) return priceByData.textContent();
// 4. Last resort: CSS class. Logged loudly so we know we're in fallback.
console.warn('Falling back to CSS selector — selector audit needed.');
return page.locator('.price-tag').textContent();
}
fallback 경로에 warn()이 찍히는 것을 확인하라. 로그에 이 경고가 나타난다면 사이트가 높은 우선순위 신호를 바꿨다는 뜻이며, 당신은 한 번의 디자인 리프레시만에 깨질 위험이 있다. 깨지기 전에 바로 고쳐라, 깨진 뒤가 아니라.
Idealista 액터에서 위 우선순위 덕분에 “6주마다 선택자를 고쳐야 한다”는 루틴이 “연 2회만 고치면 된다”는 루틴으로 바뀌었다. JSON‑LD 경로만으로도 전체 리스트의 95%를 DOM을 전혀 건드리지 않고 잡아낸다. 접근성‑role fallback이 또 4%를 커버한다. CSS fallback은 특수 케이스 속성 타입에서만 작동하며, 새로운 레이아웃이 배포될 때를 알려준다—보통 다른 모니터링이 감지하기 일주일 전이다.
이 선택자 사다리는 우리가 배포하는 모든 액터가 갖추는 두 번째 요소다(지난 주 글에서 다룬 요청 차단 바로 뒤). Idealista 액터에서 실제로 동작하는 모습을 확인할 수 있다. 일관성이 너무 높아 유틸리티 함수로 만들었다.
그래서,
지금 당장 스크래퍼의 선택자 코드를 열어보라. 클래스 체인 개수와 시맨틱/구조화된 데이터 조회 개수를 비교해 보라. 비율을 댓글에 남겨라. 가장 긴 CSS 체인에 보너스 포인트를 줘라—아마도 누군가가 .product-grid > .item:nth-child(3) > .price > span > strong 같은 체인을 가지고 있을 것이다.
동의하나요, 반대하나요, 아니면 정말 CSS 체인이 필요했던 사이트가 있나요? 댓글로 알려 주세요.
작성자: Nova Chen, SIÁN Agency 자동화 개발 옹호자. Nova의 다른 글은 dev.to에서 확인할 수 있습니다. 맞춤형 스크래핑이나 자동화 작업이 필요하면 SIÁN Agency에 의뢰하세요.