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.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use Beautiful Soup’s find_all() with a list of tag names when you want every element whose tag is one of several alternatives:

matches = soup.find_all(["a", "b"])

The CSS equivalent is a comma-separated selector list:

matches = soup.select("a, b")

Both forms return all matching elements. Choose find_all() for a simple set of tag names and select() when you need full CSS selectors, classes, IDs or attribute logic.

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

Choose the right multiple-tag query

“Multiple tags” can mean two different things. You may want alternatives such as every <a>, <b> and <img> element, or you may want one element that satisfies several conditions. Beautiful Soup expresses those cases differently.

Goal Query Meaning
Any of several tag names soup.find_all(["a", "b", "img"]) Match an element whose tag name is a, b or img.
Any of several CSS selectors soup.select("a, b, img") Match whichever selector alternative applies.
All conditions on one element soup.select("p.strikeout.body") Match a p element having both classes.
Only the first CSS match soup.select_one("a, b") Return one matching element instead of a list.

A comma means “or.” It does not require the same element to satisfy every selector. To require multiple classes on one element, join class selectors without a comma.

Use find_all() for alternative tag names

Minimal example

from bs4 import BeautifulSoup

html = """
<main>
  <h1>Links and emphasis</h1>
  <a href="/docs">Documentation</a>
  <b>Important</b>
  <p>Read the guide.</p>
  <img src="diagram.png" alt="Diagram">
</main>
"""

soup = BeautifulSoup(html, "html.parser")
matches = soup.find_all(["a", "b", "img"])

for tag in matches:
    print(tag.name, tag.get_text(" ", strip=True), tag.attrs)

The list passed as the first argument is a set of acceptable tag names. The result is a list of matching Tag objects in document order. In the example, the loop visits the link, bold text and image.

Filter several tag names by an attribute

You can combine the list with keyword arguments interpreted as tag-attribute filters. This finds links or images that carry the same class:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
matches = soup.find_all(["a", "img"], class_="item")

The tag must be either a or img, and it must also match the class filter. This is different from a comma-separated CSS selector, which expresses alternatives.

Limit the search to direct children

find_all() searches descendants recursively by default. If you only want tags that are immediate children of the object on which you call it, pass recursive=False:

container = soup.find("main")
direct = container.find_all(["a", "b"], recursive=False)

Nested links or bold elements inside another child are excluded. This is useful when the HTML structure gives direct-child meaning to a row, list or panel.

Retrieve one result instead of a list

find_all() always returns a collection. If you need one result, you can index the list after checking that it is non-empty, or use find() for a first-match query. For CSS syntax, the direct first-match method is select_one().

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
first = soup.select_one("a, b")
if first is not None:
    print(first.name, first.get_text(strip=True))

Use select() for CSS selector alternatives

Comma-separated selectors mean OR

CSS selector lists are often clearer when each alternative has its own class, ID or attribute condition:

matches = soup.select("a.download, button.download, [role='button']")

This returns elements matching any one of the three selectors. The result is still a list, and the elements are returned in document order.

Combine conditions on the same element

To require both classes on a paragraph, write the classes together:

both_classes = soup.select("p.strikeout.body")

Do not write p.strikeout, p.body when you mean “both.” That comma changes the query to “a paragraph with strikeout or a paragraph with body,” so either class is sufficient.

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

Use attributes, descendants and child relationships

CSS becomes more expressive as the query grows. Examples:

# Either links or images with an alt/title-like attribute
media = soup.select("a[href], img[alt]")

# Links inside navigation, regardless of nesting depth
nav_links = soup.select("nav a")

# Only direct article children that are headings
headings = soup.select("article > h2, article > h3")

Use select_one() with the same selector string when only the first matching element matters:

primary = soup.select_one("h1, h2")

A complete Python workflow

The following script parses a local HTML string, finds alternatives with both APIs, applies an attribute filter, and demonstrates the difference between descendant and direct-child searches.

from bs4 import BeautifulSoup

html = """
<section id="products">
  <article class="card featured">
    <a class="item" href="/one">One</a>
    <div class="details">
      <img class="item" src="one.png" alt="One product">
    </div>
  </article>
  <article class="card">
    <b class="item">Two</b>
  </article>
</section>
"""

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

# Alternative tag names: a, b or img.
by_tag_name = soup.find_all(["a", "b", "img"])

# Equivalent alternatives expressed as CSS.
by_css = soup.select("a, b, img")

# Alternative tags with an attribute filter.
items = soup.find_all(["a", "b", "img"], class_="item")

# Multiple conditions on the same element.
featured_cards = soup.select("article.card.featured")

# Direct children only; nested img is not included here.
products = soup.find("section", id="products")
direct_articles = products.find_all("article", recursive=False)

print("find_all:", [tag.name for tag in by_tag_name])
print("select:", [tag.name for tag in by_css])
print("filtered:", [(tag.name, tag.get_text(" ", strip=True)) for tag in items])
print("featured:", [tag.get_text(" ", strip=True) for tag in featured_cards])
print("direct articles:", len(direct_articles))

Install Beautiful Soup with the package name beautifulsoup4, then run the script with the Python interpreter used by your project. The html.parser parser is included with Python; you can substitute another parser already configured in your environment when your project requires it.

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

How matching, ordering and filtering behave

Tag names are alternatives, not a sequence

find_all(["h1", "h2", "h3"]) does not look for an h1 followed by an h2 followed by an h3. It returns every matching heading wherever it occurs in the searched tree.

Results are collections of tags

Each result is a Beautiful Soup tag object. Use tag.name for the element name, tag.attrs for its attributes and tag.get_text(" ", strip=True) for readable text. For an image, text content is normally empty, so inspect tag.get("src") or tag.get("alt").

Apply the narrowest scope first

Searching the whole document is convenient, but selecting a containing element first makes intent clearer and avoids unrelated matches:

main = soup.select_one("main.content")
if main:
    links_or_buttons = main.select("a, button")

If the container is optional, test for None before calling methods on it.

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

Understand class matching

With find_all(), class_="item" filters for an element carrying that class. CSS syntax gives explicit control over one or several classes: .item means the class is present, while .item.featured requires both classes on the same element.

Common mistakes and fixes

Passing one comma-separated string to find_all()

This is a common error:

# Not a CSS selector list for find_all()
wrong = soup.find_all("a, b")

Use a Python list for tag-name alternatives:

right = soup.find_all(["a", "b"])

If you want CSS syntax, use select("a, b").

Using commas when conditions must all hold

Replace a comma with adjacent selectors when the same element needs every class:

# Either class
soup.select("p.strikeout, p.body")

# Both classes
soup.select("p.strikeout.body")

Assuming select_one() returns every match

select_one() returns the first match or None. Use select() for all matches, then iterate or count them.

Searching too deeply

If nested elements unexpectedly appear, select the parent and pass recursive=False to find_all(), or use the CSS child combinator (>) with select().

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

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

Getting an empty result

  • Confirm the HTML actually contains the tag names you requested.
  • Check spelling and capitalization in your selector and attributes.
  • Print or inspect the parsed HTML to verify that the content was present when parsing occurred.
  • Make sure you selected the intended container rather than an empty or missing element.
  • For CSS queries, verify that the selector expresses alternatives with commas and combined conditions without commas.

CSS support, dependencies and speed considerations

Beautiful Soup’s CSS selection is powered by Soup Sieve. Soup Sieve is installed along with Beautiful Soup when Beautiful Soup is installed through pip, so select() and select_one() are available in a normal Beautiful Soup setup.

If your task needs only CSS selectors, the Beautiful Soup documentation recommends skipping Beautiful Soup and parsing with lxml, describing it qualitatively as “a lot faster.” That guidance does not provide a numeric benchmark, so treat it as a tool-selection recommendation rather than a guaranteed speed ratio. If you need Beautiful Soup’s convenient tree navigation, text helpers or mixed API style, keep using Beautiful Soup and narrow the search scope.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance and reliability practices

Reduce the search area

Find a stable parent once, then search inside it. This reduces accidental matches and makes later HTML changes easier to diagnose.

Prefer one expressive query over repeated whole-document scans

When the requirement is naturally a CSS expression, one select() call can be clearer than several independent searches followed by manual merging. For simple tag-name alternatives, one find_all() call is direct and readable.

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

Keep parsing and extraction separate

Parse the response first, then run selectors and validation. If a page is generated by JavaScript, the HTML received by your HTTP client may not contain the elements visible in a browser; Beautiful Soup can only search the markup it receives.

Validate assumptions in production

Check for required containers and expected result counts. Log the URL or document identifier and the selector when a query unexpectedly returns no matches. This turns silent scraper drift into an actionable error.

Or skip the browser setup

If you need a rendered website image rather than parsed HTML, ScreenshotNeo provides a website screenshot API and MCP server. It accepts a URL in one request and can return PNG, JPEG, WebP or PDF. Before capture, it accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup step can be disabled.

Only clean shots are billed. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits cost nothing, and each response reports the result in X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients.

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

cURL

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

Python

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

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 ScreenshotNeo documentation for the full option set, including full-page captures with lazy images loaded, CSS-element captures, dark mode, device presets, custom viewports, retina scale, PDF paper settings, custom CSS and JavaScript, clicks, wait conditions, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, TTL caching, signed links, asynchronous jobs, webhooks, bulk capture of up to 100 URLs per call, usage data and the OpenAPI specification. Existing parameter names used by other screenshot APIs also work, which can simplify migration.

Every plan includes all features. The Free plan provides 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Sign up for the free ScreenshotNeo plan to try it without a card.

Frequently Asked Questions

Can I mix find_all() and select() in one scraper?

Yes. Use each query where it is clearest: find_all() for tag-name lists and select() for CSS relationships or attribute expressions. Both return Beautiful Soup tag objects that can be processed in the same loop.

What should I use when I need a predictable first match?

Use select_one() for a CSS query and check for None. For a tag-name query, use find() or verify that the list from find_all() is non-empty before indexing it.

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

Does a selector retrieve content loaded after parsing?

No. Beautiful Soup searches the tree supplied to it. If markup is inserted later by JavaScript, obtain rendered HTML first, then parse that markup.

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.