iT邦幫忙

0

用requests爬取Youtube影片一直失敗

  • 分享至 

  • xImage

我正在嘗試用requests把Youtube的影片透過網路爬蟲的方式爬下來(我知道有library可以直接用,但是之前因為Youtube反爬蟲更新,導致之前用的Pytube直接失效,因此想要自己試著爬,這樣就算之後yt改了反爬蟲也不至於從0開始重寫)
我先描述我現在的進度: 我已成功把youtube抓取video跟audio檔案的網址找到了
現在遇到的問題: 我看了許多用requests爬取的教學,但裡面都是用get方法去爬取影片的,但隨著反爬蟲的更新,現在已經要用post方法了! 而雖然我用了post方法,但是我還是抓不到檔案,以下是我的程式

import requests
import re
import json
import aiohttp
import aiofiles
import os
import asyncio
import random
url = 'https://www.youtube.com/watch?v=7LkIUfpX-k0'
user_agent_list = ["Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
                   "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
                   "Mozilla/5.0 (Windows NT 10.0; …) Gecko/20100101 Firefox/61.0",
                   "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36",
                   "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36",
                   "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36",
                   "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)",
                   "Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.5; en-US; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15",
                   ]


headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36",
    "Range": "bytes=0-",
    "Accept": "*/*",
    "Connection": "keep-alive",
    "Accept-Encoding": "gzip, deflate, br",
    "Accept-Language": "en-US,en;q=0.9"
}
cookies = {
    "YSC": "e2LSE4IOe5g",
    "VISITOR_INFO1_LIVE": "BHEJagtnezo",
    "VISITOR_PRIVACY_METADATA": "CgJUVxIEGgAgag%3D%3D",
    "PREF": "f4=4000000&tz=Asia.Taipei",
    "GPS": "1"
}
response = requests.get(url=url, headers=headers, cookies=cookies)
ans = re.findall(
    'var ytInitialPlayerResponse = (.*?);var', response.text)[0]
ans = json.loads(ans)
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36",
    "Referer": "https://www.youtube.com/",
    "Range": "bytes=0-",
    "Accept": "*/*",
    "Connection": "keep-alive",
    "Accept-Encoding": "gzip, deflate, br",
    "Accept-Language": "en-US,en;q=0.9"
}
url = ans['streamingData']['adaptiveFormats'][-2]['url']
headers['Content-length'] = ans['streamingData']['adaptiveFormats'][-2]['contentLength']
headers['User-Agent'] = random.choice(user_agent_list)


video = requests.post(url=url, headers=headers, cookies=cookies)
print(video.status_code)
if video.status_code == 200:
    print(video.content)
    with open('video.webm', mode='wb')as file:
        file.write(video.content)

在我headers不加入Content-length前,他會一直status code: 403,但在我把Content-length加入cookie後,他卻一直出現以下報錯

Traceback (most recent call last):
  File "C:\Users\Brian\AppData\Local\Programs\Python\Python311\Lib\site-packages\urllib3\connectionpool.py", line 793, in urlopen
    response = self._make_request(
               ^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Brian\AppData\Local\Programs\Python\Python311\Lib\site-packages\urllib3\connectionpool.py", line 537, in _make_request
    response = conn.getresponse()
               ^^^^^^^^^^^^^^^^^^
  File "C:\Users\Brian\AppData\Local\Programs\Python\Python311\Lib\site-packages\urllib3\connection.py", line 466, in getresponse
    httplib_response = super().getresponse()
                       ^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Brian\AppData\Local\Programs\Python\Python311\Lib\http\client.py", line 1378, in getresponse
    response.begin()
  File "C:\Users\Brian\AppData\Local\Programs\Python\Python311\Lib\http\client.py", line 318, in begin    
    version, status, reason = self._read_status()
                              ^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Brian\AppData\Local\Programs\Python\Python311\Lib\http\client.py", line 287, in _read_status
    raise RemoteDisconnected("Remote end closed connection without"
http.client.RemoteDisconnected: Remote end closed connection without response

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\Brian\AppData\Local\Programs\Python\Python311\Lib\site-packages\requests\adapters.py", line 667, in send
    resp = conn.urlopen(
           ^^^^^^^^^^^^^
  File "C:\Users\Brian\AppData\Local\Programs\Python\Python311\Lib\site-packages\urllib3\connectionpool.py", line 847, in urlopen
    retries = retries.increment(
              ^^^^^^^^^^^^^^^^^^
  File "C:\Users\Brian\AppData\Local\Programs\Python\Python311\Lib\site-packages\urllib3\util\retry.py", line 470, in increment
    raise reraise(type(error), error, _stacktrace)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Brian\AppData\Local\Programs\Python\Python311\Lib\site-packages\urllib3\util\util.py", line 38, in reraise
    raise value.with_traceback(tb)
  File "C:\Users\Brian\AppData\Local\Programs\Python\Python311\Lib\site-packages\urllib3\connectionpool.py", line 793, in urlopen
    response = self._make_request(
               ^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Brian\AppData\Local\Programs\Python\Python311\Lib\site-packages\urllib3\connectionpool.py", line 537, in _make_request
    response = conn.getresponse()
               ^^^^^^^^^^^^^^^^^^
  File "C:\Users\Brian\AppData\Local\Programs\Python\Python311\Lib\site-packages\urllib3\connection.py", line 466, in getresponse
    httplib_response = super().getresponse()
                       ^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Brian\AppData\Local\Programs\Python\Python311\Lib\http\client.py", line 1378, in getresponse
    response.begin()
  File "C:\Users\Brian\AppData\Local\Programs\Python\Python311\Lib\http\client.py", line 318, in begin    
    version, status, reason = self._read_status()
                              ^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Brian\AppData\Local\Programs\Python\Python311\Lib\http\client.py", line 287, in _read_status
    raise RemoteDisconnected("Remote end closed connection without"
urllib3.exceptions.ProtocolError: ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "c:\Users\Brian\Desktop\coding-practice\youtube\demo.py", line 54, in <module>
    video = requests.post(url=url, headers=headers, cookies=cookies)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Brian\AppData\Local\Programs\Python\Python311\Lib\site-packages\requests\api.py", line 115, in post
    return request("post", url, data=data, json=json, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Brian\AppData\Local\Programs\Python\Python311\Lib\site-packages\requests\api.py", line 59, in request
    return session.request(method=method, url=url, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Brian\AppData\Local\Programs\Python\Python311\Lib\site-packages\requests\sessions.py", line 589, in request
    resp = self.send(prep, **send_kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Brian\AppData\Local\Programs\Python\Python311\Lib\site-packages\requests\sessions.py", line 703, in send
    r = adapter.send(request, **kwargs)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Brian\AppData\Local\Programs\Python\Python311\Lib\site-packages\requests\adapters.py", line 682, in send
    raise ConnectionError(err, request=request)
requests.exceptions.ConnectionError: ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))

希望有人能告訴我怎麼改謝謝(可以的話希望盡量不要使用selenium,因為我之後要丟上伺服器,用selenium很不方便)
(這是我第一次問問題所以可能章法有點亂,希望不要介意)

看更多先前的討論...收起先前的討論...
haward79 iT邦研究生 1 級 ‧ 2024-08-24 17:29:18 檢舉
1. 如果是爬影片,還有很多其他工具可用,例如:yt-dlp,理論上會比自己寫快
2. 好奇為什麼不方便用 selenium?browser 可以設定 headless mode
zaber8787 iT邦新手 5 級 ‧ 2024-08-24 19:50:16 檢舉
因為yt-dlp也是別人寫的爬蟲,只是做成library而已,要是有一天youtube改了反爬蟲機制,就會像之前的pytube一樣廢掉,到時候我只能等他更新而不能自己嘗試修改程式碼。
而不用selenium的原因是因為我的程式是要丟上伺服器的,而伺服器並不是window系統,而且我只要抓取音訊檔
haward79 iT邦研究生 1 級 ‧ 2024-08-25 01:43:41 檢舉
1. 用 pytube 會有問題是因為似乎已經停止開發了,但是 yt-dlp 一直都有持續更新,我自己 yt-dlp 用了三年,只要爬不了立刻更新 yt-dlp,都能夠成功復活,所以其實 yt-dlp 跟上的速度還算蠻快了。當然,自己寫當然「最快」,但是若是遇到無法應付的狀況,也是沒法處理只能中斷開發
2. selenium 也可以在其他平台上跑,我自己在 Windows 跟 Linux 都能正常執行
haward79 iT邦研究生 1 級 ‧ 2024-08-25 01:58:22 檢舉
1. 以實用性的角度來說:別人寫的 Library 會相對可靠且方便,如果真的要自己寫,相對於 requests 來說,selenium 這類的 Lib 較能模擬真實瀏覽器行為,所以較不會被檔

2. 以學術研究來說(只看你的code,而不論其他事情):有可能是 Cookie 的值有問題,導致 server 不回應或被防火牆擋了。你的 cookie 值是怎麼來的?從一般瀏覽器直接 copy 過來嗎?
ccutmis iT邦高手 2 級 ‧ 2024-08-25 11:57:58 檢舉
只下載音檔 youtube_dl 也是個不錯的選擇
https://zonego.tw/2022/04/10/youtube-dl-quality/
自己造輪子不是不行,如果自己寫的出比現有套件好又更易維護的代碼,那自己寫沒什麼毛病,不然就是空中樓閣了。
zaber8787 iT邦新手 5 級 ‧ 2024-08-25 16:22:59 檢舉
回 haward: 我的cookie是開無痕從chrome上copy來的沒錯
回 ccutmis: 我覺得我可以試試youtube_dl,我現在有先拿yt-dlp代替,結果丟到伺服器上一直說要login,丟cookie進去後不久也會壞掉
haward79 iT邦研究生 1 級 ‧ 2024-08-26 01:48:26 檢舉
你的爬蟲量很大?
根據你的描述,那有可能是你爬的太過火了,所以或許跟 cookie 或使用的 lib 都無關,因為同個 source IP 爬蟲太多所以被抓到了,需要登入才能繼續爬。
如果你的爬蟲量很大,就需要考慮其他作法,而非只是自行寫Lib或更換Lib,例如透過多個不同 public IP 去分攤流量,以及爬蟲時設定適當的休息區間,再不然也可以考慮使用 Youtube 官方 API (GCP裡面的那些API)。
zaber8787 iT邦新手 5 級 ‧ 2024-08-26 10:31:01 檢舉
我第一次丟上伺服器就被要求登入了! 但在我電腦上都沒有出現類似情況,爬蟲量方面應該也沒有問題,我做的是discord music bot,但根據我觀察,使用音樂功能的也不多
froce iT邦大師 1 級 ‧ 2024-08-26 11:40:19 檢舉
> 我第一次丟上伺服器就被要求登入了! 但在我電腦上都沒有出現類似情況
伺服器是公有雲還是自己在家架?
zaber8787 iT邦新手 5 級 ‧ 2024-08-26 12:09:25 檢舉
Amazon的虛擬機
froce iT邦大師 1 級 ‧ 2024-08-26 13:43:25 檢舉
> Amazon的虛擬機

那被要求登入很正常。
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 個回答

0
froce
iT邦大師 1 級 ‧ 2024-08-26 08:31:25
POST /videoplayback?expire=1724652730&ei=WsjLZqLaAqyNvcAP-P3HqQw&ip=220.130.216.120&id=o-ADgOY5ErqaKlaTHoqc4UdV8vRTNZDHiIG57JQSA1qJYf&source=youtube&requiressl=yes&xpc=EgVo2aDSNQ%3D%3D&mh=ru&mm=31%2C29&mn=sn-ipoxu-umbk%2Csn-un57snee&ms=au%2Crdu&mv=m&mvi=2&pl=24&ctier=A&pfa=5&initcwndbps=986250&hightc=yes&siu=1&spc=Mv1m9uTGGUanOfycH_wktspC2_HeA8d3gTzAfuv1AbITXDqlY6WSKi3DL-shLOU8TTlU6KH-Vg&svpuc=1&ns=b3R0trgOANLZimS9tV6BGP0Q&sabr=1&rqh=1&mt=1724630692&fvip=3&keepalive=yes&c=WEB&n=4ry5RPz8gxCZ6A&sparams=expire%2Cei%2Cip%2Cid%2Csource%2Crequiressl%2Cxpc%2Cctier%2Cpfa%2Chightc%2Csiu%2Cspc%2Csvpuc%2Cns%2Csabr%2Crqh&sig=AJfQdSswRgIhAM9fr4Lp8lDTLIpd0fgrW0vgO_5YYTrh7Fgc2znfYLz8AiEAo5W8FdfV0lClvQ2gkXSEv8-z3ctnucTbc3u67M2kEow%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&lsig=AGtxev0wRAIgO89CGLmgQXzcE1N_79mKra54ekJMdiiiVLSzjY6dU0QCIGy3kKQNykZ80-QvIiIWLxNv8uAVNkGjfsB_yFM_Rqr2&cpn=bKGSZJcyKECqVBBq&cver=2.20240823.01.00&rn=2 HTTP/1.1
Host: rr2---sn-ipoxu-umbk.googlevideo.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:129.0) Gecko/20100101 Firefox/129.0
Accept: */*
Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br, zstd
Content-Length: 2196
Referer: https://www.youtube.com/
Origin: https://www.youtube.com
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
Connection: keep-alive
Priority: u=4
Pragma: no-cache
Cache-Control: no-cache

我沒認真爬過,不過youtube影片的request header應該是這樣。
Content-Length怎麼會放在cookie?
然後我也建議用外部人家寫好的來做。

然後cookie應該是在爬蟲開發的過程為了測試才從瀏覽器拿。cookie有可能會過期啊...
開發後要利用登錄機制作正常的request。甚至可以用selenium登錄拿cookie,因為登錄機制通常是有可能會涉及到跳轉頁面,拿到後再用request去打。

selenium有headless模式,裝完瀏覽器後程式碼開啟就不會有視窗了。

看更多先前的回應...收起先前的回應...
zaber8787 iT邦新手 5 級 ‧ 2024-08-26 10:28:15 檢舉

我之後又重新看了一下payload和查了許多資料,發現的確和你說的一樣我送的payload不對(我用的是第一個requests送回來的),而會no response的原因應該也是content-length不對導致的,而cookie其實應該不是必要的東西,但相對的,我缺少了他所需要的cpn和rn(cpn可能要直接去他的base.js檔裡面搞逆向,而rn應該是順序,但是不知道那個順序是怎麼來的)

froce iT邦大師 1 級 ‧ 2024-08-26 10:31:39 檢舉

我看你放content-length在cookie就有疑問了,因為正常來說都是放在header。

zaber8787 iT邦新手 5 級 ‧ 2024-08-26 11:36:27 檢舉

呃...我放的是header啊!

headers['Content-length'] = ans['streamingData']['adaptiveFormats'][-2]['contentLength']

問題應該是content-length的長度不對,應該不是直接從第一份下來的response抓,我比了一下,response裡的長度起碼是真正的10倍以上,所以content-length應該是可以不填的

froce iT邦大師 1 級 ‧ 2024-08-26 11:40:52 檢舉

但在我把Content-length加入cookie後,他卻一直出現以下報錯

你上面的原文。

request header裡的Content-length是request http post帶的body長度,應該就是上面post後面帶的那一大串的大小。
你可以看出有的參數是hash過的,所以參數大小應該可以固定大小。

你送出的是request,google回你的影片叫response,至於能不能不填我不知道,搞不好看你送過來請求格式大小不對就先把你斷掉了。
這是伺服器檢查的,我的作法通常都是不管怎樣,瀏覽器送出的都放上去就是了。

真的有興趣的話直接去看人家套件的源碼啦,人家都寫出來了,比你我在這猜還快。

zaber8787 iT邦新手 5 級 ‧ 2024-08-26 12:12:13 檢舉

呃...我的確想過直接看yt-dlp的原碼,可是翻了半天找不到他在哪裡爬的 @_@

froce iT邦大師 1 級 ‧ 2024-08-26 13:42:48 檢舉

https://github.com/yt-dlp/yt-dlp/blob/master/yt_dlp/extractor/youtube.py

比較可疑的是這裡,不過要送的東西我猜是經過自動化處理的,沒看到上面的設定就是了。
畢竟這個套件看起來是先檢查你的browser,看有沒有存cookie,有的話會用,除了要手動輸入的部分大概都自動化了。

zaber8787 iT邦新手 5 級 ‧ 2024-08-26 14:21:00 檢舉

喔! 好,謝謝,我再看看(我的天啊! 裡面也好亂)/images/emoticon/emoticon06.gif

我要發表回答

立即登入回答