adafi tips

PR / 18歳未満閲覧禁止

DMMジャンル検索APIの使い方|ジャンルIDをPHPで全部取る

公開日
更新日
タグ
API
DMMジャンル検索APIの使い方|ジャンルIDをPHPで全部取る

DMMのジャンル検索API(GenreSearch)は、フロアIDを渡すと、そのフロアのジャンルID・名前・読み仮名・作品一覧のURLを、1回に最大500件返します。FANZAのビデオ(フロアID 43)には、2026年10月17日の時点で341のジャンルがあり、1回の呼び出しで全部取れました。

ジャンル検索APIは、2023年12月のオーナーが、サイトを作る途中で使うことになったAPIです。そのときの記録を、記事担当の伊緒が今のAPIで呼び直して、書き直したのがこの記事。

確かめてみると、当時のコードのままでは取りこぼすフロアがありました。ジャンルが500件を超えるフロアがあり、1回の呼び出しでは全部取れないので、後半に、最後まで取るコードを載せています。

341のジャンル名は、全部読みました。読み仮名まで、です。……業務として、です!

指定できる条件と、返ってくる項目

必ず指定するのは、API ID・アフィリエイトID・フロアID(floor_id)の3つです。フロアIDを省くと、400のエラーが返りました。

パラメーター中身決まり
floor_idフロアID(数字)必須。フロアAPIで調べる
initial読みの頭文字ひらがな1文字をUTF-8で
hits1回に取る件数初期値100・最大500
offset何件目から取るか初期値1
output出力の形式json か xml

hits=501 にすると、400のエラーになり、「hits は500以下にする」という意味の英語の文が返ります。initial=あ で呼ぶと、FANZAのビデオでは15件に絞られました。DMMのご利用ガイドには「一度のリクエストで取得できるのは100件まで」とありますが、ジャンル検索のリファレンスでは最大500件で、実際に341件が1回で返っています。

返ってくる項目は、フロアの情報と、ジャンルの配列(genre)です。

中身項目名FANZAのビデオでの例
売り場の名前site_name・service_name・floor_nameFANZA(アダルト)・動画・ビデオ
商品検索に渡すコードsite_code・service_code・floor_codeFANZA・digital・videoa
件数total_count・result_count全体341・今回返した341
ジャンルIDgenre[].genre_id4031
ジャンルの名前と読み仮名genre[].name・rubyコスプレ・こすぷれ
作品一覧へのリンクgenre[].list_urlコスプレの作品一覧へのアフィリエイトリンク

コードを書くときに気をつけたいのは、値の型です。genre_id・total_count・status は文字列(“341”)で返り、result_count だけが数で返ってきました。商品検索APIの応答では、ジャンルのIDは数(4031)で入っています。=== で比べると一致しないので、(int) でそろえてから比べてください。

一覧ページのURLは、公式ドキュメントの例では www.dmm.co.jp/digital/videoa/-/list/ の形ですが、今の応答は video.dmm.co.jp/av/list/?genre=ID の形になっていました。

フロアを選んで試すなら、DMM ジャンル検索のツールが手軽です。

フロアごとのジャンルの数

FANZAの23のフロアのうち、ジャンルが500件を超えたのは同人(558件)だけでした。DMM.comでは、ホビー(1,308件)・コミック(904件)・文芸・ラノベ(580件)・DVD・Blu-ray(508件)が500件を超えています。2026年10月17日に、全39フロアを呼んだ結果です。

サービスフロアの名前フロアIDジャンルの数
動画ビデオ43341
動画素人44243
動画成人映画45192
動画アニメ動画46132
月額動画見放題ch デラックス68345
月額動画VRch91254
月額動画見放題ch980
通販DVD74456
通販大人のおもちゃ75219
通販アニメ76167
通販PCゲーム77168
通販ブック78357
通販フィギュア・グッズ94172
アダルトPCゲームアダルトPCゲーム80347
同人同人81558
同人らぶカル(BL)9925
同人らぶカル(TL)10018
FANZAブックスコミック82289
FANZAブックス美少女ノベル・官能小説83197
FANZAブックスアダルト写真集・雑誌84258
FANZAブックスBL960
FANZAブックスTL970
FANZAブックス読み放題FANZAブックス読み放題92217

ジャンルが0件のフロアは、エラーにはならず、空の配列が返ります。DMM.comでも、AKB48グループの7フロアとDMMTVは0件でした。DMM TVの作品はジャンルの情報を取れないと、DMMのヘルプにも書かれています。

午後の博物館の展示室で、伊緒が標本の引き出しを両手で引き出したものの、中が空っぽで、拍子抜けした顔でのぞきこんでいる

フロアIDは、フロアAPIで調べます。全部のフロアのIDとコードは、こちらの記事に表でまとめました。

一覧をそのまま使う前に外したいジャンル

FANZAのビデオの341件には、そのまま使うと困るジャンルが2種類ありました。過去の企画で使ったジャンルと、同じ名前でIDが違うジャンルです。

過去の企画で使ったジャンル

「AV OPEN 2014 スーパーヘビー級」から「AV OPEN 2018 マニア・フェチ部門」まで、2014〜2018年の企画のジャンルが36件残っていました。商品検索で作品の数を全部数えると0〜32件で、29件は7件以下です。オーナーは2023年の時点で「削除して使いたい」と書いていて、今もその判断のままでいいと思います。

同じ名前でIDが違うジャンル

「コスプレ」「制服」「ドラマ」など7つの名前に、IDが2つか3つずつ、合わせて16個ありました。商品検索で、FANZAのビデオのフロアに絞って作品の数を調べると、どの名前も作品があるIDは1つだけで、残りの9個は0件です。

ジャンルの名前作品があったID(件数)0件だったID
4時間以上作品6012(48,614件)6179
コスプレ4031(13,233件)66314
制服48(12,497件)66317
ドラマ4114(18,681件)66329
和服・浴衣3002(6,686件)3021・66318
セクシー4098(2,529件)79002・66310
ディルド6941(1,223件)10138

名前だけでIDを選ぶと、作品が1件も出ないジャンルのページができてしまうので要注意。後半のコードでは、名前が重なるジャンルだけ商品検索で数を調べて、0件のIDを外しています。

休日の朝、自分の部屋のクローゼットの前で、伊緒が同じ形のハンガーを2本持ち上げ、服が掛かっている方と空っぽの方を見くらべて首をかしげている

同じ名前が2つあるなんて、オーナーの記事には書いてありませんでした。気づいたからには、16個とも作品の数を調べるしかありません。

PHPのコード:ジャンルを全部取って、対応表に保存する

オーナーが2023年に書いたコードは、2026年10月17日にそのまま動かすと、FANZAのビデオの341件を警告なしで表示できました。ただ、hits を500にした1回きりの呼び出しなので、同人やホビーのような500件を超えるフロアでは、残りを取りこぼします。

直したコードは、offset をずらして最後まで取り、外したいジャンルを除いて、「ジャンルID → 名前・読み仮名」の対応表をJSONで保存する作りです。ブラウザで開くページではなく、コマンドラインで php genre.php と実行して使います。

図解。FANZAの同人のジャンルは558件あり、1回に取れるのは500件まで。1回目は offset=1・hits=500 で1〜500件目の500件を取り、2回目は offset=501 で501〜558件目の58件を取る。次の開始位置の559が全体の558件を超えたら止める。500件以下のフロアは1回目だけで終わる
<?php
// DMM ジャンル検索API(GenreSearch)で、1つのフロアのジャンルを全部取り、
// 「ジャンルID → 名前・読み」の対応表を JSON に保存する(コマンドラインで php genre.php と実行する)
const API_ID = 'あなたのAPI ID';
const AFFILIATE_ID = 'あなたのアフィリエイトID'; // 末尾が990〜999のもの
const FLOOR_ID = 43; // フロアAPIで調べたフロアID(43は FANZA の動画・ビデオ)

function dmm_api(string $name, array $params): array
{
    $query = http_build_query(['api_id' => API_ID, 'affiliate_id' => AFFILIATE_ID, 'output' => 'json'] + $params);
    $context = stream_context_create(['http' => ['ignore_errors' => true, 'timeout' => 10]]);
    // 先頭の @ は、つながらなかったときの警告に API ID 入りの URL を出さないため
    $body = @file_get_contents('https://api.dmm.com/affiliate/v3/' . $name . '?' . $query, false, $context);
    $result = json_decode((string) $body, true)['result'] ?? null;
    if (!is_array($result) || (int) ($result['status'] ?? 200) !== 200) {
        exit('APIのエラー: ' . json_encode($result ?? '応答がありません', JSON_UNESCAPED_UNICODE) . "\n");
    }
    return $result;
}

// 1回に取れるのは500件まで。500件を超えるフロアがあるので、offset をずらして最後まで取る
$genres = [];
$offset = 1;
do {
    $result = dmm_api('GenreSearch', ['floor_id' => FLOOR_ID, 'hits' => 500, 'offset' => $offset]);
    foreach ($result['genre'] as $genre) {
        // 過去の企画で使った「AV OPEN 2014 〜」のようなジャンルは外す
        if (preg_match('/^AV OPEN \d{4}/', $genre['name'])) {
            continue;
        }
        $genres[$genre['genre_id']] = ['name' => $genre['name'], 'ruby' => $genre['ruby']];
    }
    $offset += (int) $result['result_count'];
    sleep(1); // 続けて呼ぶときは間を空ける
} while ($result['result_count'] > 0 && $offset <= (int) $result['total_count']);

// 同じ名前のジャンルが2つ以上あるときは、商品検索で作品が0件の ID を外す
$count = array_count_values(array_column($genres, 'name'));
foreach ($genres as $id => $genre) {
    if ($count[$genre['name']] < 2) {
        continue;
    }
    $items = dmm_api('ItemList', [
        'site' => $result['site_code'],
        'service' => $result['service_code'],
        'floor' => $result['floor_code'],
        'article' => 'genre',
        'article_id' => $id,
        'hits' => 1,
    ]);
    if ((int) $items['total_count'] === 0) {
        unset($genres[$id]);
    }
    sleep(1);
}

// 読みの50音順に並べて保存する
uasort($genres, fn($a, $b) => strcmp($a['ruby'], $b['ruby']));
file_put_contents(__DIR__ . '/genres-' . FLOOR_ID . '.json', json_encode($genres, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
echo $result['floor_name'] . ' のジャンル ' . $result['total_count'] . '件のうち、' . count($genres) . "件を保存しました\n";

2026年10月17日に動かした結果は、次のとおりです。

  • FANZAのビデオ(43):341件のうち296件を保存(AV OPENの36件と、0件だった9個を外した)。20秒ほどで終わった
  • FANZAの同人(81):558件を2回に分けて全部取り、名前が重なって0件だった14個を外して544件を保存。40秒ほどかかった
図解。FANZAのビデオ(フロアID 43)では、ジャンル検索APIで341件が取れた。そのうち過去の企画のジャンル「AV OPEN 2014」〜「AV OPEN 2018」の36件と、「コスプレ」「制服」など7つの名前の16個のIDのうち作品が0件の9個を外し、残りの296件を対応表のJSONに保存した

保存した対応表は、ジャンルの一覧ページやメニューを作るときに読み込んで使います。たとえば、50音順のリンクを並べるなら、次の数行です。

<?php
// 保存した対応表から、ジャンルのリンクを50音順に並べる(対応表は公開フォルダの外の data/ に置いた例)
$genres = json_decode(file_get_contents(__DIR__ . '/../data/genres-43.json'), true);
foreach ($genres as $id => $genre) {
    echo '<a href="/genre/' . $id . '/">' . htmlspecialchars($genre['name']) . '</a>' . "\n";
}

リンク先のジャンルのページでは、商品検索APIに article=genre&article_id=ID を渡して作品を出します。対応表のJSONは、サイトの公開フォルダの外に置いてください。DMMウェブサービスの利用規約では、APIで得た情報を、不特定の人が共有できる場所に保管することが禁止されています。公開のGitHubのリポジトリに入れるのも避けたほうが安心です。

2023年のオーナーの考えを、今の仕様で見直すと

オーナーがジャンル検索を使い始めたのは、SEOを考えると、ジャンルIDではなくジャンルの名前を使いたくなったからでした。当時の考えを、今のAPIに当てはめるとこうなります。

項目オーナーが2023年に書いたこと2026年10月17日に確かめた結果
ジャンル名商品が持っているジャンルIDから、ジャンル名を取りたい商品検索の応答に、名前もIDと一緒に入っている。作品ページに名前を出すだけなら、ジャンル検索は要らない
IDでの絞り込みジャンルIDで絞り込めないのが残念今もできない。指定できるのはフロアID・頭文字・件数・開始位置だけ
データベースジャンルのデータをデータベースに入れて対応したい今も有効。一覧ページやメニューを作るなら、対応表を保存して使い回すのが早い
一時的なジャンル「AV OPEN」のようなジャンルは削除して使いたい今も36件残っている。名前の先頭で見分けて外せる

ジャンル検索が本当に役に立つのは、作品を取る前に、そのフロアのジャンルを全部、読み仮名つきで並べたいときです。ジャンルごとのページを作るサイトなら、最初に一度取って保存しておけば、毎回APIを呼ばずに済みます。

「削除して使いたい」と書かれてから3年。一時的なジャンルは、今も36件残っていました。……一覧に出す前に外しておくのが、いちばん早いです。

ジャンルIDを使って作品を絞り込む方法は、商品検索APIの記事で書いています。

よくある質問

ジャンルIDは、フロアが違っても同じ?

FANZAの動画のビデオ(フロアID 43)と、同じ動画サービスのフロアID 44(コードは videoc)を比べると、両方にあるIDは214個で、どれも同じ名前でした。ただ、フロアによって無いジャンルもあるので、使うフロアごとに一覧を取ってください。

頭文字は、どう指定する?

initial に、ひらがなを1文字だけUTF-8で渡します。FANZAのビデオで「あ」を渡すと、読みが「あ」で始まる15件が返りました。

何回まで続けて呼んでいい?

回数の上限は、ドキュメントに数字で書かれていません。利用規約には、アクセス回数などの制約を別に定めることがある、とあります。ジャンルの一覧はめったに変わらないので、保存して使い回し、続けて呼ぶときは間を空けてください。

この記事で作ったAPIは、ツールで試せます

キーワードや条件を変えて、APIが返す実際のデータを確かめられます。

この記事を書いた人

伊緒

オーナーの下で、記事を担当しているスタッフです。オーナーが試したことを聞いて記事にし、公式の資料やAPIで今も合っているかを確かめ直しています。週末の朝は、道場で弓を引いています。

伊緒の自己紹介|伊緒の業務日誌

「API」のほかの記事

すべて見る