1. 생활 코딩 #2
: 싸이월드 백업하기
백승용 / seungyong.baek@gmail.com
생활 코딩:
가정 또는 업무의 반복적인 작업을 간단한 코딩으로 편안하게 하자.
2. 개선 필요 사항
▪ 싸이월드는 한국의 오래된 SNS
▪ 와이프는 2004년 부터 현재 까지도 싸이월드를 이용중
▪ 지금은 가족의 일상 생활을 일기장 형식으로 이용하고 있음
▪ 그러나, 혹시라도 언제 서비스가 없어질지 모름(???)
▪ 그래서, 싸이월드 포스트의 글과 사진을 백업 받고 싶어함
▪ 2004년 ~ 2017년 까지 3,231개의 포스트가 게시되어 있음
• 실제로 백업을 받은 html 파일의 개수
▪ 따라서, 많은 포스트를 보다 쉽고 빠르게 백업할 방법이 필요
3. 개선 목표
1
▪ 윈도우의 명령 프롬프트(CMD)에, 백업 대상 연도를
파라미터로 주어서 스크립트 실행
2
1
2
▪ 바탕화면에 지정한 연도의 폴더가 생성이 되고, 해당 연도의
포스트들을 html 형식의 파일로 저장
▪ 포스트의 개수와 html 파일 개수 비교 가능
▪ CYWORLD_XXXX.txt 파일에 포스트의, 모든 URL이 기록
되므로 포스트 개수 확인 가능
▪ 스크립트 위치: C:UsersSeungYong
▪ 스크립트 파일명: cyworld.py
4. 준비 사항
1. 윈도우용 Python 설치
▪ https://www.python.org/downloads/
▪ Python 3.6.x 버전 설치
2. Python 라이브러리 설치
▪ 필요 표준 라이브러리: os, sys, re, random, time
▪ 추가 라이브러리: beautifulsoup4, selenium, pyautogui
▪ 설치된 추가 라이브러리 목록 확인
• C:>pip list --format=columns
▪ 추가 라이브러리 설치
• C:>pip install beautifulsoup4
• C:>pip install selenium
• C:>pip install pyautogui
▪ 추가 드라이버 설치
• 크롬 웹 드라이버(적당한 폴더에 다운로드 및 압축 해제)
• https://sites.google.com/a/chromium.org/chromedriver/d
ownloads
▪ 설치된 추가 라이브러리 목록 확인
▪ pip는 python 패키지 관리자로 python
설치시에 함께 설치됨
▪ 추가 라이브러리 설치
▪ 이미 설치된 경우에는 설치됨으로 나옴
5. 필요 라이브러리/모듈/드라이버
Beautiful Soup
https://www.crummy.com/software/BeautifulSoup/
Selenium
http://www.seleniumhq.org/
라이브러리/모듈 설명
os
This module provides a portable way of using operating
system dependent functionality.
sys
This module provides access to some variables used or
maintained by the interpreter and to functions that
interact strongly with the interpreter.
re
This module provides regular expression matching
operations similar to those found in Perl.
random
This module implements pseudo-random number
generators for various distributions.
time This module provides various time-related functions.
selenium Selenium automates browsers.
beautifulsoup4
Beautiful Soup is a Python library for pulling data out of
HTML and XML files.
pyautogui
PyAutoGUI is a cross-platform GUI automation Python
module for human beings. Used to programmatically
control the mouse & keyboard.
ChromeDriver
WebDriver is an open source tool for automated testing
of webapps across many browsers.
• 설명은 공식 홈페이지의 설명 인용(www.python.org, Beautiful Soup, Selenium,
PyAutoGUI, ChromeDriver)
• 실제 사용 방법은 코드에서 설명
PyAutoGUI
https://github.com/asweigart/pyautogui
ChromeDriver
https://sites.google.com/a/chromium.org/chromedriver/home
6. 전체 코드
2
1
1
▪ download_cyworld() 함수
1. 백업 대상 연도를 매개 변수로 받음
2. 크롬 브라우저 시작
3. 싸이월드 로그인
4. 백업 대상 연도의 모든 포스트 리스팅
5. 모든 포스트를 html 파일로 저장
2
▪ 스크립트 시작점
1. 스크립트가 정상적으로 실행이 되었을 경우
2. 필요한 모듈을 임포트하고
3. download_cyworld() 함수로 해당 연도를 매개
변수로 전달하여 실행
7. 코드 설명 #1
라인 설명
86
▪ cyworld.py라는 스크립트가 아래의 **와 같이 독립적으로
실행될 때만 86번 라인 이후의 코드가 바로 실행됨
▪ 즉, 다른 스크립트에서 모듈로 임포트되는 경우에는 바로
실행되지 않음
** C:>cyworld.py 2009
88~100
▪ 표준 및 추가 라이브러리 임포트
▪ BeautifulSoup은 “as”로 임포트 하였기 때문에, BS라는
별칭으로 사용됨
▪ selenium expected_conditions는 EC로 사용됨
102~106
▪ cyworld.py 스크립트로 전달된 매개 변수의 개수가, 2개
로 올바르게 입력된 경우, 매개 변수를 변수로 할당하고
download_cyworld()함수 호출
▪ 아래의 **와 같이 실행된 경우, len(sys.argv) == 2가 됨
• sys.argv[0]=cyworld.py
• sys.argv[1]=2009 input_year로 할당
▪ 즉, download_cyworld(2009)의 형태로 함수 호출
** C:>cyworld.py 2009
107~110
▪ cyworld.py 스크립트로 전달된 매개 변수의 개수가 2개가
아닌 경우에는 안내 메시지 출력
8. 코드 설명 #2
라인 설명
4 ▪ input_year를 매개 변수로 받는 함수 정의
6 ▪ ChromeDriver 설치(압축 해제) 경로를 명시
7, 8
▪ 웹 드라이버를 이용해 크롬 브라우저를 실행시키고
cyworld 객체로 생성한 후에, 창을 최대화 함
▪ 크롬 브라우저가 실행이 되면, 소프트웨어에 의해 제어됨
이 표시됨
• “코드 설명 #2 - 부가설명 #2” 참조
11 ▪ 브라우저에서 로그인 페이지를 로딩
12, 13 ▪ 로그인 정보(uid, upw)를 브라우저로 전달
14 ▪ “로그인” 버튼을 클릭
15 ▪ “내싸이홈가기”가 클릭이 가능한 상태가 될 때까지 대기
16 ▪ “내싸이홈가기”를 클릭
17
▪ time 모듈을 사용하여 5초간 대기
▪ 실제 사람처럼 웹 브라우저를 조작하므로, 웹 페이지가
정상적으로 로드 될 때 까지, WebDriverWait()와
time.sleep()으로 적정한 대기 코드가 필수
20~26
▪ 11~17 라인과 비슷한 방식으로 브라우저 조작
▪ 특정 연도의 포스트 리스트 첫 페이지를 가져옴
▪ sDate, eDate에 ’00’이 붙는 것은, 싸이월드 특성
• cyworld.find_element_by_xxxxxxxx로 시작하는, 모든 코드는 “코드
설명 #2 - 부가설명 #1” 참조
• 브라우저 조작은 “코드 설명 #2 - 부가설명 #2” 참조
9. 코드 설명 #2 - 부가 설명 #1(웹 ELEMENT 찾기)
1. 브라우저에서 대상 요소 우 클릭 검사
2. Elements 창에서 해당 요소를 우 클릭
3. 필요한 요소를 복사 해서 사용
▪ 코드에서 아래와 같은 모든 라인은 이 방법을 통하여 찾음
▪ id의 경우 Elements 창에서 확인 가능
• cyworld.find_element_by_id
• cyworld.find_element_by_xpath
• cyworld.find_element_by_css_selector
▪ xpath: XML Path 참고: https://ko.Wikipedia.org/wiki/XPath
▪ css selector: CSS Selector 참고: http://www.nextree.co.kr/p8468
▪ 웹 페이지에서 가장 unique하고 변경이 적을 만한 요소의 사용을 권장
10. 코드 설명 #2 - 부가 설명 #2(브라우저 조작)
라인 11: 로그인 페이지
라인 12: uid
라인 13: upw
라인 14: //*[@id="tab_cont1"]/div/input
라인 15, 16: //*[@id="loginbox"]/ul/li[1]/a
라인 20: //*[@id="all"]
라인 21, 22: //*[@id="allArea"]/div/ul/li[2]/label/span
라인 23, 24: sDate, eDate
라인 25, 26: //*[@id="allArea"]/div/div/button
라인 7: 웹 드라이버로 제어되는 크롬 브라우저 실행, cyworld 객체
11. 코드 설명 #3
라인 설명
29~37
▪ 20~26 라인을 통해 2009년 포스트의 첫 페이지가 표시됨
▪ 그러나, 모든 포스트가 아닌 최근 24개만 표시가 됨
▪ 페이지 하단의 “+” 버튼으로 모든 포스트의 목록을 표시
▪ “+” 버튼 누르기는 xpath로는 원하는 구현이 되지 않아
css selector 사용
▪ while 구문으로 “+”가 나타나면 계속 클릭
▪ 더 이상 “+“ 버튼이 나타나지 않아, selenium exception이
발생하면 메시지를 표시하고 pass 시킴
40~43
▪ cyworld 객체의 page_source를 beautifulsoup를 이용하
여 파싱하고 soup 객체 생성
▪ 각 포스트의 url들을 저장할 빈 post_urls set 생성
45~48
▪ soup 객체에서 포스트 URL이 담긴 “postSection” 검색
▪ 검색된 “postSection”내에서, 간단한 정규 표현식으로
“/home/25967997/post/”로 시작하는 html “a” tag만 검색
하여 link로 전달
▪ link에서 “href” 속성의 값(URL)만 link_url로 생성
▪ link_url로 각 포스트의 전체 URL을 생성
▪ post_urls set에 각 포스트의 전체 URL을 추가
51~53
▪ 해당 연도의 전체 포스트 개수가 표기된 “제목“ 출력
▪ 실제로 post_urls set에 저장된 post_url의 개수 출력
• 브라우저 조작과 변수는 “코드 설명 #3 - 부가 설명 #1, #2” 참조
12. 코드 설명 #3 - 부가 설명 #1(브라우저 조작)
라인 31, 33: #divMoreBtn > button
▪ soup 객체: 아래의 항목들을 검색하고 참조할 수 있음
• postTitle
• postSection
• 개별 포스트의 URL
• 개별 포스트의 subject
• 등등등
13. 코드 설명 #3 - 부가 설명 #2(45~48 라인 변수)
▪ 라인 45: link
<a href="/home/25967997/post/4A512E18280003187AFA6401" onclick="Post.ClickView('4A512E18280003187AFA6401');return false;">
<span class="date">7.6</span>
<h3 class="subject">산책로 입구</h3>
<div class="data"><figure alt="산책로 입구 " class="postImage" style="background-
image:url('http://cythumb.cyworld.com/269x269/cyimg25.cyworld.com/common/file_down.asp?redirect=%2F250012%2F2009%2F7%2F6%2F62%2F200907060
74949_25967997.jpg');"></figure>
</div>
</a>
▪ 라인 46: link_url
/home/25967997/post/4A512E18280003187AFA6401
▪ 라인 47: post_url
http://cy.cyworld.com/home/25967997/post/4A512E18280003187AFA6401
▪ 라인 48: post_urls
http://cy.cyworld.com/home/25967997/post/4A512E18280003187AFA6401
http://cy.cyworld.com/home/25967997/post/4A512ECC3DC003187AFA6401
http://cy.cyworld.com/home/25967997/post/4A512F08920003187AFAA801
http://cy.cyworld.com/home/25967997/post/4A512C38014003187AFA6401
……………
14. 코드 설명 #4
라인 설명
56, 57
▪ html 파일 저장 폴더인 save_dir 정의
▪ 포스트 URL을 저장할 텍스트 파일 post_list 정의
59, 60 ▪ save_dir이 없으면, 폴더 생성
62, 63 ▪ post_list가 없으면, 쓰기 모드로 신규 파일 생성
65 ▪ post_urls에서 각 URL을 하나씩 post로 전달
66 ▪ 브라우저에서 해당 페이지를 로딩
67
▪ 페이지 하단의 “댓글 박스”가 로딩될 때까지 대기
▪ 사진이 많아서 로딩이 오래 걸릴 경우를 대비하여 타임아
웃을 보다 길게 설정
68 ▪ 해당 포스트의 “등록일시”를 post_date로 생성
69
▪ save_dir과 post_date를 슬라이싱하여 입력 파일 명 생성
▪ random 함수는 제목이 없는 포스트의 경우, 파일명이 중
복되어 제대로 저장되지 않는 경우가 발생. 이를 방지하
기 위하여 저장 파일명에 임의의 정수를 추가
70 ▪ CYWORLD_XXXX.txt에 저장될 파일명과 URL을 기록
72~79
▪ pyautogui로 웹 브라우저의 “우 클릭" “다른 이름으로
저장” “파일명 입력” “저장”을 구현
▪ 브라우저의 “다른 이름으로 저장” 기능은, OS가 제어를
하는 영역이라 selenium(브라우저)에서는 제어 불가능
82, 83 ▪ post_list(CYWORLD_XXXX.txt) 및 웹 브라우저 닫기
• 브라우저 조작과 변수는 “코드 설명 #4 - 부가 설명 #1” 참조
15. 코드 설명 #4 - 부가 설명 #1(브라우저 조작 및 변수)
라인 67: //*[@id="inputWrap123456"]/textarea
라인 68: #postData > div.postInfo > ul > li.date > p
▪ 라인 68: post_date
2009.07.06 08:42 (업로드 2009.07.06 08:42)
▪ 라인 69: save_file_name
C:UsersSeungYongDesktop200907.06_39_
• 실제로 저장될 파일명은 save_file_name + “해당 포스트의 제목“
• 예를 들어 제목이 “제주도”라면 아래와 같은 파일명으로 저장됨
• C:UsersSeungYongDesktop200907.06_39_제주도
▪ C:UsersSeungYongDesktop2009CYWORLD_2009.txt 파일의 내용
16. 코드 스타일 점검
▪ 코드의 문법 및 스타일 점검을 위하여 pycodestyle 사용
▪ pycodestyle은 기존의 pep8의 이름이 변경된 것임
▪ 여러 항목의 코드 스타일에 대한 체크를 해 줌
▪ E501: 일반적으로 79 컬럼 내에 코드를 작성할 것을 권고하는 에러
• C:UsersSeungYong>pip install pycodestyle
• C:UsersSeungYong>pycodestyle cyworld.py
17. 고려 사항
1. 이 스크립트는 다양한 경우의 예외 또는 에러가 발생할 수 있으나, 이에 대한 오류 처리 코드가 없음
▪ 모든 오류를 코드로 처리하는 것보다, 단순 재실행하는 방법이 보다 생산성이 높을 것으로 생각
2. 10회를 수행하였을 때 PC, 네트워크, 서버등의 다양한 원인으로 2~3회 이상 실패할 수 있음
▪ 이러한 경우, 바탕화면의 폴더를 지우고 재실행
3. 웹 브라우저를 직접 실행하는 형태여서, 스크립트를 실행하면 PC를 사용할 수 없음
▪ pyautogui 모듈의 경우, 마우스 및 키보드를 직접 제어하기 때문에 사용자의 개입시에 오류 발생
▪ 스크립트를 수행하고 취침하는 방식을 권장
▪ PhantomJS를 이용하여 headless로도 시도를 하여 보았으나, 제대로 수행되지 않음(실력의 한계)
4. 백업 수행 속도는 환경에 따라 다르나, 분당 평균 6개 포스트~10개 정도
5. 실행시에 아래와 같은 에러는 브라우저, selenium, ChromeDriver등 버전에 따라 나오기도 하고 안 나오기도 함
▪ 그러나, 싸이월드 포스트를 html 파일로 백업하는 것에는 지장 없음 http://phantomjs.org/