$ pip install beautifulsoup4The package isbeautifulsoup4; you import it asbs4.$ pip install lxml html5libOptional faster / more-lenient parsers (see card 02).from bs4 import BeautifulSoup★The one class you need to start.import bs4; bs4.__version__Check the version, e.g.'4.14.3'.
soup = BeautifulSoup(html, "html.parser")★stdlib, no deps, decent — the safe default.BeautifulSoup(html, "lxml")★Fastest; needspip install lxml. Great for big pages.BeautifulSoup(html, "html5lib")Browser-lenient, fixes broken markup like a browser; slowest.BeautifulSoup(xml, "lxml-xml")Parse XML (or passfeatures="xml").BeautifulSoup(open("page.html"), "lxml")Feed a file handle directly, or a str / bytes.# always name the parsergotchaOmitting it triggersGuessedAtParserWarning& can change behaviour across machines.
bs4.BeautifulSoupThe whole document / root. Behaves like a Tag.bs4.Tag★An element — has.name,.attrs, children. What you search & navigate.bs4.NavigableStringText inside a tag — astrsubclass, so string methods work.bs4.Comment<!-- -->; a special NavigableString (also CData, Doctype).isinstance(x, bs4.Tag)Test node type when walking mixed children.
tag.name★The tag's name, e.g."a". Assignable to rename.tag.attrsAll attributes as a dict:{"href": "/x", "id": "c1"}.tag["href"]★One attribute value.KeyErrorif missing.tag.get("href")★Safe access — returnsNoneif absent.tag.contents · len(tag.contents)Direct children as a list.str(tag) · repr(tag)The tag rendered back to HTML.
soup.find_all("a")★Every matching tag — a list (empty if none).soup.find("a")★First match only, orNone. Same assoup.a.soup("a")Calling the soup isfind_all— a handy shortcut.find_all(["h1", "h2"])A list matches any of several tag names.find_all(re.compile("^h[1-6]$"))A regex matches tag names by pattern.find_all(True) · find_all(fn)Truematches every tag; a function is a custom filter.find_all("a", limit=5, recursive=False)Cap results;recursive=False= direct children only.
find_all("div", class_="lead")★class_(trailing underscore) —classis reserved.find_all(id="main")★Any attribute works as a keyword arg.find_all(attrs={"data-id": "7"})★Useattrs=for hyphenated / reserved names.find_all("a", href=re.compile("^/docs"))Regex-match an attribute value.find_all("a", string="Next")string=filters by the tag's text (wastext=).class_="a b" vs class_="b a"gotchaMulti-class match is order-sensitive; useselect("div.a.b")instead.
soup.select("div.content a")★CSS selector → list of tags. Familiar if you know CSS.soup.select_one("h1#title")★First match orNone— thefindof the CSS world.select("ul > li") · select("a[href]")Child combinator, attribute-presence — full CSS4 support.select("p.lead.intro")Multiple classes the easy way (vsclass_).select("li:nth-of-type(2)")Pseudo-classes via the bundledsoupsieveengine.select(".sister", limit=2)limit=caps results likefind_all.
tag.find_parent("div")Nearest ancestor matching;find_parentsfor all.tag.find_next_sibling("td")★Next sibling tag — skips whitespace strings (see card 10).tag.find_previous_sibling(…)The sibling before it.tag.find_next("p") · find_all_next()Anything later in the document (parse order, any depth).tag.find_previous(…) · find_all_previous()Anything earlier in the document.
next_sibling → often '\n'gotchaWhitespace between tags is a NavigableString sibling — usefind_next_sibling().
tag.get_text()★All text under the tag, concatenated. Alias:tag.text.tag.get_text(separator=" ", strip=True)★Join pieces with a separator & trim whitespace — clean output.tag.stringText only if the tag has exactly one string child, elseNone.list(tag.stripped_strings)Each non-empty text chunk as its own list item..string is None here?gotchaMultiple children →.stringisNone. Reach forget_text().
a["href"]★Direct access — raisesKeyErrorif the attr is absent.a.get("href", "")★Safe access with a default — the scraping-friendly form.img["src"] · a["title"]Any attribute by name.tag["class"] → ["a", "b"]gotchaclassis multi-valued — you get a list, not a string."href" in a.attrsCheck presence before reading.
tag.name = "div"Rename an element.tag["class"] = "new"; del tag["id"]Set or delete attributes.tag.string = "new text"Replace all contents with a single string.new = soup.new_tag("a", href="/x")★Build a fresh tag (then set.string/ append it).tag.append(new) · tag.insert(0, x)Add a child at the end / at an index.tag.insert_before(x) · tag.insert_after(x)Place a node as a sibling.
tag.decompose()★Destroy the tag & its contents in place.removed = tag.extract()Remove from the tree but return it for reuse.tag.clear()Empty the tag's contents, keep the tag.tag.replace_with(new)Swap this node for another.tag.unwrap()Remove the tag, keep its children (strip a wrapper).tag.wrap(soup.new_tag("div"))Surround a node with a new parent.
print(soup.prettify())★Indented, human-readable HTML — great for debugging.str(soup) · str(tag)★Compact HTML for the whole tree or one node.soup.encode("utf-8")Serialize to bytes in a chosen encoding.tag.decode(formatter="html")Formatters:"minimal"(default),"html","html5",None, or a function.soup.prettify(formatter="minimal")Control entity escaping in the output.
BeautifulSoup(resp.content, "lxml")★Pass bytes so BS4 can sniff the encoding itself.BeautifulSoup(data, from_encoding="latin-1")Force an input encoding when detection is wrong.soup.original_encodingWhat BS4 decided the input encoding was.from bs4 import UnicodeDammitUnicodeDammit(data).unicode_markup— standalone encoding guesser.soup.prettify("latin-1")Choose the output encoding (default is UTF-8).
[a["href"] for a in soup.select("a[href]")]★Every link on the page.[img["src"] for img in soup.find_all("img")]All image sources.soup.get_text("\n", strip=True)All visible text, one line per chunk.for tr in soup.select("table tr"):Walk table rows; thentr.find_all(["td","th"])for cells.
# use lxml for speed on big docs★The parser choice is the biggest lever on performance.from bs4 import SoupStrainerParse only part of a document to save time & memory.only = SoupStrainer("a")Keep just<a>tags…BeautifulSoup(html, "lxml", parse_only=only)…the rest of the tree is never built.str(navstr) # detach textCallstr()on a NavigableString you keep, or it pins the whole tree in memory.