Metadata-Version: 2.5
Name: scrape-airing-anime
Version: 1.0.0
Summary: One stop shop for currently airing anime: MyAnimeList (via Jikan), AniList and Kitsu, normalised into one table. CLI and library, standard library only.
Project-URL: Homepage, https://github.com/vsevolod-mineev/scrape-airing-anime
Project-URL: Repository, https://github.com/vsevolod-mineev/scrape-airing-anime
Project-URL: Issues, https://github.com/vsevolod-mineev/scrape-airing-anime/issues
Project-URL: Changelog, https://github.com/vsevolod-mineev/scrape-airing-anime/releases
Author-email: Vsevolod Mineev <vsevolod.mineev@gmail.com>
License: MIT
License-File: LICENSE.md
Keywords: airing,anilist,anime,jikan,kitsu,myanimelist,scraper,season
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Utilities
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# scrape-airing-anime

[![PyPI](https://img.shields.io/pypi/v/scrape-airing-anime)](https://pypi.org/project/scrape-airing-anime/)
[![CI](https://github.com/vsevolod-mineev/scrape-airing-anime/actions/workflows/ci.yml/badge.svg)](https://github.com/vsevolod-mineev/scrape-airing-anime/actions/workflows/ci.yml)
[![Python 3.9+](https://img.shields.io/pypi/pyversions/scrape-airing-anime)](https://pypi.org/project/scrape-airing-anime/)

The one stop shop for currently airing anime. Ask three of the biggest anime
databases what is on the air right now, get one normalised table back.

```
$ scrape-airing-anime --dedupe --limit 5
#  title                                       score  eps  type  season       source
-  ------------------------------------------  -----  ---  ----  -----------  ------
1  Sousou no Frieren 2                         9.12   24   TV    summer 2026  mal
2  Mushoku Tensei: Isekai Ittara Honki Dasu 3  8.42   14   TV    summer 2026  kitsu
3  One Piece                                   8.40   ?    TV    fall 1999    kitsu
...
```

## Sources

| source    | backed by                                                | what you get                                                    |
|-----------|----------------------------------------------------------|-----------------------------------------------------------------|
| `mal`     | [Jikan](https://jikan.moe), the public MyAnimeList API   | current season: scores, members, genres, studios, synopsis      |
| `anilist` | [AniList GraphQL](https://docs.anilist.co)               | everything RELEASING (long-runners included), genres, studios   |
| `kitsu`   | [Kitsu JSON:API](https://kitsu.docs.apiary.io)           | everything currently airing, user counts, synopsis              |

No API keys, no HTML scraping, no headless browsers. Scores are normalised to
one 0-10 scale across all three. If one source is having a bad day (AniList
once switched its entire public API off, mid-development of this very tool),
you get a warning on stderr and results from the others: the pipeline never
dies with the fleet.

## Install

```sh
pip install scrape-airing-anime      # or: uv tool install scrape-airing-anime
```

Or run it without installing: `uvx scrape-airing-anime` / `pipx run scrape-airing-anime`.

No dependencies. Python 3.9+ and the standard library, that is the whole stack.

## Use

```sh
scrape-airing-anime                              # table of everything, all sources
scrape-airing-anime --dedupe                     # collapse duplicates across sources
scrape-airing-anime -s mal -n 25                 # top of MAL's current season
scrape-airing-anime --min-score 8 -f csv -o top.csv
scrape-airing-anime -f json -o ~/Desktop         # airing-anime.json on the Desktop
scrape-airing-anime -f jsonl -o - | jq .title    # stdout carries only data
scrape-airing-anime -f csv -o - | duckdb -c \
  "SELECT source, count(*) FROM read_csv('/dev/stdin') GROUP BY 1"
```

Every diagnostic (progress, warnings, the final summary) goes to stderr, so
stdout is always clean CSV/JSON/JSONL you can pipe.

Full options: `--source {mal,anilist,kitsu,all}`, `--format {table,csv,json,jsonl}`,
`--limit N` (per source), `--min-score X`, `--sort {score,popularity,title}`,
`--dedupe`, `--timeout SECONDS`, `--version`.

## Library

```python
from scrape_airing_anime import REGISTRY

shows = REGISTRY["kitsu"](limit=10)
for show in shows:
    print(f"{show.score or '-':>5}  {show.title}  ({show.url})")
```

Every source returns `list[AiringAnime]`, a frozen dataclass with `title`,
`source`, `url`, `media_type`, `episodes`, `score`, `popularity`, `season`,
`year`, `genres`, `studios` and `synopsis`.

## Behaviour worth knowing

- **Politeness built in:** a descriptive User-Agent, retries with exponential
  backoff, `Retry-After` respected, and a pause between Jikan pages (they ask
  for at most 1 request/second).
- **`popularity` is source-local** (MAL members vs AniList popularity vs Kitsu
  user count). Compare it within one source, not across them.
- **`--dedupe` keeps the first hit** in source order (mal, anilist, kitsu),
  matching on accent-and-punctuation-insensitive titles.
- Kitsu does not ship genres/studios on its base resource, so those two
  columns are empty for that source.

## Test

```sh
python3 -m unittest discover tests
```

36 tests, all offline: every HTTP interaction is faked, so the suite runs in
milliseconds and does not hammer anyone's API.

## Heritage

This repo began life in 2021 as `scrape-myanimelist-airing`: a requests-html
scraper of MAL's top-airing page and a pandas notebook. The HTML scraping is
gone (that is what always breaks), the ambition grew, the name followed.

## License

[MIT](LICENSE.md).
