| @@ -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__': | ||||