Skip to content
On this page

XPath (XML Path Language) is a query language for selecting nodes in an HTML or XML document, widely used in web scraping to pinpoint the exact elements you want to extract. Where CSS selectors target elements by tag, class, and id, XPath can also select by an element's text content and navigate in any direction — to parents, siblings, and ancestors. It is supported by Python's lxml and Scrapy, browser DevTools, Selenium, and Playwright.

Quick facts

What it isA path language for selecting nodes in HTML/XML
Select by text//button[text()="Buy"] — CSS cannot do this
Navigate upAxes: parent::, ancestor::, following-sibling::
Pythonlxml: tree.xpath("//...") ; also Scrapy/parsel
Browser/JSSelenium, Playwright, DevTools $x("//...")

Why use XPath for scraping?

HTML is a tree of nested elements, and XPath is a way to write a path to any node in that tree. You can test any XPath instantly in your browser: open DevTools, go to the Console, and run $x('//h1') to get an array of matching elements.

XPath earns its place alongside CSS selectors because it can do two things CSS cannot: select an element by its visible text (//a[text()="Next"]) and walk back up the tree to a parent or previous sibling. When a page has no helpful classes or ids, or you need "the price next to this label," XPath is often the only clean option.

XPath syntax cheat sheet

ExpressionSelects
//divAll <div> elements anywhere in the document
/html/body/div<div> that is a direct child of body
//div/p<p> that is a direct child of any <div>
//div//a<a> anywhere inside any <div> (descendant)
//*[@id="main"]Any element with id="main"
//a[@class="btn"]<a> with class exactly "btn"
//a/@hrefThe href attribute value of every <a>
//h1/text()The text node inside each <h1>
(//div[@class="p"])[1]The first matching <div>
//li[last()]The last <li> in its list

The two leading-slash forms are the ones you use constantly: // means "anywhere below," and / means "direct child." Attributes are matched in square brackets with @.

Predicates, functions, and axes

Predicates (square brackets) filter matches, and XPath ships handy functions for partial and text matching:

ExpressionSelects
//div[contains(@class,"product")]class contains "product" (partial match)
//a[starts-with(@href,"/p/")]href begins with "/p/"
//button[text()="Add to cart"]button whose text is exactly that
//span[contains(text(),"in stock")]span whose text contains the phrase
//input[@type="email" and @required]multiple conditions with and / or

Axes are XPath's superpower — they let you move in any direction, which CSS cannot:

AxisExampleMeaning
parent//span[@class="price"]/parent::divThe div wrapping the price span
ancestor//a/ancestor::articleThe article a link sits inside
following-sibling//dt[text()="Price"]/following-sibling::ddThe value next to a label
preceding-sibling//dd/preceding-sibling::dt[1]The label before a value

The following-sibling pattern — "find the label, then take the value beside it" — is one of the most useful real-world scraping tricks, and it is impossible with CSS selectors alone.

XPath in Python with lxml

The standard way to use XPath in Python is the lxml library (also the engine behind Scrapy and parsel). Install with pip install lxml requests.

import requests
from lxml import html

resp = requests.get("https://books.toscrape.com/")
tree = html.fromstring(resp.content)

# Select titles and prices with XPath:
titles = tree.xpath('//article[@class="product_pod"]//h3/a/@title')
prices = tree.xpath('//p[@class="price_color"]/text()')

for title, price in zip(titles, prices):
    print(title, "|", price)

# Using an axis: the rating sits as a class on a sibling element.
first = tree.xpath('(//article[@class="product_pod"])[1]')[0]
rating = first.xpath('.//p[contains(@class,"star-rating")]/@class')
print(rating)   # e.g. ['star-rating Three']

tree.xpath() returns a list — of elements, strings (for text()), or attribute values (for @attr). Note the leading . in .// when running XPath relative to an element you already selected.

XPath in Node.js with Playwright

Almost every XPath tutorial is Python-only, but XPath works just as well in JavaScript. Playwright supports XPath selectors directly with the xpath= prefix, and it runs the JavaScript first so XPath sees the fully rendered DOM:

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto('https://books.toscrape.com/');

  // Playwright accepts XPath with the xpath= prefix:
  const titles = await page
    .locator('xpath=//article[@class="product_pod"]//h3/a')
    .evaluateAll((els) => els.map((e) => e.getAttribute('title')));

  console.log(titles);
  await browser.close();
})();

For static HTML in Node without a browser, the xpath + @xmldom/xmldom packages evaluate XPath against a parsed document. But Playwright is the cleaner route when the page needs JavaScript anyway.

XPath vs CSS selectors — and why scrapers fail on protected sites

NeedXPathCSS selector
Select by tag/class/idYesYes (shorter)
Select by visible textYesNo
Navigate to parent/ancestorYesNo
Select previous siblingYesNo
Readability for simple casesVerboseCleaner

Use CSS selectors for everyday selecting and XPath when you need text matching or tree navigation. Many scrapers mix both.

One thing neither solves: a selector only works if you actually received the real HTML. If the page is JavaScript-rendered or the site blocks you, your perfect XPath matches nothing. A scraping API returns the fully rendered HTML so your XPath always has real markup to query.

Code example

python
import requests
from lxml import html

# Get real, rendered, unblocked HTML, then query it with XPath.
resp = requests.post(
    'https://api.your-scraping-provider.com/v1?key=YOUR_API_KEY',
    json={'cmd': 'request.get', 'url': 'https://example.com/products'},
    timeout=120,
)
page = resp.json()['solution']['response']
tree = html.fromstring(page)

for row in tree.xpath('//article[@class="product_pod"]'):
    title = row.xpath('.//h3/a/@title')[0]
    price = row.xpath('.//p[@class="price_color"]/text()')[0]
    print(title, '|', price)

End of path

That is all 8 entries in Scraping in every language.

A tour rather than a sequence - the same job in eight ecosystems, so you can compare.

Browse all topics

Related terms

Concept map

Concept map

How XPath for Web Scraping: A Complete 2026 Guide connects

The terms most directly tied to this one. Hover a node to see its neighbours, click to preview, drag to rearrange.

0 terms · 0 connections
You are here · Web Scraping by Language
Building map…

Frequently asked questions

What is XPath in web scraping?

XPath (XML Path Language) is a query language for selecting nodes in an HTML or XML document. In web scraping you use it to pinpoint the elements you want to extract — by tag, attribute, position, or text content. Unlike CSS selectors, XPath can also select elements by their visible text and navigate to parent, ancestor, and sibling nodes.

XPath vs CSS selectors — which is better for scraping?

Use CSS selectors for everyday selection by tag, class, and id; they are shorter and very readable. Use XPath when you need something CSS cannot do: selecting by visible text (//button[text()="Buy"]), walking up to a parent or ancestor, or grabbing the value next to a label with following-sibling. Most experienced scrapers use both, picking whichever is cleaner for each case.

How do I test an XPath expression?

In your browser, open DevTools, switch to the Console tab, and run $x("//your/xpath") — it returns an array of matching elements you can inspect immediately. In the Elements panel you can also right-click an element and choose Copy > Copy XPath, though hand-written XPaths are usually more robust than the auto-generated ones.

Can I use XPath in JavaScript and Node.js?

Yes. Most tutorials are Python-focused, but Playwright supports XPath selectors directly with the xpath= prefix, and Selenium does too. For static HTML in Node without a browser, the xpath package combined with @xmldom/xmldom evaluates XPath against a parsed document. In the browser itself, document.evaluate and the DevTools $x() helper run XPath natively.

Last updated: 2026-06-08