개발 기록/자동 포스팅 프로그램

유튜브 영상 다운로드

풀뿌리 2024. 3. 12. 22:12

유튜브 영상을 다운로드하다가 내가 원하는 음악을 다운 받기는 요원하다는 생각이 들었다.

 

왜냐하면 K-pop이나 music 같은 검색 결과는 범위가 너무 넓기 때문이다.

 

그리고 아이돌 혹은 아티스트 명으로 검색하더라도 커버나 댄스 영상들을 구분할 수 없기 때문이다.

 

이에 목표를 다소 변경하여 아티스트 명을 받아 채널을 검색하고 해당 아티스트가 발표한 곡들을 다운받기로 하였다.

 

def get_channel_id(youtube, artist_name):
    search_response = youtube.search().list(
        q=artist_name,
        part='snippet',
        type='channel',
        maxResults=1
    ).execute()
    
    if search_response['items']:
        return search_response['items'][0]['id']['channelId']
    else:
        return None

 

이것이 해당 채널을 호출하는 함수이다.

이름을 받아와 검색하고 그것을 해당 채널의 이름과 채널 식별 번호로 완료하는 식이다.

 

이 다음에는 발표곡에 해당하는 재생목록을 받아오면 youtube-dl 명령을 활용하여 곡을 받을 수 있었지만 몇 가지 문제점이 있었다.

 

1. API로는 발매곡이라는 기준을 가지고 하는 검색의 어려움

2. 재생목록이 꼭 해당 채널의 영상은 아님

3. 영상 중 노래가 아닌 영상이 많음, API는 이를 구분 못함

 

위와 같은 문제 때문에 해당 채널의 영상을 전부 가져오거나 그러면서도 음악은 가져오지 못했기 때문이다.

 

이 때문에 API를 대신하여 웹 크롤링 기법을 사용하였다.

 

웹 크롤링 기법을 사용하기 위해서는 Selenium 라이브러리를 활용하였다.

 

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.keys import Keys
from bs4 import BeautifulSoup
import time

chrome_options = Options()
# 필요한 경우, Chrome을 headless 모드로 실행하기 위한 옵션을 추가
# chrome_options.add_argument("--headless")

driver = webdriver.Chrome(options=chrome_options)

# 유튜브 아티스트 채널로 이동
driver.get('https://www.youtube.com/@LESSERAFIM_official/releases')

# "발표곡" 섹션을 찾아 클릭 이벤트 시뮬레이션
time.sleep(5) # 페이지 로딩 대기
tab = driver.find_element(By.CSS_SELECTOR, '[tab-title="발표곡"]')
tab.click()

# 클릭 후 콘텐츠 로딩을 위해 충분한 시간 대기
time.sleep(5)

# 변경된 페이지 소스로 BeautifulSoup 객체 생성
soup = BeautifulSoup(driver.page_source, 'html.parser')

# 변경된 페이지에서 원하는 정보 추출
# 예를 들어, 동적으로 로드된 비디오 제목들을 가져오기
# 정확한 선택자는 페이지 구조에 따라 다를 수 있으므로, 실제 페이지를 검사하여 확인 필요
videos = soup.find_all('a', class_='yt-simple-endpoint focus-on-expand style-scope ytd-rich-grid-media')
for video in videos:
    print(video.text.strip())

driver.quit()

 

이것이 처음 크롤링을 통해 만들은 내용이다.

 

클래스를 통해 제목 타이틀을 찾아 리스트화 하여 준다.

 

출력 결과

이게 그 결과이다.

문제는 이 제목만으로는 영상을 찾을 수 없다는 것이다.

 

그래서 여기서 이를 좀 더 활용하여 지정된 class를 찾은 다음 해당 클래스나 그 부모 태그에서 href 속성을 찾아 링크를 따오는 것을 목표로 하였다.

 

from selenium import webdriver
from bs4 import BeautifulSoup
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
import time

# Selenium 설정
chrome_options = Options()
# 필요한 경우 Chrome을 headless 모드로 실행하기 위한 옵션을 추가
# chrome_options.add_argument("--headless")

driver = webdriver.Chrome(options=chrome_options)

# 웹 페이지 로딩
driver.get('https://www.youtube.com/@LESSERAFIM_official/releases')

# 충분한 로딩 시간 대기
time.sleep(5)

# 페이지의 HTML 소스 가져오기
page_source = driver.page_source

# BeautifulSoup 객체 생성
soup = BeautifulSoup(page_source, 'html.parser')

# 'style-scope ytd-playlist-thumbnail' 클래스를 포함하는 모든 요소 찾기
elements = soup.find_all(class_='style-scope ytd-rich-grid-media')

# 중복 링크를 방지하기 위한 집합
found_hrefs = set()

# 링크 추출
for element in elements:
    # 각 요소의 부모에서 <a> 태그를 찾습니다.
    a_tag = element.find_parent('a')
    if a_tag:
        href = a_tag.get('href')
        if href and href not in found_hrefs:
            found_hrefs.add('https://www.youtube.com/' + href)  # 새로운 링크를 집합에 추가

for href in found_hrefs:
    print(href)
# 드라이버 종료
driver.quit()

 

이것이 해당 내용이다.

 

처음에 당면한 문제는 해당 채널의 홈에서는 원하는 결과를 얻을 수 없다는 점이었다.

왜냐하면 채널 메인은 동적으로 구성되어 있었으며 클릭하지 않으면 표시되지 않았다.

 

그렇기에 발표곡을 확인하기 위하여 발표곡을 cs 탐색하여 클릭해준 다음 탐색하는 방법을 사용하였다.

 

그런데 코드를 짜다보니 /releases라는 것을 주소 후방에 붙이면 처음부터 링크가 발매곡이 선택된 상태로 표현되는 것을 알게되었다.

 

이를 API 방식에서도 활용할 수 있을까?

 

만약 가능하다면 웹 크롤링을 하지 않아도 되기 때문에 드라이버에 의지하지 않아도 되는 장점이 있다.

 

하지만 API 방식과 웹 크롤링의 도구가 다르기 때문에 동일한 방식으로 한다고 하여 API가 해당 주소에 있는 재생목록을 유의미하게 파악할 수 있는지는 더욱 확인해 볼 사안이다.