Sitemap

爬蟲擷取YT影片資訊和影片字幕

20 min readJan 9, 2025

--

本偏要說的是如何利用爬蟲爬取特定 YouTube 頻道的影片資訊以及影片字幕。

使用了幾個 Python 外掛程式庫來完成不同的任務,如控制瀏覽器進行網頁操作 (Selenium) 和抓取 YouTube 字幕 (YouTubeTranscriptApi)。

Note: 安裝 Chrome 瀏覽器,確保你的系統上安裝了 Chrome 瀏覽器,並且版本是最新的。

下載 ChromeDriver: 到 ChromeDriver 官方網站 下載對應於你 Chrome 瀏覽器版本的 chromedriver。

確保將下載的 chromedriver 添加到系統的環境變數(PATH)

ChromeDriver 的版本必須與你的 Chrome 瀏覽器版本匹配

如果環境設定有問題直接在code寫下面這行就好

driver = webdriver.Chrome(executable_path="/path/to/chromedriver")

重點解說:

主要功能

1. 取指定頻道的影片資訊

程式使用 Selenium 自動化瀏覽器,模擬用戶訪問 YouTube 頻道頁面。

它會自動滾動頁面以載入所有影片資訊。

2. 儲存影片標題與連結

抓取影片標題和連結後,將其儲存為一個字典列表。

3. 抓取影片字幕

對於每個影片,程式會透過 YouTubeTranscriptApi 嘗試抓取對應的字幕。

預設嘗試使用繁體中文字幕(zh-TW),但如果抓取失敗,則會記錄為空。

4. 將結果儲存為 JSON 檔案

抓取的影片資訊與字幕會被存為 JSON 檔案,方便後續分析或使用。

程式碼架構

整份解說的完整程式碼我會放在最後面,想直接用可以拉到最下面copy就好

1. 輸入參數和初始化瀏覽器

channel-name,用於指定要爬取的 YouTube 頻道名稱。

使用 Selenium 的 ChromeDriver 開啟 YouTube 頻道影片頁面。

自動滾動頁面,直到所有影片被載入。

2. 影片資訊的爬取

使用 CSS 選擇器 (div#details 和 a#video-title-link) 定位影片標題與連結。

將每個影片的標題與連結儲存為字典。

3. 字幕爬取和資料儲存

對於每個影片連結,提取其影片 ID,並使用 YouTubeTranscriptApi 抓取字幕。如果字幕抓取成功,將其儲存到對應的字典中。影片資訊與字幕結果被保存為 JSON 檔案,檔案名稱基於影片 ID 命名。

範例YT channel

我們以大家說英語雜誌 Official Channel的YT來進行擷取影片字幕,需要複製YT帳號 LTStudioclassroomCom。

本來要用泱泱的YT,但我找了一下一些知名的YT都沒有提供字幕檔案。

1. 輸入參數和初始化瀏覽器

channel-name,用於指定要爬取的 YouTube 頻道名稱。

channel_name透過selenium內的webdriver.Chrome()來開啟網頁。

import json
import time
import os

# import mysql.connector
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from youtube_transcript_api import YouTubeTranscriptApi


channel_name = 'LTStudioclassroomCom'
print('channel_name: {}'.format(channel_name))
url = f"https://www.youtube.com/@{channel_name}"
print('url: {}'.format(url))

# init driver
driver = webdriver.Chrome()
driver.get(url + "/videos") # 打開網頁
# driver.quit() # 關閉瀏覽器

上面程式是打開chrome瀏覽器然後開啟YT影片連結,通常打開網頁的時候只會讀部分影片,我們一般人工在操作都是要滾動畫面才會繼續將之前的影片讀進來。

所以如果要自動化全部影片都讀進來瀏覽器,需要自動滾動畫面,滾動到網頁底部,並確保所有內容已加載完成。

現代網頁經常採用延遲加載(Lazy Loading)技術,特別是像 YouTube 這樣的網站。這種技術會在頁面滾動到一定位置時,才向伺服器請求並載入更多內容。如果你不模擬滾動,僅停留在頁面初始狀態,可能只能抓到一小部分影片資訊。

- 這邊寫一個Selenium WebDriver的滾動函式 (```scroll_to_bottom```)

scroll_to_bottom 的主要目的是讓網頁中的所有內容(特別是動態加載的內容,例如影片清單)被完全加載到頁面上。

三個參數:

1. driver: Selenium WebDriver 物件。

2. max_scrolls: 限制滾動次數,避免遇到無法加載完全的特殊情況。

3. wait_time: 每次滾動後的等待時間,避免網頁來不及加載新內容。

在滾動時持續檢查網頁高度是否發生變化,如果沒有變化則停止滾動。


def scroll_to_bottom(driver, max_scrolls=20, wait_time=2):
"""
滾動到網頁底部,並確保所有內容已加載完成。
Args:
driver: Selenium WebDriver 物件
max_scrolls: 最大滾動次數(防止無窮迴圈)
wait_time: 每次滾動後的等待時間(秒)
"""
print("Starting to scroll the page...")
scrolls = 0 # 記錄滾動次數

while scrolls < max_scrolls:
# 獲取當前網頁高度
prev_ht = driver.execute_script("return document.documentElement.scrollHeight;")

# 滾動到頁面底部
driver.execute_script("window.scrollTo(0, document.documentElement.scrollHeight);")

# 等待加載
time.sleep(wait_time)

# 再次獲取新高度
ht = driver.execute_script("return document.documentElement.scrollHeight;")

# 如果高度未變化,則停止滾動
if prev_ht == ht:
print("Reached the bottom of the page.")
break

scrolls += 1
print(f"Scroll {scrolls}/{max_scrolls} complete.")

# 如果達到最大滾動次數
if scrolls == max_scrolls:
print("Reached the maximum scroll limit.")
# 滾動到頁面底部
scroll_to_bottom(driver, max_scrolls=20, wait_time=2)

理論上前面執行成功會跳出一個新的視窗如上,會有「Chrome目前受到自動測試軟體控制」,然後螢幕畫面會自己再往下滑。

2. 影片資訊的爬取

## 抓取影片資料

使用 Selenium 的 find_element 和 get_attribute 抓取每個影片的標題 (title) 和連結 (href)。

這邊也一個函數```get_video_details```

我這邊會先宣告一個video的字典dict(```inin_video```)

Keys為

1. title

2. href

3. video_id

4. transcript

5. channel_name

然後將抓所有影片的標題 (title) 和連結 (href)寫到這個字典內

def get_video_details(driver, channel_name):
"""
抓取影片標題和連結。

Args:
driver: Selenium WebDriver 物件

Returns:
videos: 包含影片 title 和 href 的清單
"""
videos = []
def inin_video():
video={}
video['video_id'] = ''
video['transcript'] = ''
video['title'] = ''
video['href'] = ''
return video
try:
# 等待影片元素加載完成
elements = WebDriverWait(driver, 20).until(
EC.presence_of_all_elements_located((By.CSS_SELECTOR, "div#details"))
)
for element in elements:
title = element.find_element(By.CSS_SELECTOR, "a#video-title-link").get_attribute(
"title"
)
href = element.find_element(By.CSS_SELECTOR, "a#video-title-link").get_attribute(
"href"
)
video = inin_video()
video['title'] = title
video['href'] = href
video_id = href.split("=")[-1]
video['video_id'] = video_id
video['channel_name'] = channel_name

videos.append(video)
except Exception as e:
print(f"Error while fetching video details: {e}")
return videos
# 抓取影片資料
videos = get_video_details(driver, channel_name)
# 輸出抓到影片資訊的結果
for i, video in enumerate(videos, 1):
print(f"{i}. {video['title']}: {video['href']}")
我這邊有發現我沒有全部影片都抓到,因為前面自動螢幕下滑的設定, max_scrolls=20,所以螢幕指下滑20次,大家說英文影片很多有8百多部,20次還沒到底。

這邊就是將網頁資訊抓到videos的list裡面,裡面每個video都是我宣告的data structure,如下(但此函數我寫到get_video_details內)

def inin_video():
video={}
video['video_id'] = ''
video['transcript'] = ''
video['title'] = ''
video['href'] = ''
return video

所以get_video_details是將每個影片的video_id、title、href和channel_name寫入,transcript字幕部分還沒。

3. 字幕爬取和資料儲存

對於每個影片連結,提取其影片 ID,並使用 YouTubeTranscriptApi 抓取字幕。如果字幕抓取成功,將其儲存到對應的字典中。

利用函數```YouTubeTranscriptApi.get_transcript```開始從剛剛爬到的影片資訊來擷取字幕,並且將字幕寫到json檔案內。

def get_all_transcript_tojson(videos, output_path):
# videos: output from function: ```get_video_details````
try:
os.mkdir(output_path) #創建儲存檔案夾
print('{} is not exit, create folder'.format(output_path) )
except:
pass

for video_i in videos:
video_id = video_i['video_id']
save_fname = os.path.join(output_path, f"{video_id}.json")
# check if file exist
if os.path.isfile(entity_fname):
continue

try:
transcript_i = YouTubeTranscriptApi.get_transcript(
video_id, languages=["en", "zh-TW"]
) # get transcripts
video_i['transcript'] = transcript_i
except Exception as e:
e
video_i['transcript'] = []
finally:
# save obj as json to local
with open(save_fname, "w") as f:
json.dump(video_i, f)

get_all_transcript_tojson(videos, output_path='./transcript_' + channel_name)

執行上面的程式就會將所有影片的字幕抓下來,languages=[“en”, “zh-TW”] 這邊是因為有可能有中文字幕也有英文,所以兩個都去抓,如果字幕抓不到就直接給字幕的變數(transcript)空的list。

因為我們video已經是dict,所以可以直接寫到output_path的資料夾內,檔案名稱就是影片的ID,也就是YT給予影片的編碼(你看網址後面那串)。

Example: https://www.youtube.com/watch?v=ji3Qoy1vIFE

ID就是: ji3Qoy1vIFE

以我的寫法是output_path=’./transcript_’ + channel_name

所以大家說英文的字幕會存在我執行檔案夾下開一個新的檔案夾(transcript_LTStudioclassroomCom)

直接去點開json是亂碼看不懂,我們可以用python在環境來打開json

# 讀取 JSON 檔案
file_path = "transcript_LTStudioclassroomCom/_9WVNgjV_4c.json" # 替換成你的檔案路徑
with open(file_path, "r", encoding="utf-8") as f:
data = json.load(f)
# 輸出解析後的 JSON 內容
print("JSON 資料:")

print(f"標題: {data['title']}")
print(f"連結: {data['href']}")
for tmp in data['transcript']:
print(f"{tmp}")

完整程式碼

import json
import time
import os

# import mysql.connector
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from youtube_transcript_api import YouTubeTranscriptApi


def scroll_to_bottom(driver, max_scrolls=20, wait_time=2):
"""
滾動到網頁底部,並確保所有內容已加載完成。
Args:
driver: Selenium WebDriver 物件
max_scrolls: 最大滾動次數(防止無窮迴圈)
wait_time: 每次滾動後的等待時間(秒)
"""
print("Starting to scroll the page...")
scrolls = 0 # 記錄滾動次數

while scrolls < max_scrolls:
# 獲取當前網頁高度
prev_ht = driver.execute_script("return document.documentElement.scrollHeight;")

# 滾動到頁面底部
driver.execute_script("window.scrollTo(0, document.documentElement.scrollHeight);")

# 等待加載
time.sleep(wait_time)

# 再次獲取新高度
ht = driver.execute_script("return document.documentElement.scrollHeight;")

# 如果高度未變化,則停止滾動
if prev_ht == ht:
print("Reached the bottom of the page.")
break

scrolls += 1
print(f"Scroll {scrolls}/{max_scrolls} complete.")

# 如果達到最大滾動次數
if scrolls == max_scrolls:
print("Reached the maximum scroll limit.")

def get_video_details(driver, channel_name):
"""
抓取影片標題和連結。

Args:
driver: Selenium WebDriver 物件

Returns:
videos: 包含影片 title 和 href 的清單
"""
videos = []
def inin_video():
video={}
video['video_id'] = ''
video['transcript'] = ''
video['title'] = ''
video['href'] = ''
return video
try:
# 等待影片元素加載完成
elements = WebDriverWait(driver, 20).until(
EC.presence_of_all_elements_located((By.CSS_SELECTOR, "div#details"))
)
for element in elements:
# title = element.find_element(By.CSS_SELECTOR, "a#video-title").get_attribute("title")
# href = element.find_element(By.CSS_SELECTOR, "a#video-title").get_attribute("href")
title = element.find_element(By.CSS_SELECTOR, "a#video-title-link").get_attribute(
"title"
)
href = element.find_element(By.CSS_SELECTOR, "a#video-title-link").get_attribute(
"href"
)
video = inin_video()
video['title'] = title
video['href'] = href
video_id = href.split("=")[-1]
video['video_id'] = video_id
video['channel_name'] = channel_name

videos.append(video)
except Exception as e:
print(f"Error while fetching video details: {e}")
return videos

def get_all_transcript_tojson(videos, output_path):
# videos: output from function: ```get_video_details````
try:
os.mkdir(output_path)
print('{} is not exit, create folder'.format(output_path) )
except:
pass

for video_i in videos:
video_id = video_i['video_id']
entity_fname = os.path.join(output_path, f"{video_id}.json")
# check if file exist
if os.path.isfile(entity_fname):
continue
try:
transcript_i = YouTubeTranscriptApi.get_transcript(
video_id, languages=["en", "zh-TW"]
) # get transcripts
video_i['transcript'] = transcript_i
except Exception as e:
e
video_i['transcript'] = []
finally:
# save obj as json to local
with open(entity_fname, "w") as f:
json.dump(video_i, f)


channel_name = 'LTStudioclassroomCom'
print('channel_name: {}'.format(channel_name))
url = f"https://www.youtube.com/@{channel_name}"
print('url: {}'.format(url))

# init driver
driver = webdriver.Chrome()
driver.get(url + "/videos") # 打開網頁


# 滾動到頁面底部
scroll_to_bottom(driver, max_scrolls=20, wait_time=2)

# 抓取影片資料
videos = get_video_details(driver, channel_name)
# 輸出抓到影片資訊的結果
for i, video in enumerate(videos, 1):
print(f"{i}. {video['title']}: {video['href']}")

get_all_transcript_tojson(videos, output_path='./transcript_' + channel_name)

driver.quit() # 關閉瀏覽器

備註: 這份資料文字和文字內容有利用ChatGPT來修改和生成。

--

--

Tommy Huang
Tommy Huang

Written by Tommy Huang

怕老了忘記這些吃飯的知識,開始寫文章記錄機器/深度學習相關內容。Medium現在有打賞功能(每篇文章最後面都有連結),如果覺得寫的文章不錯,也可以Donate給個Tipping吧。黃志勝 Chih-Sheng Huang (Tommy), mail: chih.sheng.huang821@gmail.com

No responses yet