# PyTeaser: extractive summaries that score sentences by title, keywords, length and position

> PyTeaser is a Python library that produces a short extractive summary from article text or a news URL, ranking sentences on four weighted features and returning the winners in the original order. It ships a bundled Goose article extractor, a CLI, optional extras for images and Chinese segmentation, and an offline test suite.

**xiaoxu193/PyTeaser** — Summarizes news articles

- Repository: https://github.com/xiaoxu193/PyTeaser
- Website: http://xiaoxu193.github.io/PyTeaser/
- Stars: 1,170 · Forks: 172
- Language: Python
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/xiaoxu193-pyteaser

## Four features decide which sentences survive

PyTeaser does not generate text. It selects sentences from the input and returns them in the order they appeared in the article, which is what makes it an extractive summariser and what makes its output verifiable against the source.

The ranking is a weighted sum of four features, and the weights are the whole algorithm. Title relevance, keyword frequency, sentence length and position carry default weights of 1.5, 2.0, 1.0 and 1.0 respectively. Keyword frequency is weighted highest, so a sentence that repeats the terms the article is about outranks one that merely sits near the top, while title relevance at 1.5 gives a second signal that is not purely about repetition.

The weights are not hard coded. The README says you can pass a `ScoringWeights` instance, or a dictionary keyed by those feature names, to tune the heuristic, which means the defaults are a starting point rather than a decision. A summariser tuned for a sports feed and one tuned for a legal filing want different length weights, and the API allows that without forking.

Two further behaviours sit on top of the scoring. Repeated content is removed, so a sentence that says the same thing as an already selected one does not appear twice in the output. And the selected sentences are returned in the article's original order rather than in score order, which keeps the summary readable, since a summary that jumps backwards through the piece reads as a list of facts rather than a passage.

## Summarising text you already hold

The library needs Python 3.10 or newer and installs from PyPI.

```bash
python -m pip install pyteaser
```

The core call takes a title and the article text. The title may be `None`, the text must be a string or UTF-8 bytes, and blank text returns an empty list rather than raising.

```python
from pyteaser import summarize

title = "A sample article"
text = "The first sentence explains the announcement. A second sentence gives context. A third sentence describes what happens next."

summary = summarize(title, text, sentence_count=2, language="en")
for sentence in summary:
    print(sentence)
```

`sentence_count` defaults to 5, must be a non-negative integer, and may be set to 0 to request no sentences at all. There is also an optional `max_words` that acts as a total token budget, and the behaviour there is worth knowing: candidates that do not fit the budget are skipped, so a small budget returns fewer sentences than requested rather than truncating one in the middle.

For debugging the ranking, the README documents a second entry point, `summarize_detailed(...)`, which returns records with each selected sentence's original `index`, the text in `sentence`, and the overall `score`. The plain `summarize` function keeps returning strings, so the detailed variant is additive and the normal APIs do not change shape.

The legacy `Summarize(title, text)` function remains available as well, which is how a codebase that predates the current API keeps working.

## Fetching a page, and the errors that can come back

The second entry point takes a URL and does the extraction as well as the summarisation.

```python
from pyteaser import summarize_url

summary = summarize_url("https://example.com/news/article", sentence_count=3)
```

The extraction step pulls the page's title and article text first, and the summary is built from those. Language detection happens here: if the page declares a supported language, PyTeaser uses that language's stopword list, and otherwise it falls back to English. You can override the guess by passing `language="es"` explicitly.

The bundled language resources cover English, Spanish, Italian, German, Swedish, Russian, French, Arabic and Chinese. Chinese is the interesting case because it needs word segmentation, which uses jieba when the extra is installed and falls back to character tokenisation when it is not. That fallback works, but it changes what a keyword counts as, so the ranking for Chinese text is not identical depending on whether the extra is present.

The README is candid about the limits of this: language support is heuristic, so summaries should be reviewed for your content and audience.

Errors are typed rather than generic. A page that cannot be fetched safely raises `ArticleFetchError`, and a page from which no title or body can be extracted raises `ArticleExtractionError`. Invalid text, language and sentence-count inputs raise `TypeError` or `ValueError`. The legacy `SummarizeUrl(url)` function is still there as well.

## The fetch restrictions are the part to read twice

URL summarisation means the library makes a request on your behalf to an address someone else chose, and the README treats that as a security boundary rather than a detail.

The stated restrictions are: HTTP(S) on standard ports only, a 10 second request timeout, a 5 MiB limit on the HTML, validation of resolved addresses and of redirect targets, and blocking of private and non-routable addresses by default. That last one is the important line. A URL that resolves to a machine on your internal network is a way to reach services that are not exposed to the internet, and PyTeaser refuses to go there unless you explicitly change the setting.

Image fetching is off by default as well, and enabling it is a separate step.

```python
from goose import Goose

article = Goose({"enable_image_fetching": True}).extract(
    url="https://example.com/news/article"
)
print(article.top_image.src)
```

Image downloads are capped at 15 MiB and need the `images` extra. The README says applications processing untrusted URLs should keep private network access disabled, and that Goose supports `allow_private_network=True` for trusted local development only, with an explicit warning not to enable it for user controlled URLs.

The closing line of that section is a request rather than a technical fact: use article extraction responsibly and comply with the target site's terms and applicable law. For a library that fetches third-party pages on a user's behalf, that is the constraint that is easiest to forget in a batch job.

## Optional extras, and what each one changes

The core install is deliberately small, and the optional pieces are separate.

```bash
python -m pip install 'pyteaser[images]'   # Pillow-backed image extraction
python -m pip install 'pyteaser[chinese]'   # jieba Chinese word segmentation
python -m pip install 'pyteaser[soup]'      # BeautifulSoup-based fallback parser
python -m pip install 'pyteaser[all]'       # all optional features
```

`images` pulls in Pillow and is what makes image extraction work. `chinese` pulls in jieba, which is what replaces character tokenisation with proper word segmentation for Chinese text. `soup` pulls in beautifulsoup4 for a fallback parser. `all` installs the three together, and there is a `dev` extra in `setup.py` that brings in ruff for linting.

When you work from a source checkout instead of PyPI, the README tells you to replace `pyteaser` with `.` in those commands, or with `.[all]` for everything, so the same extras resolve against the local tree.

The required dependencies are `lxml>=5.0` and `cssselect>=1.2`, and the package metadata records `python_requires=">=3.10"` with classifiers for Python 3.10 through 3.14. The build system is setuptools with `requires = ["setuptools>=68", "wheel"]`, and the linting configuration in `pyproject.toml` targets `py310` with ruff selecting the `E4`, `E7`, `E9` and `F` rule sets.

The `pyteaser` module is a single file, `pyteaser.py`, listed in `py_modules`, with the Goose extractor vendored under `goose/` and its resources included as package data.

## A CLI, an offline test suite, and a two-licence codebase

Installation gives you a command line entry point, registered in `setup.py` as `console_scripts` with `pyteaser=pyteaser:main`.

```bash
pyteaser --title "Example article" --text "First sentence. Second sentence." --sentence-count 1
pyteaser --input-file article.txt --title "Example article" --max-words 120
pyteaser --url "https://example.com/news/article" --sentence-count 3
```

The three forms mirror the library: text on the command line, a UTF-8 file, or a URL. The `--max-words` flag applies the same token budget as the library option, so a shell script can cap output size without writing any Python.

The test suite is offline by design. The README states that URL cases use mocked responses or a local loopback server, which means `python -m unittest discover -v` does not reach the network, and that matters for a project whose whole purpose is making outbound requests. The repository layout backs this up with a `tests.py` file, a `demo.py` example, and a `benchmarks/` directory.

Licensing is the one place where two licences sit side by side, and the README is precise about it. PyTeaser's original code is MIT licensed. The bundled Goose article extractor and its included resources are Apache-2.0 licensed. The `setup.py` metadata reflects the combination as `license="MIT AND Apache-2.0"` and lists `LICENSE`, `goose/LICENSE.txt` and `NOTICE` as the license files, with the `NOTICE` file present at the top level for attribution details. A project that vendors this code into a proprietary product has to respect the Apache attribution requirement, which the `NOTICE` file exists to support.

The version in `setup.py` is 2.0.0, the description reads Extractive summaries and article text extraction, and the author is listed as Xiao Xu with the Cornell address on the package metadata.

## Conclusion

Adopt PyTeaser if you need a lightweight extractive summariser that runs offline on text you already have, or that fetches and parses a single news page for you, since the scoring weights are exposed and every selected sentence can be traced back to its original index. Do not adopt it if you need abstractive output, because the sentences returned are copied verbatim from the input, and do not point it at user supplied URLs without reading the fetch restrictions, since private address blocking is on by default and the README says it should stay that way for untrusted input. Before deploying, check that Python 3.10 or newer is available, decide which extras you need, and note that the language handling is heuristic, so summaries for non-English content still need review.

## FAQ

### How do I install PyTeaser and summarize a piece of text?

Install it with `python -m pip install pyteaser`, which requires Python 3.10 or newer, then call `summarize(title, text, sentence_count=2, language="en")` from the `pyteaser` package. The title may be `None`, and blank text returns an empty list.

### How does PyTeaser score the sentences it selects?

It ranks on title relevance, keyword frequency, sentence length and position, with default weights of 1.5, 2.0, 1.0 and 1.0. You can pass a `ScoringWeights` instance or a dictionary keyed by those feature names to tune the heuristic, and `summarize_detailed(...)` returns each selected sentence's original index and score.

### Can PyTeaser summarize a news URL?

Yes, `summarize_url("https://example.com/news/article", sentence_count=3)` extracts the page title and body first and then summarizes them. If the page declares a supported language its stopword list is used, otherwise English is assumed, and you can force one with `language="es"`.

### Which languages does PyTeaser handle?

The bundled language resources cover English, Spanish, Italian, German, Swedish, Russian, French, Arabic and Chinese. Chinese segmentation uses jieba when the `chinese` extra is installed and falls back to character tokenisation otherwise, and the README notes that language support is heuristic, so summaries should be reviewed.

### Is it safe to point PyTeaser at URLs supplied by users?

The README says applications processing untrusted URLs should keep private network access disabled. Fetching accepts HTTP(S) on standard ports, applies a 10 second timeout and a 5 MiB HTML limit, validates resolved addresses and redirect targets, and blocks private and non-routable addresses by default, and `allow_private_network=True` is documented as for trusted local development only.

### What licence does PyTeaser use?

PyTeaser's original code is MIT licensed, and the bundled Goose article extractor and its resources are Apache-2.0 licensed. The package metadata records `license="MIT AND Apache-2.0"` and points at `LICENSE`, `goose/LICENSE.txt` and `NOTICE` for the details.

## Sources

- [Issues](https://github.com/xiaoxu193/PyTeaser/issues)
- [License: MIT](https://github.com/xiaoxu193/PyTeaser/blob/master/LICENSE)
- [Project website](http://xiaoxu193.github.io/PyTeaser/)
- [README](https://github.com/xiaoxu193/PyTeaser/blob/master/README.md)
- [xiaoxu193/PyTeaser on GitHub](https://github.com/xiaoxu193/PyTeaser)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/xiaoxu193-pyteaser
