【維持費0円】FANZA APIから動画データをスクレイピングし続けるGASスクリプトを公開

FANZA

今回はFANZA動画のデータを維持費0円でgoogleスプレッドシートに収集し続ける仕組みを作ってみました。

FANZAの動画データを収集するだけならDMMAPIから取得できるんですが、アフィリエイトでデータを活用するには不足しているデータがいくつかあるので、それらをスクレイピングで補って使いやすいFANZA動画のリストを作ってみたいと思います。

完成したスクリプトはこんな感じ

ジャンルを指定してFANZA動画の情報をごっそり取得できるので結構便利ですね。アフィリエイトに使える「動画説明文」や「生のサンプル動画URL」を工夫して補完していますんで、普通にAPIを叩いて取得したリストよりも用途の幅は広くなっていると思います。

またデータベースの代わりにgoogleスプレットシートを利用しているので、プログラミングに馴染みのない方でも取っつきやすいんじゃないでしょうか。という事で、こいつの作り方を解説していきます。

自作に必要な物

  • DMM APIID
  • DMMアフィリエイトID
  • googleアカウント

まずはDMMwebサービスを利用するためのAPIID・アフィリエイトID、googleスプレットシート・GASを利用するためのgoogle無料アカウントがそれぞれ必要になるので準備して下さい。

DMMアフィリエイトのwebサービスを利用するには「DMM会員登録DMMアフィリエイト登録webサービス利用登録」と順番に作業する必要がありますが、特に難しい部分はないので説明は省略します。分からなければ「dmm api 登録」とかで検索して調べてみて下さい。

googleの無料アカウントの取得方法はこちらで解説しています。

google無料アカウントによる制限

これからGAS上でコードを色々動かしますが無料アカウントだとリソースに制限が存在するので覚えとくべきポイントをご紹介しときます。いずれもオーバーするとエラーが発生し無料分のリソースが回復するまで処理が実行できなくなります。

  • スクリプト1回の実行時間制限:6分
  • 1日のスクリプト総実行時間:90分
  • 1日のURLFetchの呼び出し回数:20,000回

ここでは必要のある制限しかピックアップしていませんので、詳しく知りたい方は「Google Apps Script 無料制限」とかで検索して最新情報を確認してみて下さい。

APIで取得できるデータの中身を確認してみる

DMMwebサービスでは複数のAPIが提供されてますが、今回はFANZA動画の関連データだけを取得できれば良いので「商品情報API」がメインになりそうです。とりあえずAPIの中身を確認してみます。

GETリクエスト

商品情報APIのリクエストはこんな感じ、APIIDとアフィリエイトIDを取得した物に書き換えて下さい。

https://api.dmm.com/affiliate/v3/ItemList?api_id=[APIID]&affiliate_id=[アフィリエイトID]&site=FANZA&service=digital&floor=videoa&hits=10&sort=date&keyword=%e4%b8%8a%e5%8e%9f%e4%ba%9c%e8%a1%a3&output=json

レスポンス

叩くとこんな感じ。

{
    "request": {
        "parameters": {
            "api_id": "example",
            "affiliate_id": "affiliate-990",
            "site": "FANZA",
            "service": "digital",
            "floor": "videoa",
            "keyword": "上原亜衣"
        }
    },
    "result": {
        "status": 200,
        "result_count": 20,
        "total_count": 1201,
        "first_position": 1,
        "items": [
            {
                "service_code": "digital",
                "service_name": "動画",
                "floor_code": "videoa",
                "floor_name": "ビデオ",
                "category_name": "ビデオ (動画)",
                "content_id": "dvaj00419",
                "product_id": "dvaj00419",
                "title": "発射直後チ○ポをむさぼる美少女お掃除フェラ97連発",
                "volume": "300",
                "review": {
                    "count": 2,
                    "average": "3.00"
                },
                "URL": "https://www.dmm.co.jp/digital/videoa/-/detail/=/cid=dvaj00419/",
                "URLsp": "https://www.dmm.co.jp/digital/videoa/-/detail/=/cid=dvaj00419/",
                "affiliateURL": "https://al.dmm.co.jp/?lurl=https%3A%2F%2Fwww.dmm.co.jp%2Fdigital%2Fvideoa%2F-%2Fdetail%2F%3D%2Fcid%3Ddvaj00419%2F&af_id=affiliate-990&ch=api",
                "affiliateURLsp": "https://al.dmm.co.jp/?lurl=https%3A%2F%2Fwww.dmm.co.jp%2Fdigital%2Fvideoa%2F-%2Fdetail%2F%3D%2Fcid%3Ddvaj00419%2F&af_id=affiliate-990&ch=api",
                "imageURL": {
                    "list": "https://pics.dmm.co.jp/digital/video/dvaj00419/dvaj00419pt.jpg",
                    "small": "https://pics.dmm.co.jp/digital/video/dvaj00419/dvaj00419ps.jpg",
                    "large": "https://pics.dmm.co.jp/digital/video/dvaj00419/dvaj00419pl.jpg"
                },
 --------------------長いので省略---------------------
            }
        ]
    }
}

使いそうなデータをまとめる

色々データが入ってますが使えそうなデータをざっくりまとめるとこんな感じ。

項目内容
total_countデータの合計件数
first_position何番目のレコードから動画データを取得するか指定できる
content_id商品ID
title動画タイトル
volume収録時間
review商品レビュー、 { "count": レビュー件数, "average": 平均点数 },
URL商品ページのURL
affiliateURLアフィリエイトID付きの商品ページのURL
imageURL商品のパッケージ画像、"imageURL": { "list": 小, "small": 中, "large": 大 },
sampleMovieURL動画プレイヤー付きのサンプル動画URL
date動画のリリース日
genre動画のジャンル
actress出演しているセクシー女優

使いそうなデータはこれくらいですかね、価格なんかは度々変わりますんで今回は入れていません。データは結構充実していると思いますが「動画の説明文」と「ダウンロード可能なサンプル動画のURL」がAPIには入ってませんでした。

アフィリエイトを目的としてAPIを利用するならこの2つのデータが利用できないのは結構痛いです。動画説明は自然言語処理APIなんかと組み合わせるとオリジナルの文章コンテンツを自動生成できますし、プレイヤーの付いていない生のサンプル動画URLはダウンロードや加工など柔軟に宣伝の幅を広げられます。

という事でこれらの不足データを自動で補完する方法を考えてみました。

不足データを補完する

補完と言っても動画説明文もサンプル動画URLもFANZAの商品ページから目視できるので、どこかに必ずデータが存在するハズです。まずはページのソースコードをじっくり見て対策を考えてみます。

動画説明文はスクレイピングで取得

とりあえず動画説明文がどうやって表示されているのか調べてみます。静的コンテンツとして普通にベタ書きされていればスクレイピングで簡単に取得できるんですが、javascriptとかで動的に表示されていると面倒なのでチェックしてみます。

この動画説明文の一部をコピペし右クリックしてソースコードを開きます。コピペした動画説明文の一部を検索すると3ヶ所同じ文章が見つかりました。どうやら静的コンテンツとしてDBから単純に書き出しているだけっぽいです。今回は一番最初に表示されて正規表現も書きやすそうなhtmlのmetaデータから取得する事にしました。具体的には下記の部分です。

簡単そうなのでとりあえず正規表現でスクレイピングしてみます。今回はGASを使ってスクレイピングしているのでUrlFetchApp.fetchを使います。1点コツとしてFANZA動画はcookieでage_check_doneにフラグが立ってないと年齢認証ページに飛ばされてしまうので、UrlFetchApp.fetchのoptionsでcookieを設定してやる必要があります。

GASはjavascriptベースですしスクレイピングのサンプルコードは検索すればたくさん出てきますが、僕は下記のように書いてみました。

//取得先の商品ページ
const affiliateURL = 'https://www.dmm.co.jp/digital/videoa/-/detail/=/cid=ssis00187/';
//dmmの商品ページをスクレイピングして動画説明文章を取得
const regexp_description = new RegExp("<meta name=\"description\" content=\"(.*?)\" />", "s");

//年齢認証ページをスキップするためのcookieを設定
headers = { // リクエストヘッダー
  'cookie': 'age_check_done=1;'
}

options = {
  'method': 'get', // POSTメソッド
  'headers': headers
}

//dmmの商品ページをスクレイピングして動画説明文章を取得
const dmm_product_response = UrlFetchApp.fetch(affiliateURL, options).getContentText();

//動画説明文の加工
if(dmm_product_response.match(regexp_description) !== undefined){
  //動画説明格納
  description = dmm_product_response.match(regexp_description)[1];
  //htmlエンティティのデコード
  description = XmlService.parse('<d>' + description + '</d>').getRootElement().getText();
  //【FANZA(ファンザ)】文字除外
  description = description.replace("【FANZA(ファンザ)】", "");
  //htmlタグ除外
  description = description.replace(/<("[^"]*"|'[^']*'|[^'">])*>/g,'');
  console.log(description);
}else{
  description = 0;
}

GASで実行してみるとこんな感じ。

動画説明文がちゃんと取得できましたんでAPIで取得した他のデータと一緒に、googleスプレットシートに保存すれば補完完了です。

サンプル動画はURLのパターンを特定して総当たりで取得

お次はダウンロード可能なサンプル動画URLを探してみます。まずAPIで取得できるサンプル動画URLは何がダメなのか見てみましょう。APIで取得できたsampleMovieURLを開いてみます。

するとDMMの動画プレイヤーを通して動画が表示されました。一見問題なさそうですが、このページ上からは動画ファイルをダウンロードする事ができません。ちなみにダウンロード可能な動画URLはこんな感じです。

こんな感じで右クリックからでも普通に動画が保存できるので、すべての動画作品で上記の動画URLを用意できれば理想的です。試行錯誤して調べた結果、FANZAのサンプル動画の生URLは下記の8パターンに振り分けられる事が分かりました。

noURLパターン
1サンプル動画なし
2https://cc3001.dmm.co.jp/litevideo/freepv/[content_id[1]]/[content_id[1:3]]/[content_id]/[content_id]_sm_s.mp4
3https://cc3001.dmm.co.jp/litevideo/freepv/[content_id[1]]/[content_id[1:3]]/[content_id]/[content_id]_sm_w.mp4
4https://cc3001.dmm.co.jp/litevideo/freepv/[content_id[1]]/[content_id[1:3]]/[content_id]/[content_id]_dmb_s.mp4
5https://cc3001.dmm.co.jp/litevideo/freepv/[content_id[1]]/[content_id[1:3]]/[content_id]/[content_id]_dmb_w.mp4
6https://cc3001.dmm.co.jp/litevideo/freepv/[content_id[1]]/[content_id[1:3]]/[content_id]/[content_id]_mhb_s.mp4
7https://cc3001.dmm.co.jp/litevideo/freepv/[content_id[1]]/[content_id[1:3]]/[content_id]/[content_id]_mhb_w.mp4
8 https://cc3001.dmm.co.jp/vrsample/[content_id[1]]/[content_id[1:3]]/[content_id]/[content_id]vrlite.mp4

上記のようにURLのパターンが分かったので、恰好良い方法ではないですが総当たりで生動画URLを探す事にします。作品のcontent_idをURLパターンに埋め込んでアクセスしhtmlレスポンスで200が返ってきたら、その生動画URLは生きているとみなします。

コードはこんな感じで書いてみました。

//動画のコンテンツID
const content_id = 'rbd00185';
//サンプル動画urlの取得
const sample_movie_url_mhb_w = 'https://cc3001.dmm.co.jp/litevideo/freepv/'+content_id.slice(0,1)+'/'+content_id.slice(0,3)+'/'+content_id+'/'+content_id+'_mhb_w.mp4';
const sample_movie_url_mhb_s = 'https://cc3001.dmm.co.jp/litevideo/freepv/'+content_id.slice(0,1)+'/'+content_id.slice(0,3)+'/'+content_id+'/'+content_id+'_mhb_s.mp4';
const sample_movie_url_dmb_w = 'https://cc3001.dmm.co.jp/litevideo/freepv/'+content_id.slice(0,1)+'/'+content_id.slice(0,3)+'/'+content_id+'/'+content_id+'_dmb_w.mp4';
const sample_movie_url_dmb_s = 'https://cc3001.dmm.co.jp/litevideo/freepv/'+content_id.slice(0,1)+'/'+content_id.slice(0,3)+'/'+content_id+'/'+content_id+'_dmb_s.mp4';
const sample_movie_url_sm_w = 'https://cc3001.dmm.co.jp/litevideo/freepv/'+content_id.slice(0,1)+'/'+content_id.slice(0,3)+'/'+content_id+'/'+content_id+'_sm_w.mp4';
const sample_movie_url_sm_s = 'https://cc3001.dmm.co.jp/litevideo/freepv/'+content_id.slice(0,1)+'/'+content_id.slice(0,3)+'/'+content_id+'/'+content_id+'_sm_s.mp4';
const sample_movie_url_vrlite = 'https://cc3001.dmm.co.jp/vrsample/'+content_id.slice(0,1)+'/'+content_id.slice(0,3)+'/'+content_id+'/'+content_id+'vrlite.mp4';
const sample_movie_urls = [sample_movie_url_mhb_w, sample_movie_url_mhb_s, sample_movie_url_dmb_w, sample_movie_url_dmb_s, sample_movie_url_sm_w, sample_movie_url_sm_s, sample_movie_url_vrlite];
for(const index in sample_movie_urls){
  const response_code = UrlFetchApp.fetch(sample_movie_urls[index], { muteHttpExceptions:true }).getResponseCode();
  if(response_code == 200){
    sample_movie_url = sample_movie_urls[index];
    console.log(sample_movie_url);
    break;
  }else{
    sample_movie_url = 0;
  }
}

こいつを実行してみるとこんな感じ。

これで生のサンプル動画URLが取得できました。作業をしている中で気付いたんですが、FANZAのサンプル動画って全作品のうち1/3程度しか用意されていないみたいですね。なので、APIからガバっとデータを取得してもサンプル動画URLの部分だけスカスカになっていたりしますがこれはこれで正解です。

googleスプレットシートへ保存する

必要なデータはこれで全部揃ったのでデータベース代わりのgoogleスプレットシートへ保存します。データの保存もGASでスクリプトを書いて自動保存させますが、MySQLの操作とかに比べると割と簡単にできると思います。

シートの作成までは手動で作業を行ってその中のデータの入出力を自動化する感じで作っていきます。

シートの準備

今回のスクリプトは下記2つのシートを用いて自動化を行っています。

  • fanza_video_db(FANZAの動画データを保存するDB)
  • offset_master(取得済みの商品情報APIのレコード番号を記録するシート)

このシート名が間違っているとデータの保存や更新が出来なくなるので気を付けて下さい。下記のダウンロードリンクでひな形の.xlsxファイルを用意したんで「新しいスプレットシートを作成 > ファイル > インポート > アップロード > デバイスのファイルを選択 > fanza_get_video_info.xlsx」で簡単にシートの準備ができます。

上記のひな形の中には「genre_master」というシートも含まれていますが、これは商品検索APIで指定できるジャンルIDの一覧なので必要があれば参考に使って下さい。システムには関係ないので削除しても大丈夫です。

前回の続きから商品検索APIを叩く工夫

理想を言えばDMMの商品検索APIを1回実行するだけですべてのFANZA動画データを取得できれば一番良いんですが、下記の制約によりできません。

  • 商品検索APIは1回のリクエストで最大100件までのデータしか取得できない
  • 無料アカウントで利用するGASではスクリプトの実行時間制限がある(6分/回、90分/日)

これらの制限以内に収まるように動画データの収集処理を細切れに行う必要があるのですが、この際「前回の処理で何件目までのレコードを取得したか」を記録し、この値を次回の収集処理時にoffsetパラメータとして指定する事で前回の続きから処理を引き継ぐ事が可能になります。

今回のスクリプトではgoogleスプレットシートのoffset_masterにこの値を保存して、処理の度に入出力するようにしています。

FANZAサンプル動画URLの重複を回避する

FANZA動画の取得処理を実際にスクリプトで定期実行してみると、処理の失敗などで取得済みの動画情報が重複してシートに保存されてしまう事がありますが、どんな用途で使うにしても同じ動画のレコードが何件も重複しているのはおかしいので、動画データの収集処理の〆としてcontent_idが重複しているレコードを削除する処理を行います。

仕組み的には簡単で、fanza_video_dbの最終行と最終列を取得し見出し以外の全範囲を指定してremoveDuplicates()メソッドを使うだけです。

//重複行を削除
const ss = SpreadsheetApp.getActiveSpreadsheet()
const sheet0 = ss.getSheetByName("fanza_video_db");
const dup_del_lastRow = sheet0.getLastRow();
const dup_del_range = sheet0.getRange(2, 1, dup_del_lastRow-1,12);
dup_del_range.removeDuplicates();

定期実行の登録

GASは定期実行の設定がとても簡単で今まで作ったfunctionをトリガーで選択できるだけで作業が完了します。

スクリプトの実行頻度をリソースから計算する

今まで何度か出てきましたがGASを無料アカウントで利用するとスクリプトの実行時間に制限があり(6分/回、90分/日)、定期実行の頻度もこの制限を加味して設定する必要があります。

例えば、僕の環境ではFANZA動画を上記のスクリプトで1件取得するのに約5.25秒かかりますがここでは切り良く6秒としましょう。1回のスクリプトの実行時間制限は6分=360秒なので、1回のスクリプトの実行で約60件動画データを取得する事ができます。

そして1日で実行できるスクリプトの合計時間は90分=5,400秒なので、5,400秒/360秒で1日に実行できるスクリプトの回数は15回となり、1回約60件取得できるので理論上の1日で取得できる最大データ件数は60件×15回=900件となります。

後は利用用途に応じて1日に何回実行するか(=何時間毎に実行するか)を決めれば良いだけですね、僕の場合はtwitterにFANZAのサンプル動画を自動投稿させるbotを作りたかったので、1日に20-30件も新しい動画データが追加されていれば十分です、なので4時間毎に自動実行させるようにしました。

サンプルコード(コピペで動きます)

スクリプトの解説が長くなってしまいましたが、ここまでで完成したスクリプトのコピペコードと具体的な設置作業手順を、プログラミングの知識が無い方でも実現できるように実際の操作画面をスクショした24枚の画像を元に詳しく説明します。

導入手順

今回作成したFANZA動画の自動収集システムは次の3ステップで構築が可能です。

  1. googleスプレットシートの準備
  2. GASスクリプトをコピペ
  3. トリガーの設定

1回設置が完了すればサービスの仕様が変わらない限りデータを収集し続け、生産性の低い手作業からは解放されるのでコツコツやってみて下さい。

2022-07-03追記:当記事で公開しているコピペコードはFANZA側に対策されてしまったため、現在ではコピペで動かなくなっています。サンプル動画のURLパターンなどは参考になるので一応このままにしておきますが、代替えとなるデスクトップアプリを作りましたのでDMMのデータ取得にはこちらをご利用下さいm(_ _)m

----▽ ここから有料です ▽----

決済方法料金返金アカウント登録
note.com¥1,990 – 購入する24時間以内不要
paypal
¥1,990 – 購入する
いつでも必要

以降のコンテンツは閲覧パスワードを購入された方限定で公開しております。必要な方は上記の購入ボタンより各サイトにてお支払い手続きをお願いしますm(_ _)m

【返金保証付き】記事の内容に不満があれば全額返金します!

記事の内容に満足頂けない場合はお支払い頂いた料金をすべてお返しします。

  • noteよりご購入された場合:note.comにてご確認下さい。
  • paypalよりご購入された場合:お問い合わせより「購入日」と「paypalの取引ID」をご連絡下さい。

有料記事に関するNG行為

  • パスワードを第三者に譲渡する行為
  • 不正に入手したパスワードで有料記事を閲覧する行為

上記のルールを違反した場合は筆者が怒りますので、お気を付けください(゚Д゚;)