tag, .class, #id, [attr], [attr=value], descendant, child — with the failure each one causes when you get it wrong.
Selector syntax is not the hard part of scraping. Knowing which selector will quietly return the wrong rows is. Here are the eight that matter, and the trap attached to each.
tag — the blunt instrumenth3 every heading on the page
Trap: a page has 40 div and 12 span. Use a bare tag only when you also
scope it (inside a row container), otherwise you are guessing.
.class — the workhorse.price any element with class="price"
Trap: class="price old-price" matches too. If the page has both a current and a struck
price, this returns whichever is first in the DOM — usually the original price, not the sale price.
tag.class — narrow it downspan.price
Trap: still matches multiple. Always print how many rows and how many non-empty values you got before trusting the output.
#id — unique, when the page is honest#main
Trap: duplicated ids are invalid HTML and extremely common. If #price appears three
times, your selector silently returns the first.
[attr] — has the attributea[href] links that actually link somewhere
Trap: a[href="#"] and a[href="javascript:void(0)"] also pass. Filter the
value, not the presence.
[attr=value] — exact matchdiv.card[data-sku]
Trap: attribute matching is case-sensitive and exact. rel="nofollow noopener" does not
equal [rel="nofollow"].
A B) — anywhere belowdiv.list h3
Trap: matches any depth. A nested table inside a card carries its own h3, and
you get extra rows. This is the single most common source of "why do I have 47 rows instead of 9".
A > B) — one level onlydiv.list > div.card
This is the one you want for row containers. It excludes the wrapper that reuses the same class (see the scraping guide) and it excludes nested repeats.
| Selector | Why it is skipped |
|---|---|
:nth-child(n) | breaks the moment the page adds a banner row |
:not(...) | usually a symptom of the container being wrong |
A + B, A ~ B | sibling order is not stable on real pages |
[attr^=x], [*=x] | convenient, but easy to over-match |
If your extraction needs these, the page structure is fighting you and it is usually faster to find a better container than a cleverer selector.
document.querySelectorAll(sel).length in the browser console versus the
number of rows your script produced. They must be equal.WebScrape Kit implements selectors 1 to 8 above in a single dependency-free Python file, and it runs check 2 for you: at the end it lists any field that came back empty on every row, because that is a selector bug rather than missing data. 29 USDT, offline test site included.
Turn an HTML page into a spreadsheet with a CSS selector — one file, stdlib only, nothing to install.
Get it — 29 USDT