Python + Selenium을 통한 페이스북 동영상 다운로드
안녕하세요, kentakang 입니다.
이번에 개인적인 용도로 페이스북 동영상을 다운로드 하는 프로그램을 만들었습니다.
제가 프로그램을 개발하면서 겪은 시행착오나, 여러가지 팁들을 공유하고자 게시글을 작성하게 되었습니다.
저는 Python을 통해 개발했고, 로그인 구현, 영상 다운로드를 위해 Selenium을 이용했습니다.
페이스북 동영상은 어떻게 다운받아요? (공개 동영상)
우선 공개 동영상을 다운로드 할 때 제가 이용한 방법을 적어보도록 하겠습니다.

우선 제가 다운로드 할 영상에 들어왔습니다.
여기서 소스 보기로 들어가시면 영상의 소스를 찾을 수 있습니다.

여기서 영상의 각 화질 별로 가져와야 할 주소가 달라집니다.
HD 지원 영상 : hd_src에서 HD 영상을 가져오거나, sd_src에서 SD 영상을 가져올 수 있습니다.
SD 지원 영상 : hd_src는 null입니다. sd_src에서 SD 영상을 가져올 수 있습니다.
이렇게 해서 src에 들어가 있는 주소를 빠짐없이 복사하면,

이렇게 원본 영상에 접근이 가능합니다.
저는 이렇게 영상을 다운 받는 로직을 아래 사진과 같이 작성했습니다.
# 영상 다운로드
def download(video_url):
driver.get(video_url)
page_source = driver.page_source
if check_isHD(page_source):
regex = "hd_src:\"[^\"\s()]+"
video_source = re.findall(regex, page_source)[0].replace('&', '&').replace('hd_src:"', '')
else:
regex = "sd_src:\"[^\"\s()]+"
video_source = re.findall(regex, page_source)[0].replace('&', '&').replace('sd_src:"', '')
urlretrieve(video_source, get_videoName(video_source))
print("다운로드 완료")
# HD 동영상인지 SD 동영상인지 확인
def check_isHD(page_source):
regex = "hd_src:[^\\s(),]+"
if re.findall(regex, page_source)[0] == 'hd_src:null':
return False
else:
return True
# URL에서 영상 파일 명 추출
def get_videoName(video_source):
regex = r"(\w+\.\w+)(?=\?|$)"
return re.findall(regex, video_source)[0]
여기까지 작성하면서 사용한 모듈은 Selenium, re, urllib.request.urlretrieve 입니다.
이렇게 하면 해당 디렉토리에 서버에 저장된 이름으로 비디오가 저장됩니다.
그럼 이제 비공개 그룹이나 친구 공개 동영상을 받아보도록 하겠습니다.
페이스북 동영상은 어떻게 받아요? (로그인이 필요한 영상)
사실 이 부분 때문에 Selenium을 사용한다고 봐도 될 정도입니다.
비공개 동영상의 경우 Selenium으로 접근하면

영상대신 이렇게 로그인 화면이 저를 반겨줍니다.
그래서 영상의 주소를 통해서 접근 했을 때, 이 영상이 비공개 영상인지 공개 영상인지 판단하는 로직이 필요합니다.
저는 판단의 기준을 2개로 잡았습니다.
우선 로그인 화면이 바로 뜨는 영상은 로그인 버튼이 있는지 찾고,

이렇게 그룹 메인 화면이 뜨는 비공개 그룹은, 그룹 가입 버튼을 찾도록 작성했습니다.
그리고 로그인을 했는지 확인하는 함수도 작성해줘야겠죠?
로그인을 했는지 확인하는 함수는

상단바에 있는 자신의 프로필로 가는 버튼을 찾도록 작성했습니다.
# 로그인 여부 확인
def check_login(video_url, login_driver):
try:
login_driver.find_element_by_class_name("_1k67")
except NoSuchElementException:
return False
return True
# 로그인이 필요한 영상인지 확인
def check_private(video_url):
try:
driver.find_element_by_class_name("login_page")
except NoSuchElementException:
if check_privategroup(video_url):
return True
return False
return True
# 비공개 그룹 여부 확인
def check_privategroup(video_url):
try:
driver.find_element_by_tag_name("video")
except NoSuchElementException:
regex = "joinButton_[0-9]+"
if bool(re.search(regex, driver.page_source)):
return True
return False
# 로그인이 필요한 영상 다운로드
def download_private(video_url):
print("로그인이 필요한 영상입니다.")
login_driver = webdriver.Chrome('chromedriver.exe')
login_driver.get(video_url)
while True:
if check_login(video_url, login_driver):
break
login_driver.get(video_url)
page_source = login_driver.page_source
if check_isHD(page_source):
regex = "hd_src:\"[^\"\s()]+"
video_source = re.findall(regex, page_source)[0].replace('&', '&').replace('hd_src:"', '')
else:
regex = "sd_src:\"[^\"\s()]+"
video_source = re.findall(regex, page_source)[0].replace('&', '&').replace('sd_src:"', '')
urlretrieve(video_source, get_videoName(video_source))
print("다운로드 완료")
login_driver.quit()
저는 이렇게 작성했습니다.
로그인 여부 확인 함수의 경우, 상단 프로필이 갖고 있는 클래스 네임 중 _1k67이 있어서 해당 클래스를 찾게 했습니다.
해당 Element가 존재하지 않을 경우, 로그인이 되지 않은걸로 판단 False를 리턴합니다.
비공개 영상 확인의 경우, 들어갔을때 로그인 페이지가 나오는지 확인하고
로그인 페이지가 나오지 않았더라도, joinButton_(임의 숫자)의 아이디를 가지고 있는 그룹 가입 버튼이 있는지 찾아
해당되는 경우 True를 리턴하도록 했습니다.
로그인은 유저가 직접 하도록 한 뒤 로그인 여부가 확인 될 경우 위에서 작성한 다운로드와 동일하게 다운로드를 진행합니다.
글을 마무리 하며
사실 처음에는, 어려운 작업이라 생각했는데 진행하다보니 그렇게 어려운 작업은 아니였습니다.
하지만 항상 크롤링 작업은 노가다입니다.
해당 페이지의 구조를 분석한 뒤, 모든 상황을 가정해보고 해당 상황에 맞게 프로그램을 작성해야합니다.
그렇게 해서 프로그램을 짜도, 페이지 구조가 바뀌면 갈아 엎어줘야 하구요.
크롤링, 파싱을 공부하기에는 난이도도 조금 있고 생각보다 재미있는 작업인 것 같습니다.
혹시 공부를 위해 소스가 필요하실 경우를 대비해 소스를 깃허브에 올려두셨으니,
소스를 확인하시려면 아래 Github를 확인하시고, 항상 궁금한 점은 댓글로 질문해주세요!
글 읽어주셔서 감사합니다.
facebook_video_downloader Github