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.

187 lines
4.1 KiB

  1. """
  2. ニコニコ動画から動画情報を取得するためのヘルパ集
  3. """
  4. from __future__ import annotations
  5. from typing import TypedDict
  6. import requests
  7. from bs4 import BeautifulSoup
  8. from requests.exceptions import Timeout
  9. def fetch_video_info (
  10. video_code: str,
  11. ) -> VideoInfo | None:
  12. """
  13. 動画コードからタイトル、タグ、説明を取得して返す.
  14. Parameters
  15. ----------
  16. video_code: str
  17. 動画コード
  18. Return
  19. ------
  20. VideoInfo | None
  21. 動画情報
  22. """
  23. bs = _create_bs_from_url (f"https://www.nicovideo.jp/watch/{ video_code }")
  24. if bs is None:
  25. return None
  26. try:
  27. title_tag = bs.find ('title')
  28. if title_tag is None:
  29. return None
  30. title = '-'.join (title_tag.text.split ('-')[:(-1)]).strip ()
  31. tags_str: str = (bs.find ('meta', attrs = { 'name': 'keywords' })
  32. .get ('content')) # type: ignore
  33. tags = tags_str.split (',')
  34. description = (bs.find ('meta', attrs = { 'name': 'description' })
  35. .get ('content')) # type: ignore
  36. except (AttributeError, TypeError):
  37. return None
  38. return { 'contentId': video_code,
  39. 'title': title,
  40. 'tags': tags,
  41. 'description': str (description) }
  42. def fetch_embed_info (
  43. url: str,
  44. ) -> tuple[str, str, str]:
  45. """
  46. ニコニコ動画の URL からタイトル、詳細、サムネールを取得して返す.
  47. Parameters
  48. ----------
  49. url: str
  50. 動画 URL
  51. Return
  52. ------
  53. tuple[str, str, str]
  54. タイトル、詳細、サムネールからなるタプル
  55. """
  56. title = ''
  57. description = ''
  58. thumbnail = ''
  59. bs = _create_bs_from_url (url)
  60. if bs is None:
  61. return ('', '', '')
  62. tmp = bs.find ('title')
  63. if tmp is not None:
  64. title = tmp.text
  65. tmp = bs.find ('meta', attrs = { 'name': 'description' })
  66. if tmp is not None and hasattr (tmp, 'get'):
  67. try:
  68. description = str (tmp.get ('content'))
  69. except (AttributeError, TypeError):
  70. pass
  71. tmp = bs.find ('meta', attrs = { 'name': 'thumbnail' })
  72. if tmp is not None and hasattr (tmp, 'get'):
  73. try:
  74. thumbnail = str (tmp.get ('content'))
  75. except (AttributeError, TypeError):
  76. pass
  77. return (title, description, thumbnail)
  78. def fetch_latest_video (
  79. tags: list[str],
  80. ) -> VideoInfo | None:
  81. """
  82. ニコニコから指定したタグが含まれる動画を検索し,最新のものを返す.
  83. Parameters
  84. ----------
  85. tags: list[str]
  86. タグ・リスト
  87. Return
  88. ------
  89. VideoInfo | None
  90. 動画情報
  91. """
  92. tag = ' OR '.join (tags)
  93. url = f"https://www.nicovideo.jp/tag/{ tag }"
  94. params = { 'sort': 'f',
  95. 'order': 'd' }
  96. video_info = { }
  97. bs = _create_bs_from_url (url, params)
  98. if bs is None:
  99. return None
  100. try:
  101. video = (bs.find_all ('ul', class_ = 'videoListInner')[1]
  102. .find ('li', class_ = 'item'))
  103. video_info['contentId'] = video['data-video-id']
  104. except (AttributeError, IndexError, KeyError, TypeError):
  105. return None
  106. return fetch_video_info (video_info['contentId'])
  107. def _create_bs_from_url (
  108. url: str,
  109. params: dict | None = None,
  110. ) -> BeautifulSoup | None:
  111. """
  112. URL から BeautifulSoup オブゼクトを作成する.
  113. Parameters
  114. ----------
  115. url: str
  116. URL
  117. params: dict | None
  118. パラメータ
  119. Return
  120. ------
  121. BeautifulSoup | None
  122. BeautifulSoup オブゼクト
  123. """
  124. if params is None:
  125. params = { }
  126. try:
  127. req = requests.get (url, params = params, timeout = 60)
  128. except Timeout:
  129. return None
  130. if req.status_code != 200:
  131. return None
  132. req.encoding = req.apparent_encoding
  133. return BeautifulSoup (req.text, 'html.parser')
  134. class VideoInfo (TypedDict):
  135. """
  136. 動画情報
  137. """
  138. contentId: str
  139. title: str
  140. tags: list[str]
  141. description: str