Advanced filters: regex, lists and functions

Beautiful Soup · Practical · 10 min

Real pages need fuzzy matching (any heading, links to PDFs, tags with some attribute) that plain strings can't express.

Wherever find_all accepts a value (the tag name, an attribute, string), you can pass richer filters:

  • A compiled regex: soup.find_all(re.compile(r"^h[1-6]$")) matches all headings; href=re.compile(r"\.pdf$") matches PDF links. The regex is applied with search, so anchor it if needed.
  • A list: soup.find_all(["h1", "h2"]) matches any of them.
  • `True`: matches anything present: find_all(True) is every tag; attrs={"data-id": True} is "has data-id".
  • A function: called with each tag, returning True to keep it. For example lambda t: t.name == "a" and "download" in t.get("class", []). When passed as an attribute filter (href=func), it receives the attribute value (or None) instead.

The string= argument also accepts these, matching text nodes: soup.find_all(string=re.compile("total", re.I)) returns the matching strings, from which .parent leads back to the tag.

Functions are the escape hatch for any condition, like "a <td> with exactly two children" or "an element whose text parses as a number". Keep them small and named for readability.

For the same patterns in CSS you'd use attribute operators and :-soup-contains; choose whichever reads clearer.

Example

import re
from bs4 import BeautifulSoup

html = """<h1>Report</h1><h3>Q1</h3><p>Total: 120</p>
<a href="/files/q1.pdf">Q1 PDF</a> <a href="/about">About</a>
<span data-id="7">x</span><span>y</span>"""
soup = BeautifulSoup(html, "html.parser")

print([h.name for h in soup.find_all(re.compile(r"^h[1-6]$"))])  # ['h1', 'h3']
print([a.string for a in soup.find_all("a", href=re.compile(r"\.pdf$"))])  # ['Q1 PDF']
print(soup.find_all("span", attrs={"data-id": True}))  # [<span data-id="7">x</span>]

def is_numeric_total(tag):
    return tag.name == "p" and re.search(r"\d+$", tag.get_text()) is not None

print(soup.find(is_numeric_total).get_text())          # Total: 120
s = soup.find(string=re.compile("total", re.I))
print(repr(s), s.parent.name)                          # 'Total: 120' p

Remember

  • Filters can be strings, regexes, lists, True or functions
  • A function filter receives each tag and returns True to keep it
  • string= with a regex returns matching text nodes; use .parent
  • Regexes are searched, so anchor with ^ and $ when needed

Watch out for

  • Passing a regex string instead of re.compile(...), which is matched literally.

Check yourself

  1. What does soup.find_all(['th', 'td']) return?

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/#kinds-of-filters