CSS selectors with select()

Beautiful Soup · Essentials · 10 min

CSS selectors are a compact, familiar language for locating elements, and they map directly from browser dev tools.

soup.select("css selector") returns a list of all matching tags; soup.select_one(...) returns the first or None. They're powered by the soupsieve library, installed with Beautiful Soup.

Selectors you'll use constantly:

  • div.price, #main, a[href], a[href^="https"] (starts with), img[src$=".png"] (ends with), [data-id="42"].
  • Descendant ul li, child ul > li, adjacent sibling h2 + p, general sibling h2 ~ p.
  • Pseudo-classes: li:nth-of-type(2), tr:not(.header), p:-soup-contains("Price") for text matching (a soupsieve extension).
  • Grouping: h1, h2, h3.

Like find_all, select works on any tag to scope the search: card.select_one(".price").

When to prefer which? select shines for structural queries (child of, nth, attribute prefix) and when you can copy a selector from the browser's developer tools; find_all is more convenient with Python values like regex or functions. Many scrapers mix both.

A caution about selectors copied from dev tools: they're often absurdly specific (div:nth-child(3) > div > span) and break with the next redesign. Prefer selectors based on stable classes, ids or data attributes.

Example

from bs4 import BeautifulSoup

html = """<article id="post">
  <h2>Specs</h2><p>Weight: 1.2 kg</p><p>Color: red</p>
  <ul class="links"><li><a href="https://x.io/a">A</a></li>
    <li><a href="/b">B</a></li><li class="ad"><a href="https://ads.io">Ad</a></li></ul>
</article>"""
soup = BeautifulSoup(html, "html.parser")

print(soup.select_one("#post > h2").string)              # Specs
print([p.string for p in soup.select("h2 ~ p")])         # ['Weight: 1.2 kg', 'Color: red']
print([a["href"] for a in soup.select('ul.links li:not(.ad) a[href^="https"]')])
# ['https://x.io/a']
print(soup.select_one('p:-soup-contains("Color")').string)  # Color: red
print(soup.select_one(".missing"))                       # None

Remember

  • select() returns a list, select_one() the first match or None
  • Attribute operators ^= $= *= and combinators > + ~ are supported
  • :-soup-contains() matches by text
  • Prefer stable classes, ids and data attributes over positional selectors

Watch out for

  • Pasting long nth-child selectors from dev tools that break on the next layout change.

Check yourself

  1. What does soup.select('ul > li') match?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.crummy.com/software/BeautifulSoup/bs4/doc/#css-selectors