Skip to content

fix: recover pages wrapped in a single form, and remove extraction artifacts - #898

Open
cvl01 wants to merge 2 commits into
adbar:masterfrom
cvl01:fix/wrapper-forms-and-bottom-token
Open

fix: recover pages wrapped in a single form, and remove extraction artifacts#898
cvl01 wants to merge 2 commits into
adbar:masterfrom
cvl01:fix/wrapper-forms-and-bottom-token

Conversation

@cvl01

@cvl01 cvl01 commented Aug 5, 2026

Copy link
Copy Markdown

Problem

On alsumaria.tv, an Arabic news site, extraction returns an unrelated sidebar video caption instead of the article. In precision mode that caption is the only output:

$ trafilatura -u "https://www.alsumaria.tv/news/international/572277/..." --formatting --markdown --precision
الأربعين الحسيني .. دروس التضحية ومسيرة الإصلاح - عشرين م٥ - الحلقة ٥٤ | الموسم 5

Tracing it turned up four independent causes. Each reproduces on its own, and two are not site-specific — any page with <br>-separated text in a div and an inline ad block hits them.

Causes and fixes

1. A single wrapping <form> discards the whole document. tree_cleaning() deletes every <form>. ASP.NET WebForms puts the entire page inside one <form id="aspnetForm">, so nothing was left but the chrome outside it, which is what got extracted.

A form holding most of the remaining text is a layout wrapper, not a widget, and is now demoted to a plain container. Genuine widget forms (search, login, newsletter) hold little text and are still removed. Forms are handled after the other noise is gone, so <script> and <head> text cannot skew the comparison.

MANUALLY_CLEANED keeps "form", so the documented list still reflects that forms are removed. Since that list is public API users mutate in place (docs/settings.rst, #746), an in-place removal of "form" is now honoured rather than raising.

2. 'bottom' matched CSS spacing utilities. PRECISION_DISCARD_XPATH matched bottom as a bare substring. The article column carries Padding-bottom-lg-30, so precision mode discarded the article itself. bottom now has to start or end a class token — the same tightening already applied to 'link'. Page-bottom chrome (bottom, bottom-bar, article-bottom) is still discarded; Padding-bottom-lg-30 and border-bottom-0 are not.

3. A paragraph was emitted twice. handle_other_elements() emits a text-bearing div whole, and appending it moves it into result_body — but _extract() iterates a subelems list captured beforehand, so an <lb> inside that div was visited again and re-emitted its tail as a second paragraph. Elements already moved into result_body are now skipped. handle_paragraphs() marks the children it consumes "done"; this covers the ones it cannot retag.

4. Blank, space-filled lines. handle_paragraphs() treated whitespace-only text as content (plain truthiness), and stripping divs merges their indentation into the preceding <lb>'s tail — so pruned ad slots and clearfix spacers surfaced as lines of spaces. Both are now dropped, leaving code and pre untouched, where that whitespace is the indentation itself.

Result

$ trafilatura -u "https://www.alsumaria.tv/news/international/572277/..." --formatting --markdown --precision
## أعلن الجيش الإسرائيلي، اليوم الأربعاء، بدء هجمات قال إنها "مركزة" في جنوب لبنان، عقب إصدار إنذار بإخلاء بلدة المنصوري، متهماً حزب الله بخرق اتفاق وقف إطلاق النار.

وقال الجيش الإسرائيلي، في بيان، إنه وجّه إنذاراً إلى سكان بلدة المنصوري في جنوب لبنان بالإخلاء، مدعياً أن حزب الله خرق اتفاق وقف إطلاق النار، وأن قواته "ستعمل ضده بقوة".

وأضاف الجيش الإسرائيلي أنه بدأ تنفيذ "هجمات مركزة" في جنوب لبنان، مؤكداً أن هذه العمليات تأتي رداً على ما وصفه بـ"خرق حزب الله لوقف إطلاق النار".

Title, author, date and categories also come out correctly with --with-metadata.

Testing

  • Three new regression tests, each verified to fail on master and pass with the fix: test_precision_discard_bottom_token_boundary, test_wrapper_form_kept, test_no_duplicate_paragraph_from_lb_tail. The form test also covers the MANUALLY_CLEANED in-place override.
  • Full suite passes (278 tests), ruff and mypy clean.
  • Scored the eval corpus with the chunk metric from tests/evaluate.py: default, fast and recall modes are unchanged; precision mode gains one true negative.
variant before (P / R / F1) after (P / R / F1)
trafilatura 0.9140 / 0.9480 / 0.9307 0.9140 / 0.9480 / 0.9307
trafilatura fast 0.9081 / 0.9182 / 0.9131 0.9081 / 0.9182 / 0.9131
trafilatura precision 0.9104 / 0.9071 / 0.9088 0.9139 / 0.9071 / 0.9104
trafilatura recall 0.8975 / 0.9442 / 0.9203 0.8975 / 0.9442 / 0.9203

I used Claude Opus for coding

cvl01 and others added 2 commits August 5, 2026 16:42
…tifacts

Extraction returned an unrelated sidebar caption instead of the article on
alsumaria.tv, an Arabic news site, and precision mode returned only that
caption. Four independent causes, each reproducible on its own:

- tree_cleaning() deletes every <form>. ASP.NET WebForms puts the entire
  page inside one <form id="aspnetForm">, so the whole document was
  discarded and only chrome outside the form was left to extract. A form
  holding most of the remaining text is a layout wrapper and is now demoted
  to a plain container, while widget forms (search, login, newsletter) are
  still removed. Forms are handled after the other noise is gone so that
  script and head text cannot skew the comparison.

- PRECISION_DISCARD_XPATH matched 'bottom' as a bare substring, so it also
  matched CSS spacing utilities. The article column carries
  'Padding-bottom-lg-30', which made precision mode discard the article
  itself. 'bottom' now has to start or end a class token, the same
  tightening already applied to 'link'.

- handle_other_elements() emits a text-bearing div whole, and appending it
  moves it into result_body -- but _extract() iterates a subelems list
  captured beforehand, so an <lb> inside that div was visited again and
  re-emitted its tail. That duplicated a paragraph. Elements already moved
  into result_body are now skipped; handle_paragraphs() marks the children
  it consumes "done", and this covers the ones it cannot retag.

- handle_paragraphs() treated whitespace-only text as content, and
  stripping divs merges their indentation into the preceding <lb>'s tail,
  so pruned ad slots and clearfix spacers surfaced as blank, space-filled
  lines. Both are now dropped, leaving code and pre untouched, where that
  whitespace is the indentation itself.

MANUALLY_CLEANED keeps "form" so the documented list still reflects that
forms are removed, and an in-place removal by a user is honoured rather
than raising.

No change on the evaluation corpus for default, fast and recall modes;
precision mode gains one true negative (F1 0.9088 -> 0.9104).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.66%. Comparing base (c1bc953) to head (f174cf2).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #898      +/-   ##
==========================================
- Coverage   99.68%   99.66%   -0.03%     
==========================================
  Files          21       21              
  Lines        4110     4132      +22     
==========================================
+ Hits         4097     4118      +21     
- Misses         13       14       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant