完成(たぶん)
このコミットが含まれているのは:
+135
-19
@@ -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: コメント取得すること
|
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)
|
||||||
|
|
||||||
# TODO: 削除処理,存在しなぃ動画リストを取得した上で行ふこと
|
alive_video_ids = [d['contentId'] for d in api_data]
|
||||||
# video_dao.delete (video_ids, now)
|
|
||||||
|
|
||||||
# TODO: 書くこと
|
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,
|
video_code: str,
|
||||||
) -> list:
|
) -> 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__':
|
||||||
|
|||||||
新しい課題から参照
ユーザをブロックする