Skip to content

Commit 56bd698

Browse files
authored
eval: update benchmark, dependencies and docs (#894)
* fix: review internal evaluation data * eval: update benchmark, dependencies and docs
1 parent c1bc953 commit 56bd698

23 files changed

Lines changed: 1623 additions & 19665 deletions

.gitattributes

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
# override github/linguist settings
2-
tests/cache/* linguist-vendored
3-
tests/eval/* linguist-vendored
4-
tests/resources/* linguist-vendored
1+
# override github/linguist settings; -text keeps the fixtures byte-exact (hashed by eval_gate.py)
2+
tests/cache/* linguist-vendored -text
3+
tests/evaldata.json -text
4+
tests/eval/* linguist-vendored -text
5+
tests/resources/* linguist-vendored -text

.github/workflows/tests.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ jobs:
8282
run: |
8383
python -m pytest --cov=./ --cov-report=xml
8484
85+
- name: Evaluation quality gate
86+
# deterministic given the corpus; one cell is enough, no need for the whole matrix
87+
if: ${{ matrix.env.MINIMAL == 'false' && matrix.python-version == '3.13' }}
88+
run: |
89+
python tests/eval_gate.py
90+
8591
- name: Test docs
8692
# version matches .readthedocs.yaml
8793
if: ${{ matrix.env.MINIMAL == 'false' && matrix.python-version == '3.13' }}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ docs/_template/
3434
# pipenv
3535
Pipfile*
3636

37+
# uv (library project: no committed lockfile)
38+
uv.lock
39+
3740
# older stuff
3841
old/
3942

docs/evaluation.rst

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ The extraction focuses on the main content, which is usually the part displayed
2121
External evaluations
2222
--------------------
2323

24-
- Most efficient open-source library in *ScrapingHub*'s `article extraction benchmark <https://github.com/scrapinghub/article-extraction-benchmark>`_
24+
- Most efficient open-source library in *ScrapingHub*'s `article extraction benchmark <https://github.com/scrapinghub/article-extraction-benchmark>`_ (older evaluation)
2525
- Best overall tool according to `Bien choisir son outil d'extraction de contenu à partir du Web <https://hal.archives-ouvertes.fr/hal-02768510v3/document>`_ (Lejeune & Barbaresi 2020)
2626
- Comparison on a small `sample of Polish news texts and forums <https://github.com/tsolewski/Text_extraction_comparison_PL>`_ (now integrated in the internal benchmark, Trafilatura has improved since)
2727
- Best single tool by ROUGE-LSum Mean F1 Page Scores in `An Empirical Comparison of Web Content Extraction Algorithms <https://webis.de/downloads/publications/papers/bevendorff_2023b.pdf>`_ (Bevendorff et al. 2023)
@@ -34,6 +34,7 @@ Although a few corresponding Python packages are not actively maintained the fol
3434

3535
These packages keep the structure intact but do not focus on main text extraction:
3636

37+
- `BeautifulSoup <https://www.crummy.com/software/BeautifulSoup/>`_ is a general-purpose HTML parser, used here to grab all the text in the document
3738
- `html2text <https://github.com/Alir3z4/html2text>`_ converts HTML pages to Markup language
3839
- `html_text <https://github.com/TeamHG-Memex/html-text>`_ converts HTML code to plain text
3940
- `inscriptis <https://github.com/weblyzard/inscriptis>`_ converts HTML to text with a particular emphasis on nested tables
@@ -44,10 +45,12 @@ These packages focus on main text extraction:
4445
- *dragnet* is not maintained anymore, it is provided for reference only (in older evaluations)
4546
- `goose3 <https://github.com/goose3/goose3>`_ can extract information for embedded content but doesn't preserve markup
4647
- `jusText <https://github.com/miso-belica/jusText>`_ is designed to preserve mainly text containing full sentences along with some markup, it has been explicitly developed to create linguistic resources
47-
- `newspaper3k <https://github.com/codelucas/newspaper>`_ is mostly geared towards newspaper texts, provides additional functions but no structured text or comment extraction
48+
- `magic-html <https://github.com/opendatalab/magic-html>`_ extracts the main content of a page as HTML, geared towards LLM training data
49+
- `newspaper4k <https://github.com/AndyTheFactory/newspaper4k>`_ (the maintained successor of *newspaper3k*) is mostly geared towards newspaper texts, provides additional functions but no structured text or comment extraction
4850
- `news-please <https://github.com/fhamborg/news-please>`_ is a news crawler that extracts structured information
4951
- `readability-lxml <https://github.com/buriy/python-readability>`_ cleans the page and preserves some markup
50-
- `readabilipy <https://github.com/alan-turing-institute/ReadabiliPy>`_ contains a Python wrapper for Mozilla's Node.js package, as well as article extraction routines written in pure Python
52+
- `resiliparse <https://github.com/chatnoir-eu/chatnoir-resiliparse>`_ converts HTML to plain text with a focus on speed and robustness, run here in main-content mode
53+
- *readabilipy* is not part of the comparison anymore, it is provided for reference only (in older evaluations)
5154
- `trafilatura <https://github.com/adbar/trafilatura>`_ is the library documented here, several options are tested regarding main text extraction only, without metadata or comments
5255

5356
The tools are compared to the raw page source and to a meaningful baseline consisting of extracting the raw text contained in the JSON article element or in a combination of paragraph, code and quote elements.
@@ -60,9 +63,9 @@ Description
6063

6164
**Evaluation**: Decisive document segments are singled out which are not statistically representative but very significant in the perspective of working with the texts, most notably left/right columns, additional header, author or footer information such as imprints or addresses, as well as affiliated and social network links, in short boilerplate. Raw text segments are expected which is also a way to evaluate the quality of HTML extraction in itself.
6265

63-
**Time**: The execution time is provided as an indication. As the baseline extraction is simple and fast, it is used for the benchmark. Certain packages are noticeably slower than the rest: *goose3* and *newspaper*, while *news-please*'s execution time isn't comparable because of operations unrelated to text extraction. ReadabiliPy is very slow for unclear reasons.
66+
**Time**: The execution time is provided as an indication. As the baseline extraction is simple and fast, it is used for the benchmark. Certain packages are noticeably slower than the rest: *goose3* and *newspaper*, while *news-please*'s execution time isn't comparable because of operations unrelated to text extraction and is by far the slowest.
6467

65-
**Errors**: The *boilerpy3*, *newspaper3k*, and *readabilipy* modules do not work without errors on every HTML file in the test set, probably because of malformed HTML, encoding or parsing bugs. These errors are ignored in order to complete the benchmark.
68+
**Errors**: The *boilerpy3* and *newspaper4k* modules do not work without errors on every HTML file in the test set, probably because of malformed HTML, encoding or parsing bugs. These errors are ignored in order to complete the benchmark.
6669

6770
**Results**: The baseline beats a few systems, showing its interest. *justext* is highly configurable and tweaking its configuration (as it is done here) can lead to better performance than its generic settings. *goose3* is the most precise algorithm, albeit at a significant cost in terms of recall. The packages focusing on raw text extraction *html_text* and *inscriptis* are roughly comparable and achieve the best recall as they try to extract all the text. Rule-based approaches such as *trafilatura*'s obtain balanced results despite a lack of precision. Combined with an algorithmic approach they perform significantly better than the other tested solutions. Trafilatura consistently outperforms other open-source libraries, showcasing its efficiency and accuracy in extracting web content.
6871

@@ -71,9 +74,44 @@ Description
7174
The evaluation script is available on the project repository: `tests/README.rst <https://github.com/adbar/trafilatura/blob/master/tests/>`_. To reproduce the tests just clone the repository, install all necessary packages and run the evaluation script with the data provided in the *tests* directory.
7275

7376

74-
Results (2022-05-18)
77+
Results (2026-08-04)
7578
--------------------
7679

80+
=============================== ========= ========== ========= ========= ======
81+
990 documents, 2951 text & 2966 boilerplate segments, Python 3.13
82+
--------------------------------------------------------------------------------
83+
Python Package Precision Recall Accuracy F-Score Diff.
84+
=============================== ========= ========== ========= ========= ======
85+
html2text 2025.4.15 0.525 0.900 0.544 0.663 2.8x
86+
*raw HTML* 0.528 0.906 0.549 0.667 0.03x
87+
beautifulsoup4 4.15.0 0.532 0.980 0.561 0.690 2.1x
88+
html_text 0.7.1 0.531 0.988 0.559 0.691 0.7x
89+
inscriptis 2.7.3 (html to txt) 0.534 **0.991** 0.564 0.694 1.1x
90+
newspaper4k 0.9.6 0.878 0.736 0.817 0.801 6.6x
91+
boilerpy3 1.0.7 (article mode) 0.818 0.796 0.810 0.807 1.6x
92+
goose3 3.1.22 **0.936** 0.714 0.833 0.810 10.2x
93+
resiliparse 1.0.9 0.705 0.955 0.778 0.811 0.3x
94+
*baseline (text markup)* 0.767 0.869 0.803 0.815 **1x**
95+
readability-lxml 0.8.4.1 0.898 0.764 0.839 0.826 2.6x
96+
news-please 1.6.16 0.932 0.758 0.852 0.836 20.5x
97+
justext 3.0.2 (custom) 0.864 0.859 0.862 0.862 2.3x
98+
magic-html 0.1.8 0.887 0.891 0.889 0.889 3.5x
99+
trafilatura 2.2.0 (recall) 0.899 0.939 0.917 0.918 2.1x
100+
trafilatura 2.2.0 (fast) 0.907 0.930 0.917 0.918 2.2x
101+
trafilatura 2.2.0 (precision) 0.925 0.915 0.921 0.920 3.2x
102+
trafilatura 2.2.0 (standard) 0.906 0.943 **0.923** **0.924** 3.2x
103+
=============================== ========= ========== ========= ========= ======
104+
105+
Each package receives the raw HTML bytes and handles character encoding itself; packages evaluated on string input (*boilerpy3*, *html2text*, *html_text*, *inscriptis*, *magic-html*, *news-please*, *readability-lxml*) get it pre-decoded by the benchmark, with the conversion counted in their execution time.
106+
107+
108+
Older results
109+
-------------
110+
111+
112+
Older results (2022-05-18)
113+
^^^^^^^^^^^^^^^^^^^^^^^^^^
114+
77115
=============================== ========= ========== ========= ========= ======
78116
750 documents, 2236 text & 2250 boilerplate segments, Python 3.8
79117
--------------------------------------------------------------------------------
@@ -97,11 +135,6 @@ trafilatura 1.2.2 (standard) 0.914 0.904 **0.910** **0.909** 7.1x
97135
=============================== ========= ========== ========= ========= ======
98136

99137

100-
101-
Older results
102-
-------------
103-
104-
105138
Older results (2021-06-07)
106139
^^^^^^^^^^^^^^^^^^^^^^^^^^
107140

docs/tests.rst

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,39 @@ If you work on text extraction, it is useful to check whether the performance is
6767
equal or better on the benchmark. The evaluation allows for comparing changes
6868
made to Trafilatura, for example in a new version or pull request.
6969

70-
Install the evaluation dependencies and run the evaluation script:
70+
The quality gate scores the whole benchmark corpus with Trafilatura alone and
71+
exits non-zero if the F1-scores fall below the pinned baseline. It needs no
72+
other extractor and is also run in CI:
73+
74+
.. code-block:: bash
75+
76+
$ pip install -e ".[all]"
77+
$ python3 tests/eval_gate.py
78+
79+
The ``all`` extra matches the environment of the CI gate cell; a plain install
80+
can score slightly differently on non-UTF-8 pages. Editing the annotations or an
81+
HTML input requires a re-pin with ``python3 tests/eval_gate.py --update``. A
82+
re-pin never lowers the baseline on its own: a measured F1 below a pinned floor
83+
keeps the floor and exits non-zero, and accepting a lower bar takes an explicit
84+
``--allow-regression``.
85+
86+
On Windows, the corpus fingerprint requires the HTML inputs exactly as
87+
committed: on a clone made before the ``.gitattributes`` rules were added, run
88+
``git add --renormalize .`` and reset (or re-clone) so line endings match the
89+
repository.
90+
91+
Comparing Trafilatura with other extractors needs the ``eval`` extra:
7192

7293
.. code-block:: bash
7394
7495
$ pip install -e ".[eval]"
7596
$ python3 tests/evaluate.py --help
7697
98+
Each competitor library is imported only by the algorithm that uses it, so any
99+
algorithm whose library is missing or does not import is reported and dropped
100+
from the comparison instead of stopping the run. The ``magic-html`` package
101+
requires Python 3.12 or later; on older versions it is skipped.
102+
77103
Use ``--small`` to run the Trafilatura-based components only, or ``--all`` to run
78104
all supported algorithms. See the `tests README
79105
<https://github.com/adbar/trafilatura/blob/master/tests/README.rst>`_ for more

pyproject.toml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,21 +108,27 @@ all = [
108108
"urllib3[socks]",
109109
"zstandard >= 0.25.0",
110110
]
111-
# Benchmark dependencies for tests/evaluate.py. magic_html is git-only: install
112-
# manually via "pip install git+https://github.com/opendatalab/magic-html".
111+
# Benchmark deps for tests/evaluate.py, which skips the competitors it cannot import.
113112
eval = [
114113
"pandas",
115114
"tabulate",
116-
"tqdm",
117115
# alternative extractors compared in tests/evaluate.py
118116
"beautifulsoup4",
119117
"boilerpy3",
120118
"goose3",
121119
"html2text",
122120
"html-text",
123121
"inscriptis",
124-
"newspaper3k",
122+
# pinned: the PyPI upload is not an upstream release (audited: matches the 0.1.6 wheel)
123+
"magic-html == 0.1.8 ; python_version >= '3.12'",
124+
# not newspaper3k: clashes with newspaper4k on the "newspaper" namespace.
125+
# newspaper4k is listed explicitly (not just via news-please) because
126+
# evaluate.py looks its version up by name.
125127
"news-please",
128+
# required by news-please on non-Latin scripts; also needs a one-time punkt
129+
# download (see tests/README.rst), else it attempts one per document
130+
"nltk",
131+
"newspaper4k",
126132
"readability-lxml",
127133
"resiliparse",
128134
]

tests/README.rst

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,33 @@ Running the code
2828
The results and a list of comparable benchmarks are available on the `evaluation page of the docs <https://trafilatura.readthedocs.io/en/latest/evaluation.html>`_.
2929

3030

31-
Evaluation
32-
----------
31+
Quality gate
32+
------------
3333

3434
The following allows for comparing changes made to Trafilatura, for example in a new version or pull request:
3535

36-
1. Install Trafilatura
37-
2. Run the script ``comparison_small.py``
36+
1. Install Trafilatura from the working tree: ``pip install -e ".[all]"`` (from the repository root; the ``all`` extra matches the environment of the CI gate, plain ``pip install -e .`` can score slightly differently on non-UTF-8 pages)
37+
2. Run ``python tests/eval_gate.py``
38+
39+
``eval_gate.py`` scores the whole corpus with Trafilatura alone and compares the F1-scores with the floors pinned in ``eval_baseline.json``, exiting non-zero on a regression. It needs no competitor library and is also run in CI.
40+
41+
After editing the annotations or an HTML input, re-pin the corpus fingerprint with ``python tests/eval_gate.py --update``. A re-pin never lowers the baseline on its own: an F1 below a pinned floor keeps the floor and exits non-zero, and accepting a lower bar takes an explicit ``--allow-regression``.
3842

43+
Note for Windows: the corpus fingerprint requires the HTML inputs exactly as committed. On a clone made before the ``.gitattributes`` rules were added, run ``git add --renormalize .`` and reset (or re-clone) so line endings match the repository.
3944

40-
A comparison with similar software is run periodically. As the packages tend to evolve the script may not always be up-to-date and all packages may not be available. If that happens, commenting out the corresponding sections is the most efficient solution. Fixes to the file can be submitted as pull requests.
45+
46+
Comparison with other software
47+
------------------------------
48+
49+
``evaluate.py`` additionally runs other extractors. Each competitor library is imported by the algorithm that uses it, not at module level, so ``pip install -e ".[eval]"`` is enough to get started and an algorithm whose library is missing or does not import is reported and dropped from the comparison rather than stopping the run. ``--small`` needs no competitor at all.
4150

4251
Note: As numerous packages are installed it is recommended to create a virtual environment, for example with ``pyenv`` or ``venv``.
4352

44-
1. Install the evaluation extra: ``uv pip install -e ".[eval]"`` (or ``pip install -e ".[eval]"``); optionally add ``magic_html`` with ``pip install git+https://github.com/opendatalab/magic-html``
45-
2. Run the script ``evaluate.py``
53+
1. Install the evaluation dependencies from the repository root: ``pip install -e ".[eval]"`` (``magic-html`` requires Python 3.12+ and is skipped on older versions)
54+
2. For ``news-please``, download the NLTK tokenizer data once: ``python -m nltk.downloader punkt punkt_tab`` (otherwise it attempts a network download per document)
55+
3. Run the script ``evaluate.py``
56+
57+
The published results record the version of each package next to its name, since the packages evolve and their output changes with them.
4658

4759
Options:
4860

@@ -52,7 +64,7 @@ Options:
5264

5365
``python3 evaluate.py --help``: Display all algorithms and further options.
5466

55-
More comprehensive evaluations are available, mostly focusing on English and/or a particular text type. With minimal adaptations, the evaluation can support the use gold standard files in JSON format.
67+
More comprehensive evaluations are available, mostly focusing on English and/or a particular text type. The evaluation only supports the handcrafted with/without segment format described above; an external annotation file can be passed with ``--testfile`` as long as its HTML documents live in ``tests/cache`` or ``tests/eval``.
5668

5769

5870
Sources

0 commit comments

Comments
 (0)