Saturday, July 3, 2021

pytubeがpython3.9で404エラーを返したので直した

pythonユーザーには嬉しい、pytubeというものがある。これはpipでインストールできるのだが、私の場合pip3.9しかないのでpip3.9で取得した。

これは私が昔作ったYouTubeの動画をmp4で落とした後にmp3とかにしたいよ〜という願望をかなえるためのもので、重たる用途としてはビートなどを作るときのサンプリングのためであった・・・・

が・・・・

ある日、このyoutube-sample-pyちゃんが動かなくなっちゃったのだ・・・・

  File "/usr/local/bin/sampler", line 93, in <module>
   download        ()
 File "/usr/local/bin/sampler", line 21, in download
   YouTube(url)     \
 File "/opt/homebrew/lib/python3.9/site-packages/pytube/__main__.py", line 313, in streams
   return StreamQuery(self.fmt_streams)
 File "/opt/homebrew/lib/python3.9/site-packages/pytube/__main__.py", line 218, in fmt_streams
   if "adaptive_fmts" in self.player_config_args:
 File "/opt/homebrew/lib/python3.9/site-packages/pytube/__main__.py", line 192, in player_config_args
   self._player_config_args = self.vid_info
 File "/opt/homebrew/lib/python3.9/site-packages/pytube/__main__.py", line 283, in vid_info
   return dict(parse_qsl(self.vid_info_raw))
 File "/opt/homebrew/lib/python3.9/site-packages/pytube/__main__.py", line 108, in vid_info_raw
   self._vid_info_raw = request.get(self.vid_info_url)
 File "/opt/homebrew/lib/python3.9/site-packages/pytube/request.py", line 52, in get
   response = _execute_request(url, headers=extra_headers, timeout=timeout)
 File "/opt/homebrew/lib/python3.9/site-packages/pytube/request.py", line 36, in _execute_request
   return urlopen(request, timeout=timeout)  # nosec
 File "/opt/homebrew/Cellar/python@3.9/3.9.4/Frameworks/Python.framework/Versions/3.9/lib/python3.9/urllib/request.py", line 214, in urlopen
   return opener.open(url, data, timeout)
 File "/opt/homebrew/Cellar/python@3.9/3.9.4/Frameworks/Python.framework/Versions/3.9/lib/python3.9/urllib/request.py", line 524, in open
   response = meth(req, response)
 File "/opt/homebrew/Cellar/python@3.9/3.9.4/Frameworks/Python.framework/Versions/3.9/lib/python3.9/urllib/request.py", line 633, in http_response
   response = self.parent.error(
 File "/opt/homebrew/Cellar/python@3.9/3.9.4/Frameworks/Python.framework/Versions/3.9/lib/python3.9/urllib/request.py", line 556, in error
   result = self._call_chain(*args)
 File "/opt/homebrew/Cellar/python@3.9/3.9.4/Frameworks/Python.framework/Versions/3.9/lib/python3.9/urllib/request.py", line 494, in _call_chain
   result = func(*args)
 File "/opt/homebrew/Cellar/python@3.9/3.9.4/Frameworks/Python.framework/Versions/3.9/lib/python3.9/urllib/request.py", line 748, in http_error_302
   return self.parent.open(new, timeout=req.timeout)
 File "/opt/homebrew/Cellar/python@3.9/3.9.4/Frameworks/Python.framework/Versions/3.9/lib/python3.9/urllib/request.py", line 524, in open
   response = meth(req, response)
 File "/opt/homebrew/Cellar/python@3.9/3.9.4/Frameworks/Python.framework/Versions/3.9/lib/python3.9/urllib/request.py", line 633, in http_response
   response = self.parent.error(
 File "/opt/homebrew/Cellar/python@3.9/3.9.4/Frameworks/Python.framework/Versions/3.9/lib/python3.9/urllib/request.py", line 562, in error
   return self._call_chain(*args)
 File "/opt/homebrew/Cellar/python@3.9/3.9.4/Frameworks/Python.framework/Versions/3.9/lib/python3.9/urllib/request.py", line 494, in _call_chain
   result = func(*args)
 File "/opt/homebrew/Cellar/python@3.9/3.9.4/Frameworks/Python.framework/Versions/3.9/lib/python3.9/urllib/request.py", line 642, in http_error_default
   raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 404: Not Found

ファッ??????

おかしいやん?おかしいや〜ん涙目

と思い、いろいろネットを漁った結果pytubeのダウンロードの仕方を変えたらいいと思うよ、とかいろいろアドバイスあって試したんだけど結果変わらず・・・・

しゃあねぇ、pytubeのソースコードみっか!(CV:悟空)となったのである。

パイソンの場合はスタックの最後が一番したにくるので、まず

/opt/homebrew/Cellar/python@3.9/3.9.4/Frameworks/Python.framework/Versions/3.9/lib/python3.9/urllib/request.py

の642行目から順番にみていく。

で、エラーの中身とかはとくに参考にならなそうなので、このコードまで戻った

class HTTPErrorProcessor(BaseHandler):
   """Process HTTP error responses."""
   handler_order = 1000  # after all other processing

   def http_response(self, request, response):
       code, msg, hdrs = response.code, response.msg, response.info()

       # According to RFC 2616, "2xx" code indicates that the client's
       # request was successfully received, understood, and accepted.
       if not (200 <= code < 300):
           response = self.parent.error(
               'http', request, response, code, msg, hdrs)

       return response

   https_response = http_response

ほっほ〜ぅ。requestとresponseあたりを見てみるか、ということでここより一個前の箇所にコンソールでダンプしていろいろみてみる

       # post-process response
       meth_name = protocol+"_response"
       for processor in self.process_response.get(protocol, []):
           meth = getattr(processor, meth_name)
           print(f"{req.headers}|{req.full_url}|{req.data}|{response}")
           response = meth(req, response)

       return response

それで、もう一回使ってみると、

{'User-agent': 'Mozilla/5.0', 'Accept-language': 'en-US,en'}|https://youtube.com/watch?v=NRKHOvrXGZA|None|<http.client.HTTPResponse object at 0x101dc6310>
{'User-agent': 'Mozilla/5.0', 'Accept-language': 'en-US,en'}|https://www.youtube.com/watch?v=NRKHOvrXGZA|None|<http.client.HTTPResponse object at 0x101dc6dc0>
--- vid_info_url ---
--- else ---
--- ['https://youtube.com/get_video_info?video_id=NRKHOvrXGZA', 'ps=default', 'eurl=https%253A%2F%2Fyoutube.com%2Fwatch%253Fv%253DNRKHOvrXGZA', 'hl=en_US'] ---
{'User-agent': 'Mozilla/5.0', 'Accept-language': 'en-US,en'}|https://youtube.com/get_video_info?video_id=NRKHOvrXGZA&ps=default&eurl=https%253A%2F%2Fyoutube.com%2Fwatch%253Fv%253DNRKHOvrXGZA&hl=en_US|None|<http.client.HTTPResponse object at 0x101dc6be0>
{'User-agent': 'Mozilla/5.0', 'Accept-language': 'en-US,en'}|https://www.youtube.com/get_video_info?video_id=NRKHOvrXGZA&ps=default&eurl=https%253A%2F%2Fyoutube.com%2Fwatch%253Fv%253DNRKHOvrXGZA&hl=en_US|None|<http.client.HTTPResponse object at 0x101dc6f10>

こんなかんじになってて、はじめは

「あれ?そういえばFirefox入れてなかったな?」

と思ってFirefoxを入れたがこれは関係なかった・・・・

なので、urlをよく見て欲しいのだが、

https://www.youtube.com/get_video_info?video_id=NRKHOvrXGZA&ps=default&eurl=https%253A%2F%2Fyoutube.com%2Fwatch%253Fv%253DNRKHOvrXGZA&hl=en_US

こうなってるんですな。

で、これ、ブラウザで試しても確かに404エラーになる・・・・・

これは、おかしいぞ!!!!!

ということでwebで youtubeのget_video_infoを調べてみたところ、stack overflowにこんな質問があった!

で、実際に提示されていたURLをブラウザで試すとビデオがダウンロードできるっ!!!!!!

ちな、エラーを出したURLとそうでないURLを比較するとこんなかんじに

404エラーのURL: https://www.youtube.com/get_video_info?video_id=NRKHOvrXGZA&ps=default&eurl=https%253A%2F%2Fyoutube.com%2Fwatch%253Fv%253DNRKHOvrXGZA&hl=en_US
動くURL:    https://www.youtube.com/get_video_info?video_id=NRKHOvrXGZA&eurl=https%253A%2F%2Fyoutube.com%2Fwatch%253Fv%253DNRKHOvrXGZA&html5=1&c=TVHTML5&cver=6.20180913

おわかりいただけたであろうか?

&html5=1&c=TVHTML5&cver=6.20180913

この部分が足りてなかったんですねぇ、つまりはyoutubeさんがAPIのルールを変えたっていう・・・・・

なので、__main__.pyを以下のように修正した:

    @property
   def vid_info_url(self):
       if self._vid_info_url:
           return self._vid_info_url

       if self.age_restricted:
           self._vid_info_url = extract.video_info_url_age_restricted(
               self.video_id, self.watch_url
           )
       else:
           self._vid_info_url = extract.video_info_url(
               video_id=self.video_id, watch_url=self.watch_url
           )
       additional_url_tokens_required = '&html5=1&c=TVHTML5&cver=6.20180913'
       return self._vid_info_url + additional_url_tokens_required

で、再度実行したところ、動いた!!!!!!!!!!!!

ちなみにGitHubのissueを探したところ特に私のようなエラーはでてなかたので、プルリクはまだ出さないで様子見かな・・・・・


いちおう、そんな感じで!!!!!

*追記:2021年7/23日時点では取得できなくなったようです。YouTubeさん、API仕様を変えたのかな?



No comments:

Post a Comment