

Paste HTML, type a CSS selector, see every match highlighted live. Runs querySelectorAll in your browser — no server round-trip.
Need structural queries or text matching? Try the XPath Tester.
Try a sample:
CSS selectors are pattern expressions that pick HTML elements by tag, class, id, attribute, position, or relation. Originally designed for styling, they've become the default DOM query language thanks to document.querySelectorAll. Every scraping library worth its salt — BeautifulSoup (via SoupSieve), parsel, lxml's cssselect, Cheerio in Node — accepts the same selector syntax.
For most scraping work CSS is the right starting point: it's terse, readable, and fast. Use the panel above to draft a selector against real HTML, then drop it into your scraper.
| Pattern | What it matches | Example |
|---|---|---|
| tag | All elements with this tag | a → every link |
| .class | Elements with this class | .product-title |
| #id | Element with this id | #cart |
| A B | B descendants of A | article p |
| A > B | B direct children of A | ul > li |
| A + B | B immediately after A (sibling) | h2 + p |
| A ~ B | B siblings after A | h2 ~ p |
| [attr] | Has attribute | a[rel] |
| [attr=val] | Attribute equals value | input[type="email"] |
| [attr^=val] | Attribute starts with | a[href^="https://"] |
| [attr$=val] | Attribute ends with | img[src$=".webp"] |
| [attr*=val] | Attribute contains substring | span[class*="price"] |
| :nth-child(n) | nth child of its parent | tr:nth-child(odd) |
| :not(sel) | Does not match | a:not([rel]) |
| :has(sel) | Has a matching descendant (modern only) | article:has(img) |
CSS wins on readability and tooling. article > h2.title reads cleanly, every browser DevTools panel speaks CSS, and the selectors compose 1:1 between styling and scraping. For querying by class, attribute, position, or simple structure, CSS is shorter and faster than the XPath equivalent.
XPath wins when you need things CSS can't express: matching by text content, moving up the tree (parent / ancestor), or selecting attributes themselves rather than the elements that bear them. See the XPath Tester for the same SERP-grade tooling on the other side of the fence.
BeautifulSoup ships .select() for CSS via SoupSieve. parsel (used by Scrapy) supports both .css() and .xpath().
# pip install beautifulsoup4 parsel requests
import requests
from bs4 import BeautifulSoup
from parsel import Selector
resp = requests.get("https://example.com")
# BeautifulSoup (via SoupSieve)
soup = BeautifulSoup(resp.content, "html.parser")
titles = [el.get_text(strip=True) for el in soup.select("a.product-title")]
# parsel (used by Scrapy)
sel = Selector(text=resp.text)
prices = sel.css('span[class*="price"]::text').getall()
print(titles, prices):has() only landed in Safari (2022), Chrome (2022), and Firefox (2023). Older browsers and lxml's cssselect throw a SyntaxError. SoupSieve added :has support, but Scrapy users on parsel<1.8 won't have it. Test in a current browser first; the tester above uses your browser's engine so the support window matches reality.
.price matches the WHOLE class token list, but [class="price"] only matches if class is EXACTLY "price". An element with class="price price--current" matches .price but not [class="price"]. Use [class~="price"] for an exact word match.
article > p (with extra spaces) parses fine, but article >p without space after > also parses fine in modern engines and was historically buggy. Stick to single-space separators to keep selectors portable across older Python parsers.
:nth-child(2) is "second child of its parent regardless of tag". :nth-of-type(2) is "second of THIS tag among its siblings". If your <p> is the second child but the first <p>, :nth-child(2) matches; :nth-of-type(1) does too — they target different things.
Try It For Free. No Subscription Required. No Credit Card Required. Instant Set-Up. 150 Free Requests Are Waiting For You!