|
|
|
@@ -1,10 +1,10 @@ |
|
|
|
""" |
|
|
|
ニコニコ動画から動画情報を取得するためのヘルパ集 |
|
|
|
""" |
|
|
|
|
|
|
|
from __future__ import annotations |
|
|
|
|
|
|
|
import json |
|
|
|
import random |
|
|
|
import string |
|
|
|
import time |
|
|
|
from typing import Any, TypedDict |
|
|
|
from typing import TypedDict |
|
|
|
|
|
|
|
import requests |
|
|
|
from bs4 import BeautifulSoup |
|
|
|
@@ -14,6 +14,20 @@ from requests.exceptions import Timeout |
|
|
|
def fetch_video_info ( |
|
|
|
video_code: str, |
|
|
|
) -> VideoInfo | None: |
|
|
|
""" |
|
|
|
動画コードからタイトル、タグ、説明を取得して返す. |
|
|
|
|
|
|
|
Parameters |
|
|
|
---------- |
|
|
|
video_code: str |
|
|
|
動画コード |
|
|
|
|
|
|
|
Return |
|
|
|
------ |
|
|
|
VideoInfo | None |
|
|
|
動画情報 |
|
|
|
""" |
|
|
|
|
|
|
|
bs = _create_bs_from_url (f"https://www.nicovideo.jp/watch/{ video_code }") |
|
|
|
if bs is None: |
|
|
|
return None |
|
|
|
@@ -24,10 +38,12 @@ def fetch_video_info ( |
|
|
|
return None |
|
|
|
title = '-'.join (title_tag.text.split ('-')[:(-1)]).strip () |
|
|
|
|
|
|
|
tags_str: str = bs.find ('meta', attrs = { 'name': 'keywords' }).get ('content') # type: ignore |
|
|
|
# type: ignore |
|
|
|
tags_str: str = bs.find ('meta', attrs = { 'name': 'keywords' }).get ('content') |
|
|
|
tags = tags_str.split (',') |
|
|
|
|
|
|
|
description = bs.find ('meta', attrs = { 'name': 'description' }).get ('content') # type: ignore |
|
|
|
# type: ignore |
|
|
|
description = bs.find ('meta', attrs = { 'name': 'description' }).get ('content') |
|
|
|
except (AttributeError, TypeError): |
|
|
|
return None |
|
|
|
|
|
|
|
@@ -37,59 +53,26 @@ def fetch_video_info ( |
|
|
|
'description': str (description) } |
|
|
|
|
|
|
|
|
|
|
|
def fetch_comments ( |
|
|
|
video_code: str, |
|
|
|
) -> list[Comment]: |
|
|
|
time.sleep (1.2) |
|
|
|
|
|
|
|
headers = { 'X-Frontend-Id': '6', |
|
|
|
'X-Frontend-Version': '0' } |
|
|
|
|
|
|
|
action_track_id = ( |
|
|
|
''.join (random.choice (string.ascii_letters + string.digits) |
|
|
|
for _ in range (10)) |
|
|
|
+ '_' |
|
|
|
+ str (random.randrange (10 ** 12, 10 ** 13))) |
|
|
|
|
|
|
|
url = (f"https://www.nicovideo.jp/api/watch/v3_guest/{ video_code }" |
|
|
|
+ f"?actionTrackId={ action_track_id }") |
|
|
|
|
|
|
|
res = requests.post (url, headers = headers, timeout = 60).json () |
|
|
|
|
|
|
|
try: |
|
|
|
nv_comment = res['data']['comment']['nvComment'] |
|
|
|
except KeyError: |
|
|
|
return [] |
|
|
|
if nv_comment is None: |
|
|
|
return [] |
|
|
|
|
|
|
|
headers = { 'X-Frontend-Id': '6', |
|
|
|
'X-Frontend-Version': '0', |
|
|
|
'Content-Type': 'application/json' } |
|
|
|
|
|
|
|
params = { 'params': nv_comment['params'], |
|
|
|
'additionals': { }, |
|
|
|
'threadKey': nv_comment['threadKey'] } |
|
|
|
|
|
|
|
url = nv_comment['server'] + '/v1/threads' |
|
|
|
|
|
|
|
res = (requests.post (url, json.dumps (params), |
|
|
|
headers = headers, |
|
|
|
timeout = 60) |
|
|
|
.json ()) |
|
|
|
|
|
|
|
try: |
|
|
|
return res['data']['threads'][1]['comments'] |
|
|
|
except (IndexError, KeyError): |
|
|
|
return [] |
|
|
|
|
|
|
|
|
|
|
|
def fetch_embed_info ( |
|
|
|
url: str, |
|
|
|
) -> tuple[str, str, str]: |
|
|
|
title: str = '' |
|
|
|
description: str = '' |
|
|
|
thumbnail: str = '' |
|
|
|
""" |
|
|
|
ニコニコ動画の URL からタイトル、詳細、サムネールを取得して返す. |
|
|
|
|
|
|
|
Parameters |
|
|
|
---------- |
|
|
|
url: str |
|
|
|
動画 URL |
|
|
|
|
|
|
|
Return |
|
|
|
------ |
|
|
|
tuple[str, str, str] |
|
|
|
タイトル、詳細、サムネールからなるタプル |
|
|
|
""" |
|
|
|
|
|
|
|
title = '' |
|
|
|
description = '' |
|
|
|
thumbnail = '' |
|
|
|
|
|
|
|
bs = _create_bs_from_url (url) |
|
|
|
if bs is None: |
|
|
|
@@ -103,14 +86,14 @@ def fetch_embed_info ( |
|
|
|
if tmp is not None and hasattr (tmp, 'get'): |
|
|
|
try: |
|
|
|
description = str (tmp.get ('content')) |
|
|
|
except Exception: |
|
|
|
except (AttributeError, TypeError): |
|
|
|
pass |
|
|
|
|
|
|
|
tmp = bs.find ('meta', attrs = { 'name': 'thumbnail' }) |
|
|
|
if tmp is not None and hasattr (tmp, 'get'): |
|
|
|
try: |
|
|
|
thumbnail = str (tmp.get ('content')) |
|
|
|
except Exception: |
|
|
|
except (AttributeError, TypeError): |
|
|
|
pass |
|
|
|
|
|
|
|
return (title, description, thumbnail) |
|
|
|
@@ -119,6 +102,20 @@ def fetch_embed_info ( |
|
|
|
def fetch_latest_video ( |
|
|
|
tags: list[str], |
|
|
|
) -> VideoInfo | None: |
|
|
|
""" |
|
|
|
ニコニコから指定したタグが含まれる動画を検索し,最新のものを返す. |
|
|
|
|
|
|
|
Parameters |
|
|
|
---------- |
|
|
|
tags: list[str] |
|
|
|
タグ・リスト |
|
|
|
|
|
|
|
Return |
|
|
|
------ |
|
|
|
VideoInfo | None |
|
|
|
動画情報 |
|
|
|
""" |
|
|
|
|
|
|
|
tag = ' OR '.join (tags) |
|
|
|
url = f"https://www.nicovideo.jp/tag/{ tag }" |
|
|
|
|
|
|
|
@@ -136,7 +133,7 @@ def fetch_latest_video ( |
|
|
|
.find ('li', class_ = 'item')) |
|
|
|
|
|
|
|
video_info['contentId'] = video['data-video-id'] |
|
|
|
except Exception: |
|
|
|
except (AttributeError, IndexError, KeyError, TypeError): |
|
|
|
return None |
|
|
|
|
|
|
|
return fetch_video_info (video_info['contentId']) |
|
|
|
@@ -146,6 +143,22 @@ def _create_bs_from_url ( |
|
|
|
url: str, |
|
|
|
params: dict | None = None, |
|
|
|
) -> BeautifulSoup | None: |
|
|
|
""" |
|
|
|
URL から BeautifulSoup オブゼクトを作成する. |
|
|
|
|
|
|
|
Parameters |
|
|
|
---------- |
|
|
|
url: str |
|
|
|
URL |
|
|
|
params: dict | None |
|
|
|
パラメータ |
|
|
|
|
|
|
|
Return |
|
|
|
------ |
|
|
|
BeautifulSoup | None |
|
|
|
BeautifulSoup オブゼクト |
|
|
|
""" |
|
|
|
|
|
|
|
if params is None: |
|
|
|
params = { } |
|
|
|
|
|
|
|
@@ -163,23 +176,11 @@ def _create_bs_from_url ( |
|
|
|
|
|
|
|
|
|
|
|
class VideoInfo (TypedDict): |
|
|
|
""" |
|
|
|
動画情報 |
|
|
|
""" |
|
|
|
|
|
|
|
contentId: str |
|
|
|
title: str |
|
|
|
tags: list[str] |
|
|
|
description: str |
|
|
|
|
|
|
|
|
|
|
|
class Comment (TypedDict): |
|
|
|
id: str |
|
|
|
no: int |
|
|
|
vposMs: int |
|
|
|
body: str |
|
|
|
commands: list[str] |
|
|
|
userId: str |
|
|
|
isPremium: bool |
|
|
|
score: int |
|
|
|
postedAt: str |
|
|
|
nicoruCount: int |
|
|
|
nicoruId: Any |
|
|
|
source: str |
|
|
|
isMyPost: bool |