Guide complet de l'exploration XPath 2026 : L'essentiel de l'extraction précise de données web

J'ai complètement résumé la méthode de crawling web avec XPath, de début à fin. J'ai inclus des exemples en Python avec lxml/requests, une comparaison avec les sélecteurs CSS et une référence des expressions clés, le tout jusqu'en 2026.

46
Guide complet de l'exploration XPath 2026 : L'essentiel de l'extraction précise de données web

웹 크롤링을 처음 시작할 때 대부분 CSS Selector를 먼저 배웁니다. 그런데 실전에서 조금 복잡한 페이지를 만나면 CSS Selector만으로는 원하는 요소를 집어내기 어려운 상황이 생깁니다. 특정 텍스트를 포함한 요소만 선택하거나, 특정 자식 요소를 가진 부모 노드를 역으로 찾거나, 조건부 필터링을 해야 할 때입니다.

이럴 때 필요한 것이 XPath입니다.

XPath는 XML/HTML 문서에서 특정 노드를 탐색하는 표준 언어입니다. CSS Selector보다 표현력이 강력하고, 일부 경우에는 CSS로는 아예 불가능한 선택을 할 수 있습니다. 2026년 현재도 Python lxml, Scrapy, Playwright, Selenium 등 주요 크롤링 도구가 모두 XPath를 지원합니다.

이 글에서는 XPath의 기본 개념부터 실전 크롤링 코드, 디버깅 팁까지 한 번에 정리합니다.


1. XPath란?

XPath(XML Path Language)는 W3C 표준으로, XML과 HTML 문서의 트리 구조를 탐색하기 위한 쿼리 언어입니다. 1999년 처음 발표된 이후 웹 표준의 핵심 기술로 자리 잡았습니다.

웹 페이지의 HTML은 트리 구조입니다. html 루트에서 시작해 body, 각종 div, span, a 태그들이 부모-자식 관계로 연결됩니다. XPath는 이 트리 구조를 파일 시스템 경로처럼 표현합니다.

예를 들어, 파일 시스템에서 /home/user/documents/report.txt처럼 경로를 지정하듯, HTML에서는 /html/body/div/p 형태로 노드를 특정합니다.

CSS Selector vs XPath: 언제 무엇을 쓸까?

기능 CSS Selector XPath
기본 요소 선택 간결 가능
특정 텍스트 포함 요소 선택 불가 contains(text(), '키워드')
부모 노드 역방향 선택 불가 .. 또는 parent::
형제 노드 접근 제한적 following-sibling::, preceding-sibling::
조건부 필터링 기본적인 것만 복잡한 조건 가능
학습 난이도 쉬움 중간
브라우저 성능 빠름 약간 느림

일반적인 웹 크롤링에서는 CSS Selector를 기본으로 쓰되, CSS로 표현이 안 될 때 XPath를 보조로 사용하는 전략이 효과적입니다.


2. XPath 핵심 문법 레퍼런스

기본 경로 표현식

/html/body/div          # 절대 경로 (루트부터 전체 경로)
//div                   # 문서 전체에서 모든 div 요소
//div[@class='title']   # class 'title' div
//a[@href]              # href 속성이 있는 모든 a 태그

//(이중 슬래시)가 핵심입니다. 실전에서는 절대 경로(/html/body/...)보다 //를 사용한 상대 경로를 훨씬 많이 씁니다. 페이지 구조가 바뀌어도 비교적 안정적이기 때문입니다.

속성 선택자

//div[@id='main']                    # id 'main' div
//input[@type='submit']              # type 'submit' input
//a[contains(@class, 'btn')]         # class 'btn' 포함된 a
//img[@src and @alt]                 # src alt 속성 모두 있는 img
//a[not(@href)]                      # href 없는 a 태그

contains()는 CSS의 [class*="btn"]과 유사하지만, 텍스트 내용에도 적용할 수 있어 훨씬 유연합니다.

텍스트 선택자

//h2[text()='이벤트 공지']                    # 정확히 '이벤트 공지' 텍스트인 h2
//p[contains(text(), '할인')]                  # '할인' 텍스트를 포함하는 p
//button[normalize-space(text())='구매하기']   # 공백 제거  '구매하기' 버튼

이것이 XPath의 핵심 강점입니다. CSS Selector는 텍스트 내용으로 요소를 선택할 수 없지만, XPath는 text()contains()를 조합해 자유롭게 텍스트 기반 선택이 가능합니다.

축(Axis) 탐색

//span[@class='price']/parent::div          # 특정 span 부모 div
//td[contains(@class, 'total')]/../td[1]    # 형제 td  번째
//h3/following-sibling::p                   # h3 다음에 오는 형제 p 태그
//li[last()]                                # 마지막 li 항목
//li[position() <= 5]                       #  5 li 항목

부모 역방향 탐색은 XPath에서만 가능한 기능입니다. 예를 들어 특정 클래스의 버튼을 포함하는 카드 컨테이너 전체를 가져오고 싶을 때 유용합니다.


3. Python으로 XPath 크롤링 (lxml)

기본 설치 및 구조

import requests
from lxml import html

url = "https://example.com/products"
response = requests.get(url, headers={
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"
})

tree = html.fromstring(response.content)

# XPath로 데이터 추출
titles = tree.xpath('//h2[@class="product-title"]/text()')
prices = tree.xpath('//span[@class="price"]/text()')
links = tree.xpath('//a[@class="product-link"]/@href')

for title, price, link in zip(titles, prices, links):
    print(f"{title} | {price} | {link}")

lxml.html.fromstring()으로 HTML을 파싱한 뒤, .xpath() 메서드에 XPath 표현식을 전달하면 됩니다. 반환값은 리스트입니다.

텍스트 vs @속성 선택

# 요소 텍스트 추출: /text()
titles = tree.xpath('//h1/text()')

# 속성값 추출: /@속성명
hrefs = tree.xpath('//a/@href')
images = tree.xpath('//img/@src')

# 요소 객체 자체를 가져올 때 (하위 탐색 필요 시)
product_cards = tree.xpath('//div[@class="product-card"]')
for card in product_cards:
    title = card.xpath('.//h2/text()')  # 점(.)은 현재 요소 기준
    price = card.xpath('.//span[@class="price"]/text()')
    print(title[0] if title else "N/A", price[0] if price else "N/A")

반드시 알아야 할 것: 요소 객체를 loop로 순회할 때 내부 .xpath()에는 점(.)을 붙여 현재 요소 기준으로 탐색해야 합니다. 점 없이 //h2/text()를 쓰면 전체 문서에서 다시 찾습니다.

contains()로 유연한 선택

# class에 특정 단어가 포함된 경우
items = tree.xpath('//*[contains(@class, "item")]')

# 텍스트에 특정 단어가 포함된 링크
discount_links = tree.xpath('//a[contains(text(), "할인")]/@href')

# 여러 조건 조합 (and/or)
main_buttons = tree.xpath('//button[contains(@class, "btn") and @type="submit"]')

4. 동적 페이지 크롤링: Playwright + XPath

정적 HTML은 requests + lxml로 충분하지만, JavaScript로 렌더링되는 동적 페이지는 Playwright나 Selenium이 필요합니다. 두 도구 모두 XPath를 지원합니다.

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()
    page.goto("https://target-site.com")

    # XPath로 요소 선택
    # locator 방식 (권장)
    price = page.locator('xpath=//span[@class="final-price"]').first.text_content()

    # evaluate로 XPath 직접 실행
    result = page.evaluate("""
        () => {
            const el = document.evaluate(
                '//h1[@class="product-name"]/text()',
                document,
                null,
                XPathResult.FIRST_ORDERED_NODE_TYPE,
                null
            ).singleNodeValue;
            return el ? el.nodeValue : null;
        }
    """)

    print(price, result)
    browser.close()

Playwright의 locator('xpath=...')는 XPath 표현식 앞에 xpath= 접두어를 붙이면 됩니다.


5. XPath 디버깅: 브라우저에서 바로 테스트

크롤링 코드를 짜기 전에 XPath가 올바른지 브라우저에서 먼저 확인할 수 있습니다.

Chrome 개발자 도구 사용법:
1. F12 또는 오른쪽 클릭 → 검사
2. Console 탭 선택
3. 아래 코드 입력:

// 단일 요소 찾기
$x('//h1[@class="title"]')

// 모든 매칭 요소 텍스트 출력
$x('//span[@class="price"]').map(e => e.textContent)

// 첫 번째 매칭 텍스트
$x('//div[@class="product-name"]')[0]?.textContent

$x()는 Chrome과 Firefox 모두에서 사용 가능한 내장 XPath 테스트 함수입니다. 코드 작성 전에 원하는 요소가 올바르게 선택되는지 반드시 확인하세요.


6. 실전 예제: 이커머스 상품 데이터 추출

import requests
from lxml import html

def scrape_product_list(url):
    headers = {"User-Agent": "Mozilla/5.0 (compatible; DataCollector/1.0)"}
    resp = requests.get(url, headers=headers, timeout=10)
    tree = html.fromstring(resp.content)

    # 상품 카드 전체 목록 가져오기
    cards = tree.xpath('//li[contains(@class, "product-item")]')

    results = []
    for card in cards:
        name = card.xpath('.//strong[@class="product-name"]/text()')
        price = card.xpath('.//span[contains(@class, "price")]/text()')
        rating = card.xpath('.//span[@class="rating"]/text()')
        reviews = card.xpath('.//span[@class="review-count"]/text()')
        link = card.xpath('.//a[@class="product-link"]/@href')

        results.append({
            "name": name[0].strip() if name else None,
            "price": price[0].strip() if price else None,
            "rating": rating[0].strip() if rating else None,
            "reviews": reviews[0].strip() if reviews else None,
            "url": link[0] if link else None,
        })

    return results

products = scrape_product_list("https://example-shop.com/category/shoes")
print(f"수집된 상품 수: {len(products)}")

7. 자주 발생하는 오류와 해결법

IndexError: list index out of range

# 잘못된 방법
title = tree.xpath('//h1/text()')[0]  # 요소 없으면 에러 발생

# 올바른 방법
titles = tree.xpath('//h1/text()')
title = titles[0] if titles else None

요소는 보이는데 XPath로 안 잡힐 때

원인 대부분은 네임스페이스 또는 동적 렌더링 문제입니다.

# 네임스페이스가 있는 경우
namespaces = {'ns': 'http://www.w3.org/1999/xhtml'}
result = tree.xpath('//ns:div[@id="content"]', namespaces=namespaces)

# 실제로 화면에는 있지만 XPath로 안 잡힐 때 → 동적 렌더링 확인
# requests로 받은 HTML에는 없고, 브라우저에서만 보이는 경우
# → Playwright/Selenium으로 전환 필요

텍스트 추출 시 공백 문제

# normalize-space()로 앞뒤 공백 및 연속 공백 제거
clean_text = tree.xpath('normalize-space(//h1/text())')

# Python에서 후처리
texts = [t.strip() for t in tree.xpath('//p/text()') if t.strip()]

8. XPath의 한계와 대안

XPath가 강력하긴 하지만 한계도 있습니다.

구조 변경에 취약: 절대 경로(/html/body/div[2]/div[3]/ul/li)를 사용하면 사이트 리디자인 한 번에 전체 XPath가 무용지물이 됩니다. 가능하면 @id, @class 속성 기반 XPath를 사용하세요.

JavaScript 렌더링: XPath는 HTML 파싱 도구이지, 렌더링 도구가 아닙니다. 동적으로 로딩되는 컨텐츠는 requests + lxml 조합으로는 수집 불가능합니다.

대규모 크롤링: 수천 개 이상의 페이지를 수집할 때는 Scrapy를 활용하는 것이 훨씬 효율적입니다. Scrapy는 XPath와 CSS Selector를 모두 지원하고 비동기 처리, 미들웨어, 파이프라인을 갖추고 있습니다.


해시스크래퍼로 XPath 없이 크롤링하기

XPath를 배우는 것 자체는 유익하지만, 프로덕션 크롤러를 직접 유지보수하는 비용은 생각보다 큽니다. 사이트 구조가 바뀔 때마다 XPath를 업데이트해야 하고, 자바스크립트 렌더링 처리, IP 차단 우회 등을 별도로 처리해야 합니다.

해시스크래퍼는 이 과정을 API 한 번 호출로 처리합니다. 별도의 XPath나 CSS Selector 없이 URL만 넘기면 JavaScript 렌더링된 HTML을 마크다운, JSON, HTML 형태로 반환합니다.

import requests

response = requests.post(
    "https://api.hashscraper.com/v1/scrape",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={
        "url": "https://target-site.com/products",
        "renderJs": True,
        "outputFormat": "markdown"
    }
)

data = response.json()
print(data["content"])  # 정제된 마크다운으로 반환

직접 크롤러를 구축하기 어렵거나, 크롤링 인프라 관리에 시간을 쓰고 싶지 않다면 해시스크래퍼 무료 체험을 시작해 보세요.


정리

  • XPath는 CSS Selector로 해결 안 되는 복잡한 선택에 사용합니다
  • 텍스트 기반 선택, 부모 역방향 탐색, 조건부 필터링이 핵심 강점입니다
  • Python: lxml + .xpath() 조합이 가장 보편적
  • 동적 페이지: Playwright locator('xpath=...') 사용
  • 디버깅: Chrome 콘솔 $x()로 빠르게 테스트
  • 대규모 크롤링은 직접 XPath를 관리하는 것보다 해시스크래퍼 API 활용을 검토하세요

이 글이 도움이 됐다면, Playwright 크롤링 완전 가이드Scrapy 크롤링 완전 가이드도 함께 읽어보세요.

Comments

Add Comment

Your email won't be published and will only be used for reply notifications.

Continuer la lecture

Get notified of new posts

We'll email you when 해시스크래퍼 기술 블로그 publishes new content.

Your email will only be used for new post notifications.