ぼざろクリーチャーシリーズ DB 兼 API(自分用)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

update_db.py 8.4 KiB

10 hours ago
10 hours ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. import json
  2. import os
  3. import random
  4. import string
  5. import time
  6. from dataclasses import dataclass
  7. from datetime import datetime
  8. import mysql.connector
  9. import requests
  10. def main (
  11. ) -> None:
  12. conn = mysql.connector.connect (host = os.environ['MYSQL_HOST'],
  13. user = os.environ['MYSQL_USER'],
  14. password = os.environ['MYSQL_PASS'])
  15. video_dao = VideoDao (conn)
  16. api_data = search_nico_by_tags (['伊地知ニジカ', 'ぼざろクリーチャーシリーズ'])
  17. update_video_table (video_dao, api_data)
  18. # TODO: 書くこと
  19. def update_video_table (
  20. video_dao,
  21. api_data: list[dict],
  22. ) -> None:
  23. # TODO: 書くこと
  24. video_dao.upsert_all (videos)
  25. video_dao.delete_nonexistent_data (video_id_list)
  26. # TODO: 書くこと
  27. def fetch_comments (
  28. video_id: str,
  29. ) -> list:
  30. headers = { 'X-Frontend-Id': '6',
  31. 'X-Frontend-Version': '0' }
  32. action_track_id = (
  33. ''.join (random.choice (string.ascii_letters + string.digits)
  34. for _ in range (10))
  35. + '_'
  36. + str (random.randrange (10 ** 12, 10 ** 13)))
  37. url = (f"https://www.nicovideo.jp/api/watch/v3_guest/{ video_id }"
  38. + f"?actionTrackId={ action_track_id }")
  39. res = requests.post (url, headers = headers, timeout = 60).json ()
  40. try:
  41. nv_comment = res['data']['comment']['nvComment']
  42. except KeyError:
  43. return []
  44. if nv_comment is None:
  45. return []
  46. headers = { 'X-Frontend-Id': '6',
  47. 'X-Frontend-Version': '0',
  48. 'Content-Type': 'application/json' }
  49. params = { 'params': nv_comment['params'],
  50. 'additionals': { },
  51. 'threadKey': nv_comment['threadKey'] }
  52. url = nv_comment['server'] + '/v1/threads'
  53. res = (requests.post (url, json.dumps (params),
  54. headers = headers,
  55. timeout = 60)
  56. .json ())
  57. try:
  58. return res['data']['threads'][1]['comments']
  59. except (IndexError, KeyError):
  60. return []
  61. def search_nico_by_tag (
  62. tag: str,
  63. ) -> list[dict]:
  64. return search_nico_by_tags ([tag])
  65. def search_nico_by_tags (
  66. tags: list[str],
  67. ) -> list[dict]:
  68. url = ('https://snapshot.search.nicovideo.jp'
  69. + '/api/v2/snapshot/video/contents/search')
  70. # TODO: 年月日の設定ができてゐなぃのと,100 件までしか取得できなぃので何とかすること
  71. query_filter = json.dumps ({ 'type': 'or',
  72. 'filters': [
  73. { 'type': 'range',
  74. 'field': 'startTime',
  75. 'from': f"{year}-{start}T00:00:00+09:00",
  76. 'to': f"{year}-{end}T23:59:59+09:00",
  77. 'include_lower': True }] })
  78. params: dict[str, int | str]
  79. params = { 'q': ' OR '.join (tags),
  80. 'targets': 'tagsExact',
  81. '_sort': '-viewCounter',
  82. 'fields': 'contentId,title,tags,viewCounter,startTime',
  83. '_limit': 100,
  84. 'jsonFilter': query_filter }
  85. res = requests.get (url, params = params, timeout = 60).json ()
  86. return res['data']
  87. class VideoDao:
  88. def __init__ (
  89. self,
  90. conn
  91. ):
  92. self.conn = conn
  93. def fetch (
  94. self,
  95. video_id: int,
  96. with_relation_tables: bool = True,
  97. ) -> VideoDto | None:
  98. with self.conn.cursor () as c:
  99. c.execute ("""
  100. SELECT
  101. id,
  102. code,
  103. title,
  104. description,
  105. uploaded_at,
  106. deleted_at
  107. FROM
  108. videos
  109. WHERE
  110. id = %s
  111. ORDER BY
  112. id""", video_id)
  113. row = c.fetchone ()
  114. if row is None:
  115. return None
  116. return self._create_dto_from_row (row, with_relation_tables)
  117. def fetch_all (
  118. self,
  119. with_relation_tables: bool = True,
  120. ) -> list[VideoDto]:
  121. with self.conn.cursor () as c:
  122. c.execute ("""
  123. SELECT
  124. id,
  125. code,
  126. title,
  127. description,
  128. uploaded_at,
  129. deleted_at
  130. FROM
  131. videos
  132. ORDER BY
  133. id""")
  134. videos: list[VideoDto] = []
  135. for row in c.fetchall ():
  136. videos.append (self._create_dto_from_row (row, with_relation_tables))
  137. return videos
  138. def _create_dto_from_row (
  139. self,
  140. row,
  141. with_relation_tables: bool,
  142. ) -> VideoDto:
  143. video = VideoDto (id_ = row['id'],
  144. code = row['code'],
  145. title = row['title'],
  146. description = row['description'],
  147. uploaded_at = row['uploaded_at'],
  148. deleted_at = row['deleted_at'],
  149. video_tags = None,
  150. comments = None,
  151. video_histories = None)
  152. if with_relation_tables:
  153. video.video_tags = VideoTagDao (self.conn).fetch_by_video_id (video.id_, False)
  154. for i in range (len (video.video_tags)):
  155. video.video_tags[i].video = video
  156. video.comments = CommentDao (self.conn).fetch_by_video_id (video.id_, False)
  157. for i in range (len (video.comments)):
  158. video.comments[i].video = video
  159. video.video_histories = VideoHistoryDao (self.conn).fetch_by_video_id (video.id_, False)
  160. for i in range (len (video.video_histories)):
  161. video.video_histories[i].video = video
  162. return video
  163. @dataclass (slots = True)
  164. class VideoDto:
  165. id_: int
  166. code: str
  167. title: str
  168. description: str
  169. uploaded_at: datetime
  170. deleted_at: datetime | None
  171. video_tags: list[VideoTagDto] | None
  172. comments: list[CommentDto] | None
  173. video_histories: list[VideoHistoryDto] | None
  174. class VideoTagDao:
  175. def __init__ (
  176. self,
  177. conn,
  178. ):
  179. self.conn = conn
  180. def fetch_by_video_id (
  181. self,
  182. video_id: int,
  183. with_relation_tables: bool = True,
  184. ) -> list[VideoTagDto]:
  185. with self.conn.cursor () as c:
  186. c.execute ("""
  187. SELECT
  188. id,
  189. video_id,
  190. tag_id,
  191. tagged_at,
  192. untagged_at
  193. FROM
  194. video_tags
  195. WHERE
  196. video_id = %s
  197. ORDER BY
  198. id""", video_id)
  199. video_tags: list[VideoTagDto] = []
  200. for row in c.fetchall ():
  201. video_tags.append (self._create_dto_from_row (row, with_relation_tables))
  202. return video_tags
  203. def _create_dto_from_row (
  204. self,
  205. row,
  206. with_relation_tables: bool,
  207. ) -> VideoTagDto:
  208. video_tag = VideoTagDto (id_ = row['id'],
  209. video_id = row['video_id'],
  210. tag_id = row['tag_id'],
  211. tagged_at = row['tagged_at'],
  212. untagged_at = row['untagged_at'],
  213. video = None,
  214. tag = None)
  215. if with_relation_tables:
  216. video_tag.video = VideoDao (self.conn).fetch (video_tag.video_id, True)
  217. video_tag.tag = TagDao (self.conn).fetch (video_tag.tag_id, True)
  218. return video_tag
  219. @dataclass (slots = True)
  220. class VideoTagDto:
  221. id_: int
  222. video_id: int
  223. tag_id: int
  224. tagged_at: datetime
  225. untagged_at: datetime
  226. video: VideoDto
  227. tag: TagDto
  228. if __name__ == '__main__':
  229. main ()