Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use soup.find_all(class_="target") to collect every element whose class list contains target. Use soup.find(class_="target") for the first match. If you need CSS selector syntax, use soup.select(".target") or soup.select_one(".target"). The examples below show how to parse a page, narrow matches by tag, require multiple classes, inspect results safely, and troubleshoot the cases that commonly produce empty or unexpected results.

Install Beautiful Soup and parse the HTML

Install the parser library and an HTML parser (the standard-library parser is sufficient for the examples):

python -m pip install beautifulsoup4

Then create a BeautifulSoup object from a string, file, or HTTP response:

from bs4 import BeautifulSoup

html = '''

Second

A note

''' soup = BeautifulSoup(html, "html.parser")

html.parser ships with Python. Beautiful Soup can also use other parsers, but the class-search APIs shown here are the same.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Find every element with one class

Call find_all() with the keyword class_:

cards = soup.find_all(class_="card")

for card in cards:
    print(card.get_text(strip=True))

The output is:

First
Second

The underscore matters. class is a reserved Python keyword, so Beautiful Soup exposes the HTML attribute as class_. A class is not necessarily unique; if five elements use card, find_all() returns all five. The official documentation describes this shortcut as available since Beautiful Soup 4.1.2 (Beautiful Soup documentation).

Get only the first match

first_card = soup.find(class_="card")
if first_card is not None:
    print(first_card.get_text(" ", strip=True))

find() returns a tag or None when nothing matches. Check for None before reading text or attributes so a changed page does not cause an AttributeError.

Restrict the search to a tag

Pass the tag name first when a class can occur on several kinds of elements:

links = soup.find_all("a", class_="sister")
featured_divs = soup.find_all("div", class_="featured")

This still matches an element if the requested class is one item in its space-separated class list.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use CSS class selectors with select()

select() accepts CSS syntax and returns all matching tags:

cards_with_css = soup.select(".card")
first_card_with_css = soup.select_one(".card")

A leading dot means “class” in CSS. The official documentation says that select() uses SoupSieve to run a CSS selector and return matching elements (Beautiful Soup documentation). The same page identifies SoupSieve-backed selector support as available since Beautiful Soup 4.7.0. Treat those as documented feature thresholds; verify the version installed in your own environment.

Require two or more classes

To match an element that has both card and featured, join class selectors without a space:

featured_cards = soup.select(".card.featured")
for tag in featured_cards:
    print(tag.get_text(" ", strip=True))

A space would mean a descendant selector, not “and.” You can also include the tag name: soup.select("div.card.featured").

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

By contrast, soup.find_all(class_="card") deliberately means “has the card class,” even when additional classes are present. Do not pass a whole string such as "body strikeout" when you need order-independent “both classes” logic. The documentation demonstrates that a whole class-attribute string is order-sensitive; "strikeout body" does not match class="body strikeout". Use a compound CSS selector instead.

Express structure with CSS

Selectors are useful when the class is meaningful only in context:

# all article titles
headings = soup.select("article .title")

# a direct child with a class
items = soup.select("ul.results > li.result")

# a class on a paragraph
paragraphs = soup.select("p.note")

For a plain class filter, find_all(class_=...) is often clearer. Choose select() when the query includes combinations, descendants, siblings, or other CSS conditions.

Understand multi-valued class attributes

HTML commonly assigns several classes to one element:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<div class="body strikeout">Text</div>

Beautiful Soup represents that attribute as a list:

tag = soup.find("div")
print(tag["class"])       # ['body', 'strikeout']
print("body" in tag.get("class", []))

Therefore, soup.find_all(class_="body") matches the tag above. If you need to test class membership in Python after a broader search, use tag.get("class", []); get() avoids a KeyError when an element has no class attribute.

Choose the right method

Need Beautiful Soup expression Result
All tags containing one class soup.find_all(class_="target") List of matching tags
First tag containing one class soup.find(class_="target") One tag or None
All tags of a type and class soup.find_all("a", class_="target") List limited to that tag
All matches using CSS syntax soup.select(".target") List of matching tags
First CSS match soup.select_one(".target") One tag or None
Both classes required soup.select(".body.strikeout") Tags containing both classes

The two query styles are alternatives, not different result types: plural methods return all matches, while find() and select_one() return only the first.

Extract text, attributes, and HTML

A search returns Tag objects. Convert them into the value your program needs:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
for card in soup.select(".card"):
    title = card.get_text(" ", strip=True)
    link = card.find("a")
    href = link.get("href") if link else None
    print({"title": title, "href": href})
  • get_text(" ", strip=True) joins descendant text with spaces and removes surrounding whitespace.
  • tag.get("href") returns an attribute value or None when it is absent.
  • str(tag) gives the matched tag and its contents as HTML.

If a class occurs on a wrapper, search inside the matched tag to avoid accidentally collecting unrelated page content:

for card in soup.find_all("div", class_="card"):
    price = card.select_one(".price")
    if price:
        print(price.get_text(" ", strip=True))

Searching a downloaded page

When parsing a local file, read it and pass the contents to Beautiful Soup:

from pathlib import Path
from bs4 import BeautifulSoup

html = Path("page.html").read_text(encoding="utf-8")
soup = BeautifulSoup(html, "html.parser")
results = soup.find_all(class_="product")

For an HTTP response, check the response and parse its body:

import requests
from bs4 import BeautifulSoup

response = requests.get("https://example.com", timeout=30)
response.raise_for_status()
soup = BeautifulSoup(response.text, "html.parser")
products = soup.select(".product")

Beautiful Soup parses the HTML it receives. If a site builds its elements with JavaScript after the initial response, the class will not be present in response.text; use a browser automation tool to render the page first, or identify an underlying data endpoint where permitted.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Troubleshooting empty or incorrect results

“It says class is invalid” or the code will not run

Use class_, not class:

soup.find_all(class_="item")

No tags are returned

  • Print or save the exact HTML passed to Beautiful Soup and verify the class spelling, capitalization, and hyphens.
  • Confirm that you are parsing the response body you expect by checking response.status_code and calling raise_for_status().
  • Check whether the content is injected by JavaScript; a server response may contain no target element.
  • Look for an iframe. The target may be in a separate document that must be fetched or rendered separately.
  • Ensure the class is not generated or changed between requests.

Too many tags are returned

Add a tag name, a parent scope, or a compound selector. For example, replace find_all(class_="title") with select("article.product h2.title") when only product headings are wanted.

Only one of two classes is being enforced

find_all(class_="body") requires only body. To require both body and strikeout, use select(".body.strikeout") or inspect the class list in Python.

A first-match call crashes

find() and select_one() return None when there is no match. Guard the result:

node = soup.select_one(".price")
if node is None:
    print("price was not found")
else:
    print(node.get_text(" ", strip=True))
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance and maintainability

For a single class, both built-in styles are straightforward. Limit the search scope when possible: finding a class within one article is less work and less error-prone than scanning the entire document repeatedly. If you process many pages, parse once, reuse the soup object, and avoid calling the same broad selector inside a loop. CSS selectors are a convenience, not evidence of a speed advantage over Beautiful Soup’s direct API; the documentation notes that parsing with lxml can be faster when CSS selectors are all you need, but that is a parser choice rather than a claim that select() is faster than find_all().

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Write selectors around stable semantics rather than presentation-only classes. Keep a test fixture containing representative HTML, including elements with multiple classes and missing attributes, so a site redesign fails visibly instead of silently producing incomplete data.

Or skip the browser setup

If your goal is to obtain a clean screenshot of the page before inspecting it, ScreenshotNeo returns an image or PDF from one request. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in X-Page-Verdict and X-Billed headers. Its MCP server includes take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

cURL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the parameter reference and options in the ScreenshotNeo documentation. The API supports full-page captures with lazy images loaded, element-by-CSS-selector capture, device presets and custom viewports, dark mode, retina scale, PDF paper and page controls, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen-TTL caching, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Existing parameter names used by other screenshot APIs also work, which can simplify migration.

Every feature is included on every plan. The Free plan provides 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to get started.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently asked questions

Can I search for a class whose name contains unusual characters?

Yes, but CSS syntax may require escaping characters that have selector meaning. For a literal class value, find_all(class_=...) can be simpler; otherwise consult CSS escaping rules and test the selector against a small fixture.

Does Beautiful Soup download a web page?

No. It parses HTML you provide. Use an HTTP client to download a response, or a rendering tool when the target markup is created by JavaScript.

What happens if an element has no class attribute?

It is not returned by a class search. When inspecting an arbitrary tag, use tag.get("class", []) rather than indexing tag["class"].

Frequently Asked Questions

Is find_all(class_="name") case-sensitive?

Yes. Class values are matched as written, so Card and card are different values.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Can I combine a class search with an attribute condition?

Yes. CSS selectors such as .item[data-state="open"] express both conditions; you can also filter the returned Tag objects in Python.

Which parser should I use for malformed HTML?

Start with html.parser. If a particular document needs different error recovery, try another parser supported by your installation and verify the resulting tree before relying on selectors.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.