|
@@ -5,31 +5,72 @@ import string |
|
|
import time |
|
|
import time |
|
|
from dataclasses import dataclass |
|
|
from dataclasses import dataclass |
|
|
from datetime import datetime |
|
|
from datetime import datetime |
|
|
|
|
|
from typing import TypedDict, cast |
|
|
|
|
|
|
|
|
import mysql.connector |
|
|
import mysql.connector |
|
|
import requests |
|
|
import requests |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DbNull = (None,) |
|
|
|
|
|
DbNullType = tuple[None] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VideoResult (TypedDict): |
|
|
|
|
|
contentId: str |
|
|
|
|
|
title: str |
|
|
|
|
|
tags: list[str] |
|
|
|
|
|
description: str |
|
|
|
|
|
viewCounter: int |
|
|
|
|
|
startTime: str |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VideoSearchParam (TypedDict): |
|
|
|
|
|
q: str |
|
|
|
|
|
targets: str |
|
|
|
|
|
_sort: str |
|
|
|
|
|
fields: str |
|
|
|
|
|
_limit: int |
|
|
|
|
|
jsonFilter: str |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main ( |
|
|
def main ( |
|
|
) -> None: |
|
|
) -> None: |
|
|
conn = mysql.connector.connect (host = os.environ['MYSQL_HOST'], |
|
|
conn = mysql.connector.connect (host = os.environ['MYSQL_HOST'], |
|
|
user = os.environ['MYSQL_USER'], |
|
|
user = os.environ['MYSQL_USER'], |
|
|
password = os.environ['MYSQL_PASS']) |
|
|
password = os.environ['MYSQL_PASS']) |
|
|
|
|
|
|
|
|
|
|
|
now = datetime.now () |
|
|
|
|
|
|
|
|
video_dao = VideoDao (conn) |
|
|
video_dao = VideoDao (conn) |
|
|
|
|
|
tag_dao = TagDao (conn) |
|
|
|
|
|
|
|
|
api_data = search_nico_by_tags (['伊地知ニジカ', 'ぼざろクリーチャーシリーズ']) |
|
|
api_data = search_nico_by_tags (['伊地知ニジカ', 'ぼざろクリーチャーシリーズ']) |
|
|
|
|
|
|
|
|
update_video_table (video_dao, api_data) |
|
|
|
|
|
|
|
|
update_video_and_tag_table (video_dao, tag_dao, api_data, now) |
|
|
|
|
|
|
|
|
# TODO: 書くこと |
|
|
# TODO: 書くこと |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def update_video_table ( |
|
|
|
|
|
|
|
|
def update_video_and_tag_table ( |
|
|
video_dao: VideoDao, |
|
|
video_dao: VideoDao, |
|
|
api_data: list[dict], |
|
|
|
|
|
|
|
|
tag_dao: TagDao, |
|
|
|
|
|
api_data: list[VideoResult], |
|
|
|
|
|
now: datetime, |
|
|
) -> None: |
|
|
) -> None: |
|
|
# TODO: videos 取得書くこと |
|
|
|
|
|
|
|
|
videos: list[VideoDto] = [] |
|
|
|
|
|
for datum in api_data: |
|
|
|
|
|
tags: list[TagDto] = [] |
|
|
|
|
|
for tag_name in datum['tags']: |
|
|
|
|
|
tags.append (TagDto (name = tag_name)) |
|
|
|
|
|
tag_dao.upsert_all (tags) |
|
|
|
|
|
# TODO: タグの対応づけすること |
|
|
|
|
|
# TODO: コメント取得すること |
|
|
|
|
|
video = VideoDto (code = datum['contentId'], |
|
|
|
|
|
title = datum['title'], |
|
|
|
|
|
description = datum['description'], |
|
|
|
|
|
uploaded_at = datum['startTime'], |
|
|
|
|
|
deleted_at = DbNull) |
|
|
|
|
|
videos.append (video) |
|
|
|
|
|
|
|
|
video_dao.upsert_all (videos) |
|
|
video_dao.upsert_all (videos) |
|
|
|
|
|
|
|
@@ -85,13 +126,13 @@ def fetch_comments ( |
|
|
|
|
|
|
|
|
def search_nico_by_tag ( |
|
|
def search_nico_by_tag ( |
|
|
tag: str, |
|
|
tag: str, |
|
|
) -> list[dict]: |
|
|
|
|
|
|
|
|
) -> list[VideoResult]: |
|
|
return search_nico_by_tags ([tag]) |
|
|
return search_nico_by_tags ([tag]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def search_nico_by_tags ( |
|
|
def search_nico_by_tags ( |
|
|
tags: list[str], |
|
|
tags: list[str], |
|
|
) -> list[dict]: |
|
|
|
|
|
|
|
|
) -> list[VideoResult]: |
|
|
url = ('https://snapshot.search.nicovideo.jp' |
|
|
url = ('https://snapshot.search.nicovideo.jp' |
|
|
+ '/api/v2/snapshot/video/contents/search') |
|
|
+ '/api/v2/snapshot/video/contents/search') |
|
|
|
|
|
|
|
@@ -105,15 +146,15 @@ def search_nico_by_tags ( |
|
|
'to': f"{year}-{end}T23:59:59+09:00", |
|
|
'to': f"{year}-{end}T23:59:59+09:00", |
|
|
'include_lower': True }] }) |
|
|
'include_lower': True }] }) |
|
|
|
|
|
|
|
|
params: dict[str, int | str] |
|
|
|
|
|
|
|
|
params: VideoSearchParam |
|
|
params = { 'q': ' OR '.join (tags), |
|
|
params = { 'q': ' OR '.join (tags), |
|
|
'targets': 'tagsExact', |
|
|
'targets': 'tagsExact', |
|
|
'_sort': '-viewCounter', |
|
|
'_sort': '-viewCounter', |
|
|
'fields': 'contentId,title,tags,viewCounter,startTime', |
|
|
|
|
|
|
|
|
'fields': 'contentId,title,tags,description,viewCounter,startTime', |
|
|
'_limit': 100, |
|
|
'_limit': 100, |
|
|
'jsonFilter': query_filter } |
|
|
'jsonFilter': query_filter } |
|
|
|
|
|
|
|
|
res = requests.get (url, params = params, timeout = 60).json () |
|
|
|
|
|
|
|
|
res = requests.get (url, params = cast (dict[str, int | str], params), timeout = 60).json () |
|
|
|
|
|
|
|
|
return res['data'] |
|
|
return res['data'] |
|
|
|
|
|
|
|
@@ -172,6 +213,43 @@ class VideoDao: |
|
|
videos.append (self._create_dto_from_row (row, with_relation_tables)) |
|
|
videos.append (self._create_dto_from_row (row, with_relation_tables)) |
|
|
return videos |
|
|
return videos |
|
|
|
|
|
|
|
|
|
|
|
def upsert ( |
|
|
|
|
|
self, |
|
|
|
|
|
video: VideoDto, |
|
|
|
|
|
with_relation_tables: bool = True, |
|
|
|
|
|
) -> None: |
|
|
|
|
|
with self.conn.cursor () as c: |
|
|
|
|
|
c.execute (""" |
|
|
|
|
|
INSERT INTO |
|
|
|
|
|
videos( |
|
|
|
|
|
code, |
|
|
|
|
|
title, |
|
|
|
|
|
description, |
|
|
|
|
|
uploaded_at, |
|
|
|
|
|
deleted_at) |
|
|
|
|
|
VALUES |
|
|
|
|
|
( |
|
|
|
|
|
%s, |
|
|
|
|
|
%s, |
|
|
|
|
|
%s, |
|
|
|
|
|
%s, |
|
|
|
|
|
%s) |
|
|
|
|
|
ON DUPLICATE KEY UPDATE |
|
|
|
|
|
code = VALUES(code), |
|
|
|
|
|
title = VALUES(title), |
|
|
|
|
|
description = VALUES(description), |
|
|
|
|
|
uploaded_at = VALUES(uploaded_at), |
|
|
|
|
|
deleted_at = VALUES(deleted_at)""", (video.code, |
|
|
|
|
|
video.title, |
|
|
|
|
|
video.description, |
|
|
|
|
|
video.uploaded_at, |
|
|
|
|
|
video.deleted_at)) |
|
|
|
|
|
video.id_ = c.lastrowid |
|
|
|
|
|
if with_relation_tables: |
|
|
|
|
|
VideoTagDao (self.conn).upsert_all (video.video_tags, False) |
|
|
|
|
|
CommentDao (self.conn).upsert_all (video.comments, False) |
|
|
|
|
|
VideoHistoryDao (self.conn).upsert_all (video.video_histories, False) |
|
|
|
|
|
|
|
|
def upsert_all ( |
|
|
def upsert_all ( |
|
|
self, |
|
|
self, |
|
|
videos: list[VideoDto], |
|
|
videos: list[VideoDto], |
|
@@ -179,36 +257,7 @@ class VideoDao: |
|
|
) -> None: |
|
|
) -> None: |
|
|
with self.conn.cursor () as c: |
|
|
with self.conn.cursor () as c: |
|
|
for video in videos: |
|
|
for video in videos: |
|
|
c.execute (""" |
|
|
|
|
|
INSERT INTO |
|
|
|
|
|
videos( |
|
|
|
|
|
code, |
|
|
|
|
|
title, |
|
|
|
|
|
description, |
|
|
|
|
|
uploaded_at, |
|
|
|
|
|
deleted_at) |
|
|
|
|
|
VALUES |
|
|
|
|
|
( |
|
|
|
|
|
%s, |
|
|
|
|
|
%s, |
|
|
|
|
|
%s, |
|
|
|
|
|
%s, |
|
|
|
|
|
%s) |
|
|
|
|
|
ON DUPLICATE KEY UPDATE |
|
|
|
|
|
code = VALUES(code), |
|
|
|
|
|
title = VALUES(title), |
|
|
|
|
|
description = VALUES(description), |
|
|
|
|
|
uploaded_at = VALUES(uploaded_at), |
|
|
|
|
|
deleted_at = VALUES(deleted_at)""", (video.code, |
|
|
|
|
|
video.title, |
|
|
|
|
|
video.description, |
|
|
|
|
|
video.uploaded_at, |
|
|
|
|
|
video.deleted_at)) |
|
|
|
|
|
video.id_ = c.lastrowid |
|
|
|
|
|
if with_relation_tables: |
|
|
|
|
|
VideoTagDao (self.conn).upsert_all (video.video_tags, False) |
|
|
|
|
|
CommentDao (self.conn).upsert_all (video.comments, False) |
|
|
|
|
|
VideoHistoryDao (self.conn).upsert_all (video.video_histories, False) |
|
|
|
|
|
|
|
|
self.upsert (video, with_relation_tables) |
|
|
|
|
|
|
|
|
def _create_dto_from_row ( |
|
|
def _create_dto_from_row ( |
|
|
self, |
|
|
self, |
|
@@ -222,7 +271,7 @@ class VideoDao: |
|
|
uploaded_at = row['uploaded_at'], |
|
|
uploaded_at = row['uploaded_at'], |
|
|
deleted_at = row['deleted_at']) |
|
|
deleted_at = row['deleted_at']) |
|
|
if with_relation_tables: |
|
|
if with_relation_tables: |
|
|
video.video_tags = VideoTagDao (self.conn).fetch_by_video_id (video.id_, False) |
|
|
|
|
|
|
|
|
video.video_tags = VideoTagDao (self.conn).fetch_by_video_id (cast (int, video.id_), False) |
|
|
for i in range (len (video.video_tags)): |
|
|
for i in range (len (video.video_tags)): |
|
|
video.video_tags[i].video = video |
|
|
video.video_tags[i].video = video |
|
|
video.comments = CommentDao (self.conn).fetch_by_video_id (video.id_, False) |
|
|
video.comments = CommentDao (self.conn).fetch_by_video_id (video.id_, False) |
|
@@ -236,12 +285,12 @@ class VideoDao: |
|
|
|
|
|
|
|
|
@dataclass (slots = True) |
|
|
@dataclass (slots = True) |
|
|
class VideoDto: |
|
|
class VideoDto: |
|
|
id_: int | None = None |
|
|
|
|
|
code: str |
|
|
code: str |
|
|
title: str |
|
|
title: str |
|
|
description: str |
|
|
description: str |
|
|
uploaded_at: datetime |
|
|
uploaded_at: datetime |
|
|
deleted_at: datetime | None = None |
|
|
|
|
|
|
|
|
id_: int | None = None |
|
|
|
|
|
deleted_at: datetime | DbNullType = DbNull |
|
|
video_tags: list[VideoTagDto] | None = None |
|
|
video_tags: list[VideoTagDto] | None = None |
|
|
comments: list[CommentDto] | None = None |
|
|
comments: list[CommentDto] | None = None |
|
|
video_histories: list[VideoHistoryDto] | None = None |
|
|
video_histories: list[VideoHistoryDto] | None = None |
|
@@ -296,13 +345,32 @@ class VideoTagDao: |
|
|
|
|
|
|
|
|
@dataclass (slots = True) |
|
|
@dataclass (slots = True) |
|
|
class VideoTagDto: |
|
|
class VideoTagDto: |
|
|
id_: int | None = None |
|
|
|
|
|
video_id: int |
|
|
|
|
|
tag_id: int |
|
|
|
|
|
tagged_at: datetime |
|
|
tagged_at: datetime |
|
|
untagged_at: datetime |
|
|
|
|
|
video: VideoDto | None = None |
|
|
|
|
|
tag: TagDto | None = None |
|
|
|
|
|
|
|
|
id_: int | None = None |
|
|
|
|
|
video_id: int | None = None |
|
|
|
|
|
tag_id: int | None = None |
|
|
|
|
|
untagged_at: datetime | DbNullType = DbNull |
|
|
|
|
|
video: VideoDto | None = None |
|
|
|
|
|
tag: TagDto | None = None |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass (slots = True) |
|
|
|
|
|
class TagDto: |
|
|
|
|
|
name: str |
|
|
|
|
|
id_: int | None = None |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass (slots = True) |
|
|
|
|
|
class CommentDto: |
|
|
|
|
|
video_id: int |
|
|
|
|
|
comment_no: int |
|
|
|
|
|
user_id: int |
|
|
|
|
|
content: str |
|
|
|
|
|
posted_at: datetime |
|
|
|
|
|
id_: int | None = None |
|
|
|
|
|
nico_count: int = 0 |
|
|
|
|
|
vpos_ms: int | DbNullType = DbNull |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__': |
|
|
if __name__ == '__main__': |
|
|