     [Blog](https://scrapfly.io/blog)   /  [data-parsing](https://scrapfly.io/blog/tag/data-parsing)   /  [7 Best XPath and CSS Selector Tools for Web Scraping in 2026](https://scrapfly.io/blog/posts/best-xpath-css-selector-tools)   # 7 Best XPath and CSS Selector Tools for Web Scraping in 2026

 by [Mayada Shaaban](https://scrapfly.io/blog/author/mayada-shaaban-90143e67) Sep 11, 2026 19 min read [\#data-parsing](https://scrapfly.io/blog/tag/data-parsing) [\#tools](https://scrapfly.io/blog/tag/tools) 

 [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fbest-xpath-css-selector-tools "Share on LinkedIn") [  ](https://x.com/intent/tweet?url=https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fbest-xpath-css-selector-tools&text=7%20Best%20XPath%20and%20CSS%20Selector%20Tools%20for%20Web%20Scraping%20in%202026 "Share on X") [  ](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fbest-xpath-css-selector-tools "Share on Facebook")    

 

 

Summarize this article with

 [  ](https://chat.openai.com/?q=Summarize%20this%20article%20and%20explain%20how%20Scrapfly%20helps%20me%20scrape%20any%20website%20at%20scale%20and%20bypass%20anti-bot%20systems%20for%20my%20use%20case%3A%20https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fbest-xpath-css-selector-tools) [  ](https://claude.ai/new?q=Summarize%20this%20article%20and%20explain%20how%20Scrapfly%20helps%20me%20scrape%20any%20website%20at%20scale%20and%20bypass%20anti-bot%20systems%20for%20my%20use%20case%3A%20https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fbest-xpath-css-selector-tools) [  ](https://x.com/i/grok?text=Summarize%20this%20article%20and%20explain%20how%20Scrapfly%20helps%20me%20scrape%20any%20website%20at%20scale%20and%20bypass%20anti-bot%20systems%20for%20my%20use%20case%3A%20https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fbest-xpath-css-selector-tools) [  ](https://www.perplexity.ai/search/new?q=Summarize%20this%20article%20and%20explain%20how%20Scrapfly%20helps%20me%20scrape%20any%20website%20at%20scale%20and%20bypass%20anti-bot%20systems%20for%20my%20use%20case%3A%20https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fbest-xpath-css-selector-tools) [  ](https://www.google.com/search?udm=50&aep=11&q=Summarize%20this%20article%20and%20explain%20how%20Scrapfly%20helps%20me%20scrape%20any%20website%20at%20scale%20and%20bypass%20anti-bot%20systems%20for%20my%20use%20case%3A%20https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fbest-xpath-css-selector-tools) 



         

You copy an XPath out of a browser tab, paste it into the scraper, and get an empty list. The expression is valid. The page looks the same in both places. But your browser and your parser never read the same HTML.

The seven tools below each solve a different version of that mismatch. Some check a selector against saved HTML, some read the live DOM, and some write locators for one framework. They're ordered by job, not by score.

[Parsing HTML with CSS SelectorsIntroduction to using CSS selectors to parse web-scraped content. Best practices, available tools and common challenges by interactive examples.](https://scrapfly.io/blog/posts/parsing-html-with-css)



## Key Takeaways

- **Saved HTML beats a live tab** when you need to know why a parser returned nothing.
- **DevTools shows the rendered DOM**, which is a different document from the HTTP response.
- **SelectorsHub** generates XPath, CSS, and Playwright locators from inside the browser.
- **Framework inspectors** write locators for one runtime, not portable selectors.
- **XPather runs XPath 2.0**, so `ends-with()` works there and raises in lxml.
- **A match proves syntax, not durability**, so assert the count on a second page.

**Get web scraping tips in your inbox**Trusted by 100K+ developers and 30K+ enterprises. Unsubscribe anytime.







## Which XPath or CSS Selector Tool Is Best?

Use a standalone tester when you have the HTML your scraper received. Use DevTools when the live page is what you're debugging, and a framework inspector when the locator runs inside Playwright or Selenium.

Those are three different failures, and no single tool sees all three.

| Tool | Best for | Runs in | XPath | CSS | Output |
|---|---|---|---|---|---|
| Scrapfly Tester | Saved HTML | Browser tool | Yes | Yes | Match preview |
| Chrome DevTools | Live Chromium DOM | Browser | Yes | Yes | Elements |
| Firefox DevTools | Live Firefox DOM | Browser | Yes | Yes | Elements |
| SelectorsHub | Locator generation | Extension | Yes | Yes | Locator options |
| Playwright Inspector | Playwright locators | App + CLI | Yes | Yes | Locator code |
| Selenium IDE | Recorded flows | Extension | Yes | Yes | Test steps |
| XPather | XPath evaluation | Web tool | Yes | No | Match output |

The split that matters most in that table is source HTML against rendered DOM. An HTTP client stores the bytes the server sent. A browser parses those bytes, runs the page's JavaScript, and keeps mutating the tree afterward.

Chrome's own documentation states the rule plainly: "HTML represents initial page content and the DOM represents active, current page content. When JavaScript adds, removes, or edits nodes, the DOM becomes different than the HTML."

So a selector you verified in the Elements panel ran against the second document. Your scraper queries the first one. Everything below groups the tools by which of the two they read.



## Project Setup

The examples run against a saved page from `web-scraping.dev`. They need four packages: `parsel` to run selectors, `lxml` with `html5lib` for the parser comparison, and the Scrapfly SDK for the live-fetch example later on.

bash```bash
pip install scrapfly-sdk parsel lxml html5lib
curl --output product-1.html https://web-scraping.dev/product/1
```



The first command installs the dependencies. The second saves the exact HTML fixture the selector examples read as `product-1.html`.

With those installed, the first tool is the one that works on exactly the HTML those packages will parse.



## 1. Scrapfly CSS Selector and XPath Tester: Best for Saved HTML Fixtures

The [Scrapfly CSS Selector and XPath Tester](https://scrapfly.io/web-scraping-tools/css-xpath-tester) takes HTML you paste in and reports which elements a CSS or XPath query matches. There's no URL field and no page fetch.

That constraint is the reason to use it. The document under test is the one you supply, not one a browser rebuilt.

You either paste markup into the **HTML Input** editor or pick one of five **Example HTML Templates**. Those are Simple Page, E-commerce Product, Blog Post, Data Table, and Nested Structure.

A **CSS** and **XPath** pair of buttons switches query modes, and **XPath** is the mode the page starts in.

Results appear as you type. A **Matches** counter shows the number of hits, and **Matched Results** lists each one with its node name and the first 200 characters of content. Every result gets a **Copy** button.

The **Actions** row turns a working query into code. **Export Python Code** emits an `lxml` snippet using `cssselect` or `xpath()`.

**Export JavaScript Code** emits Puppeteer and plain DOM snippets, plus Cheerio in CSS mode. **Download Results** saves the matches as a text file.

This XPath looks correct and returns nothing against the real product page:

python```python
from parsel import Selector

source = open("product-1.html", encoding="utf-8").read()
sel = Selector(source)

print(len(sel.xpath("//h3[@class='product-title']")))
print(sel.xpath("//h3[contains(@class, 'product-title')]/text()").get())
print(sel.css("h3.product-title::text").get())
```



text```text
0
Box of Chocolate Candy
Box of Chocolate Candy
```



The heading's class attribute is `card-title product-title mb-3`, so an `@class` equality test never matches. CSS handles multi-valued class attributes natively, and the XPath form needs `contains()`.

Pasting the fixture into the tester surfaces that in one keystroke instead of one deploy.

That workflow assumes you already have the saved HTML in hand. The next tool works the opposite way, reading whatever the browser is holding right now.



## 2. Chrome DevTools: Best for Inspecting the Live Chromium DOM

[Chrome DevTools](https://developer.chrome.com/docs/devtools) reads the DOM as Chromium currently holds it, after scripts have run. Reach for it when the question is what the page became, not what the server sent.

Our guide to [browser developer tools](https://scrapfly.io/blog/answers/browser-developer-tools-in-web-scraping) covers the wider workflow.

Two surfaces matter for selector work. Press `Control+F` or `Command+F` in the Elements panel to open the Search bar. It accepts a plain string, a CSS selector, or an XPath expression, then walks you through the matched nodes.

The Console gives you the same queries as functions:

javascript```javascript
$$('#reviews .review').length
$x("//h3[contains(@class, 'product-title')]")[0].textContent
```



`$$(selector)` returns an array of elements matching a CSS selector, and `$x(path)` returns an array matching an XPath expression.

Both take an optional second argument, `startNode`, which scopes the query to a subtree instead of the whole document. That's the fastest way to check whether a selector is unique inside a container.

Right-clicking a node gives you **Copy** options including CSS selector and XPath. Treat what it produces as a first draft, since the generated path is usually a positional chain that breaks on the next layout change.

The trap is that everything above runs on the post-JavaScript tree. A count of 5 in the Console tells you nothing about what an HTTP client will see.

Chromium is only one rendering engine, though, and a selector that looks solid there can still behave differently elsewhere.



## 3. Firefox DevTools: Best for Cross-Browser DOM Inspection

[Firefox DevTools](https://firefox-source-docs.mozilla.org/devtools-user/) centers on the Page Inspector. It shows the HTML pane, the CSS Rules view, the box model, and event listeners for a selected node.

Its job here is narrower than Chrome's. Run the same selector in both browsers to see whether a failure is browser-specific or belongs to the DOM itself.

The Page Inspector search box runs three search types at once. It always runs a full text search, and it accepts XPath. It also treats valid CSS selectors as element queries, with autocomplete on matching class and ID attributes.

Mozilla's docs use `//a` as the example that finds every anchor element without also matching the letter "a" in body text.

The node context menu carries a Copy submenu with **Inner HTML**, **Outer HTML**, **CSS Selector**, **CSS Path**, **XPath**, **Image Data-URL**, and **Attribute**. `Control+C` copies outer HTML by default.

The HTML Standard defines error handling for malformed markup, so two conformant parsers should build the same tree from the same bytes. What differs is the XPath implementation each browser ships, and the JavaScript each one ran before you looked.

When a selector matches in one browser and not the other, suspect the XPath engine or the page's own scripts before the markup.

Both DevTools panels leave the work of writing the locator to you. The next tool automates that step.



## 4. SelectorsHub: Best for Generating and Verifying Locators

[SelectorsHub](https://selectorshub.com/selectorshub/) is a browser extension that writes locators for an element you pick and checks them in place. It runs on Chrome, Edge, Firefox, Opera, and Chromium builds like Brave, and it adds its own DevTools panel alongside the built-in ones.

For a selected element it offers relative and absolute XPath, a CSS selector, ID, name, link text, and partial link text. It also emits Playwright locators in `getByRole()`, `getByLabel()`, and `getByText()` form.

Where it pulls ahead of a plain Copy-as-XPath is coverage of the structures that defeat generated paths. That means shadow DOM including nested roots, multi-level iframe hierarchies, and SVG elements.

SelectorsHub also validates as you type and suggests alternatives when a locator stops matching.

The caveat applies to every generator. A tool only sees the page it has, so it picks whatever attribute happens to be present.

The tool cannot know that `class="col-2 price-wrap"` is layout scaffolding while `data-testid` is a contract. Read the suggestions, then pick the attribute you expect to survive a redesign.

That review step is easier when you re-run the chosen locator against a stored copy of the page. The parsing libraries below do exactly that.

[Guide to Parsel - the Best HTML Parsing in PythonLearn to extract data from websites with Parsel, a Python library for HTML parsing using CSS selectors and XPath.](https://scrapfly.io/blog/posts/guide-to-html-parsing-with-parsel-python)

SelectorsHub's CSS and XPath output can move between runtimes that support the same selector syntax, while its Playwright locators stay Playwright-specific. The next tool generates and tests those locators inside Playwright itself.



Scrapfly

#### Extract structured data automatically?

Scrapfly's Extraction API uses AI to turn any webpage into structured data — no selectors needed.

[Try Free →](https://scrapfly.io/register)## 5. Playwright Inspector: Best for Playwright Locator Debugging

[Playwright Inspector](https://playwright.dev/docs/debug) is the debugger that ships with [Playwright](https://scrapfly.io/blog/posts/web-scraping-with-playwright-and-python). Run it with `--debug` on the Node runner, or `PWDEBUG=1` in Python. Either one opens the inspector with a headed browser and no timeout:

bash```bash
npx playwright test --debug
PWDEBUG=1 pytest -s
```



The first command launches the inspector against the Node test runner, and the second does the same for a Python test run under `pytest`. Both pause before the first action and open the same toolbar.

The toolbar plays, pauses, and steps through each action. It highlights the current action in the code, and the elements it matches in the browser.

Dropping `await page.pause()` into a test jumps straight to that line instead of stepping there.

Locator picking is the part that replaces a copy-paste XPath. Click **Pick Locator** and hover an element. Playwright then "will look at your page and figure out the best locator, prioritizing role, text and test id locators."

You can then edit that locator in the field and watch the highlight update live.

The other half is the actionability log. Playwright runs visibility, stability, and scroll checks before it clicks, so a paused click shows you which check is still failing. That turns a vague timeout into a named condition to wait on.

Carry one detail back to your scraper. Playwright accepts `page.locator('css=button')` and `page.locator('xpath=//button')`, but its docs recommend against XPath.

They suggest "prioritizing user-visible locators like text or accessible role" instead, because an XPath encodes the current markup. XPath also does not pierce shadow roots, while Playwright's CSS selectors pierce open ones.

Playwright Inspector assumes you already have the page that holds the element. The next tool is for the case where reaching that page is the hard part.



## 6. Selenium IDE: Best for Recording Browser Flows

[Selenium IDE](https://www.selenium.dev/selenium-ide/) records what you click and type in its Firefox extension, then plays the sequence back. Its Chrome Web Store listing now returns "Item not available" following Chrome's Manifest V2 deprecation, so Firefox is the supported path.

Use it when the hard part isn't the selector but the path to the page that holds it. Think of a login, a filter, and two paginations before the data appears.

Its answer to brittle selectors is redundancy. Selenium IDE "records multiple locators for each element it interacts with," so playback falls back when the first one stops matching.

It also supports `if`, `while`, and `times` for control flow, breakpoints for debugging, and reuse of one test case inside another.

The recorded steps are a starting point rather than a scraper. Read the locators it captured and keep the ones anchored to stable attributes.

Rewrite the positional ones before porting the flow to [Selenium](https://scrapfly.io/blog/posts/web-scraping-with-selenium-and-python) or another driver.

Check the project's release history before you commit to it. The newest tagged release on GitHub is `v4.0.1-beta.14` from July 2024, and the last commit on the default `trunk` branch landed in November 2024.

Recording a whole browser session is the heavy end of this list. The last tool sits at the other end, with one pane and one query language.



## 7. XPather: Best for Quick Standalone XPath Evaluation

[XPather](https://xpather.com/) does one job. Paste XML or HTML into the left pane, type an XPath into the bar across the top, and read the matches on the right.

It has a **Format** button and a **Text** and **Node** switch for how results print. An **XML mode** and **HTML mode** label follows your input. There's no CSS support and no way to fetch a URL.

XPather is the right tool when you already have a fragment on the clipboard and want an answer before a framework inspector finishes booting.

For anything involving page state, cookies, or JavaScript, XPather has nothing to offer.

One difference is worth knowing before you trust a result. XPather evaluates through the jQuery XPath plugin, which bundles an XPath 2.0 implementation, while browsers and lxml both stop at XPath 1.0. So an expression can pass in XPather and raise in Python:

python```python
from lxml import etree
from parsel import Selector

sel = Selector(open("product-1.html", encoding="utf-8").read())

for expression in [
    "//img[ends-with(@src, '.webp')]",
    "//img[substring(@src, string-length(@src) - 4) = '.webp']",
]:
    try:
        print(f"OK    {len(sel.xpath(expression)):>3} match(es)  {expression}")
    except (etree.XPathEvalError, ValueError) as exc:
        print(f"RAISE {type(exc).__name__}: {exc}")
```



text```text
RAISE ValueError: XPath error: Unregistered function in //img[ends-with(@src, '.webp')]
OK      8 match(es)  //img[substring(@src, string-length(@src) - 4) = '.webp']
```



`ends-with()` is an XPath 2.0 function, so lxml rejects it and you need the 1.0 `substring()` form. The same applies to `matches()`. Verify anything you build in XPather against the parser that will run it.

A match in any of these seven tools still only proves the expression parsed against one document at one moment. Keeping it working is a separate discipline.



## How Do You Turn a Valid Selector Into a Stable Scraper?

Prefer semantic IDs and attributes over generated paths, and scope the query to a parent that won't move. Then assert how many elements you expect and run it against more than one saved page.

A tool tells you a selector matched once. Only your own checks tell you it keeps matching.

Five habits carry most of the weight:

1. **Skip generated positional chains when a stable attribute exists.** `div:nth-child(3) > span` encodes today's layout. `[data-testid="price"]` encodes intent.
2. **Separate the source response from the rendered DOM.** Decide which document your scraper reads before you decide where to test the selector.
3. **Assert the expected cardinality.** A query that should return one element and returns five is a bug that still passes a truthiness check.
4. **Save fixtures for regression checks.** Keep the HTML that worked, then replay selectors against it after every target change.
5. **Re-test after the target ships anything.** A redesign, an A/B test, or a country-specific layout can each move the node.

Point two is where most reports of "it worked in DevTools" end. On the product page used above, reviews load after the initial response, so the same selector answers differently depending on which document it sees:

python```python
from parsel import Selector
from scrapfly import ScrapeConfig, ScrapflyClient

client = ScrapflyClient(key="YOUR_SCRAPFLY_KEY")
url = "https://web-scraping.dev/product/1"

source = client.scrape(ScrapeConfig(url=url)).scrape_result["content"]
rendered = client.scrape(
    ScrapeConfig(url=url, render_js=True, wait_for_selector=".review")
).scrape_result["content"]

for label, doc in (("source response", source), ("rendered DOM ", rendered)):
    count = len(Selector(doc).css("#reviews .review"))
    print(f"{label}: '#reviews .review' matched {count} element(s)")
```



text```text
source response: '#reviews .review' matched 0 element(s)
rendered DOM : '#reviews .review' matched 5 element(s)
```



The selector was never wrong. The source response contains an empty `<div id="reviews">` and the reviews arrive later, so a scraper without rendering has nothing to match.

Parser choice moves the tree too, and the classic case is `<tbody>`. When a browser parser meets a `<tr>` in a `<table>` with no row group open, the HTML Standard tells it to add a `<tbody>`. [lxml](https://scrapfly.io/blog/posts/intro-to-parsing-html-xml-python-lxml) does not:

python```python
from lxml import etree, html
from lxml.html import html5parser

TABLE = "<table><tr><td>Chocolate</td><td>9.99</td></tr></table>"
lxml_tree = html.fromstring(TABLE)
browser_tree = html5parser.fromstring(TABLE)

print("lxml tree:", etree.tostring(lxml_tree).decode())
print("browser tbody count:", len(browser_tree.xpath('//*[local-name()="tbody"]')))
print("//table/tbody/tr in lxml:", len(lxml_tree.xpath("//table/tbody/tr")))
print("//table//tr in lxml:", len(lxml_tree.xpath("//table//tr")))
```



text```text
lxml tree: <table><tr><td>Chocolate</td><td>9.99</td></tr></table>
browser tbody count: 1
//table/tbody/tr in lxml: 0
//table//tr in lxml: 1
```



Copy `//table/tbody/tr` out of the Elements panel and it returns zero rows in Python, because the `tbody` you selected was never in the markup. Writing `//table//tr` sidesteps the whole question.

For the syntax itself, our [XPath cheat sheet](https://scrapfly.io/blog/posts/xpath-cheatsheet) and [CSS selector cheat sheet](https://scrapfly.io/blog/posts/css-selector-cheatsheet) cover the operators and axes these tools generate.

If you'd rather draft selectors from a prompt, [finding web selectors with ChatGPT](https://scrapfly.io/blog/posts/finding-web-selectors-with-chatgpt) walks through that approach. The same verification step still applies.

[Parsing HTML with XpathIntroduction to xpath in the context of web-scraping. How to extract data from HTML documents using xpath, best practices and available tools.](https://scrapfly.io/blog/posts/parsing-html-with-xpath)



## Getting the HTML Your Selectors Have to Parse

Every check above assumes you can retrieve the page in the first place, which is the part that fails once a target starts blocking clients.



ScrapFly's [Web Scraping API](https://scrapfly.io/products/web-scraping-api) is a single endpoint for fetching public pages through managed proxies, browser rendering, and the Unblocker.

- [Unblocker](https://scrapfly.io/docs/scrape-api/unblocker) - detects anti-bot protections and applies the browser profile, request configuration, proxy, and retry strategy needed for the target.
- [Smart proxy rotation](https://scrapfly.io/docs/scrape-api/proxy) - residential and datacenter pools with country and ASN level geo-targeting.
- [JavaScript rendering](https://scrapfly.io/docs/scrape-api/javascript-rendering) - render SPAs and dynamic pages through real cloud browsers.
- [Browser automation scenarios](https://scrapfly.io/docs/scrape-api/javascript-scenario) - scroll, click, fill forms, and wait for elements without managing a browser fleet.
- [Format conversion](https://scrapfly.io/docs/scrape-api/getting-started#api_param_format) - return pages as HTML, JSON, clean text, or LLM ready Markdown.
- [Session management](https://scrapfly.io/docs/scrape-api/session) - keep cookies, headers, and IPs consistent across multi step flows.
- [Smart caching](https://scrapfly.io/docs/scrape-api/getting-started#api_param_cache) - cache successful responses to cut cost on repeat scraping jobs.
- [Python](https://scrapfly.io/docs/sdk/python), [TypeScript](https://scrapfly.io/docs/sdk/typescript), [Scrapy](https://scrapfly.io/docs/sdk/scrapy), and [no-code integrations](https://scrapfly.io/docs/integration/getting-started) including [Make](https://scrapfly.io/integration/make), [n8n](https://scrapfly.io/integration/n8n), [Zapier](https://scrapfly.io/integration/zapier), [LangChain](https://scrapfly.io/integration/langchain), and [LlamaIndex](https://scrapfly.io/integration/llamaindex).

The `render_js` flag produced the two documents compared earlier. Save both, then point your selector tests at whichever one your scraper reads.



### Web Scraping API

Scrape any website with our powerful API. Anti-bot bypass, JavaScript rendering, and rotating proxies built-in.



[Try Web Scraping API](https://scrapfly.io/docs/scrape-api/getting-started)



## FAQ

Is XPath or CSS better for web scraping?CSS is shorter for attribute and hierarchy queries, and it handles multi-valued class attributes without extra syntax. XPath wins on text matching and tree navigation through axes, so pick the one your parser and target structure support cleanly.







Why does a selector work in DevTools but fail in the scraper?The scraper usually receives source HTML before JavaScript changes the page, or it uses a parser that builds a different tree. Test the exact HTML your client received to tell those two causes apart.







Can Playwright Inspector generate XPath?Pick Locator prioritizes role, text, and test ID locators rather than XPath, though `page.locator('xpath=//button')` works if you write it yourself. Playwright's docs recommend against XPath because it ties the locator to the current markup.







How do you test whether a selector is stable?Run it against several saved pages and assert both the match count and the field values. A single successful match proves the syntax parsed, not that the selector will survive a layout change.







Is scraping with XPath and CSS selectors legal?Parsing HTML you already retrieved is not itself restricted, since the legal questions attach to how you collected the page and what the data contains. Check the target's terms of service and stay clear of personal data and copyrighted content.









## Summary

Selector tools split by the document they read. Paste-in testers work on HTML you supply, while DevTools in Chrome and Firefox read the live post-JavaScript DOM.

SelectorsHub, Playwright Inspector, and Selenium IDE sit in a third group. They generate locators for one specific runtime.

The examples showed what that split costs. An `@class` equality test missed a multi-valued attribute, and `ends-with()` raised in lxml after passing in an XPath 2.0 evaluator.

`#reviews .review` matched five elements in the rendered DOM and zero in the response body. `//table/tbody/tr` found rows in a browser and none in Python.

Pick the tool that reads the same document as your scraper, then assert the match count and replay against a saved fixture. That pair of habits catches more breakage than any generator prevents.



Legal Disclaimer and PrecautionsThis tutorial covers popular web scraping techniques for education. Interacting with public servers requires diligence and respect:

- Do not scrape at rates that could damage the website.
- Do not scrape data that's not available publicly.
- Do not store PII of EU citizens protected by GDPR.
- Do not repurpose *entire* public datasets which can be illegal in some countries.

Scrapfly does not offer legal advice but these are good general rules to follow. For more you should consult a lawyer.

 

   [  Add as a preferred source ](https://google.com/preferences/source?q=scrapfly.io) Table of Contents















 

  Table of Contents- [Key Takeaways](#key-takeaways)
- [Which XPath or CSS Selector Tool Is Best?](#which-xpath-or-css-selector-tool-is-best)
- [Project Setup](#project-setup)
- [1. Scrapfly CSS Selector and XPath Tester: Best for Saved HTML Fixtures](#1-scrapfly-css-selector-and-xpath-tester-best-for-saved-html-fixtures)
- [2. Chrome DevTools: Best for Inspecting the Live Chromium DOM](#2-chrome-devtools-best-for-inspecting-the-live-chromium-dom)
- [3. Firefox DevTools: Best for Cross-Browser DOM Inspection](#3-firefox-devtools-best-for-cross-browser-dom-inspection)
- [4. SelectorsHub: Best for Generating and Verifying Locators](#4-selectorshub-best-for-generating-and-verifying-locators)
- [5. Playwright Inspector: Best for Playwright Locator Debugging](#5-playwright-inspector-best-for-playwright-locator-debugging)
- [6. Selenium IDE: Best for Recording Browser Flows](#6-selenium-ide-best-for-recording-browser-flows)
- [7. XPather: Best for Quick Standalone XPath Evaluation](#7-xpather-best-for-quick-standalone-xpath-evaluation)
- [How Do You Turn a Valid Selector Into a Stable Scraper?](#how-do-you-turn-a-valid-selector-into-a-stable-scraper)
- [Getting the HTML Your Selectors Have to Parse](#getting-the-html-your-selectors-have-to-parse)
- [FAQ](#faq)
- [Summary](#summary)
 
    Join the Newsletter  Get monthly web scraping insights 

 

  



Scale Your Web Scraping

Anti-bot bypass, browser rendering, and rotating proxies, all in one API. Start with 1,000 free credits.

  No credit card required  1,000 free API credits  Anti-bot bypass included 

 [Start Free](https://scrapfly.io/register) [View Docs](https://scrapfly.io/docs/onboarding) 

 Not ready? Get our newsletter instead. 

 

 ## Related Articles

 [  

 data-parsing css-selectors 

### Parsing HTML with CSS Selectors

Introduction to using CSS selectors to parse web-scraped content. Best practices, available tools and common challenges ...

 

 ](https://scrapfly.io/blog/posts/parsing-html-with-css) [  

 python data-parsing 

### Parsing HTML with Xpath

Introduction to xpath in the context of web-scraping. How to extract data from HTML documents using xpath, best practice...

 

 ](https://scrapfly.io/blog/posts/parsing-html-with-xpath) [  

 data-parsing xpath 

### Ultimate XPath Cheatsheet for HTML Parsing in Web Scraping

Ultimate companion for HTML parsing using XPath selectors. This cheatsheet contains all syntax explanations with interac...

 

 ](https://scrapfly.io/blog/posts/xpath-cheatsheet) 

  ## Related Questions

- [ Q How to find HTML elements by class? ](https://scrapfly.io/blog/answers/how-to-find-html-elements-by-class)
- [ Q XPath vs CSS selectors: what's the difference? ](https://scrapfly.io/blog/answers/xpath-vs-css-selectors)
- [ Q How to use CSS selectors in NodeJS when web scraping? ](https://scrapfly.io/blog/answers/how-to-use-css-selectors-in-nodejs)
- [ Q How to use XPath selectors in Python? ](https://scrapfly.io/blog/answers/how-to-use-xpath-selectors-in-python)
 
  



   



 Extract structured data with AI, **1,000 free credits** [Start Free](https://scrapfly.io/register)