# How to Use XPath Selectors with CSS Selectors in Scrapling's Parser

> Master Scrapling's parser by combining XPath and CSS selectors. Leverage advanced XPath features like variables and predicates for efficient web scraping. Learn how to translate CSS to XPath.

- Repository: [Karim shoair/Scrapling](https://github.com/D4Vinci/Scrapling)
- Tags: how-to-guide
- Published: 2026-03-08

---

**Scrapling's `Selector` class translates CSS selectors into XPath expressions internally, allowing you to query HTML documents using either syntax while leveraging XPath's advanced features like variables and predicates.**

Scrapling is a high-level web scraping library that unifies CSS and XPath selector syntax through a single `Selector` interface. Understanding how the library handles XPath selectors with CSS selectors in Scrapling enables you to write more resilient extraction logic, whether you prefer the familiarity of CSS or the precision of raw XPath expressions.

## How Scrapling Unifies CSS and XPath Selectors

The `Selector` class in [`scrapling/parser.py`](https://github.com/D4Vinci/Scrapling/blob/main/scrapling/parser.py) abstracts the underlying HTML document and exposes two primary querying methods: `css()` and `xpath()`. While both methods return matching elements, they differ in how the selector string is processed before evaluation.

When you invoke `css()`, Scrapling does not execute the query directly. Instead, it passes the CSS selector string to the `css_to_xpath` helper function defined in [`scrapling/core/translator.py`](https://github.com/D4Vinci/Scrapling/blob/main/scrapling/core/translator.py). This function converts the CSS expression into an equivalent XPath query, which is then forwarded to the `xpath()` method for execution against the lxml tree.

This architecture means that every CSS query ultimately becomes an XPath query, allowing the library to maintain a single evaluation engine while supporting both syntaxes.

## The CSS-to-XPath Translation Pipeline

### From css_to_xpath to lxml Evaluation

The translation process begins in [`scrapling/core/translator.py`](https://github.com/D4Vinci/Scrapling/blob/main/scrapling/core/translator.py), where the `css_to_xpath` function wraps the `cssselect.HTMLTranslator` class with a custom `TranslatorMixin`. This mix-in extends standard CSS selector capabilities by adding support for Scrapling-specific pseudo-elements.

After translation, the resulting XPath expression is passed to `Selector.xpath()` in [`scrapling/parser.py`](https://github.com/D4Vinci/Scrapling/blob/main/scrapling/parser.py). This method forwards the call directly to `self._root.xpath(selector, **kwargs)`, leveraging lxml's native XPath evaluation engine. Any keyword arguments provided to the `xpath()` method are injected as **XPath variables**, enabling dynamic queries without string concatenation.

### Handling Pseudo-Elements (::text and ::attr)

The `TranslatorMixin` in [`scrapling/core/translator.py`](https://github.com/D4Vinci/Scrapling/blob/main/scrapling/core/translator.py) rewrites CSS pseudo-elements into standard XPath fragments:

- `::text` becomes `/text()`
- `::attr(name)` becomes `/@name`

This translation allows you to extract text content and attribute values using CSS-like syntax while maintaining compatibility with the underlying XPath engine. For example, the CSS selector `p::text` is internally converted to `//p/text()` before evaluation.

## Practical Examples: Mixing XPath and CSS Selectors

Because CSS queries are translated to XPath before execution, you can seamlessly alternate between both syntaxes in the same extraction workflow. The following examples demonstrate common patterns using the `Selector` class from [`scrapling/parser.py`](https://github.com/D4Vinci/Scrapling/blob/main/scrapling/parser.py).

### Basic CSS Selection

```python
from scrapling import Selector

html = "<html><body><div class='item'>Product A</div></body></html>"
page = Selector(html)

# CSS selector automatically converted to XPath

items = page.css(".item")
print(items[0].text)  # → "Product A"

```

### XPath with Variables

```python
html = """
<table>
  <tr><td data-id="1">Cell 1</td><td>Cell 2</td></tr>
  <tr><td data-id="2">Cell 3</td><td>Cell 4</td></tr>
</table>
"""
page = Selector(html)

# Pass variables to avoid string formatting vulnerabilities

first_row = page.xpath("//td[@data-id=$row_id]", row_id="1")
print(first_row[0].text)  # → "Cell 1"

```

### Mixed Workflow

```python
page = Selector(html)

# Use CSS to find the container

container = page.css("div.product-list")[0]

# Use XPath with variables to find specific children within the container

price = container.xpath(".//span[@class='price' and text()=$val]", val="$19.99")

```

### Extracting Text and Attributes

```python
page = Selector("""<div class="box"><a href="/page">Click here</a></div>""")

# ::text pseudo-element

link_text = page.css("a::text")
print(link_text.get())  # → "Click here"

# ::attr() pseudo-element

href = page.css("a::attr(href)")
print(href.get())  # → "/page"

```

## Advanced Features: XPath Variables and Adaptive Parsing

### Passing Variables to XPath Expressions

The `xpath()` method in [`scrapling/parser.py`](https://github.com/D4Vinci/Scrapling/blob/main/scrapling/parser.py) accepts arbitrary keyword arguments that are bound as XPath variables during evaluation. This feature, powered by lxml's native support for variable substitution, allows you to write safer, parameterized queries without risking XPath injection through string concatenation.

When you call `page.xpath("//input[@name=$field]", field="username")`, the `field` keyword is passed directly to the underlying lxml `xpath()` method as a named parameter, ensuring proper escaping and type handling.

### Adaptive Relocation with Saved Selectors

Scrapling's parser includes an optional **adaptive** mode that enables element relocation across page changes. When you instantiate `Selector` with `adaptive=True` and provide a storage backend (such as `SQLiteStorageSystem` from [`scrapling/core/storage.py`](https://github.com/D4Vinci/Scrapling/blob/main/scrapling/core/storage.py)), you can save specific elements using the `save()` method and later retrieve them using the same identifier.

This feature operates independently of the CSS-to-XPath translation layer. Whether you originally located an element via `css()` or `xpath()`, the adaptive system stores structural metadata that allows the parser to relocate the element even if the underlying HTML structure changes slightly.

```python
from scrapling import Selector
from scrapling.core.storage import SQLiteStorageSystem

selector = Selector(
    html_content,
    adaptive=True,
    storage=SQLiteStorageSystem,
    storage_args={"storage_file": ":memory:", "url": "https://example.com"}
)

# Save an element for later retrieval

button = selector.css("button.submit")[0]
selector.save(button, "submit_button")

# Later, after page changes, relocate using the saved identifier

new_page = Selector(updated_html, adaptive=True, storage=selector._storage)
button_again = new_page.xpath("//button", identifier="submit_button", adaptive=True)

```

## Summary

- Scrapling's `Selector` class treats CSS selectors as a convenience layer, automatically translating them to XPath via `css_to_xpath` in [`scrapling/core/translator.py`](https://github.com/D4Vinci/Scrapling/blob/main/scrapling/core/translator.py) before execution.
- The translation process supports custom pseudo-elements (`::text` and `::attr()`) that map to standard XPath node tests, enabling text and attribute extraction through CSS-like syntax.
- You can invoke raw XPath queries directly using the `xpath()` method in [`scrapling/parser.py`](https://github.com/D4Vinci/Scrapling/blob/main/scrapling/parser.py), passing keyword arguments as XPath variables for parameterized, injection-safe queries.
- The adaptive parsing feature, controlled via [`scrapling/core/storage.py`](https://github.com/D4Vinci/Scrapling/blob/main/scrapling/core/storage.py), allows you to save and relocate elements across page changes regardless of whether they were originally selected via CSS or XPath.

## Frequently Asked Questions

### Can I use XPath and CSS selectors interchangeably in the same Scrapling script?

Yes. Scrapling's architecture converts CSS selectors to XPath internally, so you can chain operations using either syntax. For example, you can locate a container with `page.css("div.content")` and then query its children with `container.xpath(".//a[@class='link']")`, or vice versa.

### How do I extract text content using CSS selectors in Scrapling?

Use the `::text` pseudo-element, which Scrapling's translator converts to the XPath `/text()` node test. For example, `page.css("p::text")` returns text nodes from all paragraph elements. This is handled by the `TranslatorMixin` in [`scrapling/core/translator.py`](https://github.com/D4Vinci/Scrapling/blob/main/scrapling/core/translator.py).

### What are XPath variables in Scrapling and how do they prevent injection?

XPath variables are keyword arguments passed to the `xpath()` method that get bound as named parameters in the lxml evaluation engine. Instead of concatenating strings like `f"//input[@value='{user_input}']"`, which is vulnerable to injection, you use `page.xpath("//input[@value=$val]", val=user_input)`. The `xpath()` method in [`scrapling/parser.py`](https://github.com/D4Vinci/Scrapling/blob/main/scrapling/parser.py) passes these kwargs directly to lxml's native `xpath()` method.

### Does using CSS selectors instead of raw XPath impact performance?

The performance difference is negligible because CSS selectors are translated to XPath only once per query call. The translation overhead in [`scrapling/core/translator.py`](https://github.com/D4Vinci/Scrapling/blob/main/scrapling/core/translator.py) is minimal compared to the actual DOM traversal performed by lxml. For maximum efficiency in high-volume scraping, you may use raw XPath directly to skip the translation step, though the practical benefit is usually marginal.