오랜만에 들어온 김에 요즘 해보고 있는 것에 대해 글을 써보자는 취지로 작성!


요즘 Crawler 를 만들어보고 싶어서 이것저것 알아 본 결과, 개발하기 쉬운 Web Crawler를 선정했다.

 

우선, Web Crawler에 대한 설명은...

아래 링크를 참조하고, 

https://ko.wikipedia.org/wiki/%EC%9B%B9_%ED%81%AC%EB%A1%A4%EB%9F%AC

개발에 대해 얘기를 해보자.


Youtube를 Crawling 대상으로 선정한 이유는 아무래도 데이터가 많고, 다양하기 때문에 선정하게 되었다. (나름 참고할 문서들도 많았다...)


그리고 개발 언어는 Python을 이용하였고, 버전은 3.5버전이다.

원래 2.7버전을 했었는데, 3.5 버전이 더 빠르다보니(더 안정적인거 같다...) 바꾸게 되었다.


개발하기 전에 먼저 필요한 것은 Python 설치와 BeautifulSoup 설치이다.

Python 설치는 구글에 'Python 설치' 라고 검색하면 잘나와있기에 넘어간다.


BeautifulSoup 은 html 과 xml 문서를 파싱해주는 라이브러리이다.


pip를 이용하여 간단하게 설치할 수 있다.


pip install lxml



Youtube Crawling 의 경우, xml 문서를 파싱하기 때문에 위와 같이 설치하지만, html 파싱을 하는 경우에는 

아래 명령어로 설치를 할 수가 있다.


pip install html5lib



BeautifulSoup 라이브러리를 설치하고 본격적으로 개발을 해보자.


Python에 대해 잘모르지만, BeautifulSoup 문서와 다른 자료들을 참고하여 아래와 같은 소스를 만들었다. 



# -*- coding: utf-8 -*-

from bs4 import BeautifulSoup 

import lxml 

import requests 

import json 

import datetime

 


video_info = { 'title':'', 'video_link':'', 'img_link':'', 'play_time':'', 'hits' : '', 'updated_time':'', 'description':'', 'reg_time':'' } 


def get_video_link(target_url): 

response = requests.get(target_url) 

soup = BeautifulSoup(response.text, "lxml") 

lis = soup.find_all('li', {'class' : 'channels-content-item yt-shelf-grid-item'}) 

for li in lis : 

title = li.find('a', {'title' : True})['title'] 

video_link = 'https://www.youtube.com' + li.find('a', {'href' : True})['href'] 

img_link = li.find('img', {'src' : True})['src'] 


#<span class="video-time" aria-hidden="true"><span aria-label="8분, 55초">8:55</span></span>

play_time = li.find('span', {'class' : 'video-time'}).text 


#<ul class="yt-lockup-meta-info"><li>조회수 2,902,617회</li><li>6개월 전</li></ul>

hits = li.find_all('li')[2].text 

updated_time = li.find_all('li')[3].text 

video_info = { 

'title' : title, 

'video_link' : video_link, 

'img_link' : img_link, 

'play_time' : play_time, 

'hits' : hits, 

'updated_time' : updated_time 

}

 

print(video_info) 


return video_info 


def get_hot_video_info(target_url): 

response = requests.get(target_url) 

soup = BeautifulSoup(response.text, "lxml") 

lis = soup.find_all('li', {'class' : 'expanded-shelf-content-item-wrapper'}) 

for li in lis : 

# exception

try : 

title = li.find('a', {'title' : True})['title'] 

video_link = 'https://www.youtube.com' + li.find('a', {'href' : True})['href'] 

img_info = li.find('img', {'data-thumb' : True})

if img_info != None :

img_link = img_info['data-thumb'] 

else : 

img_link = li.find('img', {'src' : True})['src'] 

#<span class="video-time" aria-hidden="true"><span aria-label="8분, 55초">8:55</span></span>

#play_time = li.find('span', {'class' : 'video-time'}).text 

play_time_info = li.find('span', {'class' : 'video-time'})

if play_time_info != None :

play_time = play_time_info.text

else :

play_time = None

#<ul class="yt-lockup-meta-info"><li>조회수 2,902,617회</li><li>6개월 전</li></ul>

hits = li.find_all('li')[3].text 

updated_time = li.find_all('li')[2].text 

description_info = li.find('div',{'class':True, 'class':'yt-lockup-description yt-ui-ellipsis yt-ui-ellipsis-2'})

if description_info != None :

description = description_info.text

else :

description = None


now = datetime.datetime.now()

video_info = { 

'title' : title, 

'video_link' : video_link, 

'img_link' : img_link, 

'play_time' : play_time, 

'hits' : hits, 

'updated_time' : updated_time,

'description' : description,

'reg_time' : now.strftime('%Y-%m-%d %H:%M:%S')

}

print(video_info) 


except BaseException as e : 

print(e)

 


return video_info 



target_url = 'https://www.youtube.com/user/CJENMMUSIC/videos' 

target_url2 = 'https://www.youtube.com/feed/trending'


# 특정 채널

#get_video_link(target_url)


# 인기 리스트

get_hot_video_info(target_url2)




위의 소스를 조금씩 파헤쳐보자. 

첫번째 줄인 # -*- coding: utf-8 -*- 이 부분은 한글 인코딩과 관련하여 넣어준 부분이다.


다음으로 import 부분은 필요한 라이브러리를 선언하는 부분이다.


다음 줄에는 video_info 를 선언하는 부분이다. 이것은 Dictionary 타입으로 Key-Value 형식으로 되어 있다. Youtube Crawling 한 데이터 중 필요한 부분을 여기에 저장한다.


이 프로그램에는 두개의 함수가 있다. 하나는 get_video_link 이고 다른 하나는 get_hot_video_info 이다. 


get_video_link 는 특정 채널에 대한 영상 정보를 가져오는 함수이고, get_hot_video_info 는 인기 채널에 대한 영상 정보를 가져오는 함수이다.

참고로 get_video_link 함수는 예외처리가 제대로 안되어 있으므로, 참고를 한다면 get_hot_video_info 함수를 참고하는 것이 좋다.


설명도 get_hot_video_info 함수에 대해서만 해보자. (차근차근 설명을 남기기 위해 쪼개서 설명하자...)


response = requests.get(target_url) 

soup = BeautifulSoup(response.text, "lxml") 

lis = soup.find_all('li', {'class' : 'expanded-shelf-content-item-wrapper'}) 


먼저 target_url 은 Crawling할 Youtube 주소를 의미 하고, 

첫번째 라인은 target_url 로부터 xml 을 요청하는 부분이고, 이에 대한 반환 값은 response 이다.

두번째 라인은 반환값인  response를 BeautifilSoup 라이브러리를 이용하여 파싱을 하는 부분이다. 파싱결과는 soup이다.

세번째 부분은 파싱된 soup에서 li 속성 중 class가 expanded-shelf-content-item-wrapper 인 부분을 찾아서 리스트로 넣는 부분이다.

리스트는 lis 이다.

이 부분을 이해하기 위해선 xml에 대한 지식이 필요하다...



간단하게 설명을 하자면, 

크롬 브라우저에서 Youtube 인기를 접속하여 개발자모드(F12를 누르면됨)를 들어가보면 다음과 같이 나온다. 익스플로러도 가능하다.




위의 그림과 같이 동영상에 파란색으로 나오게 하려면 xml 정보있는 부분 위에  과 같은 표시를 누르고 

동영상 부분에 마우스를 가지고 가면 해당 부분에 대한 xml 위치를 나타낸다. 여기서 보면 저 부분이 li 속성이고 class가 expanded-shelf-content-item-wrapper 라는 것을 알 수 있다. 이 부분에서 해당 동영상에 대한 정보를 가지고 오는 것이다.





다시 코드설명으로 돌아와서 설명을 해보자.


for li in lis : 

# exception

try : 

title = li.find('a', {'title' : True})['title'] 

video_link = 'https://www.youtube.com' + li.find('a', {'href' : True})['href'] 

img_info = li.find('img', {'data-thumb' : True})

if img_info != None :

img_link = img_info['data-thumb'] 

else : 

img_link = li.find('img', {'src' : True})['src'] 

#<span class="video-time" aria-hidden="true"><span aria-label="8분, 55초">8:55</span></span>

#play_time = li.find('span', {'class' : 'video-time'}).text 

play_time_info = li.find('span', {'class' : 'video-time'})

if play_time_info != None :

play_time = play_time_info.text

else :

play_time = None

#<ul class="yt-lockup-meta-info"><li>조회수 2,902,617회</li><li>6개월 전</li></ul>

hits = li.find_all('li')[3].text 

updated_time = li.find_all('li')[2].text 

description_info = li.find('div',{'class':True, 'class':'yt-lockup-description yt-ui-ellipsis yt-ui-ellipsis-2'})

if description_info != None :

description = description_info.text

else :

description = None


now = datetime.datetime.now()


이 부분은 반복문으로 lis 리스트를 li에 넣고 실질적인 분석을 하는 부분이다. (try 는 혹시 모를 exception을 위해 걸어 놓았다.)

분석은 위에서 간단하게 설명한 xml 분석을 통해서 필요한 정보를 찾고 그 위치를 코드에 입력하는 방식이다.

title 은 동영상의 제목으로, a 속성에 title 정보가 있는 부분에 존재를 한다. 

video_link 는 동영상 페이지로 접속하는 페이지링크로, a 속성에 href 정보가 있는 부분에 존재하며, 공통 도메인인 'https://www.youtube.com'이 빠져 있으므로 직접 추가해줬다.

img_link 는 동영상의 썸네일 이미지정보로, img 속성에 data-thumb 정보가 있다면, data-thumb 에 존재하고, 

data-thumb 정보가 없는 경우, src 정보에 존재한다.

play_time 은 동영상 길이에 대한 정보로, span 속성에 video-time 정보에 존재한다. 이 정보를 존재하지 않는 경우도 있다. (정확한 이유를 아직...)

hits 는 조회 수 정보로, li 속성 중 4번째 위치에 있는 정보이다. (해당 정보안에 li 속성이 여러개 존재하는데, 앞에 3개가 있고 4번째에 조회 수 정보가 존재.)

updated_time 는 업로드 시간 정보로, li 속성 중 3번째 위치에 있는 정보이다.

description 는 동영상 설명에 대한 정보로, div 속성 중 class 정보가 yt-lockup-description yt-ui-ellipsis yt-ui-ellipsis-2 인 곳에 있는 정보이다.

now 는 현재 Crawling 한 시간 정보이다.


video_info = { 

'title' : title, 

'video_link' : video_link, 

'img_link' : img_link, 

'play_time' : play_time, 

'hits' : hits, 

'updated_time' : updated_time,

'description' : description,

'reg_time' : now.strftime('%Y-%m-%d %H:%M:%S')

}

print(video_info) 


except BaseException as e : 

print(e)

 


return video_info 


추출한 정보들을 video_info 에 위와 같이 저장을 하고, 출력을 한다.

추후에 수집한 데이터를 저장하기 위해서는 이 부분에는 데이터를 전송하는 부분이 추가되어야 한다. 

예외가 발생하는 부분이 있으면 except BaseException as e :  으로 빠지고 계속해서 반복문을 수행한다.

반복문 수행이 끝나면 리턴한다.



target_url = 'https://www.youtube.com/user/CJENMMUSIC/videos' 

target_url2 = 'https://www.youtube.com/feed/trending'


# 특정 채널

#get_video_link(target_url)


# 인기 리스트

get_hot_video_info(target_url2)


target_url2 은 Youtube 인기 채널 주소이고, get_hot_video_info(target_url2) 은 함수를 호출하는 부분이다.


코드에 대한 설명은 여기까지 한다.

혹시 몰라서 특정 채널에 대한 함수를 뺀 설명한 부분만 있는 소스코드는 아래와 같다.


# -*- coding: utf-8 -*-

from bs4 import BeautifulSoup 

import lxml 

import requests 

import json 

import datetime

 


video_info = { 'title':'', 'video_link':'', 'img_link':'', 'play_time':'', 'hits' : '', 'updated_time':'', 'description':'', 'reg_time':'' } 


def get_hot_video_info(target_url): 

response = requests.get(target_url) 

soup = BeautifulSoup(response.text, "lxml") 

lis = soup.find_all('li', {'class' : 'expanded-shelf-content-item-wrapper'}) 

for li in lis : 

# exception

try : 

title = li.find('a', {'title' : True})['title'] 

video_link = 'https://www.youtube.com' + li.find('a', {'href' : True})['href'] 

img_info = li.find('img', {'data-thumb' : True})

if img_info != None :

img_link = img_info['data-thumb'] 

else : 

img_link = li.find('img', {'src' : True})['src'] 

#<span class="video-time" aria-hidden="true"><span aria-label="8분, 55초">8:55</span></span>

#play_time = li.find('span', {'class' : 'video-time'}).text 

play_time_info = li.find('span', {'class' : 'video-time'})

if play_time_info != None :

play_time = play_time_info.text

else :

play_time = None

#<ul class="yt-lockup-meta-info"><li>조회수 2,902,617회</li><li>6개월 전</li></ul>

hits = li.find_all('li')[3].text 

updated_time = li.find_all('li')[2].text 

description_info = li.find('div',{'class':True, 'class':'yt-lockup-description yt-ui-ellipsis yt-ui-ellipsis-2'})

if description_info != None :

description = description_info.text

else :

description = None


now = datetime.datetime.now()

video_info = { 

'title' : title, 

'video_link' : video_link, 

'img_link' : img_link, 

'play_time' : play_time, 

'hits' : hits, 

'updated_time' : updated_time,

'description' : description,

'reg_time' : now.strftime('%Y-%m-%d %H:%M:%S')

}

print(video_info) 


except BaseException as e : 

print(e)

 


return video_info 



target_url2 = 'https://www.youtube.com/feed/trending'


# 인기 리스트

get_hot_video_info(target_url2)



Web Crawler는 xml 파싱을 해서 필요한 정보가 어느 위치인지를 분석하고 나면, 금방 만들 수 있을 것 같다.

결국 분석 능력이다....


다음엔 Youtube가 아닌 다른 사이트에 대한 Web Crawling을 해봐야겠다. 


반응형

+ Recent posts