Python X Selenium 유튜브 크롤링

Jake·2022년 2월 11일
0

Vote22

목록 보기
2/4

[Vote22] 2022/02/11 Upadate

현재 Vote22 앱을 위해 AWS의 window 서버를 사용해서 유튜브 크롤링을 하고 있는데, 기존 방식에서 여러 문제점들이 발생해서 코드를 개선하는 과정을 거쳤습니다.

  • 발생했던 문제점
    1. Youtube Live 관련 문제들

      기존에 사용했던 로직은

      1) 현재 DB에서 가장 마지막 URL을 가져와서

      2) 유튜브 채널 동영상 탭의 가장 최신 영상의 URL과 비교하여

      3) URL이 다르면 DB에 넣기

      의 과정을 거쳤습니다.

      하지만 Youtube Live 동영상은 live 시작 시 동영상이 처음 올라오고, live가 종료되면 다시 한 번 업데이트 되면서 동영상 탭의 가장 최신 항목으로 올라옵니다.

      따라서 라이브 시작과 종료 사이 다른 영상이 업로드 되었을 때, 기존 로직은 live 동영상을 기존 동영상과 다르다고 판단하기 때문에 라이브 영상이 중복해서 DB에 올라가는 문제가 발생했습니다.

    2. 한 번에 여러 동영상이 올라오는 경우

      기존 로직에서는 동영상 탭에서 가장 최신 항목 하나만 확인했기 때문에 이러한 케이스에 대응할 수 없었습니다.

    3. 썸네일 및 영상 길이 로딩 문제

      WebdriverWait() 함수를 사용해도 xpath로부터 항목을 가져오지 못하는 문제점이 이따금 발생했습니다.

코드 및 과정

1. 인터넷 페이지에서 xpath 가져오기

xpath는 F12 → Ctrl + Shift + C → 항목에 마우스 갖다 대기를 통해 쉽게 찾으실 수 있습니다.

def crawl_one(candidate, driver, url, cur, conn, printBool=False):
    # 열고자 하는 채널 -> 동영상 목록으로 된 url 페이지를 엶
    driver.get(url)
    time.sleep(2)

    for idx in range(8, 0, -1):
        img_xpath = '/html/body/ytd-app/div/ytd-page-manager/ytd-browse/ytd-two-column-browse-results-renderer/div[1]/ytd-section-list-renderer/div[2]/ytd-item-section-renderer/div[3]/ytd-grid-renderer/div[1]/ytd-grid-video-renderer[' + str(
            idx) + ']/div[1]/ytd-thumbnail/a/yt-img-shadow/img'

        title_xpath = '/html/body/ytd-app/div/ytd-page-manager/ytd-browse/ytd-two-column-browse-results-renderer/div[1]/ytd-section-list-renderer/div[2]/ytd-item-section-renderer/div[3]/ytd-grid-renderer/div[1]/ytd-grid-video-renderer[' + str(
            idx) + ']/div[1]/div[1]/div[1]/h3/a'

        url_xpath = '/html/body/ytd-app/div/ytd-page-manager/ytd-browse/ytd-two-column-browse-results-renderer/div[1]/ytd-section-list-renderer/div[2]/ytd-item-section-renderer/div[3]/ytd-grid-renderer/div[1]/ytd-grid-video-renderer[' + str(
            idx) + ']/div[1]/ytd-thumbnail/a'

        runtime_xpath = '/html/body/ytd-app/div/ytd-page-manager/ytd-browse/ytd-two-column-browse-results-renderer/div[1]/ytd-section-list-renderer/div[2]/ytd-item-section-renderer/div[3]/ytd-grid-renderer/div[1]/ytd-grid-video-renderer[' + str(
            idx) + ']/div[1]/ytd-thumbnail/a/div[1]/ytd-thumbnail-overlay-time-status-renderer/span'

xpath를 뜯어보았을 때, idx가 들어가는 부분이 페이지 레이아웃에서 영상의 번호였기 때문에, 위와 같이 반복문을 짰습니다.

각 대선 출마 후보의 공식 유튜브 채널에서, 한 번에 상위 8개씩 확인했습니다.

2. xpath통해 원하는 정보 저장

selenium의 driver.find_element_by_xpath를 통해 원하는 값들을 가져와 주었습니다.

def crawl_one(candidate, driver, url, cur, conn, printBool=False):
	...
	# 영상 제목
	title = driver.find_element_by_xpath(title_xpath).text
	title = remove_emoji(title)
	
	# 영상 링크
	url_toR = driver.find_element_by_xpath(url_xpath).get_attribute('href')
	
	try:
		# 썸네일
		thumbnail = get_thumbnail(driver, img_xpath)
		if thumbnail is None:
				driver.refresh()
				thumbnail = getThumbnail(driver, img_xpath)	    
		# 영상 길이
		runtime = WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.XPATH, runtime_xpath))).text + " "
		
	except TimeoutException:
		print('thumbnail failure')
		thumbnail = 'https://img.youtube.com/vi'

def get_thumbnail(driver, img_xpath):
	return WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.XPATH, img_xpath))).get_attribute('src')
def remove_emoji(str):
    return re.sub('[^-\{\}=+,#/\?:^.@*\"※~ㆍ!』 ‘|\(\)\[\]`\'…\”\“\’·0-9a-zA-Zㄱ-힗`]', '', str)

이모티콘이 있는 경우, DB가 현재 utf-8로 설정되어있어 오류가 발생했습니다.

이를 해결하려면

  1. utf8-mb4로 db 설정 변경
  2. 이모티콘 삭제

두 가지 방법이 있는데, 저는 이모티콘을 삭제하는 방법을 택했습니다. AWS에서 DB 재시작하기가 조금 무서워서... 더 공부한 후에 해보려고 합니다.

AWS window 서버에서 크롤링을 하다 보면, WebDriverWait으로 대기시간을 설정해도 xpath를 통해 값을 가져오는 것이 정상적으로 동작하지 않을 때가 있었습니다.

그럴 때는 thumbnail이 None이 되었는데, 이 때 driver.refresh()로 새로고침을 해 주면 대부분 정상적으로 동작했습니다.

3. DB에서 existence 체크하기

def youtube_url_existence_check(target, cur):
    sql = "select id from 테이블명 where url = %s;"
    val = (target)
    cur.execute(sql, val)
    list = cur.fetchall()
    if len(list) == 0:
        return -1
    else:
        return list[0][0]

def crawl_one(candidate, driver, url, cur, conn, printBool=False):
	...
	check_id = youtube_url_existence_check(url_toR, cur)
	        if printBool:
	            print(title, runtime, thumbnail, url_toR)
	        else:
	            if check_id > 0:
	                sql = "update videodata set runtime = %s where id = %s;"
	                val = (runtime, check_id)
	                cur.execute(sql, val)
	                conn.commit()
	            else:
	                sql = "INSERT INTO videodata (candidate, runtime, thumbnail, title, upload, url) VALUES(%s, %s, %s, %s, %s, %s)"
	                val = (candidate, runtime, thumbnail, title, upload, url_toR)
	                cur.execute(sql, val)
	                conn.commit()

printBool은 그냥 테스트 때 콘솔에 출력해보기 위해 넣어주었습니다.

우선 youtube_url_existence_check 함수로 영상 url이 이미 db에 존재하면 그 id 값을,

아니면 -1을 리턴하도록 했습니다.

crawl_one 함수에서는 id가 이미 있는 경우 runtime만 업데이트 하도록 해주었고

(이걸로 Live 영상의 송출이 끝나고, 영상 길이가 표시되는 상황에 대응, 문제 1번 해결)

id가 없는 경우에는 db에 없는 영상임으로, issert 해주었습니다.

profile
Java/Spring Back-End Developer

0개의 댓글