jikan

jikan-meMIT活跃维护

非官方 MyAnimeList PHP 库兼 REST API,能抓取官方接口不提供的番剧与漫画数据,方便开发者直接调用。

Unofficial MyAnimeList PHP+REST API which provides functions other than the official API

v5.0.0发布于 2026-10-19 17:06 UTC1,091star +3 / 7 天125fork同步于 2026-11-10 23:51 UTC

怎么上手

由 README 提炼,执行前请确认
composer require jikan-me/jikan "^5.0"
硬件要求按 README 说明

README 未说明;这是 PHP 库,不涉及 GPU 或显存

下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

用 PHP 抓取 MyAnimeList 网页数据的库,补上官方 API 缺失的功能

非官方 MyAnimeList PHP 库兼 REST API,能抓取官方接口不提供的番剧与漫画数据,方便开发者直接调用。

核心能力

  • 通过抓取 MyAnimeList 网页获取官方 API 没有的数据
  • 每个公开端点对应一个 MalClient 方法和一个 Request 值对象
  • 提供动漫详情与动漫搜索等请求类
  • 自带基于录制 fixture 的 hermetic 测试与质量门禁
  • 配套独立的可自托管 REST API 项目 jikan-rest

容易踩的坑

需要 PHP 8.4.4 或更高版本,v4 及更早版本已停止支持;本仓库只是 PHP 库,REST API 需另装 jikan-me/jikan-rest 且官方不提供托管实例;不支持认证请求,无法更新用户的 MyAnimeList 列表

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v5.0.0
  1. v5.0.0Jikan v5.0.0最新正式版2026-10-19 · 2026-10-19 17:06 UTC

    The first stable release of the ^5 line. The code is identical to 5.0.0-rc.2. jikan-rest validated it on its Laravel 13 migration branch (403 tests, a 114-route crawl, and the news and "recently updated" endpoints that rc.1 had broken), and a live check of 14 MyAnimeList pages found no markup drift.

    Upgrading from v4? Read UPGRADING.md. It lists every change to your code, and was checked by running v4 and v5 against the same 85 recorded pages. The sections below it record each pre-release in full.

    Breaking (from v4)

    • PHP 8.4.4 or later — you must run it; Composer will not install on older versions. PHP 8.4.0–8.4.3 have an HTML5 parser bug that corrupts some emoji and rare characters. (#642)
    • The HTTP layer is Symfony's instead of fabpot/goutte — you must run composer update. new MalClient($client) still takes a Symfony\Contracts\HttpClient\HttpClientInterface. (#607)
    • Removed: AnimeCard::getLicensors() and SeasonalAnime::getLicensors() (read Anime::getLicensors()), Model\Manga\MangaReviewer, Parser\Manga\MangaReviewerParser, Parser\Manga\MangaReviewScoresParser, Jikan\Goutte\GoutteWrapper (now Jikan\Http\HttpClientWrapper), and NewsListItemParser::getImage() and getIntro(). (#643)
    • Many getters can return null where a missing field used to be a TypeError — you must handle null. The list is in UPGRADING.md §3. (#658)
    • getNewsList() returns Model\News\ResourceNewsList; the getters have the same names. (#630)
    • Constants::SEARCH_MANGA_NOVEL is now 8 (it was 2, which MyAnimeList reads as Light Novel); use SEARCH_MANGA_LIGHT_NOVEL for 2. (#653)

    Added

    • 11 content endpoints: getArticle, getArticleSearch, getArticleTags, getArticlesByTag, getNews, getNewsByTag, getNewsSearch, getNewsTags, getPinnedArticles, getRecentArticles and getRecentNews, each with its Jikan\Request\* class. (#607)
    • Search constants for the anime types CM, PV and TV Special, the manga type Light Novel, and the manga statuses On Hiatus and Discontinued. (#653)
    • Jikan\Helper\Time: relative and partial dates resolve against a PSR-20 clock (the system clock by default; pin it in tests with Symfony\Component\Clock\Clock::set()). (#643)

    Changed

    • Anime-card start dates (getAiringStart() on genre, producer, seasonal and schedule cards) are UTC midnight of the day shown, like Anime::getAired() and the episode dates. They were midnight in Japan converted to UTC, so "Oct 2, 2026" gave 2026-10-01T15:00:00+00:00 and a year-only "2027" gave 2026-12-31T15:00:00+00:00, the previous year. The instant is 9 hours later; if you read the date part, it is now the day shown. Text that carries a broadcast time still converts from Japan time. (#666)
    • AnimeEpisode::getAired() (from getAnimeEpisode()) is UTC midnight of the printed day, like the episode list and Anime::getAired(). It was midnight in Japan (1999-10-20T00:00:00+09:00), because the page prints "Aired: Oct 20, 1999(JST)" and PHP read the "(JST)" as a UTC+9 offset, so the same episode had two different dates depending on the endpoint (jikan-rest#567). The calendar date is unchanged; the instant is 9 hours later. If you compared timestamps between the two endpoints, they are now equal. A new check in the snapshot tests fails on any date that is not UTC. (#664)
    • getHelpfulCount() on anime
  2. v5.0.0-rc.2Jikan v5.0.0-rc.2pre-release2026-10-15 · 2026-10-15 19:53 UTC
    查看更新说明

    Release candidate 2. Bug fixes only since 5.0.0-rc.1; the API is unchanged. jikan-rest found both problems while validating rc.1: three of its endpoints (/anime/{id}/news, /manga/{id}/news and /manga/{id}/userupdates) came back empty.

    Fixed

    • getNewsList() returns the articles on an anime's or manga's news page again (AnimeNewsRequest, MangaNewsRequest; jikan#661). Those pages have their own layout (no div.news-unit), which the parser stopped reading when it was realigned to the main news index in v5.0.0-beta, so it returned an empty list. Each article has its title, /news/{id} URL, date, author, discussion link, comment count, excerpt and image, with absolute links. Anything fetched through these requests since v5.0.0-beta was empty.
    • getNewsList() pagination: getLastVisiblePage() was always 1, even when hasNextPage() was true. It is the furthest page the links reach. And hasNextPage() no longer counts the "News" tab, which links to the page you're on, so the last page of a long news list no longer claims a next page.
    • "Recently updated by users" for anime and manga (getAnimeRecentlyUpdatedByUsers(), getMangaRecentlyUpdatedByUsers(); jikan#663):
      • Crashes on a bare -. MyAnimeList prints a progress cell as just - for many users (39 of 75 rows on manga 2). The total getters assumed a read/total pair and raised Undefined array key 1; where warnings become exceptions (Laravel does), a catch-all in getResults() then returned no rows at all for the page. Those cells now read as unknown.
      • An unknown total is null, not 0. 5/? (the series is still running) gave a total of 0; it is null. If you treated 0 as "unknown", check for null.
      • A row that cannot be parsed is an error, reported as ParserException, instead of an empty page next to a hasNextPage() that could be true. A page without the table still has no rows.

    Full changelog: https://github.com/jikan-me/jikan/compare/v5.0.0-rc.1...v5.0.0-rc.2

  3. v5.0.0-rc.1Jikan v5.0.0-rc.1pre-release2026-10-15 · 2026-10-15 18:10 UTC
    查看更新说明

    Release candidate. The v5 API is frozen: from here until 5.0.0 only bug fixes land, and anything that would change a signature waits for the next major. Upgrading from v4? Read UPGRADING.md. Compared with 5.0.0-beta.3, three values changed, all listed below.

    Changed

    • Anime-card start dates (getAiringStart() on genre, producer, seasonal and schedule cards) are UTC midnight of the day shown, like Anime::getAired() and the episode dates. They were midnight in Japan converted to UTC, so "Oct 2, 2026" gave 2026-10-01T15:00:00+00:00 and a year-only "2027" gave 2026-12-31T15:00:00+00:00, the previous year. The instant is 9 hours later; if you read the date part, it is now the day shown. Text that carries a broadcast time still converts from Japan time.
    • AnimeEpisode::getAired() (from getAnimeEpisode()) is UTC midnight of the printed day, like the episode list and Anime::getAired(). It was midnight in Japan (1999-10-20T00:00:00+09:00), because the page prints "Aired: Oct 20, 1999(JST)" and PHP read the "(JST)" as a UTC+9 offset, so the same episode had two different dates depending on the endpoint (jikan-rest#567). The calendar date is unchanged; the instant is 9 hours later. If you compared timestamps between the two endpoints, they are now equal. A new check in the snapshot tests fails on any date that is not UTC.
    • getHelpfulCount() on anime and manga reviews returns the review's total reactions. MyAnimeList's 2022 review redesign replaced "helpful" votes with reactions (Nice, Love it, Funny, Confusing, Informative, Well-written, Creative), and this is their total. It counts every reaction, not only approving ones; use getReactions() for the breakdown. Before this, the anime version always returned 0 and the manga version failed looking for the removed vote count.

    Full changelog: https://github.com/jikan-me/jikan/compare/v5.0.0-beta.3...v5.0.0-rc.1

  4. v5.0.0-beta.3Jikan v5.0.0-beta.3pre-release2026-10-15 · 2026-10-15 15:44 UTC
    查看更新说明

    Pre-release, and the last planned API change before the release candidate. Read Breaking before upgrading: 31 getters that could throw a TypeError now return null, and AnimeCard::getMembers() returns null instead of throwing.

    Breaking

    • 31 model getters can now return null, where they used to throw a TypeError on pages that don't show the field. You must handle null from these getters; code that only ran on pages with the field present sees no difference.

      • Anime::getDuration()
      • EpisodeListItem::getScore(), getUrl(), getForumUrl()
      • StreamEpisodeListItem::getMalId()
      • ResourceArticle::getDate()
      • NewsMeta::getDate(), getMalId()
      • NewsListItem::getDate(), ResourceNews::getDate(), ResourceNewsListItem::getDate()
      • getMalId() on AnimeGenreListItem, MangaGenreListItem, MagazineListItem and ProducerListItem
      • Person::getMemberFavorites()
      • VoiceActingRole::getRole()
      • UserSearchListItem::getLastOnline()
      • AnimeStats: getDaysWatched(), getEpisodesWatched(), getMeanScore(), getRewatched(), getTotalEntries()
      • MangaStats: getChaptersRead(), getDaysRead(), getMeanScore(), getReread(), getTotalEntries(), getVolumesRead()
      • Profile::getLocation()
      • UserReviews::getLastVisiblePage()

      Each was declared non-null while its parser already returned null when MyAnimeList leaves the field out, for example an episode nobody has voted on, or a profile without a location.

    • AnimeCard::getMembers() returns ?int: null when a card shows no member count. In beta.2 it raised ParserException, which failed the whole page. You must handle null.

    Fixed

    • getUserReviews() pagination: hasNextPage() was always true and getLastVisiblePage() always 1, so paging through a user's reviews never ended. MyAnimeList shows 10 reviews per page and links only the next page ("More Reviews"), so hasNextPage() now reports that link and getLastVisiblePage() is the next page when there is one.
    • getAnimeScores() and getMangaScores() on anime reviews, manga reviews and reviewers return the reviewer's overall rating. They always failed: the anime one called a parser that had been deleted, and the manga one read a score table that no longer exists. MyAnimeList's 2022 review redesign dropped the per-category ratings, so getOverall() is the "Reviewer's Rating" and the category getters (getStory(), getAnimation(), getArt() and so on) return null. AnimeReviewScores/MangaReviewScores are built with fromOverall(); their fromParser() and Jikan\Parser\Manga\MangaReviewScoresParser, which could not work, are removed.

    Full changelog: https://github.com/jikan-me/jikan/compare/v5.0.0-beta.2...v5.0.0-beta.3

  5. v5.0.0-beta.2Jikan v5.0.0-beta.2pre-release2026-10-15 · 2026-10-15 09:49 UTC
    查看更新说明

    Pre-release. Hardening and fidelity work on the ^5 line since v5.0.0-beta: parser fixes found by a recorded-page test corpus and mutation testing, plus fields MyAnimeList serves that were read wrong or not at all. Read Breaking and Changed before upgrading: the PHP floor is now 8.4.4, AnimeCard::getLicensors() is gone, and SEARCH_MANGA_NOVEL changed value.

    Breaking

    • AnimeCard::getLicensors() is removed, and so SeasonalAnime::getLicensors() too — you must stop calling it. Anime cards on genre, producer and seasonal lists no longer show licensors on MyAnimeList, and the method has returned an empty list since. Licensors are still on the anime itself: Anime::getLicensors() from getAnime().
    • PHP floor raised to 8.4.4 — you must run PHP 8.4.4 or later; Composer no longer installs on 8.4.0–8.4.3. Those patch levels have a bug in PHP's own HTML5 parser (Dom\HTMLDocument, php-src GH-17481) that corrupts some 4-byte characters (emoji, rare scripts) depending on where they fall in the page, so a parsed field can contain invalid UTF-8 and json_encode() fails on the model. Seen on a real MyAnimeList manga review.

    Added

    • Search filter constants for the options MyAnimeList's search form has gained:
      • Anime types SEARCH_ANIME_CM (7), SEARCH_ANIME_PV (8) and SEARCH_ANIME_TV_SPECIAL (9).
      • Manga type SEARCH_MANGA_LIGHT_NOVEL (2).
      • Manga statuses SEARCH_MANGA_STATUS_ON_HIATUS (4) and SEARCH_MANGA_STATUS_DISCONTINUED (5).
    • Jikan\Helper\Time resolves MyAnimeList's relative and partial dates ("Yesterday, 9:25 PM", "5 hours ago", "Oct 13", weekdays) against a PSR-20 clock from the new symfony/clock dependency. By default that is the system clock, so results are unchanged. You can pin "now", for example in your own tests, with Symfony\Component\Clock\Clock::set(new MockClock(...)).

    Changed

    • Constants::SEARCH_MANGA_NOVEL is now 8 (was 2). MyAnimeList's manga search now reads 2 as Light Novel and 8 as Novel, so this constant was searching for light novels. If you want light novels, use the new Constants::SEARCH_MANGA_LIGHT_NOVEL (2).
    • symfony/dom-crawler is now a declared requirement (^7.4 || ^8.0). The library has always used it directly but only received it through symfony/browser-kit, so nothing changes in your tree — no action needed unless you were excluding it.
    • A seasonal page parses roughly 3x faster (about 1.2 s down to 0.4 s in our benchmark). The marker for "continuing" shows was found by reading the text of the whole season section once per anime card; it is now read once per section. The returned Seasonal model is identical.
    • An anime card with no member count now raises ParserException from MalClient (a RuntimeException from AnimeCardParser::getMembers()), where it used to raise an uncaught \TypeError that catch (\Exception) and catch (ParserException) both missed. If you caught \TypeError around client calls to work around this, catch ParserException instead.
    • Jikan\Model\Manga\MangaReviewer and Jikan\Parser\Manga\MangaReviewerParser are removed. They extended classes that were deleted earlier, so loading either one was a fatal error and nothing referenced them; no working code can have depended on them.
    • Jikan\Model\User\Friend::getFriendsSince() returns ?\DateTimeImmutable: null when MyAnimeList shows no "Friends since" date. It used to declare a non-null date and throw a TypeError in that case.

    Fixed

    • Anime cards (genre, producer, seasonal and schedule pages; AnimeCard/SeasonalAnime) now read their type and start date from fields MyAnimeList puts on every card. This fixes four problems:
      • Year-only start dates: getAiringStart() gave today's date for a card that shows only a year ("2027"), because the year was read as the time 20:27 today. It now gives 1 January of that year.
      • Schedule page types: getType() gave the day of the week ("Friday", "Other") inst

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    Jikan

    Jikan - Unofficial MyAnimeList.net PHP API

    build stable Average time to resolve an issue Percentage of issues still open stable Discord Server

    Jikan is a PHP API for MyAnimeList.net. It scrapes the website to satisfy the need for API functionality that MyAnimeList.net lacks.

    The raison d'être of Jikan is to assist developers easily get the data they need for their apps and projects without having to depend on the lackluster official API, unstable APIs, or sidetracking their projects to develop parsers.

    The word Jikan literally translates to Time in Japanese (時間). And that's what this API saves you of. ;)

    Notice: Jikan does not support authenticated requests. You can not update your lists.

    Status: active development is on master — the v5 line, currently 5.0.0. The v4 line is end of life and no longer supported. Jikan is a PHP library, not a hosted service; a self-hosted REST API exists as a separate project.

    Getting Started

    VersionSupportPHPLumen/Laravel
    ^5✅ Current — actively maintained8.4.4^13
    ^4❌ End of life — no longer supported8.1^9,^10
    ^3❌ No longer maintained or supported8.0^8, ^9
    ^2❌ No longer maintained or supportedstable^6
    ~1❌ No longer maintained or supportedstable5.5.*
    1. composer require jikan-me/jikan "^5.0" (needs PHP 8.4.4 or later)
    2. Coming from v4? Read the upgrade guide.
    3. Read the Repository docs below — this repo holds the canonical documentation.

    Repository docs

    • Contributing — the endpoint spine, hermetic-fixture workflow, pre-commit gates
    • Changelog — what changed per version, written for consumers
    • Upgrading from v4 — what to change in your code, checked against a side-by-side run of both versions
    • Decision log — why things are the way they are
    • Deprecations — the running deprecation ledger
    • API standard — conventions every public entry point must follow

    Usage

    use Jikan\MyAnimeList\MalClient;
    use Jikan\Request\Anime\AnimeRequest;
    use Jikan\Request\Search\AnimeSearchRequest;
    
    $client = new MalClient(); // or new MalClient(HttpClientInterface $http) to inject the client
    
    $anime = $client->getAnime(new AnimeRequest(1));          // Cowboy Bebop
    $results = $client->getAnimeSearch(new AnimeSearchRequest())->getResults();
    

    Every public endpoint is one MalClient method taking one Jikan\Request\* value object; the method list, the class-per-endpoint surface, and the conventions each must follow are documented in readme (conceptually), API-STANDARD.md (requirements) and CONTRIBUTING.md §3 (how to add one). The full machine-readable catalog lives in llm.txt (and .backend-suite/endpoints.json) — derived, regenerated with composer endpoints:write. On every change that touches the surface, run composer endpoints:write and composer endpoints:check.

    REST API (self-hosted)

    The library powers a separate, self-hostable REST API project: jikan-me/jikan-rest.

    No hosted/public instance of the REST API is provided.

    Wrappers

    LanguageWrappers
    JavaScriptJikanJS by Zuritor
    JavaJikan4java by Doomsdayrsreactive-jikan by Sandro MarquesJaikan by ShindouMihou
    PythonJikanPy by Abhinav Kasamsetty
    DenoJikan.js by RPDJF
    Node.jsjikan-node by xy137jikan-nodejs by ribeirogab
    TypeScriptjikants by Julien Broyardjikan-client by Javier Blanco
    PHPjikan-php by Jan Vernieuwe
    .NETJikan.net by Ervie
    Gojikan-go by Daren Liangjikan2go by nokusukun
    RubyJikan.rb by Zerocchi
    Dartjikan-dart by Rafal Wachol
    KotlinJikanKt by Ganedra Afrasya

    Add your wrapper here

    View RoadMap

    Running Tests

    composer quality   # everything at once: php-cs-fixer dry-run + PHPStan + endpoint-manifest check + tests
    composer test      # PHPUnit via paratest — hermetic against recorded fixtures
    

    Quality gates run automatically on every commit (grumphp: php-cs-fixer, php-parallel-lint, git blacklist, paratest; plus the backend-suite error_handling/test_network_coupling audits). The same composer quality gate is enforced in CI. Full workflow: CONTRIBUTING.md.


    Backers

    A huge thank you to all our Patrons! 🙏 This project wouldn't be running without your support.

    If you'd like to support the project, you can become a Patron!

    Sugoi (すごい) Patrons

    Patrons

    • Aaron Treinish
    • Abdelhafid Achtaou
    • Aika Fujiwara
    • Bobby Williams
    • Cesar Irad Mendoza
    • CrafterSama
    • Fro116
    • Jason Weatherly
    • Jesse
    • Kundan Chintamaneni
    • Kururin
    • Purplepinapples
    • Ryo Ando
    • Sakamotodesu
    • TeraNovaLP

    Development

    JetBrainJikan's development is powered by JetBrain's Open Source License

    A shoutout to their amazing products and for supporting Jikan since early versions!


    DISCLAIMER

    • Jikan is not affiliated with MyAnimeList.net
    • You are responsible for the usage of this API. Please be respectful towards MyAnimeList's Terms Of Service