ニコニコ動画で指定したタグを持つ動画の一覧を取得してcsvで記録、サムネイル画像も併せて取得するpythonスクリプト

タイトルの通りです。主に(私のような)ニコニコ動画で投稿祭主催した後にまとめ動画などを作るための補助ツールです。だいたい最近のpython環境なら動くと思います。

公開されているスクップショットAPIと非公開のAPI併用しているため、動作保証はないですが、たぶんそうそう変更することはないと思います。
参考情報 :
ニコニコ動画 『スナップショット検索API v2』 ガイド
https://site.nicovideo.jp/search-api-docs/snapshot.html

なお、実行前に
search_query = "フリモメン誕生祭2025"
の部分を検索するタグ、
DOWNLOAD_THUMBNAILS = Falseの部分をサムネイル取得する場合はTrueに修正してください。

import requests
import xml.etree.ElementTree as ET
import pandas as pd
import time
import os
from bs4 import BeautifulSoup
from datetime import datetime

# ニコニコ動画スナップショット検索API v2のエンドポイント
SEARCH_API_URL = "https://snapshot.search.nicovideo.jp/api/v2/snapshot/video/contents/search"
# ニコニコ動画の詳細情報API(タグロックの取得用)
THUMBINFO_API_URL = "https://ext.nicovideo.jp/api/getthumbinfo/"
# ニコニコ動画の動画ページURLのプレフィックス
VIDEO_PAGE_URL = "https://www.nicovideo.jp/watch/"

# サムネイル画像のダウンロードを有効にするかどうか(True: 取得する, False: 取得しない)
DOWNLOAD_THUMBNAILS = False

# 検索クエリの設定(CSVファイル名にも使用)
search_query = "フリモメン誕生祭2025"

# 検索クエリの共通パラメータ(ジャンル情報を含む)
base_params = {
    "q": search_query,
    "targets": "tagsExact",  # 完全一致検索
    "fields": "contentId,title,description,tags,startTime,genre",
    "_sort": "+startTime",
    "_limit": 50,
    "_context": "my_app"
}

# サムネイル画像の保存フォルダ
THUMBNAIL_FOLDER = "thumbnails"
os.makedirs(THUMBNAIL_FOLDER, exist_ok=True)

all_videos = []
offset = 0

# 動画一覧の取得
def fetch_video_list():
    global offset
    try:
        while True:
            params = {**base_params, "_offset": offset}
            response = requests.get(SEARCH_API_URL, params=params)
            response.raise_for_status()
            
            data = response.json()
            videos = data.get("data", [])

            all_videos.extend(videos)

            total_count = data.get("meta", {}).get("totalCount", 0)
            print(f"現在の取得件数: {len(all_videos)} / 合計: {total_count}")

            offset += len(videos)

            if offset >= total_count or not videos:
                break

    except requests.exceptions.RequestException as e:
        print(f"動画一覧の取得中にエラーが発生しました: {e}")

# OGPタグから高画質サムネイルを取得
def fetch_high_quality_thumbnail(content_id):
    try:
        video_url = f"{VIDEO_PAGE_URL}{content_id}"
        response = requests.get(video_url)
        response.raise_for_status()

        soup = BeautifulSoup(response.text, 'html.parser')
        og_image_tag = soup.find("meta", property="og:image")
        if og_image_tag and "content" in og_image_tag.attrs:
            return og_image_tag["content"]
        else:
            print(f"高画質サムネイルが見つかりません: {content_id}")
            return None
    except requests.exceptions.RequestException as e:
        print(f"高画質サムネイル取得エラー: {content_id} - {e}")
        return None

# タグロックの詳細情報を取得
def fetch_tag_details(content_id):
    try:
        response = requests.get(f"{THUMBINFO_API_URL}{content_id}")
        response.raise_for_status()
        root = ET.fromstring(response.content)

        thumb = root.find("thumb")
        if thumb is None:
            return [], []

        tags_element = thumb.find("tags")
        if tags_element is None:
            return [], []

        tag_names = []
        tag_locks = {}

        for tag in tags_element.findall("tag"):
            tag_name = tag.text.strip()
            is_locked = "ロック済み" if tag.attrib.get("lock") == "1" else "ロックなし"
            tag_names.append(tag_name)
            tag_locks[tag_name] = is_locked

        return tag_names, tag_locks

    except requests.exceptions.RequestException as e:
        print(f"タグ情報取得エラー: {content_id} - {e}")
        return [], {}
    except ET.ParseError:
        print(f"XML解析エラー: {content_id}")
        return [], {}

# サムネイル画像をダウンロードして保存
def download_thumbnail(url, content_id):
    if not DOWNLOAD_THUMBNAILS or not url:
        return None
    
    try:
        response = requests.get(url, stream=True)
        response.raise_for_status()

        filename = f"{THUMBNAIL_FOLDER}/{content_id}_high.jpg"
        with open(filename, 'wb') as file:
            for chunk in response.iter_content(1024):
                file.write(chunk)

        print(f"サムネイル保存完了: {filename}")
        return filename

    except requests.exceptions.RequestException as e:
        print(f"サムネイル取得エラー: {url} - {e}")
        return None

# 各動画の詳細情報を取得し、タグ情報も処理
def fetch_video_details(video):
    content_id = video['contentId']

    # タグロック情報を取得
    tag_names, tag_locks = fetch_tag_details(content_id)

    # タグ情報を最大10個まで取得し、それ以外は空白にする
    tag_values = tag_names[:10] + [""] * (10 - len(tag_names))
    tag_lock_values = [tag_locks.get(tag, "ロックなし") for tag in tag_values]

    # 検索タグがロックされているかの確認
    tag_lock_status = "Yes" if search_query in tag_locks and tag_locks[search_query] == "ロック済み" else "No"

    # ジャンル情報の取得(無い場合は空欄)
    genre = video.get('genre', 'なし')

    # 高画質サムネイルの取得とダウンロード
    high_quality_thumbnail_url = fetch_high_quality_thumbnail(content_id)
    high_quality_thumbnail_path = download_thumbnail(high_quality_thumbnail_url, content_id)

    # タグとロック情報を交互に配置
    combined_tags = {f"tag_{i+1}": tag_values[i] for i in range(10)}
    combined_tags.update({f"tag_{i+1}_lock": tag_lock_values[i] for i in range(10)})

    return {
        "contentId": content_id,
        "title": video['title'],
        "description": video['description'],
        "startTime": video['startTime'],
        "genre": genre,
        "high_quality_thumbnail_url": high_quality_thumbnail_url,
        "high_quality_thumbnail_path": high_quality_thumbnail_path,
        "タグロック済み": tag_lock_status,
        **combined_tags
    }

# 処理開始
fetch_video_list()

# 取得した動画リストから詳細情報を取得し、統合
video_data = []
for video in all_videos:
    video_info = fetch_video_details(video)
    time.sleep(1)
    video_data.append(video_info)
    print(f"取得完了: {video_info['contentId']}")

# pandasデータフレームに変換
df = pd.DataFrame(video_data)

# CSVファイル名の生成
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
csv_filename = f"{search_query}_video_details_{timestamp}.csv"

# CSVファイルに保存
df.to_csv(csv_filename, index=False, encoding="utf-8-sig")

print(f"CSVファイル '{csv_filename}' に保存されました。")

結果として、thumbnailsフォルダ以下にサムネイルが、実行したパスに取得できた動画情報が格納されたcsvファイルが生成されます。
(実行時の年月日時分秒とっているので連続実行してもcsvは都度新しくなります。)

ざっくり実行結果はこんな感じ。
動画のID(SMなんとか)、タイトル、説明、ジャンル、サムネイルURLとタグロック(ここがYesだと検索に利用したタグがロックされている)

タグロックの情報は動画投稿祭はタグロックされて(=投稿者が明確にタグロックして参加意思を示している)いるかの情報なので、ここがNoだと投稿者の方以外が勝手にタグ付けている可能性あります。



以上



いいなと思ったら応援しよう!