Skip to content

[Bug]: preserve_tags/preserve_classes are a no-op for excluded tags (aside, nav, footer, header, form) #2125

Description

@bong-u

crawl4ai version

0.9.2

Expected Behavior

PruningContentFilter(preserve_tags=["aside"]) should keep <aside> content in fit_markdown.
#1900 added these options as an escape hatch for content the fit pipeline treats as boilerplate but the user knows is real content.

Same for preserve_classes=["related"] on an <aside class="related">.

Current Behavior

Both options are silently ignored for every tag in excluded_tags (content_filter_strategy.py:101-110): nav, footer, header, aside, script, style, form, iframe, noscript.

filter_content() removes those tags before pruning ever runs:

self._remove_unwanted_tags(soup)   # content_filter_strategy.py:664, decomposes aside
body = soup.find("body")
self._prune_tree(body)             # :668, the only place _is_preserved() is consulted

_remove_unwanted_tags decomposes by tag name and never consults the whitelist:

def _remove_unwanted_tags(self, soup):   # :685
    """Removes unwanted tags"""
    for tag in self.excluded_tags:
        for element in soup.find_all(tag):
            element.decompose()

By the time _is_preserved() (:691) is reached, the node is gone from the tree.
The filter raises nothing and logs nothing.

excluded_tags is hardcoded in RelevantContentFilter.__init__, not a constructor argument, so the only workaround is mutating the instance attribute (f.excluded_tags.discard("aside")).

Is this reproducible?

Yes

Inputs Causing the Bug

- URL(s): none needed, filter_content() takes an HTML string directly
- Settings used: PruningContentFilter(preserve_tags=["aside"])
- Input data: any HTML with an <aside> holding real content

Steps to Reproduce

1. Run the snippet below (no browser, no network).
2. All three cases print "aside kept: False".
3. Expected: True for the preserve_tags and preserve_classes cases.

Code snippets

from crawl4ai.content_filter_strategy import PruningContentFilter

html = (
    "<html><body>"
    "<article><p>" + "Main article body with plenty of words to pass the pruning threshold easily. " * 8 + "</p></article>"
    '<aside class="related"><h2>Related reading</h2><p>'
    + "This sidebar note is real content the author wrote and wants kept. " * 8
    + "</p></aside>"
    "</body></html>"
)

for kwargs in ({}, {"preserve_tags": ["aside"]}, {"preserve_classes": ["related"]}):
    out = " ".join(PruningContentFilter(**kwargs).filter_content(html))
    print(kwargs, "-> aside kept:", "Related reading" in out)

# 0.9.2 / develop @ 2d8f673:
# {}                                -> aside kept: False   # correct, that's the default
# {'preserve_tags': ['aside']}      -> aside kept: False   # BUG
# {'preserve_classes': ['related']} -> aside kept: False   # BUG

OS

macOS

Python version

3.12

Browser

N/A

Browser version

No response

Error logs & Screenshots (if applicable)

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    🐞 BugSomething isn't working🩺 Needs TriageNeeds attention of maintainers

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions