XGrok answer:
To add a **horizontal line** (i.e.
<hr> tag) — or any other tag — using **BeautifulSoup** in Python, you need to:
1. Create a new tag using the
.new_tag() method
2. Insert/append that tag into an existing element in the parse tree
Here are the most common and correct ways (BeautifulSoup 4):
### 1. Most common — Append at the end of a container
from bs4 import BeautifulSoup
html = """
<div class="content">
<p>First paragraph</p>
<p>Second paragraph</p>
</div>
"""
soup = BeautifulSoup(html, "html.parser")
# Find where you want to add the line
container = soup.find("div", class_="content")
# Create the <hr> tag
hr = soup.new_tag("hr") # you can also add attrs: soup.new_tag("hr", id="sep", class_="thin")
# Add it at the end
container.append(hr)
# Optional: add attributes later if needed
# hr["class"] = "separator"
# hr["style"] = "border: 2px dashed red;"
print(soup.prettify())**Result (relevant part):**
Output:
<div class="content">
<p>
First paragraph
</p>
<p>
Second paragraph
</p>
<hr/>
</div>
### 2. Insert at specific position (recommended when order matters)
# Insert after first <p>
ps = container.find_all("p")
if len(ps) >= 1:
ps[0].insert_after(hr) # ← very readable & commonly used
# OR — more precise control with index
container.insert(1, hr) # insert as 2nd child (0-based index)### 3. Quick one-liners people often use
# After a specific element
soup.find("p", string="First paragraph").insert_after(soup.new_tag("hr"))
# At the beginning of body
soup.body.insert(0, soup.new_tag("hr"))
# Before last child
soup.body.contents[-1].insert_before(soup.new_tag("hr"))### Summary — Main methods that allow adding tags
| Method | What it does | When to use | Modifies in place? |
|---------------------|----------------------------------------------------|------------------------------------------|--------------------|
|
.new_tag("hr") | Creates brand new tag (must be called on Soup object) | Always — first step | — |
|
.append(tag) | Adds as **last** child | Quick addition at end | Yes |
|
.insert(index, tag) | Inserts at specific child position (0-based) | Need exact position | Yes |
|
.insert_before(tag) | Inserts immediately before this element | "Put line before this paragraph" | Yes |
|
.insert_after(tag) | Inserts immediately after this element | Very common: "line after heading" | Yes |
|
.replace_with() | Replaces existing element/string with new one | When replacing placeholder/comment | Yes |
### Important Rules / Gotchas
- You **must** create tags with
soup.new_tag() (not
BeautifulSoup("<hr>") or manually with strings) → otherwise
.append() /
.insert() may not behave correctly or may escape content
-
.append() /
.insert() only accept
Tag,
NavigableString, or other
PageElement objects — **not raw strings** like
"<hr>"
-
<hr> is a **void element** → BeautifulSoup outputs it as
<hr/> (self-closing) in XHTML-style parsers or
<hr> in html5lib/html.parser (depending on formatter)
- Always work on the **same**
BeautifulSoup object when creating new tags (
soup.new_tag(), not
other_soup.new_tag())
Quick recommended pattern (most readable):
container = soup.select_one("div.content")
if container:
hr = soup.new_tag("hr")
# hr["class"] = "my-line"
last_p = container.find_all("p")[-1] # or any other reference element
last_p.insert_after(hr)That's basically all you need for 95% of cases when adding lines (
<hr>) or any other elements with BeautifulSoup.