| @@ -1,3 +1,7 @@ | |||||
| """ | |||||
| 日次で実行し,ぼざクリ DB を最新に更新する. | |||||
| """ | |||||
| from __future__ import annotations | from __future__ import annotations | ||||
| import json | import json | ||||
| @@ -7,7 +11,7 @@ import string | |||||
| import time | import time | ||||
| from dataclasses import dataclass | from dataclasses import dataclass | ||||
| from datetime import datetime, timedelta | from datetime import datetime, timedelta | ||||
| from typing import TypedDict, cast | |||||
| from typing import Any, TypedDict, cast | |||||
| import mysql.connector | import mysql.connector | ||||
| import requests | import requests | ||||
| @@ -16,15 +20,6 @@ DbNull = (None,) | |||||
| DbNullType = tuple[None] | DbNullType = tuple[None] | ||||
| class VideoResult (TypedDict): | |||||
| contentId: str | |||||
| title: str | |||||
| tags: list[str] | |||||
| description: str | |||||
| viewCounter: int | |||||
| startTime: str | |||||
| class VideoSearchParam (TypedDict): | class VideoSearchParam (TypedDict): | ||||
| q: str | q: str | ||||
| targets: str | targets: str | ||||
| @@ -34,8 +29,29 @@ class VideoSearchParam (TypedDict): | |||||
| jsonFilter: str | jsonFilter: str | ||||
| class VideoResult (TypedDict): | |||||
| contentId: str | |||||
| title: str | |||||
| tags: list[str] | |||||
| description: str | |||||
| viewCounter: int | |||||
| startTime: str | |||||
| class CommentResult (TypedDict): | class CommentResult (TypedDict): | ||||
| pass | |||||
| 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 | |||||
| def main ( | def main ( | ||||
| @@ -50,10 +66,13 @@ def main ( | |||||
| tag_dao = TagDao (conn) | tag_dao = TagDao (conn) | ||||
| video_tag_dao = VideoTagDao (conn) | video_tag_dao = VideoTagDao (conn) | ||||
| video_history_dao = VideoHistoryDao (conn) | video_history_dao = VideoHistoryDao (conn) | ||||
| comment_dao = CommentDao (conn) | |||||
| user_dao = UserDao (conn) | |||||
| api_data = search_nico_by_tags (['伊地知ニジカ', 'ぼざろクリーチャーシリーズ']) | api_data = search_nico_by_tags (['伊地知ニジカ', 'ぼざろクリーチャーシリーズ']) | ||||
| update_tables (video_dao, tag_dao, video_tag_dao, video_history_dao, api_data, now) | |||||
| update_tables (video_dao, tag_dao, video_tag_dao, video_history_dao, comment_dao, user_dao, | |||||
| api_data, now) | |||||
| # TODO: 書くこと | # TODO: 書くこと | ||||
| @@ -63,9 +82,12 @@ def update_tables ( | |||||
| tag_dao: TagDao, | tag_dao: TagDao, | ||||
| video_tag_dao: VideoTagDao, | video_tag_dao: VideoTagDao, | ||||
| video_history_dao: VideoHistoryDao, | video_history_dao: VideoHistoryDao, | ||||
| comment_dao: CommentDao, | |||||
| user_dao: UserDao, | |||||
| api_data: list[VideoResult], | api_data: list[VideoResult], | ||||
| now: datetime, | now: datetime, | ||||
| ) -> None: | ) -> None: | ||||
| video_ids: list[int] = [] | |||||
| for datum in api_data: | for datum in api_data: | ||||
| video = VideoDto (code = datum['contentId'], | video = VideoDto (code = datum['contentId'], | ||||
| title = datum['title'], | title = datum['title'], | ||||
| @@ -73,6 +95,7 @@ def update_tables ( | |||||
| uploaded_at = datetime.fromisoformat (datum['startTime'])) | uploaded_at = datetime.fromisoformat (datum['startTime'])) | ||||
| video_dao.upsert (video, False) | video_dao.upsert (video, False) | ||||
| if video.id_ is not None: | if video.id_ is not None: | ||||
| video_ids.append (video.id_) | |||||
| video_history = VideoHistoryDto (video_id = video.id_, | video_history = VideoHistoryDto (video_id = video.id_, | ||||
| fetched_at = now, | fetched_at = now, | ||||
| views_count = datum['viewCounter']) | views_count = datum['viewCounter']) | ||||
| @@ -99,17 +122,37 @@ def update_tables ( | |||||
| tag_id = tag.id_, | tag_id = tag.id_, | ||||
| tagged_at = now) | tagged_at = now) | ||||
| video_tag_dao.insert (video_tag, False) | video_tag_dao.insert (video_tag, False) | ||||
| # TODO: コメント取得すること | |||||
| # TODO: 削除処理,存在しなぃ動画リストを取得した上で行ふこと | |||||
| # video_dao.delete (video_ids, now) | |||||
| # TODO: 書くこと | |||||
| for com in fetch_comments (video.code): | |||||
| user = user_dao.fetch_by_code (com['userId']) | |||||
| if user is None: | |||||
| user = UserDto (code = com['userId']) | |||||
| user_dao.insert (user) | |||||
| if video.id_ is not None and user.id_ is not None: | |||||
| comment = CommentDto (video_id = video.id_, | |||||
| comment_no = com['no'], | |||||
| user_id = user.id_, | |||||
| content = com['body'], | |||||
| posted_at = datetime.fromisoformat (com['postedAt']), | |||||
| nico_count = com['nicoruCount'], | |||||
| vpos_ms = com['vposMs']) | |||||
| comment_dao.upsert (comment, False) | |||||
| alive_video_ids = [d['contentId'] for d in api_data] | |||||
| lost_video_ids: list[int] = [] | |||||
| videos = video_dao.fetch_alive () | |||||
| for video in videos: | |||||
| if video.id_ is not None and video.id_ not in alive_video_ids: | |||||
| lost_video_ids.append (video.id_) | |||||
| video_dao.delete (lost_video_ids, now) | |||||
| def fetch_comments ( | def fetch_comments ( | ||||
| video_id: str, | |||||
| ) -> list: | |||||
| video_code: str, | |||||
| ) -> list[CommentResult]: | |||||
| time.sleep (1.2) | |||||
| headers = { 'X-Frontend-Id': '6', | headers = { 'X-Frontend-Id': '6', | ||||
| 'X-Frontend-Version': '0' } | 'X-Frontend-Version': '0' } | ||||
| @@ -119,7 +162,7 @@ def fetch_comments ( | |||||
| + '_' | + '_' | ||||
| + str (random.randrange (10 ** 12, 10 ** 13))) | + str (random.randrange (10 ** 12, 10 ** 13))) | ||||
| url = (f"https://www.nicovideo.jp/api/watch/v3_guest/{ video_id }" | |||||
| url = (f"https://www.nicovideo.jp/api/watch/v3_guest/{ video_code }" | |||||
| + f"?actionTrackId={ action_track_id }") | + f"?actionTrackId={ action_track_id }") | ||||
| res = requests.post (url, headers = headers, timeout = 60).json () | res = requests.post (url, headers = headers, timeout = 60).json () | ||||
| @@ -253,6 +296,27 @@ 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 fetch_alive ( | |||||
| self, | |||||
| ) -> list[VideoDto]: | |||||
| with self.conn.cursor () as c: | |||||
| c.execute (""" | |||||
| SELECT | |||||
| id, | |||||
| code, | |||||
| title, | |||||
| description, | |||||
| uploaded_at, | |||||
| deleted_at | |||||
| FROM | |||||
| videos | |||||
| WHERE | |||||
| deleted_at IS NULL""") | |||||
| videos: list[VideoDto] = [] | |||||
| for row in c.fetchall (): | |||||
| videos.append (self._create_dto_from_row (row, False)) | |||||
| return videos | |||||
| def upsert ( | def upsert ( | ||||
| self, | self, | ||||
| video: VideoDto, | video: VideoDto, | ||||
| @@ -836,6 +900,58 @@ class CommentDto: | |||||
| nico_count: int = 0 | nico_count: int = 0 | ||||
| vpos_ms: int | DbNullType = DbNull | vpos_ms: int | DbNullType = DbNull | ||||
| video: VideoDto | None = None | video: VideoDto | None = None | ||||
| user: UserDto | None = None | |||||
| class UserDao: | |||||
| def __init__ ( | |||||
| self, | |||||
| conn, | |||||
| ): | |||||
| self.conn = conn | |||||
| def fetch_by_code ( | |||||
| self, | |||||
| user_code: str | |||||
| ) -> UserDto | None: | |||||
| with self.conn.cursor () as c: | |||||
| c.execute (""" | |||||
| SELECT | |||||
| id, | |||||
| code | |||||
| FROM | |||||
| users | |||||
| WHERE | |||||
| code = %s""", user_code) | |||||
| row = c.fetchone () | |||||
| if row is None: | |||||
| return None | |||||
| return self._create_dto_from_row (row) | |||||
| def insert ( | |||||
| self, | |||||
| user: UserDto, | |||||
| ) -> None: | |||||
| with self.conn.cursor () as c: | |||||
| c.execute (""" | |||||
| INSERT INTO | |||||
| users(code) | |||||
| VALUES | |||||
| (%s)""", user.code) | |||||
| user.id_ = c.lastrowid | |||||
| def _create_dto_from_row ( | |||||
| self, | |||||
| row, | |||||
| ) -> UserDto: | |||||
| return UserDto (id_ = row['id'], | |||||
| code = row['code']) | |||||
| @dataclass (slots = True) | |||||
| class UserDto: | |||||
| code: str | |||||
| id_: int | None = None | |||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||