블로그 이미지
자유로운설탕

calendar

1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30

Notice

2017. 4. 19. 20:34 프로그래밍

  이번 시간에는 작업 자동화라는 주제를 진행해 보려고 한다. 뭐 그다지 거창한건 아니라는걸 먼저 밝힌다. '작업' 이라는 것은 이전 시간에 얘기했던 웹이나, GUI 등 다른 자동화의 측면도 포함되는 주제지만, 좀더 단순하게 축소해서 윈도우 상에서 작업 하는 여러 자잘한 일들을 파이썬을 이용해서 쉽게 동작되게 만드는 과정이라고 정의해 보자.

 

  예제로는 하위 폴더를 가진 특정 폴더에서 특정한 확장자 들을 가진 파일 들만 zip 으로 압축하여, ftp 에 업로드 한후, 로컬에서 기간이 오래된 zip 파일을 삭제하는 작업을 순차적으로 진행하는 파이썬 프로그램을 만들려고 한다. 그리고 마지막으로 해당 작업을 주기적으로 실행 할때 사용할 수 있는 방법에 대해 살펴 보려 한다. 해당 기능을 구현할때 오직 파이썬 모듈들만을 이용해서 구현이 가능 할 수도 있겠지만, 여기서는 윈도우에서 지원하는 몇 가지 명령어와 무료 압축 프로그램인 7-zip 등을 이용해, 파이썬 코드에서 호출을 통한 구성을 해보려고 한다.

 

 

[목차]

0. 왜 파이썬 공부에 구글을 이용하는게 좋은가?

1. 언어를 바라보는 방법. 파이썬을 어떻게 바라봐야 할까?

2. 파이썬 설치와 환경, 버전 선택 하기의 이유.

3. 만들고자 하는 기능을 모르는 조각으로 나눠 조사해 보기

4. 데이터 베이스에서 내용 가져와 출력하기

5. 암호화 모듈을 이용해 암복호화 해보기

6. 퍼즐 조각들을 합쳐보기

7. 엑셀 파일 사용해 보기 -> 부록 : fuction 을 이용해서, 코드 정리해 보기

8. 정규표현식을 왜 사용해야 할까? 언어속의 미니 언어 정규표현식 살펴보기

9. 입력과 결과를 GUI 화면과 연결해 보기

10. Whois API 이용해 보기

11. 웹페이지 호출해 내용 파싱 하기(BeautifulSoup 그리고 한계)

12. 자동화 - 웹 자동화(with Selenium)

13. 자동화 - 윈도우즈 GUI 자동화(with pywinauto)

14. 자동화 - 작업 자동화

15. 수학 라이브러리 살펴보기

16. 그래픽 라이브러리 살펴보기

17. 머신러닝에서의 파이썬의 역활

18. 웹 프로그래밍 - Legacy Web

19. 웹 프로그래밍 - Flask 살펴보기(feat. d3.js)

20. 웹 프로그래밍 - Django 살펴보기

21. 정리 - 이런저런 이야기

 

 

 

[들어가면서]

  작업 자동화에 대해 어떻게 얘기를 풀어볼까 하다가 옛날 얘기로 시작해 보려 한다. 예전에 윈도우가 나오기 전에 도스란 cmd 타입의 OS 만 있을때의 컴퓨터는 지금 x-windows 를 사용안하는 리눅스 서버와 비슷한 모드로, 모든 작업이 까만 cmd 화면에서의 명령어 중심으로 이루어져 있었다(물론 그 안에서도 여러가지 트릭을 사용해서 그래픽으로 표시는 했었다). 도스 기반으로 컴퓨터를 처음 접했던 사람들은 아직도 윈도우, 맥 화면의 PC 나, 스마트 폰의 화려한 화면을 볼때도 왠지 뒷면에 놓여있는 cmd 창의 존재를 쉽게 떨쳐버리진 못할 것 이다.

 

  윈도우즈 또한 많은 부분이 GUI 로 감싸 이루어져 있어, 거의 cmd 창을 사용 안하더라도 가능한 부분들도 많지만, GUI 화면으로는 복잡하게 해야되는 일들을 좀더 간단하게 만들어주는 여러 유용한 명령어라든지, 서버 관리자용 확장쉘인 powershell 이라든지 하는 컴퓨터 관리에 도움을 주는 텍스트 기반의 툴들이 많이 내장되어 있다. 리눅스 또한 여러 쉘이나 유틸 기반의 유용한 툴들이 많으며, 어찌보면 GUI 로 구현된 화면들은 그러한 명령어 기능 중에 사람들이 자주 쓸 것 같은 기능들을 편하게 사용하도록 구현된거라고 봐도 무방할 듯 하다. 그래서 윈도우즈 10같이 점점 사용자들이 잘 안쓰는 디테일한 기능들을 디폴트로 숨기는 최신 OS 의 경우 이것저것 시스템을 건드리는 작업이 필요한 사람들 한테는 어떻게 보면 불편한 느낌까지도 주게되는 것 같다.

 

  뭐 여튼 그래서, 파일 관련 여러 작업들(복사, 압축, 백업..) 등의 기본적인 작업 들에 대해서는 대부분의 언어에서 지원하는 라이브러리보다 시스템에서 기본, 확장 지원 하는 기능들을 호출하여 사용하는 것이 좀더 효율 적인 경우도 있게 된다. 적당히 해당 명령어를 실행하고, 결과가 완료 되기만 기다릴 수 있다면, 어떤 언어를 사용하든 비슷한 효과를 가진 프로그램을 쉽게 만들 수 있게 될 것이다.

 

 

 

[무료 ftp 설치하기]

  그럼 실습을 위해서, 무료 ftp 를 하나 설치해서 운영해 보려 한다. ftp 는 'file transfer protocol' 의 약자로, 서버와 클라이언트 사이에서 서로 파일을 교환하기 위한 오래된 방식이다. 해당 방식을 이용해서, 클라이언트의 파일을 원격지에 있는 서버의 특정 폴더로 이동 등의 작업이 가능하다. 뭐 요즘으로 따지면 구글 드라이브 등의 클라우드, 웹 드라이브와 기능이  비슷하다고 봐도 무방할 듯 하다. 비슷한 용도로 쓰이는 개념으로는 공유 폴더 같은게 있다. 개인적으로 사용하고 있는 ftp 가 있다면 소스의 ftp 코드의 연결 부분만 바꾸어서 그 쪽에서 실습해도 된다.

 

 

[ftp 서버 설치]

  구글에서 'free ftp' 라고 검색하여, 파일질라를 사용하기 위해 https://filezilla-project.org/ 페이지로 이동한다. 'Download FileZiller Server' 버튼을 클릭하고, 다음 페이지에서 소스포지에서 'Download Now' 버튼을 클릭 한다.

 

  다운로드한 파일을 실행을 하여 디폴트로 설치를 진행 하다가, 테스트로 설치한 ftp 가 계속 실행 되고 있는게 좋을 건 없으니 시작 유형만 'Start Manually' 로 바꿔준다.

 

 

  설치 완료 후 '모든 프로그램' > 'FileZiller Server' > 'FileZiller Server Interface' 를 실행 한다. 아래의 다이얼로그가 나오면 'Connect' 버튼을 누른다. 그럼 서버가 실행 되게 된다.

 

  그럼 익명 사용자로 사용해도 되긴 하지만, 아무나 자기 서버에 들어와 사용하는건 그러므로, 사용자를 등록해 세팅해 보자. 그전에 우리가 매 시간 코드를 실습했던 c:\python\code 폴더에 아래와 같이 실습에 사용할 2개의 폴더를 만들어 보자

c:\python\code\ftproot

c:\python\code\ftproot\mybackup

 

  그 후 'Edit' > 'Users' 메뉴를 선택해 보자. 

 

  'Users' 창이 나오면 왼쪽에서 'General' 항목을 선택하고(기본이긴 하다), 오른쪽 'Users' 섹션에서 'Add' 버튼을 클릭한다. 이후 'Add user account' 다이얼로그가 뜨면, 'pyftpuser' 라고 사용자 이름을 넣고, 'OK' 버튼을 누른다.

 

   그리고 패스워드를 'test1234' 라고 적어 준다. 그럼 사용자의 id/pass 가 정해졌다.

 

  사용자를 생성하였으니 그 담에는 사용자에게 사용 가능한 특정 폴더를 할당해 주어야 한다. 사용자가 로그인 하였을때 해당 폴더가 디폴트로 보이며, 해당 폴더 안에서만 이런저런 작업이 가능하다(리눅스의 유저 기본 폴더와 같은 개념이다). 왼쪽에서 'Shared folders' 항목를 선택하고, 아래쪽 'add' 버튼을 눌러서 아까 만든 'c:\python\code\ftproot' 폴더를 선택한다. 이후 오른쪽의 체크 박스를 다 체크해서 해당 폴더에 대해서 풀 권한을 준다. 그리고 맨 하단의 'OK' 버튼을 눌러서, 'Users' 다이얼로그 창을 닫고 메인창으로 온다. 그럼 ftp 운영을 위한 설정이 모두 완료되게 된다.

 

 

[ftp 서버 동작 확인]

  그럼 파이썬 코드를 작성하기 전에, 세팅한 ftp 의 정상 동작을 확인해 보도록 하자(반드시 외부에서 쓰이는 프로그램들은 파이썬 코드를 만들기 전에 다른 외부 수단으로 기본 동작을 확인해 보는게 좋다. 그래야 파이썬 코드에서 연결 에러가 발생 시, 서버 세팅 문제가 아니라 코딩이나 모듈 잘못이라고 체크 하기가 쉬워 덜 헤메게 된다). 아까 파일질라 홈페이지에 있던 클라이언트를 설치해 확인해도 되지만, 그럼 또 클라이언트 사용법도 익혀야 되니, 간단히 하기 위해 브라우저를 하나 띄운 후 주소창에 ftp://localhost 라고 입력한다. 그럼 id/password 를 입력하라는 창이 뜨게 된다(시스템 환경에 따라 좀 느릴 수도 있다). 아까 설정한 pyftpuser/test1234 를 넣는다.

 

  그럼 ftp 에 연결되면서 아까 우리가 c:\python\code\ftproot 안에 만들어 놓은 'mybackup' 폴더가 보이게 된다. (/ 로 표시되는 사용자의 현재 폴더가 ftproot 폴더이다)

 

 

 

 

[7-zip 설치하기]

  그럼 마찬가지로 실습을 하기 필요한 7-zip 이라는 무료 zip 프로그램을 설치해 보자. 구글에서 '7zip' 이라고 검색하여, http://www.7-zip.org/ 로 이동한다.

 

  아래의 다운로드 링크에서 '64비트' 다운로드 링크(제 컴퓨터가 windows 10 home 64bit 라서 그렇다)를 클릭하여 설치한다. 설치 할때는 특별히 신경 쓰지 말고 디폴트 옵션으로 설치하면 된다.

 

 

 

[테스트 파일 준비하기]

  우리가 매일 실습하던 c:\python\code 폴더에 아래와 같이 실습에 사용할 4개의 폴더를 만들어 보자.

c:\python\code\source  (원본 폴더)

c:\python\code\source\subfolder   (원본폴더 서브 폴더)

c:\python\code\target   (복사할 폴더)

c:\python\code\zipfile    (압축 파일 떨굴 폴더)

 

 

  c:\python\code\source 폴더에 png 파일 한개와, txt 파일 한개, ini 파일 한개, exe 파일 한개를 넣어보자. 저 같은 경우 아래 같이 넣었다.

mypic.png
win.ini
winhlp32.exe
사고싶은 책.txt

 

  c:\python\code\source\subfolder 에는 txt 파일 한개를 넣어보자(원래 샘플 파일을 첨부 할까 하다가 왠지 사람들이 다운받기는 찜찜해 할 듯 싶어서 직접 만드는 것으로 했다). 아래와 같이 넣었다.

     사고싶은 책 2.txt

 

  png 파일은 캡쳐 도구 등으로 만들면 되고, txt 파일은 메모장 등으로 적당히 만들면 되고, ini, exe 파일은 c:\windows 폴더에서 복사(시스템 파일이니 옮기면 안된다 복사!!)해서 장만했다. 이렇게 여러 개의 확장자를 가진 파일을 만드는 이유는, 이 중 2개 정도의 확장자를 가진 파일만 선택적으로 압축하는 예제를 보여주고 싶어서이다. 실제 소스등을 백업하려다 보면 용량만 차지하고 백업은 필요없는 파일도 있기 때문에 제외하기 위해서 이다. 뭐 대충 아래와 같은 모양이다.

 

 

 

 

[파이썬 코드 만들기]

  이제 모든 밑작업이 완료 되었으니, 코드 작성을 시작해 보자. 우리가 만드려는 기능을 위해 현재 필요한 사항들을 아래와 같다.

  1) 특정 폴더에서 특정 확장자들의 파일만 서브 디렉토리 까지 포함해서 zip 으로 압축 하기

  2) 압축이 끝날때까지 기다리기

  3) 압축 된 내용을 ftp 에 올리기

  4) 7일 지난 오래된 zip 파일은 하드 용량을 줄이기 위해 삭제 하기

 

  모든 내용이 다 앞의 수업에서는 다루진 않았던 거니 하나하나 살펴보도록 하자.

 

 

[zip 만들기와 기다리기 구현]

  우선 zip 을 만드는데, 2가지 제약 사항이 있다. 첫째는 서브 폴더까지 포함되서 압축이 되야하며, 둘째는 특정 확장자 파일만을 선택 적으로 압축해야 한다. 구글에서 'python zip only extension' 라고 찾으면 아래의 python 에서 사용하는 zip 모듈이 나온다.

https://docs.python.org/2/library/zipfile.html 

 

  해당 모듈을 사용해서 특정 확장자를 포함하려 하면, 아래와 같은 스택 오버플로우에 나오는 방식처럼 특정 확장자를 하나 하나 이름에서 체크하면서, 압축 하거나 풀어야 할것 같다.

http://stackoverflow.com/questions/41965026/extracting-all-the-files-of-a-selected-extension-from-a-zipped-file

 

 

  좀더 간단하게 명령어 하나로만 완료해 보려고 7-zip을 이용하기 위해서, 다시 구글에서,  '7zip include extensions' 이라고 찾았다.

https://superuser.com/questions/409456/how-do-i-use-7-zip-to-add-a-folder-to-an-archive-including-only-files-with-cert

 

  그런데 위에 나타난 방법은 7-zip 명령어 하나만 사용해 구현할 수 있지만, 우리가 원하던 바와는 반대로 특정 확장자들을 포함 하는게 아니라, 특정 확장자들만 제외하는 것이다. 빼야하는 확장자를 잘 알고 압축해야 할 확장자를 잘 모를 경우엔 유용하지만, 반대로 압축해야 될 확장자를 잘알고, 빼야하는 확장자가 많거나 늘어날 수 있다면 조금은 귀찮은 일이다.(뭐 빼는 것도 나쁜건 아닌거 같지만 이렇게 가정해 보자^^) 

 

 

 

  일단은 원하는 확장자만를 압축하는 방식을 찾기 위해서 조금 더 검색을 해본다. 구글에서 '7zip only some extensions' 라고 검색해서 아래와 같이 2개의 페이지를 보다보면. cmd 에서 이용할 수 있는 for 명령어를 사용해서 특정 확장자를 가진 각 파일들을 하나씩 선택해서, zip 으로 만드는 예제가 있다. 그래서 왠지 구현이 잘 될거 같아서, 아래와 같이 만들어 실행해 보았다(몇 가지 테스트를 해보니 아래 페이지들에서 제시된 명령어 그대로 실행 하려면 bat 파일 안에서만 수행이 되며, cmd 창에서 바로 호출해 쓰려면 인자의 %% 를 %로 바꾸어야 한다)

https://superuser.com/questions/923775/7zip-batch-compression-for-a-specific-file-extension-in-different-folders

http://stackoverflow.com/questions/19848192/a-batch-script-to-zip-only-certain-types-of-files 

1
for /R C:/python/code/source %x in (*.txt *.png) do ("c:\Program Files\7-Zip\7z" a "myzip.zip" "%x" )
cs

 

  cmd 창에서 아래 명령을 입력한다.

c:\Python\code>for /R C:/python/code/source %x in (*.txt *.png) do ("c:\Program Files\7-Zip\7z" a "myzip.zip" "%x" )

c:\Python\code>("c:\Program Files\7-Zip\7z" a "myzip.zip" "C:\python\code\source\사고싶은 책.txt"  )

7-Zip [64] 16.04 : Copyright (c) 1999-2016 Igor Pavlov : 2016-10-04

Open archive: myzip.zip
......
Files read from disk: 1
Archive size: 9223 bytes (10 KiB)
Everything is Ok

 

  압축된 zip 파일을 보면 zip 명령어는 넘어온 파일들의 상대 경로를 따지지 않아서, 모든 파일이 하나의 폴더에 들어가 있다. 아래의 그림을 보면 alzip 으로 열었을때, 서브폴더의 파일도 정상적으로 가져오긴 하지만 원래는 source 폴더 안의 subfolder 에 들어가 있어야 할 '사고싶은 책 2.txt' 가 루트에 '사고싶은 책.txt'와 같이 저장되 있는게 보일 것이다. 그럼 해당 방식은 디렉토리 구조를 유지 못하고, 만약 이름이 같은 파일이 있을 경우 충돌이 날테므로 사용할 수 없다.

 

 

 

  그럼 명령어 하나로는 조금 힘들겠다고 생각하고, 2개로 나누어 실행하는 방식으로 다시 전략을 짜보자. 첫번째는 아까 만들어 놓은 target 폴더로 특정 확장자들을 가진 파일들만을 폴더 구조를 유지하면서 복사 후, 7-zip 을 이용하여 원하는 파일만 들어있는 target 폴더를 압축하면 된다. target 폴더의 파일과 디렉토리 들은 압축 완료 후 삭제하여 처음 상태로 리셋하면 된다. 만약 백업 대상 파일 용량이 크거나 숫자가 많아 복사가 부담스러운 경우엔 맨 처음에 찾아봤던 특정 확장자 들을 제외하는 방식으로 구현해도 좋겠지만, 그 경우는 zip 방식이 아닌 폴더 자체를 원격지 폴더와 싱크 시키는 다른 프로그램 등을 이용하는게 더 날지도 모르겠다.

 

  그럼 일단 특정 확장자들만 복사하는 명령어를 찾기위해 구글에서 이것저것 헤메다가 'xcopy copy only certain file extensions' 로 검색해, 앞에 보았던 for 명령을 이용해 파일을 xcopy 명령어로 넘겨서 복사하는 코드를 찾았다.

http://stackoverflow.com/questions/15753420/how-to-copy-specific-file-types-from-one-folder-into-another-folder 

1
for %x in (png txt) do xcopy "c:\python\code\source\*.%x" "c:\python\code\target\" /S /Y
cs

  위의 xcopy 옵션은 아래와 같다. 옵션이 궁금하면 cmd 창에서 'xcopy /?' 를 입력해 본다. 

 /S           비어 있지 않은 디렉터리와 하위 디렉터리를 복사합니다.

 /Y           기존 대상 파일을 덮어쓸지 여부를 묻지 않습니다.

 

 

  해당 명령어를 cmd 창에 입력하면 앞의 zip 과는 달리 다행히 정상적으로 서브 폴더까지 유지되면서 특정 확장자만 복사가 된다.

c:\Python\code>for %x in (png txt) do xcopy "c:\python\code\source\*.%x" "c:\python\code\target\" /S /Y

c:\Python\code>xcopy "c:\python\code\source\*.png" "c:\python\code\target\" /S /Y
C:\python\code\source\mypic.PNG
1개 파일이 복사되었습니다.

c:\Python\code>xcopy "c:\python\code\source\*.txt" "c:\python\code\target\" /S /Y
C:\python\code\source\사고싶은 책.txt
C:\python\code\source\subfolder\사고싶은 책 2.txt
2개 파일이 복사되었습니다.

 

 

  그럼 이제 target 폴더만 이제 전체 압축하면 된다. 7-zip 명령어를 사용하는데 이왕이면 'backup_현재날짜.zip' 으로 날짜를 포함하게 압축파일 이름을 지정하고 싶어서 구글에서 'cmd file name date' 로 검색해 아래 페이지와 참조해서 명령어를 만들어 낸다.

http://stackoverflow.com/questions/4984391/cmd-line-rename-file-with-date-and-time

1
"c:\Program Files\7-Zip\7z" a c:\python\code\zipfile\backup_"%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%".zip c:\python\code\target
cs

 

  위의 명령어를 cmd 창에 입력하면, 우리가 원하는 데로 정상적으로 압축되는 것을 볼 수 있다. 

c:\Python\code>"c:\Program Files\7-Zip\7z" a c:\python\code\zipfile\backup_"%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%".zip c:\python\code\target

7-Zip [64] 16.04 : Copyright (c) 1999-2016 Igor Pavlov : 2016-10-04

Open archive: c:\python\code\zipfile\backup_20170423.zip
...
Files read from disk: 3
Archive size: 9557 bytes (10 KiB)
Everything is Ok

 

 

  그럼 cmd 상으로 위의 명령어를 순차적으로 실행 시키면, 특정 확장자를 가진 파일들이 target 파일로 복사 된후, 'backup_현재날짜.zip' 로 압축되는 작업이 일어난다. 그럼 이젠 이용할 외부 로직은 다 해결 났으니, python 에서 해당 두 명령어를 순차적으로 실행해 주면서, 각각의 명령어가 끝날때까지 다음 코드의 실행을 안하고 기다리게 해야 한다. 구글에서 'python run windows cmd' 라고 검색하면 subprocess 모듈에 있는 'check_output' 명령어를 사용해 보라고 권한다. 근데 문제는 해당 subprocess 모듈로 cmd 명령을 실행 했을때, 실행한 명령이 끝날때까지 파이썬이 다음 코드를 실행 안하고 기다려 줄까 하는 부분이다.

http://stackoverflow.com/questions/14894993/running-windows-shell-commands-with-python

 

  해당 문제를 확인 하기 위해 'python subprocess check_output' 로 검색해 아래 파이썬 라이브러리 메뉴얼을 보면, 다행이 실행한 서브 프로세스가 끝날 때까지 기다린다고 한다(하나의 프로그램이 생성되면 윈도우 내부에 프로세스가 생성되고, 해당 프로세스가 다른 프로세스를 실행 했을때 해당 프로세스를 subprocess 또는 자식 프로세스라고 그런다). call 명령어는 성공 실패 코드만 반환해 가져오는 듯 하고, check_output 은 화면 출력등의 메시지도 받아 올수 있나 보다.

https://docs.python.org/2/library/subprocess.html

 

 

  그럼 첫번째 복사하는 cmd 명령어를 python 의 코드를 이용해서 호출해 동작하는지 검증해 보자.

1
2
3
4
import subprocess 
from subprocess import check_output
 
check_output('for %x in (png txt) do xcopy "c:\python\code\source\*.%x" "c:\python\code\target\" /S /Y', shell=True) 
cs

 

  위의 코드를 복사하여 'c:\python\code' 폴더에 'copytest.py' 라는 이름으로 'uft-8' 포맷으로 저장한다. 실행을 하면 계속 무한 루프가 나면서 이상하게 종료가 안된다. 'ctrl+z' 로 종료를 해본다.

c:\Python\code>python copytest.py

 

  흠 cmd 창으로 그대로 잘 실행됬던 명령어를 그대로 파이썬에서 실행했는데 에러가 나는 듯 하다. 왜 그런가 하고 상상의 나래를 펼치다가 보니 뭔가 하나 걸리는 게 있긴 하다. escape 문자 이다. escape 문자는 보통 어떤 언어에서든 문법적 요소로 쓰는 문자를 사용자의 문자열 안에 넣고 싶을때 해당 문자 앞에 적어주는 회피 문자 이다. 그 회피 문자를 보고 해당 언어는 이 문자가 문법 문자가 아닌 일반 표시 문자라는 것을 인식한다. 구글에서 'python string Double Quotation' 로 검색하면, 아래의 페이지에 escape 문자를 쓰는 방법이 나온다. 예를 들어 " 문자는 \" 로 넣어주어야 하는거다. (한국어 키보드 \(금액 표시) 문자가 밑의 예제 코드에 나오는 역 슬레쉬 이다). escape 문자는 '\n' 같이 키보드로 표시 못하는 기호를 표시할때도 쓴다.

https://docs.python.org/2.0/ref/strings.html

 

  이것 저것 조정해 보다보니 아래 코드로 정리됬다.

1
2
3
4
import subprocess 
from subprocess import check_output
 
check_output('for %x in (png txt) do xcopy \"c:\\python\\code\\source\\*.%x\" \"c:\\python\\code\\target\\\" /S /Y', shell=True) 
cs

 

  다시 실행해 보니 정상적으로 복사가 된다. 그럼 압축 하는 코드 까지 같이 적용해 만들어 보면 아래의 코드가 된다.

1
2
3
4
5
6
import subprocess 
from subprocess import check_output
 
check_output('for %x in (png txt) do xcopy \"c:\\python\\code\\source\\*.%x\" \"c:\\python\\code\\target\\\" /S /Y', shell=True) 
 
check_output('\"c:\\Program Files\\7-Zip\\7z\" a c:\\python\\code\\zipfile\\backup_\"%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%\".zip c:\\python\\code\\target', shell=True)
cs

 

  아래와 같이 실행 하면, cc:\Python\code\zipfile\ 폴더안에 backup_20170423.zip 파일이 생성되게 된다(날짜는 실행한 날짜별로 달라진다).

c:\Python\code>python copytest.py

 

 

 

[ftp 업로드 코드 만들기]

  그럼 만들어진 파일을 ftp 로 업로드하는 코드를 만들어 보려 한다. 구글에서 'python ftp upload' 로 검색을 하면, 아래의 스택오버플로우 페이지에서 바이너리와, 텍스트 형태의 파일을 각각 저장하는 ftp 샘플이 보인다.

http://stackoverflow.com/questions/17438096/ftp-upload-files-python

 

  추가로 만들어진 zip 파일 이름이 'backup_yyyymm' 형식이기 때문에 구글에서 'python filename date' 라고 검색하여 아래 페이지에서 datetime 모듈을 통해 날짜로 이름을 만드는 코드를 얻을 수 있다.

http://stackoverflow.com/questions/10607688/how-to-create-a-file-name-with-the-current-date-time-in-python

 

  두 개의 코드를 합쳐서 아까의 만들어진 zip 파일이 올라가는 코드를 만들어 보면 아래와 같다. 이미 zip 파일은 만들어져 있을테니 위의 압축하는 코드는 일단 제외하고 ftp 코드 부분만 만들어 검증해 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#-*- coding: utf-8 -*-
import ftplib
import os
from datetime import datetime
 
# datatime 모듈을 사용하여 오늘의 압축 파일 이름을 생성
filename = "backup_" + datetime.now().strftime("%Y%m%d"+ ".zip"
 
# ftp에 연결
ftp = ftplib.FTP("127.0.0.1")
ftp.login("pyftpuser""test1234")
 
# ftp 에서 myback 폴더로 이동함
ftp.cwd("/mybackup")
 
# zip 파일이 있는 폴더로 이동
os.chdir(r"c:\python\code\zipfile")
 
# 바이너리 형태로 파일을 업로드 함
ftp.storbinary("STOR " + filename, open(filename, 'rb'))
 
print ('upload completed')
cs

 

  위의 코드를 'c:\python\code' 폴더에 'utf-8' 인코딩으로 'ftpuplodtest.py' 라고 저장 후 실행해 본다.

c:\Python\code>python ftpuplodtest.py
upload completed

 

  위와 같이 업로드가 완료되었다고 출력되며, ftp 로 로그인 시 사용자에게 할당된 폴더인, 'c:\Python\code\ftproot\mybackup' 폴더로 가보면 backup_20170423.zip 파일이 업로드되어 있다(하나의 컴퓨터에서 ftp 서버를 같이 실행해서, 로컬 경로에서 확인을 하긴 했지만, 실제 프로그램 입장에서는 원격에 있는 ftp 폴더에 업로드 된 것이다)

 

 

[임시파일 삭제와 오래된 zip 파일 삭제]

  target 폴더안에 복사된 폴더와 파일들이 압축 후에는 필요 없기 때문에, 모두 삭제 하기 위해서 구글에서 'delete file and directory in directory' 를 찾았는데, powershell 명령어 이외에는 파일만 지우거나, 폴더 자체를 삭제 하는거나 둘 중에 하나만 가능한 것 같다. 밑에 제시된 바와같이 target 폴더를 지우고 같은 이름의 빈 폴더를 다시 생성하는 방법으로 구현하려 한다(파일 삭제를 테스트 할때는 조심히 테스트 폴더를 만들어 그 안에서만 명령어를 사용하는게 좋다. 저도 code 폴더에서 이것저것 시도해 보다가, 만들어 놨던 샘플 파일들을 싹 지워 버려 블로그에서 주섬주섬 복원해야 했다 --;). 리눅스 rm 명령 처럼 사용법이 모호한 상태에서 잘못 날리게 되면 시스템 파일과 같은 중요한 파일들을 다 지워 버릴 수도 있다.)

1
2
3
4
5
6
7
8
9
#-*- coding: utf-8 -*-
import subprocess 
from subprocess import check_output
 
# target 폴더를 통채로 삭제
check_output('rd /s /q c:\\python\\code\\target', shell=True) 
 
# target 폴더 다시 생성
check_output('md c:\\python\\code\\target', shell=True)
cs

 

  위와 비슷하게 c:\python\code 폴더에 utf-8 인코딩으로 deltest.py 라고 저장해 실행하면 target 폴더 전체를 삭제 된 후, 같은 이름의 빈 폴더를 생성한 것을 볼수 있다.

c:\Python\code>python deltest.py

 

 

  마지막으로 오래된 zip 파일들을 삭제하는 코드이다. 여기서는 7일이 지난 파일을 삭제한다고 해보자. 구글에서 'cmd delete file older than' 이라고 찾으면 아래 스택오버플로우 페이지가 나온다. forfiles 라는 명령어를 통해 7일 이내의 파일을 찾아 각각의 파일에 대해서 del 로 삭제 하는 코드는 아래와 같다. (비슷하게 escape 처리를 했는데, " 문자는 ' 안에 들어 있을때는 굳이 escape 처리를 안해도 되는듯 해서 가독성을 위해 제외했다).

http://stackoverflow.com/questions/51054/batch-file-to-delete-files-older-than-n-days

1
subprocess.call('forfiles /p "c:\\python\\code\\zip " /s /m backup*.zip /d -7 /c "cmd /c del @path"', shell=True)
cs

 

  'check_output' 대신 'call' 을 사용한 이유는 1주일 이상된 파일이 없어 forfiles 결과가 없을 경우엔 에러가 나버리는데, 해당 에러를 출력하는 부분이 파이썬 코드에 영향을 주어서 아래와 같은 오류가 나서, 이것저것 고민하다가 call 은 에러가 나도 실행에 관계가 없어서 대체 했다. 
오류: 검색 조건에 해당되는 파일을 찾을 수 없습니다.
Traceback (most recent call last):
  File "deltest.py", line 7, in <module>

 

 

[최종 코드]

그럼 긴 헤멤을 거쳐서 만들어진 최종 코드는 아래와 같다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#-*- coding: utf-8 -*-
import ftplib
import os
from datetime import datetime
import subprocess 
from subprocess import check_output
 
# 압축 코드
# png, txt 파일만 source 폴더에서 target 폴더로 복사한다. 
check_output('for %x in (png txt) do xcopy \"c:\\python\\code\\source\\*.%x\" \"c:\\python\\code\\target\\\" /S /Y', shell=True) 
 
# 7-zip 을 이용해 backcup_yyyymmdd 형식으로 압축을 한다.
check_output('\"c:\\Program Files\\7-Zip\\7z\" a c:\\python\\code\\zipfile\\backup_\"%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%\".zip c:\\python\\code\\target', shell=True)
 
print ('zip completed')
 
 
# ftp 코드 
# datatime 모듈을 사용하여 오늘의 압축 파일 이름을 생성
filename = "backup_" + datetime.now().strftime("%Y%m%d"+ ".zip"
 
# ftp에 연결
ftp = ftplib.FTP("127.0.0.1")
ftp.login("pyftpuser""test1234")
 
# ftp 에서 myback 폴더로 이동함
ftp.cwd("/mybackup")
 
# zip 파일이 있는 폴더로 이동
os.chdir(r"c:\python\code\zipfile")
 
# 바이너리 형태로 파일을 업로드 함
ftp.storbinary("STOR " + filename, open(filename, 'rb'))
 
print ('ftp upload completed')
 
 
# 삭제 코드
# target 폴더를 지움
check_output('rd /s /q c:\\python\\code\\target', shell=True) 
 
# taget 폴더를 재생성
check_output('md c:\\python\\code\\target', shell=True)
 
# 7일 이상 된 로컬의 backup 일을 삭제 함
subprocess.call('forfiles /p "c:\\python\\code\\zipfile " /s /m backup*.zip /d -7 /c "cmd /c del @path"', shell=True)
 
print ('del completed')
 
cs

 

  해당 내용을 'c:\python\code' 폴더에 'utf-8' 인코딩으로 'fileftpbackup.py' 파일로 저장 후 아래와 같이 실행해 본다.

c:\Python\code>python fileftpbackup.py
zip completed
ftp upload completed
오류: 검색 조건에 해당되는 파일을 찾을 수 없습니다.
del completed

 

 

[해당 기능을 주기적으로 돌리기]

  해당 기능을 주기적으로 돌리는 부분도 반복 주기가 짧다면 파이썬의 timer 모듈 등을 사용하여 구현할 수도 있겠지만, 하루에 한 번이라든지 몇 시간에 한번씩 돌릴 예정이라면, 파이썬을 계속 실행시켜 놓는 것보단, 윈도우즈 경우 작업 스케줄러에 걸어놓음 컴퓨터가 재시작 해도, 알아서 지정된 시간이나, 간격으로 돌아가기 때문에 딱히 신경 안 쓰고 반복적으로 실행할 수 있다. 내용이 넘 길어 졌으니 해당 내용은 구글에서 '윈도우즈 10 작업 스케줄러' 로 찾아서 아래와 같은 블로그를 보면 된다. 실행할 프로그램은 'python c:\python\code\fileftpbackup.py' 식으로 등록하면 될듯 하다.

http://blog.naver.com/PostView.nhn?blogId=celine2011&logNo=220673965249&beginTime=0&jumpingVid=&from=search&redirect=Log&widgetTypeCall=true

 

  참고로 만약 실행시 콘솔이 뜨는게 싫다면 아래 안내된 바와 같이,

소스 확장자를 pyw 라고 바꾸어 실행만 하면, pythonw.exe 가 실행이 되면서 콘솔 창이 없는 사일런스 모드로 실행 되는 듯하다.

http://stackoverflow.com/questions/764631/how-to-hide-console-window-in-python

 

 

 

[마무리 하면서]

  어찌 보면 그다지 복잡하지 않은 기능을 꼬아서 복잡하게 설명한 것 같기도 하다 --; 요지는 작업 자동화를 할때 꼭 해당 언어의 스콥 안에서 작업할 필요는 없다는 것이다. 그러다 보면 오히려 외부 cmd 방식의 명령어를 이용하면 좀더 쉽게 구현할 수 있는 코드를 복잡하게 구현하게 되면서 시간이 낭비될 수도 있다. 특히 파일이나 디렉토리 등을 기반으로 하는 작업들은 for 나 forfiles 같이 일괄로 작업하는 명령어 들이나 powershell 기반의 명령어들이 있으니, 가능한 필요한 기능을 구글에 검색해서 가져다 쓰는 것도 좋은 것 같다. 특별한 사정이 없는 이상 내부 모듈이 있다고 꼭 그 모듈을 쓸 필요는 없다고 생각한다. 여튼 이렇게 해서 자동화 시리즈를 마무리 하고, 다음 시간에는 파이썬으로 접근해 보는 머신러닝에 발을 살짝 담궈보기 위한 사전 작업으로 파이썬의 몇몇 수학 라이브러리들이 하는 일들을 살펴보려고 한다.

 

 

[보충]

  최근에 내용을 다시 리뷰하다가, 7zip 으로 여러확장자의 파일이 디렉토리 구조까지 유지하면서 압축 가능하다는 부분을 알게 되었습니다(어쩐지 그런 명령어가 없는게 이상하긴 했습니다--;)

 

 왜 글 쓰는 시점에서 놓쳤는지 모르겠지만, 아래 링크를 보시면 있습니다.

https://stackoverflow.com/questions/28636349/7zip-cli-whitelist-files-to-add-by-extension

 

이 글의 샘플을 예로들면 아래와 같습니다. 삽질을 했네요;

c:\"Program Files"\7-Zip\7z a -r -tzip test.zip c:\python\code\source\*.txt c:\python\code\source\*.jpg

 

 

2017.4.23 by 자유로운설탕
cs
posted by 자유로운설탕
2017. 3. 26. 20:29 프로그래밍

  이번 시간은 웹 자동화에 대해서 얘기하려 한다. 자동화에 대한 몇 가지 생각들을 얘기 한 후, 셀레늄(selenium)의 구조를 설명 후, 구글에서 특정 키워드를 검색하여, 상위 5개의 결과 링크를 새로운 탭들에  로딩하는 예제를 구현 후, 이를 다시 보이지 않는 유령 웹브라우저인 PhantomJS를 이용해 호출하여, 몇 가지 생각해 볼 만한 주제들에 관해서 이야기를 하려한다. 이후 부록으로 IE 를 통해서 같은 구현을 하는 부분을 얘기한 후 마무리 하려 한다. 

 

 

[목차]

0. 왜 파이썬 공부에 구글을 이용하는게 좋은가?

1. 언어를 바라보는 방법. 파이썬을 어떻게 바라봐야 할까?

2. 파이썬 설치와 환경, 버전 선택 하기의 이유.

3. 만들고자 하는 기능을 모르는 조각으로 나눠 조사해 보기

4. 데이터 베이스에서 내용 가져와 출력하기

5. 암호화 모듈을 이용해 암복호화 해보기

6. 퍼즐 조각들을 합쳐보기

7. 엑셀 파일 사용해 보기 -> 부록 : fuction 을 이용해서, 코드 정리해 보기

8. 정규표현식을 왜 사용해야 할까? 언어속의 미니 언어 정규표현식 살펴보기

9. 입력과 결과를 GUI 화면과 연결해 보기

10. Whois API 이용해 보기

11. 웹페이지 호출해 내용 파싱 하기(BeautifulSoup 그리고 한계)

12. 자동화 - 웹 자동화(with Selenium)

13. 자동화 - 윈도우즈 GUI 자동화(with pywinauto)

14. 자동화 - 작업 자동화

15. 수학 라이브러리 살펴보기

16. 그래픽 라이브러리 살펴보기

17. 머신러닝에서의 파이썬의 역활

18. 웹 프로그래밍 - Legacy Web

19. 웹 프로그래밍 - Flask 살펴보기(feat. d3.js)

20. 웹 프로그래밍 - Django 살펴보기

21. 정리 - 이런저런 이야기

 

 

 

[들어가면서]

  '자동화'란 무엇일까? 자동화의 반대말에 해당하는 '수동화'를 먼저 생각해 보자. '수동화'란 결국 사람이 무언가를 하는 것, 또는 사물이 사람의 도움을 통해서 움직이는 것을 얘기한다고 볼 수 있다. 그럼 자동화는 사람의 손을 거치지 않고도 사람이 원하는 동작을 하는 것을 의미하는 단어라고 볼수 있다. 사실상 자동화는 우리가 사용하는 프로그래밍 언어나, 윈도우즈 같은 운영체제 자체도 자동화가 구현된 예라고 봐도 무방할 듯하다. 운영체제는 예전의 기계식 컴퓨터와 비교한다면 전원만 넣어주면, 사람의 행동에 따라서 반응하여 동작을 해주는 인터렉티브한 자동화 구현체라고 볼 수 있다. 더 확장하면 우리가 현실의 어떤 일을 하는데 사용하는 패턴이나 노하우 같은 부분도 실체화 되있지는 않지만 자동화라고 볼 수 있을것 같다.

 

  그렇다면 자동화를 구현하는데 있어서 가장 필수적이면서도 어려운 요소들는 무얼까? 일반적으로 자동화는 초기화 단계에서는 '스스로 움직이는 것'이 가장 중요하다. (잘은 모르지만) 처음 로봇이나 자율주행 자동차를 만든다면, 사람이 수동으로 운전하는 것처럼 자동으로 움직이고, 뛰고, 코너링을 돌고, 멈추고 하는 기능들이 필요할 것이다. 그런데 해당 부분의 요소가 어느정도 해결나게 되는 순간 새로운 차원의 어려운 문제가 발생하게 된다. 그것은 '환경'과의 상호동작 이라는 요소이다. 만약 자율주행 자동차 라면 도로의 커브라든지, 앞차와의 간격, 도로 노면 상태, 기후 상태, 보행자 상태, 교통 신호 등등 여러가지 주행 중 만날 수 있는 환경적인 부분의 차이를 인지하여, 해당 부분에 대해서 자동차 스스로 가지고 있는 여러가지 자동화 능력(전진, 후진, 회전, 브레이크 등등..)을 적용할지를 결정해야 한다(이런 부분에서 요즘 한참 유행하는 딥러닝 같은 요소들이 끼어 들수도 있을 듯하다). 그럼 그러한 환경을 인지하기 위해서는 어떻게 해야 할까? 우리가 현재 개발해 놓은 센서, 또는 그러한 환경을 인지하기 위한 새로운 타입의 센서가 있어서 원하는 환경 요소를 구조적인 소프트웨어적인 정보로 변환하여 제공하여야 할 것 이다.

 

  그럼 위의 가정에서 자동화를 위한 소프트웨어에서 필요한 부분을 유추해 보면, 스스로 동작하게 하는 기능들, 환경을 인지해서 필요한 동작을 판단하게 할 수 있는 센서들이 필요하다. 전자는 셀레늄 같은 자동화 모듈의 API 들과, 파이썬, 자바 같은 범용적인 언어의 프로그램 로직으로서 커버되고, 두번째는 순수하게 자동화 모듈에서 제공하는 여러가지 센서와 동작 API 로 이루어지게 된다. 아래는 지금까지 얘기한 내용을 도시한 것이다. 

 

 

 

 

[자동화의 종류]

  개인적인 의견으로 자동화는 몇가지 타입으로 나누어 볼수 있다.

 

  1 번째는 가장 원초적인 '화면 좌표를 기준으로 한 자동화' 이다. 온라인 RPG 게임의 레벨업, 스킬업 노가다 같은 부분을 macro express 나, auto mouse 같은 프로그램으로 자동화 하는 것 같은 작업이다. 특정 프로그램 창 내의 특정한 위치를 반복적으로 클릭한다든지, 키보드 이벤트 명령을 이용해서 원하는 키를 입력한다든지, 화면의 특정한 좌표의 칼라를 기준으로 판단하여 캐릭 및 몹들의 위치를 판단하여 특정 이벤트를 일으킨다는지 하는 부분 말이다. 이런 방식의 단점은 UI의 변경이 일어나면, 화면 배치상의 좌표와 칼라 등이 달라질 가능성이 높기 때문에, 그에 따라 전체적인 자동화 코드의 수정이 필요해 질 수도 있다는 것이다. 다만 많은 변경이 없거나 변경 주기가 긴 프로그램등을 대상으로 했을 때는 적절히 정확한 동작을 보여주게 된다. 게임 패킷 등을 직접 조작하여 자동화를 구성하는 경우도 있는 것은 같지만, 그 부분은 일반적인 자동화의 영역이라기 보다는 해킹 작업에 가까운것 같다.

 

 

  2 번째는 셀레늄과 같은 자동화 모듈을 이용하는 '웹 자동화' 이다. 웹 자동화의 구현은 이전에 소개한 html, xml 파싱 모듈인 beautifulsoup 의 구현 방식과 많이 유사하다. 해당 페이지 내의 태그를 인지하거나, 원하는 값을 가져오는 방식은 거의 비슷하고, 추가적으로 특정한 행동을 해당 태그 개체에 일으킬 수 있다(텍스트 박스에 검색어를 넣거나, 특정 버튼을 누른다든지 - 실제 사람이 해당 버튼을 누를때 브라우저에서 일어나는 이벤트와 비슷하게 구현한다고 보면 될듯 하다). 또한 브라우저와 상호 작용하여 움직이기 때문에, 사람이 실제 사용하는 것과 거의 비슷하게 돌아간다고 볼 수 있어, UI 자동화 테스팅 같은 영역에서 시작하게 되었다. 

 

  좌표 방식의 자동화와는 달리 UI 가 조금 달라진다고 해도, 내부 페이지의 속성 등만 잘 유지되면, 코드의 수정이 필요 없을 가능성이 높은 반면, 화면의 모양은 같아도 내부 코드 구조가 달라져 안 돌아 갈수도 있다. 또 웹의 특성상 네트워크나 시스템 부하에 따라 응답 속도 차이나, 타 어플리케이션 또는 사용자의 마우스, 키보드 사용에 의한 이벤트 방해 등으로 동작이 멈춰버리는 등 미묘하고 골치아픈 문제들을 종종 만날 수 있다. 또한 호환이 안되는 웹 확장 컴포넌트나 플래시 컴포넌트 등이 자동화의 대상일 경우에는 표준 적인 접근이 안되어 최후의 수단인 좌표로 해결할 수 밖에 없게되서, 좌표 기준 구현과 비슷한 레벨의 유지보수 문제가 생길수도 있다. 또 단순한 숫자 입력을 벗어나 점점 어려워지고 있는 캡챠 화면이나 은행에서 사용하는 랜덤한 번호키, 보안 프로그램 등과 연관되어 인식이 힘든 문제가 발생할 수 있다. 실제 화면을 봐야될 필요가 없는 경우는 뒤에 소개할 PhantomJS 같이 화면이 표시되지 않는 브라우저를 이용하는 방식도 있는 듯 하다.

 

 

  3 번째는 '윈도우즈 자동화' 이다. 윈도우즈 자동화도 어찌보면 웹 자동화와 거의 비슷한데, 근본적인 차이라면 웹 자동화에서 웹의 여러 요소(태그, 어트리뷰트, DOM 구조)를 기준으로 인식하던 부분을, 윈도우 창들의 여러 특성(타이틀, 캡션, 클래스 등등)을 기준으로 인식한다는 점만 틀리다. 아무래도 브라우저에 비해서 응답 지연에 의한 타이밍 문제가 생길 가능성이 낮으나, 비슷한 통신을 하는 FTP 프로그램의 자동화와 같이, 껍데기는 앱 형태긴 하지만 실제 뒷단에서서의 실제적인 동작은 서버 클라이언트 통신인 경우엔 비슷하거나 더 어려운 문제가 발생할 수도 있다.

 

 

  4 번째는 가장 범용적인 부분으로 '작업의 자동화' 이다. 소스를 특별한 주기로 백업 및 압축하여 FTP, NAS 등에 올린다든지 하는 수동으로 하다보면 잊어버릴 수도 있고, 귀찮은 작업들을 해결하는 부분이다. 공장 자동화도 넓게보면 같은 영역에 포함될 것이다. 해당 부분은 1, 2, 3 번의 자동화와 연동 하여 동작할 수도 있고, OS 명령어나, 7-zip 과 같은 외부 프로그램, 외부 API, 다른 여러가지 모듈들을 조합하여 동작 될 수 있다. 4번 영역은 만들때는 귀찮거나 난이도가 어려울 순 있지만, 한번 만들어 놓으면 특별한 변경 작업 없이 계속 잘 돌아가 자동화에 대한 ROI 가 잘 증명되는 영역인 것 같긴하다. 어찌보면 머신러닝이나 딥러닝 같은 분야도 약간 고급 버전의 데이터 기반의 행동 자동화라고 봐도 되지 않을까 싶다. 결론적으로 처음에 얘기한 것 같이 우리가 PC나 스마트폰 등의 OS 상에서 행하는 모든 작업은, 또 어쩌면 우리의 사고 조차도 자동화의 요소들을 어느 정도 담고 있다고 생각한다.

 

 

  뭐 이외에도 성능 테스트 툴 등에서 쓰는 URL 기준의 자동화나, 유닛 테스트 등 포함되지 않은 다른 영역이 있을진 모르지만, 나름 4가지 분류에 대충 일반적인 자동화는 언급 되었다고 생각한다. 괜찮은 느낌을 받은 아래 쿠키런 자동화 슬라이드를 봐보자.
https://speakerdeck.com/sgonv/pythoneuro-kukireon-unyeonghagi

 

 

 

 

[Selenium 개론]

  그럼 파이썬 웹 자동화 이야기에서 단골로 등장하는 셀레늄(selenium)이란 모듈은 뭘까? 개인적으로 7~8년 전 쯤에 테스트 자동화 업무에 조금 발 담그고 있는 편이였는데, 그때는 셀레늄 같은 오픈 소스 보다는 QTP(Quick Test Professional - 현재는 unified functional testing 으로 제품명 변경)나 SilkTest 같은 상용 자동화 솔루션을 사용해서 많이 진행 했었다. 그 당시의 셀레늄은 거의 java 베이스로 사용하는게 대세였고, 딱히 브라우저 호환성도 적절하진 않아서, 내장된 자바스크립트 베이스의 API 를 포기하고 webdriver 라는 현재 통합된 오픈소스 모듈과 통합을 진행 하던 시기였다. 그때는 사실 해당 모듈을 지금 같은 일반적인 웹 접근 용도로 쓴다는 것은 상상을 못했었고, 상용툴 만큼 잘 동작 하지 못해서 아쉬울 뿐 이였다. 그런 연유로 python 공부를 하면서 다시 selenium 이라는 툴을 만나게 되서 많이 반가웠다. 셀레늄 변화에 대한 자세한 히스토리는 아래의 링크를 참조한다.    

http://www.seleniumhq.org/docs/01_introducing_selenium.jsp#brief-history-of-the-selenium-project

 

  현재 셀레늄은 우리나라 에서 주로 테스트 자동화 툴로 소개되거나, 크롤링 중 특정 문제(로그인)에 대해서 PhantomJS 브라우저 등을 이용하여 해결하는 용도로 소개되는 듯 하지만, 조금 더 범용적으로 접근하면 브라우저에서 반복적으로 하는 일을 자동화 할 수 있는 킬러 툴이 되는것 같다. 윈도우즈 쪽 자동화 모듈은 아직 살펴 보지 못했지만, 괜찮은 윈도우즈 자동화 모듈이 있다면 셀레늄과 조합해서 파이썬을 통해 사용하면 좋은 시너지가 날 듯 싶다.

 

 

 

  그럼 셀레늄 코드를 만들기 전에, 셀레늄을 어떻게 바라보면 좋은지에 대해서 간단히 얘기하려 한다(개인적으로 이렇게 생각하는 거고, 각자의 뷰에 따라 다르게 해석해도 괜찮다). 셀레늄은 크게 3개의 부분으로 나눠진다고 보면 된다. 

 

  1 번째는 원래 의도된 용도였던 UI 자동화 테스트를 위해서 Testcase 및 Testsuite(테스트 케이스를 그룹 지은 것) 관리 기능이 있다. UI 자동화 테스트를 제작하는 사람들은 해당 라이브러리들을 이용하여 케이스를 관리하고 실행한다. NUnit 프레임윅과 같은 역활이라고 보면 된다.

 

 

  2 번째는 검증(verification) 부분이다. 실행 결과로 나온 값이 예상했던 값과 같은지 체크하는 것이다. 1, 2 번 기능은 테스팅 영역에 가까우며 관련된 문서 경로는 아래와 같다.(단 해당 기능을 잘 이해 한다면 자동화 작업들을 관리하고 검증하는데 잘 사용할 수도 있을 것이다)

http://www.seleniumhq.org/docs/

 

 

  3 번째인 웹 드라이버(webdriver) 부분이 파이썬 자동화에서 보통 관심을 가지고 이용하는 부분이다. 웹 드라이버는 위에서 설명한 센서 API 및 기능 API 의 집합이라고 보면 되며, 전반적인 사용법은 이전 시간에 소개했던 beautifulsoup 과 유사하다(문법이 같다라기 보다 개념이 같다). 이 얘기는 결국 웹 드라이브를 잘 쓰려면, 웹 구조에 대해서 전반적으로 잘 이해하고 있어야 한다는 말이다. 웹 드라이버는 윈도우 장치 드라이버랑 유사하게, 브라우저에 상관없이 같은 코드로 돌아가게 되어있다. 즉 최초 웹 드라이버를 불러오는 부분에서 특정 브라우저를 지정하면 이후 나머지 코드는 (이상적으로는) 공통 적으로 사용할 수 있다. 윈도우에서 웹 드라이브는 보통 exe 형태의 실행파일로 제공되어 셀레늄과 브라우저를 연결 해주는 역활을 한다. 아래의 코드와 같이 초기 브라우저 지정만 선언하면 뒤의 코드는 모두 공통되게 돌아갈 수 있다는 개념이다. 그래서 web 'driver' 라고 하나 보다.

1
2
3
4
5
6
7
8
9
#-*- coding: utf-8 -*-
from selenium import webdriver
browser = webdriver.Firefox()
#browser = webdriver.PhantomJS()
#browser = webdriver.Ie()
 
type(browser)
 
browser.get("https://www.google.com")
cs

 

 

  4 번째로 이러한 자동화 라이브러리 들을 사용해 호출하는 언어가 파이썬 이다. 파이썬은 문법 요소를 기술할 뿐만 아니라, 자동화 모듈 이외의 다른 여러가지 유틸 모듈들을 조합하여, 자동화 구성을 풍성하게 만들어주는 기능 API 역활도 지원하게 된다(이건 나중에 얘기하겠지만 머신러닝에서도 같은 역활을 한다). 결국 셀레늄이라는 것은 자동화를 위한 라이브러리 묶음이다. 조금 더 공식적으로 얘기하면 UI 자동화 테스팅용 프레임윅이라고 볼수 있다. firefox 에서 selenium IDE 를 설치하게 되면 사용자의 액션을 자동으로 레코딩 하는 기능도 가지고 있는데, 큰 기대는 하지말고, 초기 코드를 만드는 용도로 사용해도 된다. 이번 시간에는 수동으로 코드를 만들며 레코딩 기능은 아래의 블로그를 참조하자. 

http://jeen.tistory.com/entry/Selenium-%EC%9B%B9%EC%84%9C%EB%B9%84%EC%8A%A4-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9E%90%EB%8F%99%ED%99%94%EC%99%80%EB%8A%94-%EB%B3%84-%EC%83%81%EA%B4%80%EC%97%86%EB%8A%94-%EC%9E%91%EC%97%85%EC%9D%84-%EC%9C%84%ED%95%B4

 

 

  위에서 얘기한 그림을 정리해 보면 아래와 같다.

 

 

 

 

 

[Firefox 를 이용한 구글 검색 후 Top 5 링크 새 탭에 열기]

 

  먼저 가장 셀레늄하고 궁합이 잘 맞는다고 여겨지는 파이어폭스 브라우저를 설치해서 자동화 코드를 만들어 보려 한다.  만들려는 기능은 첨에도 얘기했지만, "구글에서 특정 키워드를 검색하여, 상위 5개의 검색 결과의 링크를 새로운 탭들을 열어 각각 로딩" 하려고 한다. 먼저 우리가 해야하거나 모르는 작업들을 나열해 보자

  1) 파이어 폭스 설치

  2) 셀레늄 설치

  3) 파이썬에서 셀레늄을 이용해 동작하는 샘플 검증

  4) 구글을 열어 검색어 날리기

  5) 검색 결과 링크 중 상위 5개를 얻기

  6) 브라우저에 탭을 열어서 얻은 링크 5개를 각각 로딩하기

 

 

 

[Firefox 설치하기]

  먼저 firefox 를 설치해 보자. 구글에서 'firefox' 라고 검색해 본다. 제일 위의 파이어 폭스 링크로 이동한다.

https://www.mozilla.org/ko/firefox/new/

 

 

  '무료 다운로드' 버튼를 클릭하여 설치를 진행한다. 설치 진행 중 아래의 '가져오기 마법사' 항목이 나오면 굳이 IE 설정을 가져올 필요는 없으니 '아무 것도 가져오기 않기' 를 선택한다.

 

 

  다음에 "기본 브라우저" 설정도 굳이 취향이 아님 firefox 를 기본 브라우저로 쓸 필요는 없으니, 시작할 때마다 확인 체크 박스를 풀고, '나중에' 버튼을 누른다. 그럼 firefox 설치는 완료됬다.

 

 

 

[셀레늄 설치하기]

  다음은 셀레늄 설치 이다. pip 설치가 가능한듯 하니 아래와 같이 pip 설치를 시도해 본다. 정상적으로 설치가 된다.

c:\Python\code>pip install selenium
Collecting selenium
  Downloading selenium-3.3.1-py2.py3-none-any.whl (930kB)
    100% |################################| 931kB 297kB/s
Installing collected packages: selenium
Successfully installed selenium-3.3.1

 

 

 

[샘플 코드 구현]

  그럼 셀레늄을 이용해서 firefox 를 한번 호출해 보자. 구글에서 'python selenium firefox' 라고 검색해서, 맨 위의 공식 문서의 샘플을 참고하여 본다.

http://selenium-python.readthedocs.io/getting-started.html 

 

  해당 예제는 firefox 로 python.org 페이지를 열고 검색어를 입력해서 페이지가 잘 열렸는지 검증을 한다. 전체 코드는 굳이 필요 없으니 python.org 페이지를 여는 부분만 잘라서 가져오자. 호출하는 사이트도 구글로 바꾼다.

1
2
3
4
5
6
#-*- coding: utf-8 -*-
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
 
driver = webdriver.Firefox()
driver.get("https://www.google.com")
cs

 

  c:\python\code 폴더에 selenium_1st.py 라고 저장하고 실행을 해본다(2교시 참고)

c:\Python\code>python selenium_1st.py
Traceback (most recent call last):
... 생략

os.path.basename(self.path), self.start_error_message)
selenium.common.exceptions.WebDriverException: Message: 'geckodriver' executable needs to be in PATH.

 

 

  그런데 불행하게도 에러가 난다. geckodriver 라는 파일이 패스 경로에 없다고 한다. 해당 파일은 위에서 얘기한 webdriver 기능을 사용하기 위한 exe 형태의 실행 파일이 없어서이다. 구글에 'Message: 'geckodriver' executable needs to be in PATH.' 라고 검색한다. 아래 스택 오버 플로우 링크를 보면 파일을 다운받아 path 가 지정된 경로에 복사하라는 안내가 있다. 리눅스 기준 설명이지만, 해당 파일이 결국 path 경로 안에만 들어있음 셀레늄에서 알아서 참조해 실행 하나 보다.

http://stackoverflow.com/questions/40208051/selenium-using-python-geckodriver-executable-needs-to-be-in-path

 

this steps SOLVED for me on ubuntu firefox 50.

1.Download geckodriver
2.Copy geckodriver in /usr/local/bin

 

 

  링크된 깃허브 경로로 이동하여(https://github.com/mozilla/geckodriver/releases) 제공된 여러 파일 중 현재 환경(윈도우10, 64bit)에 맞는 'geckodriver-v0.15.0-win64.zip' 파일을 다운 받는다. 해당 압축 파일안에는 'geckodriver.exe' 파일이 하나 달랑 들어 있는데, 뭐 특정 폴더를 만들어 넣고 시스템 path 등을(path 에 대해선 파이썬 설치하는 부분에서 설명했다) 걸거나, 기존 패스가 지정되 있는 c:\windows\system32 폴더 같은데 복사해도 되지만, 일을 단순하게 만들기 위해서 파이썬 소스가 있는 경로인 c:\python\code 폴더에다 압축을 푼다.

 

  이후 아까 에러난 코드를 다시 실행해 본다.

c:\Python\code>python selenium_1st.py

 

  조금은 신기하게도 아래와 같이 파이어 폭스가 뜬 후 구글 페이지를 로딩해 준다.

 

 

 

 

 

[구글 검색해 상위 5개 링크 가져오기]

  그럼 구글에서 상위 5개 링크를 가져오는 코드를 만들어 보자. 검색 결과에서 링크를 얻기 위해서 구글에 'selenium get google results python' 라고 검색 하자. 아래의 스택 오버플로우 코드를 참고하면, 결과 중 첫번째 항목(results[0])을 참고하여 링크를 가져온다.

http://stackoverflow.com/questions/35241230/how-to-extract-a-google-links-href-from-search-results-with-selenium
1
2
3
4
5
6
7
8
9
10
11
from selenium import webdriver
 
driver = webdriver.PhantomJS()
driver.get("https://www.google.com/search?q=test")
 
results = driver.find_elements_by_css_selector('div.g')
link = results[0].find_element_by_tag_name("a")
href = link.get_attribute("href")
 
import urlparse
print(urlparse.parse_qs(urlparse.urlparse(href).query)["q"])
cs

 

 

 

  해당 코드를 firefox 웹 드라이브를 사용하도록 앞에서 만든, selenium_1st.py 파일과 적절히 합치면, 아래의 코드가 나온다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#-*- coding: utf-8 -*-
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
 
# 파이어 폭스 드라이버 로딩
driver = webdriver.Firefox()
 
# 구글 페이지에서 '파이썬 공부' 라고 검색해 옴.
driver.get("https://www.google.com/search?q=파이썬 공부")
 
# 검색 된 div 태그 안에 들은 링크 들을 가져옴
results = driver.find_elements_by_css_selector('div.g')
 
# 그중 첫번째 링크 안에서 a 태그를 찾음
link = results[0].find_element_by_tag_name("a")
 
# a태그 안에서 href 라는 인자를 가져옴 
href = link.get_attribute("href")
 
# 인자안에서 q 에 해당되는 값을 가져옴.
import urlparse
print(urlparse.parse_qs(urlparse.urlparse(href).query)["url"])
cs

 

 

  위의 링크를 가져오는 코드의 내용을 이해하기 위해서, IE의 개발자 도구 기능을 한번 사용해 보도록 하자(선호하는 다른 브라우저의 개발자 도구를 이용해도 된다). 아래의 화면에 있는 대로 구글에서 '파이썬 공부' 라고 검색 후,  첫번째 링크를 기준으로 요소 검사를 해서, 위에 있는 python 코드와 개발자 도구에서 보여주는 소스코드 내용을 비교해 놓았다. 자세히 보면 class 속성이 'g' 인 'div' 태그들을 찾은 후(페이지 전체를 분석해 보면 이 'g' 속성을 가진 'div' 태그가 각 링크 결과를 하나씩 감싸고 있을 것이다), 이후 해당 'div' 태그 안에서 다시 'a' 태그를 찾은 후, 이후 찾은 'a' 태그 안에서 다시 'href' 속성을 찾은 후 해당 url 형태의 결과 값에 대해 'urlparse' 라는 라이브러리를 이용하여, 'url' 이라는 인자 요소만 추출하게 되는 구조이다. 그 추출된 값이 바로 실제 링크된 페이지인 'http://ngee.tistory.com/263' 일 것이다. (개발자 도구를 사용하는 방법은 11교시에 설명했다)

 

  그럼 해당 파일을 c:\python\code 폴더에 selenium_2nd.py 로 저장하여 실행해 본다.

c:\Python\code>python selenium_2nd.py
  File "selenium_2nd.py", line 8
SyntaxError: (unicode error) 'utf-8' codec can't decode byte 0xc6 in position 0: invalid continuation byte

 

 

  근데 예전에 whois api 구현하면서 한번 본 적이 있는 유니코드 에러가 난다. 구글 호출 인자에 한글 검색어('파이썬 공부')를 명시한 부분 때문에 그런가 보다. 그 때 해결했던 것과 동일하게 파일을 다른 이름으로 저장 하면서 utf-8 형식으로 저장한다. 이후 다시 실행 한다.  

c:\Python\code>python selenium_2nd.py
Traceback (most recent call last):
  File "selenium_2nd.py", line 16, in <module>
    import urlparse
ImportError: No module named 'urlparse'

 

 

  이젠 구글 페이지까진 잘 뜨는데 urlparse 라는 모듈이 없다고 에러가 난다. 아마도 코드가 파이썬 2.x 대 기준이라 그런 듯 싶다. 파이썬 3 에서 사용하는 url parsing 방식을 찾기 위해서 구글에 'get some argument from url urlparse python' 이라고 찾는다. 첫번째 스택오버 플로우 페이지를 보는데, 아래와 같은 눈에 띄는 항목이 있다.

http://stackoverflow.com/questions/5074803/retrieving-parameters-from-a-url

 

There is a new library called furl. I find this library to be most pythonic for doing url algebra. To install:
pip install furl

 

Code:
from furl import furl
f = furl("/abc?def='ghi'")
print f.args['def']

 

 

  코드를 보니 전체 적인 기능은 어떨지 모르지만 현재 사용하려는 목적으로는 urlparse 보다 간략해 보인다. 그럼 위에 적힌 데로, 모듈을 설치한다.

c:\Python\code>pip install furl
Collecting furl
  Downloading furl-0.5.7.tar.gz
  Running setup.py install for furl ... done
Successfully installed furl-0.5.7 orderedmultidict-0.7.11

 

  정상적으로 설치가 됬다. 그럼 해당 코드를 이용해서 아래와 같이 기존 코드를 수정하여 다시 실행해 본다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#-*- coding: utf-8 -*-
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
 
# 파이어 폭스 드라이버 로딩
driver = webdriver.Firefox()
 
# 구글 페이지에서 '파이썬 공부' 라고 검색해 옴.
driver.get("https://www.google.com/search?q=파이썬 공부")
 
# 검색 된 div 태그 안에 들은 링크 들을 가져옴
results = driver.find_elements_by_css_selector('div.g')
 
# 그중 첫번째 링크 안에서 a 태그를 찾음
link = results[0].find_element_by_tag_name("a")
 
# a태그 안에서 href 라는 인자를 가져옴 
href = link.get_attribute("href")
 
# 인자안에서 q 에 해당되는 값을 가져옴.
from furl import furl
= furl(href) 
print (f.args['url'])
cs

 

c:\Python\code>python selenium_2nd.py
Traceback (most recent call last):
... 생략
    raise KeyError(key)
KeyError: 'url'

 

 

  그런데 이상하게도, 아까 개발자 도구상으로는 분명이 존재했던 'url' 인자가 없다고 나온다. 왜 그런지 원인을 찾기 위해서 아래와 같이 소스를 수정해 href 값을 화면에 출력해 본다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#-*- coding: utf-8 -*-
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
 
# 파이어 폭스 드라이버 로딩
driver = webdriver.Firefox()
 
# 구글 페이지에서 '파이썬 공부' 라고 검색해 옴.
driver.get("https://www.google.com/search?q=파이썬 공부")
 
# 검색 된 div 태그 안에 들은 링크 들을 가져옴
results = driver.find_elements_by_css_selector('div.g')
 
# 그중 첫번째 링크 안에서 a 태그를 찾음
link = results[0].find_element_by_tag_name("a")
 
# a태그 안에서 href 라는 인자를 가져옴 
href = link.get_attribute("href")
 
# 프린트 해봄
print(href)
cs

 

c:\Python\code>python selenium_2nd.py
http://ngee.tistory.com/263

 

 

  헐 근데 특이 하게도, 개발자 도구로 소스상에서 본 href 는 분명 '/url?.........' 하는 구글을 기준으로 한 긴 링크 문장이였는데, 실제 firefox 웹드라이브에서 참조해 온 값은 이미 해당 url 이 실행되어 해석된, 최종 url 경로를 가져오게 된다(나중에 PhantomJS 에서도 보겠지만 거기는 또 가져오는 값이 조금 틀리다). 이 부분이 예전의 beautifulsoup 을 사용할 때와 틀린 부분 중 하나이다. 해당 beautifulsoup은 정적인 페이지를 대상으로 했기 때문에, 항상 소스에 기반해 일정한 값이 나오지만, 웹 드라이브의 경우 드라이브를 만든 곳에 따라서(ie, firefox, chrome, phantomjs) 구현 방식이 틀릴 수 있기 때문에, 각각 다른 동적인 시점에 따라 상이한 결과 값을 리턴해 줄수도 있다. 이게 어찌보면 'one source multi use' 를 지향하는 셀레늄의 현실적인 한계 부분 일지도 모른다(웹 드라이버는 만드는 단체가 각각 다를 수 있기 때문에 입출력 인터페이스는 동일 하지만 실제 얻어오는 값이나, 동작에는 조금씩 차이가 날 수 있다. 브라우저 특성도 영향을 줄 것이고 말이다). 여튼 이렇게 되면 굳이 설치한 furl 을 이용해서 한번더 주소를 가져올 필요가 없어진다. (나중에 PhantomJS 에 사용할 테니 굳이 삭제 하진 말길...)

 

 

 

[새로운 탭에 링크 URL 로딩하기]

  여튼 그럼 마지막으로 새로운 탭을 열어 특정 웹 주소를 여는 방식을 알아보자. 구글에 'selenium open link in new tab python' 이라고 검색한다. 아래 스택오버플로우 페이지를 보면 ctrl+t 키를 눌러서 새탭을 열고, 웹 주소를 해당 탭에 로딩하는 코드가 있는데, 마지막에 간단한 코드가 하나 눈에 뜨인다. 

http://stackoverflow.com/questions/28431765/open-web-in-new-tab-selenium-python

 

browser.execute_script('''window.open("http://bings.com","_blank");''')

 

  위의 코드가 정상적으로 돌아 간다면 좋을 것 같다(확인 결과 다행히 firefox 는 기본 동작 옵션이 _blank 로 새창을 열면 새 탭으로 열어준다. 넘 시행 착오 코드가 많아서 줄임 차원에서...). 실제 최종 확인 결과 어떤 사유인지는 모르지만 최근 브라우저 들에서는 아래 제시된 예제들과 같이 웹드라이버에서 ctrl + t 키를 body 태그에 날려도 새 탭이 안 생기는 거 같다. 그래서 결국 위의 코드를 쓰게 되었다.

https://gist.github.com/lrhache/7686903

 

 

 

[최종 코드 완성하기]

   앞에서 해결된 코드들을 합쳐, 제어 로직을 추가하여 최종 완성된 코드는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#-*- coding: utf-8 -*-
from selenium import webdriver
 
# firefox 웹 드라이버를 연다 
browser = webdriver.Firefox()
 
# 구글에 '파이썬 공부'로 검색어 조회
browser.get("https://www.google.com/search?q=파이썬 공부")
 
# 결과 div 태그를 클래스 기준으로 검색해 다 가져옴.
results = browser.find_elements_by_css_selector('div.g')
 
# 인자를 담을 리스트
hrefs = []
 
# div 중 최초 5개를 가져와서, 그 안에서 a 태그를 찾고, a 태그 안의 href 속성을 찾는다. 
for i in range(05):
   link = results[i].find_element_by_tag_name("a")
   hrefs.append(link.get_attribute("href"))
 
# 화면에 그냥 프린트 해봄
for href in hrefs:
   print(href)
 
# 각 링크에 대해서 새 탭에서 연다
for href in hrefs:
   browser.execute_script('window.open("' + href + '","_blank");')
cs

 

  그럼 해당 파일을 c:\python\code 폴더에 selenium_firefox.py 로 저장하여 실행해 본다.

c:\Python\code>python selenium_firefox.py
http://ngee.tistory.com/263
https://nolboo.kim/blog/2014/08/10/the-best-way-to-learn-python/
http://analyticsstory.com/1
http://www.boxnwhis.kr/2016/03/25/how_to_be_a_developer_as_a_statistician.html
https://wikidocs.net/43

 

  아래와 같이 파이어 폭스 브라우저가 뜨면서 5개의 결과가 탭들에 담긴다. 

 

 

 

 

 

 

 

[팬텀JS(PhantomJS) 브라우저로 같은 작업 하기]

  위에 firefox 웹 드라이버로 했던 부분을 비교해보는 의미가 있을 것 같아서, 이번엔 PhantomJS 브라우저를 이용해 해보려고 한다. PhantomJS 는 화면에는 없고 메모리 상에만 존재하는 브라우저 같다. 웃긴건 스크린 캡쳐는 가능하다(다행히 이거라도 있어서 에러가 발생할 때 확인이 수월하다). 대신에 실제로 브라우저가 화면 상에 표시되어 움직이지 않기 때문에, 자동화 코드를 실행하면서도 비교적 해당 컴퓨터에서 다른 작업을 자유롭게 할수 있을 것 같고(기존 브라우저 베이스로 자동화 코드를 실행해 본 사람들은, 해당 실행이 끝나기 전에 컴퓨터를 사용 못하는 경험을 겪어봤을 것이다), 실제 화면에 모든 개체를 표시되지 않기 때문에 부하나 외부 환경에 대한 에러 요소도 좀 적을 것 같다. 단점은 해당 브라우저로 만든 코드가 다른 브라우저와 호환성을 100% 보장한다고는 할수 없을 것 같다. webkit 베이스 이기 때문에 사파리나 스마트폰 용 브라우저와는 어느정도 호환성이 있다고 볼 수 있을 거 같다.

 

 

[샘플 코드 구현]

  그럼 구글에 'python use PhantomJS' 라고 검색한다. 아래의 스택 오버플로우 페이지를 확인해 보면, 다른 부분은 동일하고, 처음에 웹 드라이브 정의 하는 부분만 아래와 같이 바꾸면 되는 것 같아 보인다.

http://stackoverflow.com/questions/13287490/is-there-a-way-to-use-phantomjs-in-python

 

1
driver = webdriver.PhantomJS()
cs

 

 

  그럼 해당 방식으로 아까 만든 코드를 가져와 실행해 본다. 탭을 띄우는 부분만 굳이 의미 없을것 같으니 제외 시키고, url 만 프린트 하고 종료되게 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#-*- coding: utf-8 -*-
from selenium import webdriver
 
# firefox 웹 드라이버를 연다 
browser = webdriver.PhantomJS()
 
# 구글에 '파이썬 공부'로 검색어 조회
browser.get("https://www.google.com/search?q=파이썬 공부")
 
# 결과 div 태그를 클래스 기준으로 검색해 다 가져옴.
results = browser.find_elements_by_css_selector('div.g')
 
# 인자를 담을 리스트
hrefs = []
 
# div 중 최초 5개를 가져와서, 그 안에서 a 태그를 찾고, a 태그 안의 href 속성을 찾는다. 
for i in range(05):
   link = results[i].find_element_by_tag_name("a")
   hrefs.append(link.get_attribute("href"))
 
# 화면에 그냥 프린트 해봄
for href in hrefs:
   print(href)
cs

 

  그럼 해당 파일을 c:\python\code 폴더에 selenium_phantom.py 로 저장하여 실행해 본다.

c:\Python\code>python selenium_phantom.py

Traceback (most recent call last):  .....
      os.path.basename(self.path), self.start_error_message)
selenium.common.exceptions.WebDriverException: Message: 'phantomjs.exe' executable needs to be in PATH.

 

 

  역시 한번에 될리가 없고 무언가 에러가 난다. 하지만 이미 firefox 일때 웹드라이버 파일이 경로에 필요했던 부분을 겪어 봤으니, 이번엔 좀더 쉬울듯 하다. 구글에서 "Message: 'phantomjs' executable needs to be in PATH windows" 로 검색한다.

http://stackoverflow.com/questions/37903536/phantomjs-with-selenium-error-message-phantomjs-executable-needs-to-be-in-pa

 

  아래의 내용이 눈에 띈다

you need to download the DRIVER

after that session = webdriver.PhantomJS("c:\driverPath")

 

 

  해당 글에 명시된 http://phantomjs.org/ 페이지로 이동하여 'Download Ver 2.1(버전은 틀려질 수 있을 듯) 버튼을 클릭 하여, 'phantomjs-2.1.1-windows.zip' 파일을 다운 받는다. 이전같이 같은 폴더에 복사하거나, 또는 path가 지정되 있는 폴더에 복사하거나, 또는 특정 폴더에 복사후 시스템 path를 지정해도 좋지만, 조금 다른 방식을 보여주기 위해서 c:\python 폴더에 압축을 풀고(phantomjs-2.1.1-windows 폴더로 풀린다), Phantomjs 호출하는 코드 부분을 아래와 같이 조금 수정한다(어느 페이지에서 본 코드 인지는 잘 생각 안난다;).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#-*- coding: utf-8 -*-
from selenium import webdriver
 
# 수정된 코드 
phantomjs_path = r'c:\Python\phantomjs-2.1.1-windows\bin\phantomjs.exe'
browser = webdriver.PhantomJS(phantomjs_path)    
 
# 구글에 '파이썬 공부'로 검색어 조회
browser.get("https://www.google.com/search?q=파이썬 공부")
 
# 결과 div 태그를 클래스 기준으로 검색해 다 가져옴.
results = browser.find_elements_by_css_selector('div.g')
 
# 인자를 담을 리스트
hrefs = []
 
# div 중 최초 5개를 가져와서, 그 안에서 a 태그를 찾고, a 태그 안의 href 속성을 찾는다. 
for i in range(05):
   link = results[i].find_element_by_tag_name("a")
   hrefs.append(link.get_attribute("href"))
 
# 화면에 그냥 프린트 해봄
for href in hrefs:
   print(href)
cs

 

  해당 내용을 selenium_phantom.py 에 엎어쓰고 다시 실행해 본다.

c:\Python\code>python selenium_phantom.py
Traceback (most recent call last):
  File "selenium_phantom.py", line 19, in <module>
    link = results[i].find_element_by_tag_name("a")
IndexError: list index out of range

 

 

  ※ 아마 실행 전에 아래와 같은 윈도우즈 방화벽 허용 창이 뜰것이다. 허가되지 않은 프로그램이 외부랑 통신을 할까봐 그러는 것으로 같다. 실행시 필수이니 '엑세스 허용'하고, 다시 프로그램을 실행 시켜야 한다.  

 

 

 

[에러 원인 확인 하기]

  근데 브라우저 실행까진 잘 됬는데 엉뚱하게 'a' 태그가 없다고 나온다. 화면이 보이지도 않으니 어떤 일이 일어났는지 알수 없으니 에러가 나기 전 코드 위치에서 스크린 샷을 찍어 본다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#-*- coding: utf-8 -*-
from selenium import webdriver
 
# firefox 웹 드라이버를 연다 
phantomjs_path = r'c:\Python\phantomjs-2.1.1-windows\bin\phantomjs.exe'
browser = webdriver.PhantomJS(phantomjs_path)    
 
# 구글에 '파이썬 공부'로 검색어 조회
browser.get("https://www.google.com/search?q=파이썬 공부")
 
# 결과 div 태그를 클래스 기준으로 검색해 다 가져옴.
results = browser.find_elements_by_css_selector('div.g')
 
# 인자를 담을 리스트
hrefs = []
 
# 디버그용 스크린 샷
browser.save_screenshot('screen.png')
 
# div 중 최초 5개를 가져와서, 그 안에서 a 태그를 찾고, a 태그 안의 href 속성을 찾는다. 
for i in range(05):
   link = results[i].find_element_by_tag_name("a")
   hrefs.append(link.get_attribute("href"))
 
# 화면에 그냥 프린트 해봄
for href in hrefs:
   print(href)
cs

 

 

  소스와 같은 폴더에 저장된 screen.png 파일을 열어보니, 글자가 ??? ?? 나오는거 보면 PhantomJS 는 url 에 한글을 포함해 보내면 제대로 처리를 못해주는 것 같다. --;

 

 

  그럼 어떻게 내부에서 인코딩을 처리하는지 모르는 상태에서(물론 홈 페이지에서 전체 소스를 제공해 주긴 하지만) 뭔가 인코딩 부분을 건드리긴 무섭고, url 에 넣는 방식이 아닌 한글 검색어를 입력 후 한글 검색어를 넣고 검색버튼을 누르는 방향으로 전환해 보며 한글 처리를 잘 하길 기대해 보자. 구글에 'selenium google search python' 라고 검색한다. 처음 링크로 나오는 github 페이지를 참고한다.

https://gist.github.com/azam-a/32b89944b98a3fd79d44ebfdac16b63d#file-google-py

http://stackoverflow.com/questions/24598648/searching-google-with-selenium-and-python

 

 

  해당 움직이는? 방식으로 코드를 수정하면 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#-*- coding: utf-8 -*-
from selenium import webdriver
 
# firefox 웹 드라이버를 연다 
phantomjs_path = r'c:\Python\phantomjs-2.1.1-windows\bin\phantomjs.exe'
browser = webdriver.PhantomJS(phantomjs_path)    
 
# 구글에 '파이썬 공부'로 검색어 조회
browser.get("https://www.google.com")
input_element = browser.find_element_by_name("q"
input_element.send_keys("파이썬 공부"
input_element.submit() 
 
 
# 결과 div 태그를 클래스 기준으로 검색해 다 가져옴.
results = browser.find_elements_by_css_selector('div.g')
 
# 인자를 담을 리스트
hrefs = []
 
# 디버그용 스크린 샷
# browser.save_screenshot('screen.png')
 
# div 중 최초 5개를 가져와서, 그 안에서 a 태그를 찾고, a 태그 안의 href 속성을 찾는다. 
for i in range(05):
   link = results[i].find_element_by_tag_name("a")
   hrefs.append(link.get_attribute("href"))
 
# 화면에 그냥 프린트 해봄
for href in hrefs:
   print(href)
cs

 

c:\Python\code>python selenium_phantom.py
https://www.google.co.kr/url?q=http://ngee.tistory.com/263&sa=U&ved=0ahUKEwi8sJba-_bSAhVEzbwKHQfuD5oQFggZMAA&usg=AFQjCNFNP7xOQT4RepCgxW-lSoAIucugwA
https://www.google.co.kr/url?q=https://nolboo.kim/blog/2014/08/10/the-best-way-to-learn-python/&sa=U&ved=0ahUKEwi8sJba-_bSAhVEzbwKHQfuD5oQFgggMAE&usg=AFQjCNFtcAIvPG3wUHOowvFHpgBjf7XQsw
.. 뒤에 3개는 생략

 

 

[웹 자동화의 타이밍 문제]

  흠 그런데 PhantomJS 코드를 처음 만들어 실행 했을 때 났던 에러가 지금은 안 나서 조금 당황스러운 상태다. --;

 

  어떤 일이 발생 할  수 있냐면, 코드 중에서 처음 find_elements_by_css_selector('div.g') 찾는 부분에서 원래 에러가 났었다. 그 이유는 browser.get() 함수는 아마 페이지 전체가 로딩될때 까지 대기 후, 다음 코드가 실행되지만, 위와 같이 submit 버튼으로 전송한 경우는 버튼을 누른 순간 코드가 끝났기 때문에 다음 코드가 실행 됬을때, 페이지가 아직 로딩 중일 수 있기 때문에, find_elements_by_css_selector('div.g') 코드에서 아직 페이지에 div.g 태그가 로딩되지 않아서 에러가 발생할 수 있다. 그래서 그것을 보여주며 타이밍 이슈에 대해서 얘기하려 했는데 재현이 안 된다. 보통 사용의 UI 자동화 솔루션들이 이러한 태그나 속성 요소를 찾는 함수에 기본으로 지정가능한 '대기시간'이 있어서, 해당 부분의 요소가 없더라도 '대기시간'동안 계속 해당 요소를 반복해서 찾아 웹페이지에 로딩에 의한 타이밍 에러를 줄여준다.

 

 

  셀레늄에도 그러한 타이밍 이슈를 위한 기능들이 있는데, 보통 2가지 방식으로 해결한다. 1 번째는 쉬는 시간을 무조건 지정하는 것이다. 하지만 경험상 시간을 충분히 주더라도 특정 경우에 타이밍 에러가 날 수도 있도,  또 이미 해당 부분이 로딩 됬더라도 무조건 기다리게 되어 실행 시간이 많이 걸리기 때문에 그다지 바람직한 방식은 아니다.  

 

  2 번째로 페이지 로딩이 충분이 됬는지를 특정 엘리먼트를 계속 확인하여 기다리는 것이다. 이 경우 해당 엘리먼트를 찾게되면 바로 다음 단계로 넘어가고, 아니면 지정된 시간이 지나서 에러를 내게 된다. 이런 코드를 만들 경우 현재 참조 하려는 태그나, 페이지에서 가장 마지막에 로딩 될것 같은 태그를 페이지 구조를 분석하여 분석하여 지정해야 한다. 이것 역시 쉬운 작업은 아니지만, 이렇게 구현하면 빠르고, 정확한 코드가 된다. 그런 면에서 상용 솔루션이 이런 부분에서 조금 더 유연성은 있을 것 같다.  아마 7년전의 지식 기준이니 지금은 좀 더 해당 부분이 강화되었을 것 같긴 한다. 두 가지 방식에 대한 상세한 얘기는 밑의 링크를 참고하고, 여기서는 쉽게 가려고 sleep 함수를 2초간 사용하려 한다. (firefox 같은 실제 브라우저를 사용하는 코드에서는 조금 더 시간을 주는게 좋다)

http://stackoverflow.com/questions/26566799/selenium-python-how-to-wait-until-the-page-is-loaded 

1
2
import time
time.sleep(2)
cs

 

 

  또 해당 결과에서 하나 걸리는 것은 firefox 와 비교되는 최종 url 을 얻는 부분인데, firefox 에서는 'href' 에 '최종 url' 이 나왔는데, PhantomJS 에서는 아래와 같이 정적 소스와도 좀 상이한(원본 소스는 'url' 인자에 링크가 있었는데 여기 'q' 인자임) 최종 url 이전의 링크가 나온다.

https://www.google.co.kr/url?q=http://ngee.tistory.com/263&sa=U&ved=0ahUKEwi8sJba-_bSAhVEzbwKHQfuD5oQFggZMAA&usg=AFQjCNFNP7xOQT4RepCgxW-lSoAIucugwA

 

  이 링크를 띄워도 최종 url 이 표시되긴 하지만, 굳이 구글을 거쳐서 실행 되는게 찜찜해서 firefox 와 동일하게 나오게 하기 위해서 아까 사용하려다 만 furl 을 사용하려 한다.  

 

Code:
from furl import furl
f = furl("/abc?def='ghi'")
print f.args['def']

 

 

 

[최종 코드]

  모듈 설치는 이미 했으니 해당 코드를 반영하여 최종 PhantomJS 코드를 만들면 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#-*- coding: utf-8 -*-
from selenium import webdriver
 
# firefox 웹 드라이버를 연다 
phantomjs_path = r'c:\Python\phantomjs-2.1.1-windows\bin\phantomjs.exe'
browser = webdriver.PhantomJS(phantomjs_path)    
 
# 구글에 '파이썬 공부'로 검색어 조회
browser.get("https://www.google.com")
input_element = browser.find_element_by_name("q"
input_element.send_keys("파이썬 공부"
input_element.submit() 
 
import time
time.sleep(2)
 
# 결과 div 태그를 클래스 기준으로 검색해 다 가져옴.
results = browser.find_elements_by_css_selector('div.g')
 
# 인자를 담을 리스트
hrefs = []
 
# 디버그용 스크린 샷
# browser.save_screenshot('screen.png')
 
# div 중 최초 5개를 가져와서, 그 안에서 a 태그를 찾고, a 태그 안의 href 속성을 찾는다. 
for i in range(05):
   link = results[i].find_element_by_tag_name("a")
   hrefs.append(link.get_attribute("href"))
 
# href 에서 q 인자를 추출해 옴   
from furl import furl
for href in hrefs:
   f = furl(href) 
   print (f.args['q'])
cs

 

  해당 내용을 selenium_phantom.py 에 엎어쓰고 마지막으로 실행해 본다. 원하는 결과가 나온다.

c:\Python\code>python selenium_phantom.py
http://ngee.tistory.com/263
https://nolboo.kim/blog/2014/08/10/the-best-way-to-learn-python/
... 뒤의 3개 생략

 

 

 

 

 

[부록 - IE (Internet explorer) 로 같은 작업 하기]

  마지막으로 자주 사용하는 IE 브라우저를 무시하긴 그래서 IE 코드도 하나 넣으려 한다. 결론 부터 말하자면 생각보다 실행을 위해 해야 될 귀찮은 일들이 좀 많고, 실행 해 보면 firefox 보다 상당히 느리게 동작한다(물 안에서 움직이는 느낌이라고 할까?). 그래서 가능한 동작을 꼭 IE 브라우저에서 할 필요가 없으면 firefox 나 해보진 않았지만 크롬에서 해보는게 어떨까 싶다(구글에서 webdriver 를 만드는 것 같으니 최적화는 잘되지 않았을까 싶어서). 아니면 화면을 꼭 봐야될 필요가 없다면 PhantomJS 도 괜찮고 말이다. 앞에서 같이 경험 목적으로 반복해서 시행착오 과정을 굳이 보여줄 필요는 없을 것 같아서, 실행에 필요한 세팅들을 안내 후, 실행 결과를 보여주고 마무리를 하려 한다.

 

 

[웹드라이버 세팅]

http://stackoverflow.com/questions/24925095/selenium-python-internet-explorer

http://selenium-release.storage.googleapis.com/2.42/IEDriverServer_x64_2.42.0.zip

  다운로드 후 c:\python 폴더에 c:\Python\IEDriverServer_x64_2.42.0\ 로 압축 해제를 하면 된다. 코드내에 경로를 반영하기가 귀찮다면 c:\python\code 에 직접 exe 파일을 복사후, browser = webdriver.Ie() 로 소스를 바꾸어 호출해도 된다.  

 

 

[IE 보안 설정]

  Unexpected error launching Internet Explorer. Protected Mode settings are not the same for all zones 에러 발생함.

 

  인터넷 옵션 > 보안 탭 > 아래 4개 영역에 대해서 보호 모드 사용이 꺼져 있어야 한다. 테스트 완료 후 인터넷 쪽은 꼭 원래대로 복원하길 바란다.

 

 

[IE 확대/축소 사이즈 설정]

  Message: Unexpected error launching Internet Explorer. Browser zoom level was set to 125%. It should be set to 100% 에러 발생.

 

  확대 설정이 100% 여야만 동작한다(IE 브라우저는 왜 이런 설정까지 맞춰야 하는지 싶다 --;)

 

 

[방화벽 설정]

  PhantomJS 와 비슷하게 방화벽 혀용을 묻는 창이 뜨면 허용을 해주고 다시 코드를 실행 해주어야 한다.

 

 

 

[최종 코드]

  세팅은 좀 달랐지만 최종 정리한 코드는 브라우저 호출 부분만 바뀌었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#-*- coding: utf-8 -*-
from selenium import webdriver
 
# IE 웹 드라이버를 연다 
ie_path = r'c:\Python\IEDriverServer_x64_2.42.0\IEDriverServer.exe'
browser = webdriver.Ie(ie_path)    
 
# 구글에 '파이썬 공부'로 검색어 조회
browser.get("https://www.google.com")
input_element = browser.find_element_by_name("q"
input_element.send_keys("파이썬 공부"
input_element.submit() 
 
# 시간 좀 늘임
import time
time.sleep(5)
 
# 결과 div 태그를 클래스 기준으로 검색해 다 가져옴.
results = browser.find_elements_by_css_selector('div.g')
 
# 인자를 담을 리스트
hrefs = []
 
# 디버그용 스크린 샷
# browser.save_screenshot('screen.png')
 
# div 중 최초 5개를 가져와서, 그 안에서 a 태그를 찾고, a 태그 안의 href 속성을 찾는다. 
for i in range(05):
   link = results[i].find_element_by_tag_name("a")
   hrefs.append(link.get_attribute("href"))
 
# 링크 출력 해보기 
for href in hrefs:
   print(href)
 
# 새 창에 띄움
for href in hrefs:
   browser.execute_script('window.open("' + href + '","_blank");')
cs

 

  selenium_ie.py 로 저장하여 실행하면 아래와 같이 새창으로 뜬다. (firefox 처럼 탭으로 뜨게 하고 싶으면, 현재 버전의 브라우저 들은 코드가 지원하는지 불분명하니 아래 설명한 바와 같이 브라우저 옵션을 바꾸는게 현실적일 것 같다 -> 밑에 코맨트 단 것과 같이 IE 웹 드라이버에서는 ctrl+t, ctrl+click, 팝업 옵션을 바꾸어도 모두 탭으로 열리진 않는다. 현재로서는 새창으로 밖에 안 열리는 것 같다는 결론을 내렸다.)

http://meaningone.tistory.com/52

c:\Python\code>python selenium_ie.py

 

 

 

  마지막으로 웹 자동화 연습용 페이지 링크를 소개한다(사실 실제 사이트 들에서 하면 되서 효율성은 잘 모르겠지만, 구조가 쉬워 연습할때 도움이 되지 않을까도 싶다). 다음 시간에는 윈도우즈 UI 자동화 부분에 대해서 간단한 프로그램 대상으로 진행해 보려 한다.

http://www.techbeamers.com/websites-to-practice-selenium-webdriver-online/

 

 

 

 

2017.3.29 by 자유로운설탕
cs

 

posted by 자유로운설탕
2017. 3. 19. 21:03 프로그래밍

  금번 시간은 html 을 쉽게 파싱(parsing) 할 수 있는, BeautifulSoup 이라는 모듈을 사용하는 방법에 대해서 살펴보고, 지난 시간에 API 를 사용해 호출했던 WhoIS 사이트를 웹 호출을 통해 결과를 받아 파싱하여 결과를 얻는 부분을 간단히 시연하면서 파싱을 할때 생기는 현실적인 이슈들에 대해 얘기를 하려 한다. 크롤링 이라고 부르기엔 좀 조촐한 범위라서 파싱이라고 주제를 정했다.

 

 

[목차]

0. 왜 파이썬 공부에 구글을 이용하는게 좋은가?

1. 언어를 바라보는 방법. 파이썬을 어떻게 바라봐야 할까?

2. 파이썬 설치와 환경, 버전 선택 하기의 이유.

3. 만들고자 하는 기능을 모르는 조각으로 나눠 조사해 보기

4. 데이터 베이스에서 내용 가져와 출력하기

5. 암호화 모듈을 이용해 암복호화 해보기

6. 퍼즐 조각들을 합쳐보기

7. 엑셀 파일 사용해 보기 -> 부록 : fuction 을 이용해서, 코드 정리해 보기

8. 정규표현식을 왜 사용해야 할까? 언어속의 미니 언어 정규표현식 살펴보기

9. 입력과 결과를 GUI 화면과 연결해 보기

10. Whois API 이용해 보기

11. 웹페이지 호출해 내용 파싱 하기(BeautifulSoup 그리고 한계)

12. 자동화 - 웹 자동화(with Selenium)

13. 자동화 - 윈도우즈 GUI 자동화(with pywinauto)

14. 자동화 - 작업 자동화

15. 수학 라이브러리 살펴보기

16. 그래픽 라이브러리 살펴보기

17. 머신러닝에서의 파이썬의 역활

18. 웹 프로그래밍 - Legacy Web

19. 웹 프로그래밍 - Flask 살펴보기(feat. d3.js)

20. 웹 프로그래밍 - Django 살펴보기

21. 정리 - 이런저런 이야기

 

 

 

[들어가면서]

  먼저 파싱이란 어떤 작업일까? 원래 언어학에서 먼저 나온 용어 같고, 간단히 얘기하면 문장의 구조를 분석해서, 문법 요소를 뽑아서 구조를 체계화 한다고 할 수 있다. 그렇게 체계화 하면 무엇이 좋아질까? 특히 컴퓨터 분야에서는 주어진 문구들을 체계화 하고 나면 해당 문법요소에 따라서, 원하는 내용들을 추출 할 수 도 있게 된다. 뭐 잘은 모르지만 자동 번역이나, 음성 인식 등 에서도 그렇게 문장 요소를 분류하는 게 먼저 일 거 같다. 

https://ko.wikipedia.org/wiki/%EA%B5%AC%EB%AC%B8_%EB%B6%84%EC%84%9D

 

  자 그럼 html 을 파싱하려면 어떻게 해야 할까? 만약 beautifulsoup 같은 파서가 없다면, 아마 정규 표현식 같은 방법들을 별 수 없이 이용해야 할 것 이다. 그러기 위해 우리는 우선 html 이 어떤 문법 요소를 가지고 있는지 이해해야 한다. 어떤 태그들을 가지고 있고, 어떤 속성을 가지고 있고, 각 태그와 속성의 위치, 상대적 관계, 문서에서의 역활 등에 대해서 이해해야 한다. 그래야 html 문서안에 있는 어떤 필요한 정보 값에 대해서, 그러한 외부 요소들의 관계를 이용해서 콕 지정해서 가져올 수 있을 것이다.

 

  해당 부분은 beautifulsoup 같은 파서를 이용한다고 해도 마찬가지다. 기본적으로 html 구조에 대한 지식이 있어야 브라우저의 개발자 도구 같은 유틸리티 들을 이용해서 살펴 보면서 페이지도 분석하고, 원하는 값을 가져오는 코드도 만들 수 있다. 결국 예전에 얘기했던 프로그램의 외적 요소를 사용하기 쉽게 해주는 모듈들은 많지만, 해당 모듈들을 잘 사용하기 위해서 해당 외적 요소에 대해서 포괄적인 이해를 하는게 유리하다는 것이다.

 

  html 공부에 대해서는 아래 링크의 w3school 같은데서, 하나씩 항목들을 살펴보거나, head first html(헤드 퍼스트 html) 같은 쉽게 설명해 주는 책들을 한 권 정도 보게 되면 해당 모듈을 사용하는 기본 소양은 갖추게 된다고 볼 수 있다. 이 후는 실제 여러 다양한 웹 페이지를 분석해 살펴보거나, 만들어 보는 것이(특히 만들면서 생각한 데로 구현이 안 되서 헤메다 보면, 좀 더 깊이 이해되고 잘 잊혀지지 않게 된다) 최선의 방법이다. 많이 경험하면 할수록 모르면 10분 걸리는 분석이 1분 만에 끝날 수도 있으며, 해당 지식은 앞으로 만나는 어떤 언어, 어떤 모듈에도 비슷한 주제와 형태라면 마찬가지로 사용할 수 있다. 또한 xml 같이 비슷하지만 좀더 엄격한 언어를 공부하거나, javascript 같이 웹의 이런 저런 요소들에 모두 문어발 관계를 가지는 언어를 배울 때 허들을 낮추게 해준다.

https://www.w3schools.com/html/default.asp

 

 

 

 

[Beautiful Soup]

  그럼 BeatutifulSoup 에 대해서 어떻게 접근하면 되는지 간단한 브리핑과 샘플을 실행해 보도록 하겠다. 먼저 'soup' 이란 영어 단어의 뜻 대로 '스프'는 아니고 아마 'soap' 의 패러디 같은게 아닌가 싶다--; 확실히는 모르지만 soap은 http 프로토콜을 이용해서 원격에 있는 서비스를 호출하여 사용하자는 규격이다. 전송을 위한 데이터는 xml 이나 그 사촌 정도인 html 이 사용된다. 그래서 이 모듈을 만든 사람은 html 이나 xml 을 아름답게 잘 파싱하여 원하는 결과를 만들 수 있다고 해서 이름을 붙이지 않았을까 추측한다. 여기서는 순수 웹과 연관된 html 쪽만 살펴본다. 뭐 전문적인 수준의 얘기는 아니니 전체적인 이야기의 맥락만을 이해하고, 위에 얘기한 책이나, 여러 웹 자료들에서 자세한 내용들은 살펴보길 바란다.

https://ko.wikipedia.org/wiki/SOAP 

 

  html 이란 HyperText Markup Language 의 약자이다. 이것은 링크를 가진 태그 형식의 언어라는 의미가 된다. 그럼 html 의 문법 요소는 무엇 일까? 크게 Element(tag), Attribute(id, class, name) 로 나눌 수 있다.  먼저 엘리먼트는 html 모든 태그를 얘기한다고 보면 될 듯 하다. attribute(속성)은 여러가지가 있는데 태그안에 들어간 어려가지 요소로 id, class, name, height, width 등으로 태그의 identity 나 표시 방법을 나타내 준다고 보면 된다. 

 

 

  처음 html 을 쓰다보면 id, class, name 이 조금 헷깔리는데(보통 자바스크립트나, 웹 프로그래밍, css 를 다룰때야 비로소 고민된긴 하지만..), id 는 고유의 태그를 표시하는 용도로 써서 같은 p 태그라도 id="myp" 같이 지정하면 고유한 p 태그가 되는 것이다(어린왕자의 장미라 그럴까?). 중복되는 경우 javascript 등에서 접근시 에러난다곤 하는데(확인 안 해봤다), 중복 된다고 html 의 관대한 특성상 페이지에서 당장 에러가 나는 경우는 없다(beautifulsoup 에서도 id 를 지정할때 중복되도 에러가 안 나고 그냥 다른 attribute 처럼 모두 결과로 가져온다). 이러한 값들은 나중에 beautifulsoup 이나 javascript, css 나아가 여러가지 자동화 라이브러리에서도 사용하게 된다.

 

  name 이 좀 id 와 개념이 헷깔리긴 하나(누가 이름을 지었는지...), 보통 name 은 form 태그 안에서 각 폼 요소들의 종류를 가리킬때 쓰며, 복수 지정이 가능하다(예를 들어 radio 버튼 한 그룹은  type=radio 이고 name 이 myradio 같이 같을 수 있다. 그럼 브라우저나, 자바스크립트는 해당 구조를 해석해서 동일한 그룹의 라디오 버튼이라는 것을 판단해서, 클릭 시 같은 동작시 서로 배타적으로 선택되게 만든다). class 는 보통 css(Cascading Style Sheets) 에서 많이 사용하게 되는데, 같은 이름의 class 를 가진 td 태그를 css 에 지정해서 자동으로 같은 배경색으로 표시한다는지 하는 식으로 디자인 적으로 많이 사용되게 된다. 뭐 하지만 프로그래머가 얼마나 해당 개념들을 헷깔려 하느냐에 따라서, 두서 없이 섞어 쓰는 경우도 종종 있는 듯 하다. id, name, class 의 차이는 아래 링크를 참고한다.

https://css-tricks.com/the-difference-between-id-and-class/
http://stackoverflow.com/questions/1397592/difference-between-id-and-name-attributes-in-html

 

 

  이러한 html 의 많은 요소는 DOM(document object model) 이라는 체계로 표현되고 관리된다. 이것은 html 이나 xml 을 트리형태로 구성하고, 해당 요소들에 대해서 프로그래밍 적으로 접근하거나 조작하는 것을 가능하게 해준다. 우리가 보는 html 문서는 dom 형태로 정리되어 javascript 나 beautifulsoup 같은 모듈에서 접근 할수 있다고 봐도 무방하다.

https://en.wikipedia.org/wiki/Document_Object_Model

 

 

  그럼 beautifulsoup 을 가지고 페이지에서 원하는 개체를 어떻게 찾을까? 구글에서 'beautifulsoup doc' 을 찾아보면 아래의 공식 메뉴얼이 나오는데, 해당 페이지내에 설명된 방식이 사실상 전부라고 보면 된다.

https://www.crummy.com/software/BeautifulSoup/bs4/doc/

 

  우선 아까 얘기한 특정 요소들로 찾을 수 있다. id, class, name, tag name 등을 기준으로 찾아 배열에 담아서 이용할수도 있고(findall), css selector 란 방식으로도 찾을 수 있다(search). 자세한 내용은 아래 링크를 참고 한다.

https://www.crummy.com/software/BeautifulSoup/bs4/doc/#find-all

https://www.crummy.com/software/BeautifulSoup/bs4/doc/#searching-by-css-class

 

  find_all 은 find_all('a') (<a> 태그를 전부 찾아라), find_all(id="mylink") (id가 mylink 인 태그를 찾아라) 같이 명시적으로 접근하는 편이고, css select 는 css 라는 html 디자인을 나타내는 스타일 언어의 개체 접근 방식을 사용해서 select('a'), select('#mylink') 같이 약간 약어 같이 쓴다.(정규 표현식 필이 조금 난다). 그래서 css selector 형태의 경우 좀더 간결한 듯 하다. 해당 부분은 밑의 링크를 참조하길 바란다.

https://www.w3schools.com/cssref/css_selectors.asp

 

  또한 상대적 위치 또한 이용할 수 있다. 현재 id 가 'mytd' 인 <td> 태그의 부모 역활을 하는 <tr> 태그를 찾는다던지, 특정 <tr> 태그 바로 밑에 있는 동일한 레벨의 <tr> 태그(sibling)를 찾을 수도 있다. 이런 것들은 beautifulsoup 모듈과 html 코드에 대한 전체적인 개념이 잡혔으면, 메뉴얼에서 필요한 부분을 훝어 보면서 어떤 접근 방식이 가능한지 보는게 좋다.(자바스크립트나 css 의 접근 방식과 많이 비슷하다. 어차피 비슷한 목적으로 접근하기 때문에 말이다) 

 

 

 

 

[Beautiful Soup 으로 샘플 파싱 해보기]

  그럼 예제를 하나 보면서 실제 코드를 만들어 보도록 하자. 실제 웹에 있는 페이지 하나를 봐도 좋지만, 그런 페이지들은 보통 복잡한 요소들이 막 믹스되어 있으므로, 주제에 집중을 위해서 하나의 html 코드를 만들어서 원하는 요소를 선택하는 부분을 시연하려 한다. 파일을 읽어 올까도 했지만, beaufilsoup 샘플을 보다보니 변수에 문서 내용을 지정해 시연하는게 좋은 듯 해서, 우선 html 코드를 담을 변수를 만들기 위해서 구글에 'python multi line string' 이라고 검색 한다. 아래의 페이지를 보면 """ 세개로 스트링 앞 뒤를 감싸면 멀티라인을 가진 스트링이 되나 보다.

http://stackoverflow.com/questions/10660435/pythonic-way-to-create-a-long-multi-line-string

 

  그럼 해당 우리가 웹에서 가져오는 데이터와 비슷한 데이터를 담은 (이상적인) 소스를 하나 만들어 본다. 해당 소스는 하나의 테이블로 이루어져 있고, 위 줄에 <th> 태그(테이블 제일 상단 표시) 2개와, 아랫 줄에 <td> 태그 2개와 있다. <tr> 태그는 각각의 행을 나타낸다. <th> 태그는 'choco', 'cookie' 라는 두개의 id 를 각각 가지고 있고, <td> 태그는 위쪽 태그만 'candy' 라는 name 속성을 가지고 있다.

1
2
3
4
5
6
7
8
9
10
11
12
html_doc = """
<table> 
  <tr>     
     <th id = "choco">초콜릿</th>    
     <th id = "cookie">과자</th>  
  </tr>  
  <tr>    
     <td name = "candy">사탕</td>
     <td>오렌지</td>
  </tr>  
</table>
"""
cs

 

  브라우저로 보면 아래와 같은 모양일 것이다. 이해를 돕기 위해 대충 tr, td 태그의 관계적 위치와 id, name 의 위치도 표시했다. (원래 크기가 크진 않고 잘 보이라고 확대 했다 --;)

 

 

  그럼 위의 공식 문서에 안내된 대로 아래와 같이 입력해서 beautifulsoup 를 설치해 보자. 성공적으로 잘 설치 됬다.

c:\Python\code>pip install beautifulsoup4
Collecting beautifulsoup4
  Downloading beautifulsoup4-4.5.3-py3-none-any.whl (85kB)
    100% |################################| 92kB 178kB/s
Installing collected packages: beautifulsoup4
Successfully installed beautifulsoup4-4.5.3

 

 

  예제에는 아래 5가지의 접근 방식을 소개하려 한다. 아래 예를 보고 나머지는 구글이나 공식 문서를 참조해서 이것 저것 해보다 보면 자기만의 감이 올 것 이다.

   1) td 태그를 모두 찾아서 출력 한다. -> '사탕', '오렌지' 가 출력될 것이다.

   2) id 가 'choco' 인 태그를 찾아서 출력한다 -> '초콜릿' 이 출력될 것이다.

   3) td 태그이며, name 이 'candy' 인 태그의 옆에 있는 td 태그를 출력한다 -> '오렌지' 출력.

   4), 5) 앞의 2, 3번과 같지만 css selector 방식을 이용한다 -> 초콜릿, 오렌지 가 출력 된다.

 

  태그에서 내용만 뽑아내는 방법은 'beautifulsoup find_all get text'  로 구글에서 찾았다.

http://stackoverflow.com/questions/16835449/python-beautifulsoup-extract-text-between-element

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#-*- coding: utf-8 -*-
from bs4 import BeautifulSoup
 
html_doc = """
<table border=1> 
  <tr>     
     <th id = "choco">초콜릿</th>    
     <th id = "cookie">과자</th>  
  </tr>  
  <tr>    
     <td name = "candy">사탕</td>
     <td>오렌지</td>
  </tr>  
</table>
"""
 
# html 파서를 이용해서 html 형태의 데이터를 읽어온다.
soup = BeautifulSoup(html_doc, 'html.parser')
 
# 1. td 태그를 모두 찾아서 td_list 담은 후, 루프를 돌리면서 각 td 태그 내용을 출력한다.
td_list = soup.find_all('td')
for td_item in td_list:
  print(td_item.string)
 
print ('\n')
 
# 2. id 가 choco 인 항목을 찾아서 해당 태그 내용을 출력한다. 
id_list = soup.find(id='choco')
print(id_list.string)
 
# 3. td 태그이면서 name 속성이 candy 인 항목('캔디')을 찾아서 
#    그 다음에 있는 같은 td 속성을 찾아서 태그 내용을('오렌지') 출력한다. 
td_list = soup.find('td', {'name':'candy'})
print(td_list.find_next_sibling().string)
 
print ('\n')
 
# 4. 2번과 동일하지만 css selector 방식으로 사용한다.
td_list = soup.select('#choco')
print(td_list[0].string)
 
# 5) 3번과 동일하지만 css selector 방식으로 사용한다.
td_list = soup.select('td[name="candy"]')
print(td_list[0].find_next_sibling().string)
cs

 

  해당 코드를 c:\python\code 폴더에 beautiful.py 라고 저장한다(역시 방법을 모르면 2교시 때 자세히 설명한 게 있으니 본다). 이후 실행 아래와 같이 시켜본다. 그런데 에러가 났다.

c:\Python\code>python beautiful.py
  File "beautiful.py", line 14
    """
SyntaxError: (unicode error) 'utf-8' codec can't decode byte 0xc3 in position 0: invalid continuation byte

 

  작성한 내용이 utf-8 코드가 아니라고 난다. 첨에는 python 3 버전도 뭔가 한글 처리 문제가 있어서 변환 방법을 찾아봐야 하나 하다가, 혹시나 해서 py 문서의 저장 방식을 살펴 보았더니 메모장에서 저장할때 ansi 로 인코딩 되어 저장되어 있다. 아래와 같이 메모장에서 열어 "파일 > 다른 이름으로 저장..."을 통해서 utf-8로 다시 저장해 본다.

 

  이후 다시 실행해 보면 아래와 같이 정상으로 동작이 된다.

c:\Python\code>python beautiful.py
사탕
오렌지


초콜릿
오렌지


초콜릿
오렌지

 

 

  이렇게 다 해놓고 보면 별거 아닌거 같지만, 사실 별거 아닌게 맞다--; 다만 전제 조건은 html 문법에 대해서 충분히 이해하여 소스를 잘 볼 수 있고, 복잡한 html 소스(javascript, css 가 섞여 있고, 코드가 꼬여 있을 수도 있다)를 만나도 개발자 도구 정도만 있으면(최악의 경우 메모장으로 소스를 보더라도 화면하고 비교해 가면서 찾아서) 충분히 원하는 태그의 특징과 위치를 지정 할 수 있는 방법만 파악할 수만 있음 된다. 그럼 이정도로 beautifulsoup 사용법 부분은 마치고, 그래도 짧아 아쉬운 느낌이 든다면 해당 내용을 기반으로 구글에서 크롤링에 대한 다른 블로그들을 보면서 파싱 부분을 어떻게 만든건지 하나씩 소화해 보자. 

 

 

  부록으로 개발자 도구를 사용하는 방법은 구글을 예로 들면, 구글 검색 페이지로 간후 'F12' 키를 누르면 하단에 개발자 도구가 생긴다(물론 다시 F12 를 누르면 사라진다). 이후 구글 검색 박스를 클릭해 포커스를 주고, 마우스 오른쪽 버튼을 눌러서 컨택스트 메뉴를 띄워서 '요소 검사' 항목을 선택한다.

 

  그럼 아래에 해당 되는 요소(엘리먼트, 태그)에 해당되는 소스 위치가 하이라이트 되서 나타난다. 이 소스 부분 구조를 분석해서, 해당 태그에 접근하는 방법을 beautifulsoup 이든 정규식이든 이용해서 설계하는 것이다. 이게 단순하게 보이지만 웹 파싱의 전부다(파싱 난이도는 대상인 소스가 어떤가에 의해 결정난다).

 

 

 

 

 

[WhoIS 사이트 웹을 통해서 읽어오기] 

  그럼 10교시에서 API 를 이용해서 호출했던 WhoIS 페이지를 웹을 통해서 파싱해서 값을 가져와 보자. 문제를 단순해 하기 위해서 여러 IP 를 가져온다든지 하는 기능은 다 제쳐 두고 1개의 주소만 검색해 가져오려 한다(1개의 주소 호출만 해결나면 나머지 코드는 10장의 소스를 참고해서 적당히 머지해 만들면 된다). 우선 WhoIS 페이지로 이동하여 샘플 IP 를 하나 조회해 본다(조회 방법을 모른다면 10교시 내용을 보고오자).

http://whois.kisa.or.kr/kor/whois/whois.jsp

 

  위의 내용 중에 주소를 가져오고 싶은데, 주소 위에서 마우스 오른쪽 버튼을 눌러도 컨텍스트 메뉴가 안 뜬다. 그리고 위쪽 주소창을 보면 URL 뒤에 아무 인자도 없으니 없으니 post 방식으로 조회되는 듯 한다. Ctrl+U 를 눌러 소스보기 창으로 간다. IE11 기준으로 개발자 도구가 열리며 아래와 같이 하단의 탭에 두 개의 파일이 보인다.

 

 

  whois.jsp 와 whois.jsc 이다. 실제 호출은 하나만 일어난 것 같이(whois.jsp) 주소창에 보이지만, 2개의 파일이(whois.jsp, whois.jsc) 호출됬다고 볼 수 있다. '주소' 항목이 있는 소스 위치를 찾기 위해서,  'Ctrl+F' 를 눌러서 '주소'라고 입력후 검색창 옆의 '▶' 화살표를 눌러 다음 항목을 찾다보면 whois.jsc 파일 안에서 아래의 검색된 내용이 있는 것이 보인다. 소스 형태를 보니 우리가 원하던 html 태그가 아닌 일반 텍스트 형태이다. 흠 그럼 문제가 조금 복잡해 진다.

 

 

  그럼 여기서 10교시에서 사용했던 피들러를 다시 사용한다(설치나 세팅에 대해서는 10교시를 참고한다). 피들러를 띄우고 F5 를 눌러 WhoIS 페이지를 재로딩 하여 다시 주소를 조회해 본다. 피들러로 잡힌 소스 중, js, css 파일을 제외하고(del 키를 눌러 필요없는 애들을 삭제했다), 두 개의 주요 파일만 보면 아래와 같다. 분명히 호출은 jsp 파일만 했는데 내부적으로 보니 jsc 파일이 그 후에 연달아 호출된 것이 보인다.

 

 

해당 호출이 어떻게 일어났나 이해하기 위해서 왼쪽 창에서 'whois.jsp' 파일을 선택 하고, 오른쪽 하단의 Response 창에서 'TextView' 탭을 선택 하고, 아래의 검색 창에 'whois.jsc' 를 입력 후 enter 키를 누른다.

 

  그럼 안에 폼이 하나 있고, 해당 폼을 이용해서 whois.jsc 를 호출하는 듯 하다. 폼의 인자는 query(검색할 IP주소), ip(본인 PC IP-아마 감사용인듯), 그리고 호출 결과는 frm 이라는 iframe 에 담기게 되는 듯 하다. 검색 창에서 'ifrm' 을 넣어 찾아보면 javascript에서 submit 을 하는 코드가 있다.  

 

  또 'frm' 을 넣어 찾아보면, 결과를 담는 iframe 코드가 보인다.

 

  그럼 파이썬으로 구현할때는 굳이 jsp 파일을 호출할 필요 없이 jsc 파일을 직접 호출해도 될것 같다. 대신 호출 할때 post 방식으로 query(검색 대상 IP) 와 ip(본인 PC IP) 를 넣어서 보내면 된다. 이후 결과 값을 받아온 후 파싱 하면 되는데, 불행하게도 돌아오는 데이터는 일반 텍스트 형태이기 때문에, beautifulsoup 을 이용할 순 없고, 8교시에 배운 정규 표현식을 이용해 본다. 

 

 

 

 

[파이썬 코드 제작]

그럼 이제 제작에 필요한 모든 밑 작업은 완료되었으므로, 언제나처럼 우리가 현재 모르는게 무언지 체크해 보자.

1)  post 로 호출해 값을 얻어오는 방법을 모른다(예전 API 는 거의 get 방식이였다)

2) "주소(적당한 공백들  ): 원하는 주소" 를 파싱하는 정규 표현식을 모른다(정규식을 대충 배웠으니 이젠 어떻게든 할수 있어 보이긴 한다)

 

 

  먼저 post 로 호출 하는 방법을 찾아보자. 구글에서 'python requests post' 를 이용해 아래 페이지를 찾았다.
http://docs.python-requests.org/en/master/user/quickstart/

 

  해당 페이지 내에서 아래의 more-complicated-post-requests 항목의 소스를 이용하려고 한다.

http://docs.python-requests.org/en/master/user/quickstart/#more-complicated-post-requests

1
2
3
4
payload = {'key1''value1''key2''value2'}
 
= requests.post("http://httpbin.org/post", data=payload)
print(r.text)
cs

 

 

  그 담에 정규식은 8교시에서 이미 다루었으니, 해결 결과만 얘기하면 아래와 같다.

"주소.*: ([^0-9].*)" -> 해석 해보면 주소란 단어로 시작하고(주소), 아무 글자나(.* : 사실 공백을 나타내는 /s 를 써도 좋지만 굳이 현재 상황상 그럴 필요까진 없을 듯해서 임의의 문자로 지정 했다) 0개 이상 나타나고 : 기호가(:) 가 나온 후 공백( ) 이 하나 나오고, 숫자가 나오지 않으면서 다른 아무 문자들로나 채워진([^0-9].*) 부분을 지정한다. 실제 가져오는 값은 이전에 8교시에 배에 배운 괄호 그룹기호를 이용한다(([^0-9].*)) . 결과에서 숫자를 제외한 이유는 피들러에서 '주소'를 검색했을때 '주소' 란 글자로 시작하는 경우에 'IPv4주소'가 있기 때문에 배제하려고 했다. 첨엔 주소 앞에 ^를 넣어 구분하려 했으나 각 행이 문장의 처음으로는 인식되지 않아서 위와 같이 만들었다. 주소 앞에 특정 문자가 있을 때는 무시하도록 정규 표현식을 짜도 될 듯 하지만 사실 넘어오는 값에 대해서 정확히 텍스트 구조가 파악 안되서 쉬운 길로 갔다. 

1
2
3
4
import re
pattern = re.compile("주소.*: ([^0-9].*)")
match = re.findall(pattern, r.text)
print (match[0]) 
cs

 

[두 개의 주소가 나오는 상황]

IPv4주소           : 202.30.50.0 - 202.30.51.255 (/23) <- 이 부분을 배제하기
주소               : 서울특별시 송파구 중대로  <- 그룹으로 묶어 가져옴

 

 

  위 두 개의 코드를 합치면 아래와 같이 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#-*- coding: utf-8 -*-
# jsc 페이지 post 로 호출하기
import requests
payload = {'query''202.30.50.51''ip''본인의 IP 주소'}
= requests.post("https://whois.kisa.or.kr/kor/whois.jsc", data=payload)
r.encoding = 'utf-8'
# print(r.text) 
 
 
# 결과에서 주소 얻어오기
import re
pattern = re.compile("주소.*: ([^0-9].*)")
match = re.findall(pattern, r.text)
print (match[0]) 
cs

 

  위의 소스를 복사해서 '본인의 IP 주소' 부분을 본인의 IP 로 바꾼 후(피들러에서 whois.jsc 검색 후 본 IP를 적음 된다), c:\python\code 폴더에 whois_test.py 라고 저장한다. 이후 아래와 같이 실행 시켜본다. 파싱된 주소를 정상적으로 가져온다.

c:\Python\code>python whois_test.py
서울특별시 송파구 중대로

 

 

 

 

[마무리 하면서]

  위 예제는 웹 파싱이 꼭 beautifulsoup 을 사용할 수 있도록 아름답게만 제공되지 않는다는 것을 보여주려고 진행했다. 또한 html 뿐만 아니라, css, javascript 등의 연관된 언어를 잘 알아야 분석이 용이 하다는 얘기도 하고 싶었다. 실제 웹 대상으로 구현 하다보면 여러가지 파싱 장벽에 만나게 된다. 태그가 일부 깨져서 제대로 모듈이 동작 안할 수도 있고(브라우저는 왠만하면 에러를 안내고 깨진 태그를 적당히 해석해서 잘 보여주려 한다), 개발자가 기존 코드를 복사해서 개발하느라 고유해야 할 id 등이 여러 개 존재할 수도 있고, iframe 등으로 페이지가 복잡하게 꼬여 있을 경우 지금 처럼 해당 되는 실제 소스의 url을 찾아야 할때도 있고, 보안 상의 이유로 이리저리 자바스크립트를 이용해 호출을 숨겨놓거나, 난독화 시킨 코드들도 만들수 있다. 또 겨우 만든 파이썬 코드가, 해당 웹 페이지가 변경되면서 구조가 변경되어 다시 수정해야 되는 일이 어느날 갑자기 생길 수도 있다. 그것은 자기가 만든 웹 사이트가 아닌 이상(하물며 자기 회사 페이지라도 변경이 잘 통제되지 않는 이상) 언제든지 일어날 수 있는 일들이라고 생각해야 한다.

 

  위의 코드를 10교시때 만들었던 API 호출 코드와 적절히 머지하여 반복적으로 IP를 호출하게 할 수도 있지만 앞에서도 얘기했듯이 크롤링과 같은 웹페이지의 반복적인 호출은 사이트에게 절대 환영받는 행동은 아니기 때문에 가능한 API 를 정식으로 발급 받아 필요한 만큼만 사용하는 것을 권장한다. 혹시 API 등도 없고 개인용도로 사용하면서 대량의 페이지를 조회 할 필요가 있을 때에는 아래와 같은 식으로 약간의 호출시간 간격을 두어, 조회하거나 적정한 숫자로 제한해 여러번 나눠 호출 하는 것이 장기적으로 사이트에나 본인에게 바람직 할 듯 싶다. 과유불급(過猶不及)이란 말을 꼭 생각하며 살자. 좀 의견이 고루 한것도 같으나 대부분의 크롤링 관련 글에서 이런 얘기는 잘 못본 것 같아서 균형을 맞추기 위해서 부득이하게 강조하게 된다.

1
2
import time
time.sleep(0.1
cs

 

  참고로 크롤링과 관련된 소송 사례를 두개 링크 한다.

https://www.lawtimes.co.kr/legal-news/Legal-News-View?Serial=98844&kind=AA

http://www.ddaily.co.kr/news/article.html?no=130822

 

 

  그럼 다음 시간에는 이왕 API, 파싱까지 간거, 웹 자동화에 대한 이런저런 얘기들을 라이트하게 진행 할까 계획하고 있다.

 

 

[보충 - 09님 문의에 대한 답변]

 

네이버 책에서 "구글로 공부하는 파이썬" 책의 제목과 가격을 가져와 Sqlite 에 저장하는 코드 입니다. 참고로 요소 검사기로 보면 "구글로 공부하는 파이썬" 책은 옆에 부제(구글에서 찾는 파이썬 실전 예제 분석)가 달려있는데, 부제가 없는 책들은 46 라인에서 결과가 없어 에러가 나기때문에, 판단을 해서 예외처리를 해야 합니다~ 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#-*- coding: utf-8 -*-
import sqlite3
import requests
from bs4 import BeautifulSoup
import re
 
# sqlite 파일을 열음
conn = sqlite3.connect("price.db", isolation_level=None)
cursor = conn.cursor()
 
# BookInfo 테이블을 생성한다.
cursor.execute("""CREATE TABLE IF NOT EXISTS BookInfo(itemno text, title text, price INTEGER)""")
 
 
# url 요청할 세션 만들기
= requests.session()
 
# URL 만들기
searchItemno = '13394873'
searchurl = 'http://book.naver.com/bookdb/book_detail.nhn?bid='
 
# URL 호출
con = s.get(searchurl + searchItemno)
 
# html 파서 사용
soup = BeautifulSoup(con.text, 'html.parser')
 
# 책 가격 들어 있는 태그 가져오기
item_price = soup.find("div", class_="lowest")
item_price2 = item_price.find("strong")
 
# Beautiful Soup 으로 가져오기엔 태그가 묘해서, 정규표현식으로 실제 가격 가져옴
pattern = re.compile("<strong>(.*)<span"
match = re.search(pattern, str(item_price2))
 
# 책 가격 가져옴
book_price = match.group(1)
 
 
# 책 제목 가져옴(서브 제목이 없을 경우는 에러나서 다르게 찾아야함)
title = soup.find("a", class_="N=a:bil.title,i:98000001_000000000000000000CC63B9")
 
# Beautiful Soup 으로 가져오기엔 태그가 묘해서, 정규표현식으로 실제 제목만 가져옴
pattern = re.compile(">(.*)<span"
match = re.search(pattern, str(title))
book_title = match.group(1)
# "뒤의 \xa0" 문자 제거
book_title = book_title.strip()
 
 
# 테이블에 저장하기
sql = "INSERT into BookInfo(itemno, title, price) values (?, ?, ?)"
cursor.execute(sql, (searchItemno, book_title, book_price))
 
 
# 테이블에 저장한 값 불러오기
sql = "select itemno, title, price from BookInfo"
 
cursor.execute(sql)
row = cursor.fetchone()
 
if row: 
   while row:
      print(row)
      row = cursor.fetchone()
cs

 

c:\Python\code>python naver_book.py
('13394873', '구글로 공부하는 파이썬', 37800)

 

 

1
2017.3.22 by 자유로운설탕 
cs
posted by 자유로운설탕
2017. 3. 4. 12:57 프로그래밍

  8교시는 정규 표현식이 무언지를 알아보고, 파이썬에서 사용하는 샘플을 시연하려 한다.

 

 

[목차]

0. 왜 파이썬 공부에 구글을 이용하는게 좋은가?

1. 언어를 바라보는 방법. 파이썬을 어떻게 바라봐야 할까?

2. 파이썬 설치와 환경, 버전 선택 하기의 이유.

3. 만들고자 하는 기능을 모르는 조각으로 나눠 조사해 보기

4. 데이터 베이스에서 내용 가져와 출력하기

5. 암호화 모듈을 이용해 암복호화 해보기

6. 퍼즐 조각들을 합쳐보기

7. 엑셀 파일 사용해 보기 -> 부록 : fuction 을 이용해서, 코드 정리해 보기

8. 정규표현식을 왜 사용해야 할까? 언어속의 미니 언어 정규표현식 살펴보기

9. 입력과 결과를 GUI 화면과 연결해 보기

10. Whois API 이용해 보기

11. 웹페이지 호출해 내용 파싱 하기(BeautifulSoup 그리고 한계)

12. 자동화 - 웹 자동화(with Selenium)

13. 자동화 - 윈도우즈 GUI 자동화(with pywinauto)

14. 자동화 - 작업 자동화

15. 수학 라이브러리 살펴보기

16. 그래픽 라이브러리 살펴보기

17. 머신러닝에서의 파이썬의 역활

18. 웹 프로그래밍 - Legacy Web

19. 웹 프로그래밍 - Flask 살펴보기(feat. d3.js)

20. 웹 프로그래밍 - Django 살펴보기

21. 정리 - 이런저런 이야기

 

 

 

[들어가면서] 

  정규 표현식이란 건 처음 보게 되면 정말 이상한 기호로 이루어진 암호문 같은 언어라는 생각이 든다. '.',  '*',  '[', '-' 등의 보통 프로그램에서는 구조용 기호로만 쓰일 여러가지 문자로 이루어진 문법을 사용하는데, 웬만한 프로그래밍 책에서는 또 한번씩은 슬쩍 다루기도 한다. 이메일이나 전화번호 같은 형태를 검출 하는데 사용한다고 얘기는 들었지만, 당장 프로그래밍 언어도 배우기 힘들어 죽겠는데 이걸 정말 어디다 쓰나 싶다. 또 이메일 검출이 필요하다면 구글에서 관련 샘플을 가져다 쓰면 되지 않나도 싶다. 그래서 아마 한번 쯤 훝어보고 이렇구나 하고 넘어가는 경우가 많을 것 같다. 이 시간을 통해서 어떤 경우 정규 표현식이 유용하게 쓸 수 있을까 하는 부분에 대해 조금이라도 전달이 되고, 나중에 따로 시간을 내서 찬찬히 공부를 해보는 것도 괜찮겠구나 생각이 드는 계기가 된다면 더 바랄 것은 없을 듯하다.

 

  언어나 툴들 사이의 정규 표현식의 사용법 및 기능 차이가 있다. 예를 들면 언어인 perl, python, java 뿐만 아니라, 내용, 파일이름 검색에서 정규 표현식을 지원하는 울트라에디터, Total commander 같은 유틸리티 들도 약간의 사용 차이를 가지고 있다.  아래의 예를 보면 울트라에디터라는 편집기에서 perl, unix, 울트라 에디터 방식의 3가지 정규 표현식 스타일(문법)을 내용 검색을 지원 한다는 것이다.

 

  개인적으로 느끼기에 언어별 이런 정규 표현식 문법 표시 및 사용 스타일의 차이는 어느 언어든간에 해당 언어를 만든 사람이 이렇게 사용하면 편하겠다고 생각해서 디자인한 걸테고, 기본적인 틀은 사실 많이 벗어나지 않는다. 예를 들면 앞에서 얘기했던 관계형 데이터 베이스인, 오라클, Mysql, MSSQL 이 SQL의 기본베이스만 잘 이해하고 있다면 확장된 차이점만 공부하면 되는 것과 마찬가지로(물론 언어별로 깊이 들어가면 디테일한게 있다고 얘기하심 반박하긴 힘들다), 언어들의 사용법들의 세세한 차이보다 중요한건, SQL 의 여러 공통된 요소들에 대한 깊고 정확한 이해라고 본다. 어느 하나라도 기본 베이스가 되면 사실 스타일만 적용하면 된다고 보면 된다.(다만 예외인 것은 SQL 도 마찬가지지만, 사용자의 편의성을 위해서 기본 기능을 확장한 부분들은 각 언어별로 장 단점이 있으리라고 본다)

 

  정규 표현식도 SQL 과 마찬가지로 기본 베이스가 중요하다. 정규식의 기본 뼈대를 구성하는 요소들만 찬찬히 잘 이해하고, 정규 표현식으로 할수 있는 일과 할 수 없는 일을 정확하게 인식만 할수 있다면, 문법을 굳이 암기 하지 못한다고 하더라도(개인적으로는 매번 정규 표현식이 필요할때 마다 다시 가물가물해서 메뉴얼을 찾아 보곤 한다 --;) 그 부분은 구글이나 해당 언어의 정규표현식 메뉴얼을 찾아가면서 필요한 기능을 만들면 된다. 그리고 다시 한번 강조 하고 싶은 부분 중 하나는 정규 표현식 역시 파이썬에서 자주 사용은 하겠지만, 파이썬의 본질적 문법과는 그닥 상관없는 프로그래밍의 외적 요소 중 하나라는 것이다. 다만 익혀 두면 텍스트 원본에서 의미 있는 데이터를 찾아내는 파싱(parsing) 이라는 측면에서 정규식을 모르는 사람과는 조금은 차이가 난다고 생각하여 이렇게 따로 한 챕터를 분리해서 맛보기로 보여주려고 한다.

 

 

  그럼 정규 표현식은 어떨때 많이 유용할까? 텍스트 형태라고 했으니 html 을 파싱 할때, xml 을 파싱 할때, json 을 파싱 할때? 일단 기본적으로 텍스트 형식의 데이터는 모두 적용 가능하지만, 사실 json, xml 같은 많이 쓰이는 정형화된 텍스트 형태의 데이터 들은 이미 정규 표현식 보다 효율적으로 내부에 담겨진 데이터 위치를 쉽게 찾아, 접근 할수 있는 모듈들이 많이 만들어져 있다. 개인적으로 이런데에 정규 표현식을 쓰는건 계륵이라고 생각한다(전동 드라이버가 집에 있는데, 드라이버를 넣을 공간이 안되 힘들다 거나 하는 특별한 이유도 없이 굳이 일반 드라이버를 수동으로 열심히 돌리는 것과 같다고 본다). html, xml 의 구조 파싱의 경우 beautiful soup 이라는 레퍼런스가 풍부한 좋은 모듈이 있는듯 하고, json 은  파이썬에 내장된 모듈이 있다. json 은 뒤의 api 쓰는데서 사용할 예정이고, beautiful soup 도 개인적으로도 궁금해 한 챕터를 추가할까 싶다. (이미 구글에서 소스보기, 크롤링 관련해서 관련해서 잘 설명해 놓은 글들이 많은 것 같긴 하지만...). 그래서 사실 정규 표현식은 적당히 비 공식적인 포맷에 잘 어울린다. telnet 으로 연결한 터미널의 출력 결과 라든지, 로그파일 이라든지, 메신저 프로그램 등의 텍스트 형태의 내보내기(export) 파일 이라든지 말이다. 여튼 적절하게 정형화된 텍스트 형태의 데이터를 대상으로 가장 적합한 사용을 할수 있다고 개인적으로 생각한다.

 

 

[추천하는 공부방법]

  그리고 공부하기 위해서 관련 책을 선택할 때는 조금 헷깔려 할 것 같은 부분이 있다. 정규 표현식 책은 보통 2가지 스타일이 있다고 보는데, 하나는 언어별로 비교해 다루면서 설명하는 책과, 다른 하나는 하나의 언어만 선택해서 설명에 들어가는 책이 있는 듯 한다. 전자는 내가 쓰는 언어를 사용해 샘플을 만들어 시연해 볼수 있는 가능성이 크지만, 너무 여러가지 언어의 차이점을 다루느라 내용 전달에 초점이 안 맞춰질 수 있고, 반대로 후자는 한 가지 언어만을 다루면서 너무 일반적인 내용을 다루거나, 반대로 너무 깊이 들어가 난이도가 어려워 져서 처음 보는 입장에서는 힘들 수 있다. 예전에 봤던 책 중에 추천할 만한 책은(이해 관계는 전혀 없다 --;) "손에 잡히는 정규 표현식" 이라는 책과, 요즘 나온 책 중에서는 괜찮아 보이고 평도 괜찮은 책은 "다양한 언어로 배우는 정규표현식" 이다. 두 번째 책은 정규 표현식의 구조 까지 언급해 들어가는 게 좀 걸리긴 하지만, 한국어 책만 보고 싶다면 위의 두 책을 순서대로 보거나, 앞 책을 우선 보고 다음 책을 계속 볼지 고려해 보는 것도 어떨까 싶다. 앞 책은 개인적으로 평가했을 때, 초보자가 보기에도 확실히 잘 구성되 있다. 

 

  개인적으로 정규 표현식을 공부하는 방법은 일단 너무 어렵지 않은 위에 언급한 것과 비슷한 책을 한권 찬찬히 읽어 전체적인 흐름을 파악한후, 자기가 공부 하고 싶은 언어의 정규 표현식 사용법을 살펴 보는거다. 이후 좀더 전문적으로 속 내용 까지 보고 싶으면 mastering regular expressions 같은 책을 찬찬히 보심 될 듯하다.(얘 쫌 두껍긴 하다)

 

 

  마지막으로 실습을 해볼때는 3가지 방법이 있을 듯 한데, 1) 정규 표현식을 테스트 할수 있는 유틸리티 프로그램으로 실습하는 것, 2) 정규 표현식을 테스트 할 수 있는 웹 사이트를 이용하는 것, 3) 파이썬을 그대로 이용해 실행 해보는 것 이다. 파이썬을 바로 실행하면서 이것저것 공부해 보는 것도 나쁘진 않아 보이는데, 파이썬에 익숙하지 않다고 생각하는 사람은 1), 2) 번이 정규 표현식 자체의 공부에만 집중 할 수 있어 좋다.

 

  유틸리티 프로그램 사용 방식은 아래의 툴을 다운 받으면 소스가 .net 기반인 거 보니 .net 형식의 정규식 일거 같긴 하지만, 처음 공부할 레벨의 기본 정규식은 거의 호환이 될 것이다.

https://sourceforge.net/projects/regulator/

 

  사용 방법은 프로그램을 다운받아 압축을 풀고 Regulator.exe 을 실행 시킨다. 텍스트 편집기에 테스트할 내용을 아래와 같이 적은 후 c:\python\code 폴더에  reg.txt 로 저장한다.

1
2
<hi> test
  me <hello>
cs

 

  이후 아래의 프로그램 메뉴에서,  

   1) 내용 파일을 선택 해서 저장한 reg.txt 를 불러오면 해당 내용이 Input 창 내에 보인다.

   2) 이후 문서창에 우리가 원하는 정규 표현식을 넣는다. 여기서 넣은 내용은 "^<.*>" 

   3) 상단의 'Match' 버튼을 클릭 한다.

   4) 정규 표현식과 매치된 결과들이 나온다.

  입력된 정규 표현식(^<.*>)을 해석 하면 "행의 처음 시작(^)이 '<' 문자이고, 그 뒤에 어떤 글자든(.) 0개 이상 나오고(*) 다시 '>' 로 끝나는 단어들을 찾아줘" 이다. 그래서 1번째 행의 <hi> 는 '<'가 행의 처음 시작이기 때문에 선택되고, 2번째 행의 <hello> 는 행의 내용 중간에 '<' 가 시작되서 선택이 안되었다. 위쪽의 정규 표현식에서 '^' 기호만 빼고 다시 'Match' 버튼을 누르면, 이제는 '행의 시작'이라는 조건이 사라졌기 때문에 <hi>, <hello> 모두 선택 되니 해보길 바란다. 

 

 

 

  두 번째는 제가 한참 공부할땐 생각 못했던 웹 페이지에서 쓰는 방식이다. 구글에서 'regular expression tool' 이라고 찾음 맨 처음에 나오는, https://regex101.com/ 페이지 이다. 사용법은 거의 비슷하다. 첫 번째로 왼쪽에서 FLAVOR 섹션에서 'python' 을 선택한다. 그럼 이제 부터 적용하는 모든 정규 표현식은 파이썬 스타일로 적용된다.

 

  그 후 밑의 그림에 있는데로 TEST STRING 안에 아까 텍스트 파일로 저장했던 내용을 복사해 넣고, REGULAR EXPRESSION 안에 우리가 넣었었던 ^<.*> 식을 넣으면, 실시간으로 왼쪽 밑의 MATCH INFOMATION 쪽에 매칭된 내용이 나온다. 파이썬으로 정규 표현식을 만들 필요가 있을때는 이런 웹페이지에서 원하는 패턴의 텍스트와, 정규 표현식을 충분히 테스트 하고, 테스트가 완료되면 파이썬 코드에 살포시 집어 넣어주면 버그도 없고 좋을 듯 하다.

 

 

 

 

[파이썬으로 정규 표현식 실행해 보기]

  ㅎㅎ 사실 이 시간은 언어속의 부록 언어같은 정규 표현식에 대해서 얘기하는 시간 이였어서, 위의 도입 부분에서 이미 할 모든 얘기를 다 한듯 싶기도 하다 --; 그럼 이제 부턴 맘 편하게 파이썬에서 어떻게 정규 표현식이 사용 되는지 간단한 샘플과 함께 시연을 하고 마치려 한다. 우선 파이썬에서 정규 표현식이 어떻게 쓰이는지 알고 싶다면 구글에서 'python regular expression' 이라고 검색한다.  그럼 맨위에 바로 파이썬 공식 페이지의 문서가 있다.

https://docs.python.org/2/library/re.html

 

 

  본인이 정규 표현식만 다른 언어에서 잘 경험해 보았다면, 해당 페이지를 슬슬 훝어보면 다른 언어와의 차이를 빨리 캐치하고, 바로 사용에 들어갈 수도 있을 것이다.  그럼 파일을 읽어와서 파일안에서 특정 정규 표현식과 일치하는 단어들을 찾는 예제를 만들어 보려 한다. 위의 파이썬 예제 페이지의 샘플은 그다지 바로 쓰기엔 직관적이지 못한듯 해서, 구글에다 'python regular expression file' 라고 검색한다. 가장 상단의 스택오버플로우 글을 본다

http://stackoverflow.com/questions/10477294/how-do-i-search-for-a-pattern-within-a-text-file-using-python-combining-regex

 

  제일 위에 있는 아래 샘플이 맘에 든다.

1
2
3
4
5
6
7
8
9
10
11
12
13
#-*- coding: utf-8 -*-
# regular expression 모듈 임포트
import re
 
# 정규 표현식 패턴 등록
pattern = re.compile("<(\d{4,5})>")
 
# test.txt 파일을 한줄 한줄 가져와서(enumerate) 루프를 돌리면서
for i, line in enumerate(open('test.txt')):
# 해당 줄에서 원하는 패턴을 모두 찾아 한건 한 건 꺼내어서
    for match in re.finditer(pattern, line):
# 몇 번째 라인에서, 어떤 값을 찾았는지 모두(group()) 보여준다.
        print 'Found on line %s: %s' % (i+1, match.groups())
cs

 

 

  그럼 위의 샘플을 적당히 변경해서, 우리가 아까 만든 reg.txt 에 대한 정규 표현식 예제를 만들어 본다.

1
2
3
4
5
6
7
8
9
10
11
12
13
#-*- coding: utf-8 -*-
# regular expression 모듈 임포트
import re
 
# 정규 표현식 패턴 등록(처음 시작이 < 이고 > 로 닫히는 단어)
pattern = re.compile("^<.*>")
 
# reg.txt 파일을 한줄 한줄 가져와서(enumerate) 루프를 돌리면서
for i, line in enumerate(open('reg.txt')):
# 해당 줄에서 원하는 패턴을 모두 찾아 한건 한 건 꺼내어서
    for match in re.finditer(pattern, line):
# 찾은 값을 뿌림.
        print (match.groups())
cs

 

  근데 해당 파일을 c:\Python\code 폴더에 reg_sample1.py 로 저장해서 실행하면 아래와 같이 빈 괄호 기호만 나오게 된다.

c:\Python\code>python reg_sample1.py
()

 

 

  샘플이 잘못된거 같진 않은데 왜 그럴까? 원인을 찾기위해서 처음 찾았던 파이썬 공식 페이지의 regular expression 페이지에서 groups 에 대한 내용을 본다. (7.2.4 Match Objects) 페이지에 있는 group 과 groups 예제를 보니, groups 는 기본적으로 튜플로 반환하고(현재로서는 리스트랑 튜플이 뭐가 다른진 모른다), group(0) 을 하거나 group() 을 했을 때 전체 결과를 문자열로 반환한다고 한다. 그래서 마지막 print 문 안의 groups 를 group 으로 바꿔 보았다.

https://docs.python.org/2/library/re.html

 

1
2
3
4
5
6
7
8
9
10
11
12
13
#-*- coding: utf-8 -*-
# regular expression 모듈 임포트
import re
 
# 정규 표현식 패턴 등록(처음 시작이 < 이고 > 로 닫히는 단어)
pattern = re.compile("^<.*>")
 
# reg.txt 파일을 한줄 한줄 가져와서(enumerate) 루프를 돌리면서
for i, line in enumerate(open('reg.txt')):
# 해당 줄에서 원하는 패턴을 모두 찾아 한건 한 건 꺼내어서
    for match in re.finditer(pattern, line):
# 찾은 값을 뿌림.
        print (match.group()) #여기 변경
cs

c:\Python\code>python reg_sample1.py
<hi>

 

  흠 근데 실행해 보니 s 만 하나 뺐을 뿐인데, 값이 제대로 출력되어 나온다. 원래대로라면 groups 가 아무리 튜플이라는 리스트 비슷한 구조로 표시한다 하더라도, 안에 뭔가 들었기 때문에 어떤 결과든 나와야 할것 같은데, 상식적으로 빈 괄호로만 나오는게 이해가 안된다. 그럼 뭔가 원인이 있을테니 구글에 좀더 자세히 검색해 본다. 'python regular expression group vs groups'. 가운데 쯤 나오는 스택 오버플로우 페이지에 원하던 설명이 나오는 것 같다.

http://stackoverflow.com/questions/9347950/whats-the-difference-between-groups-and-group-in-pythons-re-module

 

groups() only returns any explicitly-captured groups in your regex (denoted by ( round brackets ) in your regex), whereas group(0) returns the entire substring that's matched by your regex regardless of whether your expression has any capture groups.

-> 결국 우리가 패턴을 등록할때 명시적으로 그룹이 나올 수 있도록 정규식 내에 넣는 '( )' 부분이 없기 때문이라고 한다.

  ※ group 에 대한 설명은 정규 표현식을 정식으로 공부하다보면 알수 있다. 파이썬으로 예를 들면, 클래스 개념 같은 약간 고급의 응용 문법이다.

 

 

  그럼 해당 부분을 증명하기 위해 첫번째 에러난 코드를 한번 수정해 보자. 패턴 부분을 ( )로 감싸서 그룹 결과가 나오게 만든다.

1
2
3
4
5
6
7
8
9
10
11
12
13
#-*- coding: utf-8 -*-
# regular expression 모듈 임포트
import re
 
# 정규 표현식 패턴 등록(처음 시작이 < 이고 > 로 닫히는 단어)
pattern = re.compile("(^<.*>)")  #패턴을 가로로 감싸보자
 
# reg.txt 파일을 한줄 한줄 가져와서(enumerate) 루프를 돌리면서
for i, line in enumerate(open('reg.txt')):
# 해당 줄에서 원하는 패턴을 모두 찾아 한건 한 건 꺼내어서
    for match in re.finditer(pattern, line):
# 찾은 값을 뿌림.
        print (match.groups())
cs
c:\Python\code>python reg_sample1.py
('<hi>',)

 

  자 이제 결과가 나오고 찾은 설명이 맞았다는게 증명됬다.

 

  부록으로 그럼 첫번째 잘 돌아간 reg_sample1.py 에서 group 을 출력하는 부분에서 <> 를 제외한 hi 만 가져오려 한다면 어떻게 될까? 이럴때 우리를 괴롭혔던 그룹표시() 가 반대로 도움을 준다. 패턴 코드에서 re.compile("^<(.*)>") <- 요렇게 안쪽에 괄호를 써보자 그럼 <> 안에 있는 어떤 글자든(.) 0개 이상 나오면(*) 1번째 그룹으로 지정이 된다. 그후 match.group(1) 로 수정해서 첫번째 그룹을 가져온다. 저장하고 실행 하면 아래와 같이 'hi' 만 가져오게 된다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
#-*- coding: utf-8 -*-
# regular expression 모듈 임포트
import re
 
# 정규 표현식 패턴 등록(처음 시작이 < 이고 > 로 닫히는 단어)
pattern = re.compile("^<(.*)>") #원하는 부분에 괄호 넣기
 
# reg.txt 파일을 한줄 한줄 가져와서(enumerate) 루프를 돌리면서
for i, line in enumerate(open('reg.txt')):
# 해당 줄에서 원하는 패턴을 모두 찾아 한건 한 건 꺼내어서
    for match in re.finditer(pattern, line):
# 찾은 값을 뿌림.
        print (match.group(1)) #여기 변경 #1번 그룹을
cs

c:\Python\code>python reg_sample1.py
hi

 

 

 

  그럼 하나만 하면 정이 없으니 하나 샘플을 더 만들어 보려고 한다. 먼저 c:\Python\code 폴더에 reg2.txt 파일을 만들어 아래 내용을 넣는다.

1
2
3
4
5
6
7
1cake - Right
jelly 12hey - Wrong
maybe12 - Wrong
    3joy - Wrong
4432 - Right
23b - Right
  5555b - Wrong
cs

 

  자 위에서 Right 라고 적힌 3개의 줄 내용만 출력하고 싶다. 3개의 공통 점은 무엇 일까? 숫자로 시작되고 줄 처음에서 시작된다는 것이다. 그럼 위에서 사용한 정규 표현식에다 두 가지 개념만 더 추가함 된다. 1) "숫자로 시작한다는 것" 2) "문장 전체를 가져오려 한다는것". 해당 부분을 목표로 정규 표현식을 작성하면 다음과 같다. ^[0-9]+.*

 

  설명하자면 "숫자가([0-9]) 한개이상 (+) 문장의 맨처음에 나온다(^)", "이후엔 아무 문자나(.) 나오거나 말거나 상관없다(*)" 이다. 

 

  그럼 위의 잘 돌아갔던 소스를 가져다가 파일 가져오는 부분과, 정규 표현식 패턴 부분만 수정해 보자. 해당 파일을 c:\Python\code 폴더에 reg_sample2.py 로 저장해서 실행하면 아래와 같이 Right 줄만 3개 나오게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
#-*- coding: utf-8 -*-
# regular expression 모듈 임포트
import re
 
# 정규 표현식 패턴 등록(처음 시작이 < 이고 > 로 닫히는 단어)
pattern = re.compile("^[0-9]+.*"
 
# reg.txt 파일을 한줄 한줄 가져와서(enumerate) 루프를 돌리면서
for i, line in enumerate(open('reg2.txt')):
# 해당 줄에서 원하는 패턴을 모두 찾아 한건 한 건 꺼내어서
    for match in re.finditer(pattern, line):
# 찾은 값을 뿌림.
        print (match.group())
cs

c:\Python\code>python reg_sample2.py
1cake - Right
4432 - Right
23b - Right

 

 

 

[마무리 하면서]

  이 시점에서 우리가 한번 생각해 봐야할 부분이 있다는 생각이 든다. 프로그래밍을 스트레스를 덜 받으면서 하려면, 도메인 지식이나, 정규 표현식 같이 다양하게 연결된 배경 지식들을 잘 알고 있어야 하듯이, 정규 표현식을 잘 쓰려면, 문법 이외에도, 적용하려는 데이터의 패턴을 정규 표현식에서 지원하는 방식으로 인지 할 수 있어야 한다. 만약 적절한 패턴이 없다면 데이터가 만들어질 때부터 가능한 패턴을 가지도록 디자인 하는 부분을 고려해야 할지도 모른다. 어찌 보면 정규 표현식의 문법에 대해서 아는 것보다 이러한 부분이 더 중요하고 어려운 문제일 수도 있다. 이것은 프로그래밍 뿐만 아니라 다른 여러 분야도 비슷한 부분들이 많다. 너무 트랜드에만 휩쓸리진 말고 손가락 보다는 손가락이 가르키는 대상을 보려고 노력하자^^

http://m.blog.naver.com/sukbongcho/10157104127

 

 

  그러면 얼렁 뚱땅 넘어가긴 했지만, 이렇게 정규 표현식(자꾸 정규식이라고 쓴다. 그림 등에 오타가 있는데 그러려니 해주시길... --;) 편을 마치 겠다. 배워야 할께 너무 많은 세상이지만 기초적인 부분은 꼭 찬찬히 짚어가면서 걸어가시길 바라면서..

 

 

 

 

[보충]

  본문에 나왔던 '튜플'에 대해서 궁금해서 찼아봤다. 튜플을 함수형 프로그래밍 등에서 쓰는 불변 값의 리스트로 봐도 좋겠지만, 첫 번째 링크에 나온데로, 인덱스를 가진 작은 읽기전용의 데이터 구조 라고 보는것이 맞을 듯 싶다. 

http://jtauber.com/blog/2006/04/15/python_tuples_are_not_just_constant_lists/

http://news.e-scribe.com/397

 

 

  파이썬 정규식에서 사용하는 search, match, findall, findIter 로 찾는 차이가 궁금해서 정리한다.

> search - 맨 처음 만나는 문자열을 match object 구조로 반환한다.

> match - search 아 다른 점은 항상 패턴 앞에 ^(문장 처음) 가 있는 것처럼 판단하다.

> findall - 해당 되는 문자열을 모두 찾아 문자열 리스트로 반환한다.

> finditer - 해당되는 문자열을 모두 찾아 match object 형태의 iterator 로 반환한다. 그래서 위의 예제 들에서 이 개체를 이용해서 루프를 돌리며 match object 의 group 속성을 얻어냈다.

 

 

 

1
2017.3.5 by 자유로운설탕
cs
posted by 자유로운설탕
2017. 3. 1. 13:46 프로그래밍

  이번 부록 시간에는 7교시때 만들었던 쿼리 결과를 엑셀로 저장했던 코드를 함수를 이용해서 정리해 보는 작업을 해보려고 한다. 과정을 설명하는게 좀 난해하고 속을 보이는 것 같아 창피하게 느껴지니 전개가 어설프더라도 이해하기 바란다.

 

 

[목차]

0. 왜 파이썬 공부에 구글을 이용하는게 좋은가?

1. 언어를 바라보는 방법. 파이썬을 어떻게 바라봐야 할까?

2. 파이썬 설치와 환경, 버전 선택 하기의 이유.

3. 만들고자 하는 기능을 모르는 조각으로 나눠 조사해 보기

4. 데이터 베이스에서 내용 가져와 출력하기

5. 암호화 모듈을 이용해 암복호화 해보기

6. 퍼즐 조각들을 합쳐보기

7. 엑셀 파일 사용해 보기 -> 부록 : fuction 을 이용해서, 코드 정리해 보기

8. 정규표현식을 왜 사용해야 할까? 언어속의 미니 언어 정규표현식 살펴보기

9. 입력과 결과를 GUI 화면과 연결해 보기

10. Whois API 이용해 보기

11. 웹페이지 호출해 내용 파싱 하기(BeautifulSoup 그리고 한계)

12. 자동화 - 웹 자동화(with Selenium)

13. 자동화 - 윈도우즈 GUI 자동화(with pywinauto)

14. 자동화 - 작업 자동화

15. 수학 라이브러리 살펴보기

16. 그래픽 라이브러리 살펴보기

17. 머신러닝에서의 파이썬의 역활

18. 웹 프로그래밍 - Legacy Web

19. 웹 프로그래밍 - Flask 살펴보기(feat. d3.js)

20. 웹 프로그래밍 - Django 살펴보기

21. 정리 - 이런저런 이야기

 

 

 

 

[들어가면서]

  함수라는 것은 일반적으로 반복적인 코드를 독립된 기능으로 분리해 냄으로서 유지보수 성을 높여주게 된다. 또 부가적인 효과로 함수로 분리된 기능을 원래 그런 기능이구나 하고 개념적으로 분류해 버림으로서, 함수를 호출하는 메인 코드들에만 집중하게 되어 가독성을 높일 수 있으며, 현재 우리가 사용하는 여러 모듈 처럼, 모듈 내의 구현된 코드를 알지 못하면서도 가져다가 다른 프로그램에서 사용할 수도 있게 된다. (메쏘드 등의 설명과 겹치는 것도 같지만, 어차피 역활상 비슷비슷한 개념이라고 봐서...)

 

  그리고 또 하나 부수 적인 기능 하나가, 우리가 함수안 코드를 몰라도 인자만 집어 넣으면 사용할 수 있게 설계를 하는 과정에서 자연스럽게 함수 내에 영향을 미치는 여러 요소 값들이 가능한 입력을 기준으로 판단하게 되는 '일반화' 가 된다는 것이다. 하드 코딩이 사라진다고 얘기해도 될 듯 하다.(뭐 바람직한건 아니라고들 많이 얘기하지만 글로벌 변수를 참조할 수는 있을 건 같다) 함수에 대한 얘기는 아래 링크들을 참조 해 보길 바란다.

  http://www.hackerschool.org/Sub_Html/HS_University/BOF/essential/PDF_Files/07.pdf

  http://python-guide-kr.readthedocs.io/ko/latest/writing/structure.html#object-oriented-programming

 

  그럼 원래 코드를 함 봐보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#-*- coding: utf-8 -*-
from openpyxl import Workbook
from openpyxl import load_workbook
#from openpyxl.compat import range
import pymssql
 
wb = Workbook()
 
# grab the active worksheet
ws = wb.active
ws.title = "output"
 
conn = pymssql.connect(server='localhost', user='pyuser', password='test1234', database='mytest')
cursor = conn.cursor()
 
 
column_char = 'a'
# supermarket 의 컬럼들 가져옴
cursor.execute('SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = \'supermarket\';')
 
# 한행 씩 가져오면서
row = cursor.fetchone()
while row:
# 컬럼 문자를 하나씩 증가하면서 해당 행을 넣음.
  ws[column_char + '1'= row[0]
  column_char = chr(ord(column_char) + 1)
  row = cursor.fetchone()
 
 
# 2번째 행 표시
column_num = 2
# supermarket 테이블의 내용을 가져온다.
cursor.execute('SELECT Itemno, Category, FoodName, Company, Price FROM supermarket(nolock);')
 
# 한 행씩 가져오면서
row = cursor.fetchone()
while row:
# 예전 수동 타자기 처럼, 새로운 줄이 오게 되면, 첫째 셀 a 로 돌아가는 초기 값
  column_char = 'a' 
# 1~5 까지 x 가 변하면서 컬럼 문자, row를 하나씩 늘여 결과를 하나씩 담음. 
# ws['a1'] = row[0], ws['b1'] = row[1], ws['c1'] = row[2]...
  for x in range(16):
    ws[column_char + str(column_num)] = row[x-1]
    column_char = chr(ord(column_char) + 1)
 
# 다음 행을 표시하기 위해 뒤의 숫자 증가       
  column_num = column_num + 1
  row = cursor.fetchone()
 
# 파일을 실제 저장
wb.save("test.xlsx")
cs

 

 

 

 

[함수로 기능 분리 해보기]

  함수로 분리할 부분을 생각해 보면 크게 2가지가 있다. 컬럼명 가져와서 엑셀에 저장하기, 테이블 내용 가져와서 엑셀에 저장하기 2가지 이다.

 

 그런데 개인적으로 생각하기에 두 번째 테이블 내용 가져오는 쪽의 select 쿼리에 컬럼 명들이 필요하기(위에서는 'select Itemno, Category, .... , Price' 이 부분) 때문에 첫번째 컬럼명 가져와서 엑셀에 저장하기 기능은 컬러명 가져오기 기능과, 엑셀에 저장하기 기능으로 나누려고 한다. 그리고 컬럼명 가져오기 기능의 결과는 두 번째 컨텐츠 가져올때 인자로 재 사용 하고 싶다(물론 무시하고 select * 카드로 가져와도 되지만, 혹시 나중에 특정 컬럼만 가져오는 기능을 원할 수도 있기 때문에 컬럼명을 명시적으로 정하는 부분을 그대로 쓰려 한다)

 

 

  그럼 대충 분리 시키려는 내용을 그려보면 아래와 같다. 뭐 기능이나 취향에 따라서 두 번째 테이블 내용 엑셀에 저장하는 기능을 첫 번째 엑셀을 저장하는 함수에 머지해도 무방할 듯도 싶지만, 일단은 따로 분리할 필요는 없을 듯해서, 같이 넣었다.

 

    

  그럼 A) 컬럼 이름 얻어오기 부터 보자. 처음에는 얻어온 값을 Array 에 담아서 저장하려 했다. 컬럼 숫자나 순서대로 쓰기 유용할 것 같아서 말이다. 근데, 구글에 'python array' 라고 검색했을 때, 아래와 같이 array 보다는 list 쪽이 저장하는 데이터 형이나, 사용 면에서 편리 할 거 같아서 list 로 방향을 틀었다.

  http://hashcode.co.kr/questions/1093/%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%97%90-list%EA%B0%80-%EC%9E%88%EB%8A%94%EB%8D%B0-arrayarray%EB%8A%94-%EC%99%9C-%EC%93%B0%EB%8A%94-%EA%B1%B4%EA%B0%80%EC%9A%94

  http://stackoverflow.com/questions/9405322/python-array-v-list

 

  그럼 리스트에 컬럼 조회한 값을 하나씩 넣어야 되는데, 문법을 알기 위해서 'python list add' 라고 검색한다. 아래의 페이지를 보면 append 라는 명령어를 써야 하는 것 같다.

  https://www.tutorialspoint.com/python/list_append.htm

 

  너무 자세하면 지루할 수 있으므로, 해당 부분을 이용해 만든 코드는 아래와 같다. select 쿼리 내의 \' 표시는 문자열 내에 ' 문자가 필요할때 쓰는 escape 문자 이다. escape 문자 설명은 아래를 참고. 위의 그림과 동일하게 입력은 테이블 이름을, 출력은 배열로 출력한다. 

  http://egloos.zum.com/pythondev/v/125926 

1
2
3
4
5
6
7
8
9
10
# 컬럼이름 얻어오기
def getColName(tName):
  cursor.execute('SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = \'' + tName + '\';')
  row = cursor.fetchone()
  
  cName = []
  while row:
      cName.append(row[0])
      row = cursor.fetchone()
  return cName
cs

 

 

 

  두번째, B) 컬럼이름 엑셀에 저장 부분은 기존 코드를 거의 그대로 가져온다. 입력으로 컬럼 이름들이 담긴 배열을 담고, 루프를 돌리기 위해서 구글에서 'python list loop' 로 검색 한다. 첫번째 페이지의 샘플에 잘 나와 있는데로("for 변수 in 리스트"), 코드를 구현한다.

  https://learnpythonthehardway.org/book/ex32.html

1
2
3
4
5
6
# 엑셀에 컬럼 저장
def saveColName(cName):
    column_char = 'a'
    for name in cName:
        ws[column_char + '1'= name
        column_char = chr(ord(column_char) + 1)
cs

 

 

 

  세번째 C) select 쿼리 부분 제작은 컬럼 들이 A, B, C, D.. 이런식으로 되어야 나중에 쿼리에 끼워 넣는데, 문법에 맞추려면 마지막 E 뒤의 ',' 는 제거 되어야 한다. for 문을 돌리면서 리스트 마지막 요소일때 ',' 를 안 붙이는 방법도 있겠지만, 그냥 무조건 ',' 를 붙이고 마지막 글자 하나를 빼서 마지막에 붙은 ',' 를 없애도록 만드려고 한다. 해당 기능을 위해 구글에서 'python substring' 을 검색 한다. 아래 페이지를 참조해서 맨 마지막 문자를 잘라낸다.("문자열[:-1]")

  http://stackoverflow.com/questions/663171/is-there-a-way-to-substring-a-string-in-python

1
2
3
4
5
6
7
# 컬럼 쿼리 만들기 Category, Food, Price
def makeColumnQuery(cName):
    sCol = ''
    for name in cName:
        sCol = sCol + name + ','
    sCol = sCol[:-1]
    return sCol
cs

 

 

 

  네번째 D) 테이블 컨텐츠 엑셀에 저장 하는 부분이다. 아까 얘기했듯이 엑셀을 저장하는 부분을 따로 떼어내거나 B) 기능과 합칠 수도 있겠지만, 그냥 D 기능에서 조회와 엑셀 저장을 하는게 현재 용도로는 깔끔하게 느껴져서, 그렇게 구현했다. 쿼리 부분을 보면 컬럼 리스트 넣는 부분을 위해 C) 기능(makeColumnQuery)을 이용해 컬럼을 얻어 쿼리에 조합 시켰다. 하나 또 달라진 부분은 아까 얘기한 일반화를 위해서, for 루프 안의 range 값을 고정된 숫자 6에서 인자로 넘어온 컬럼 리스트의 숫자+1 을 하도록(len(cName)+1) 바꾸었다. 이렇게 함으로써 테이블이 다른 테이블로 바뀌게 되더라도, 자동으로 컬럼 수를 추출해서 엑셀로 저장할 수  있게 기능이 일반화가 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def getTableContent(tName, cName):
    sCol = makeColumnQuery(cName)
    cursor.execute('SELECT ' + sCol + ' FROM ' + tName + '(nolock);')
    row = cursor.fetchone()
 
    column_num = 2
    while row:
        column_char = 'a'  
        for x in range(1len(cName)+1):  #컬럼수 참조하게 변경
            ws[column_char + str(column_num)] = row[x-1]
            column_char = chr(ord(column_char) + 1)
 
        column_num = column_num + 1
        row = cursor.fetchone()
cs

 

 

 

  마지막으로 해당 함수들을 호출하는 실제 메인 코드를 보면 아래와 같이 나름 심플해 진다. "컬럼 이름을 얻어와 -> 컬럼이름을 저장하고 -> 테이블 조회의 인자로 넘겨주어, 내용을 조회해 저장한다".  각각 함수의 용도를 안 다면 함수의 세부 기능 코드를 무시하게 되고, 그럼 메인 코드에만 집중해서 함수를 사용하면(사실 함수 뿐만 아니라 클래스등 모든 고급 문법 요소가 그런지도 모른다) 가독성이 높아진다는 것을 증명하게 되지 않는가 싶다.

1
2
3
4
# 실행
colName= getColName(tableName)
saveColName(colName)
getTableContent(tableName, colName)
cs

 

 

 

[합쳐 보기]

  그럼 완성된 전체 코드를 봐보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# -*- coding: utf-8 -*-
from openpyxl import Workbook
from openpyxl import load_workbook
#from openpyxl.compat import range
import pymssql
 
wb = Workbook()
 
# grab the active worksheet
ws = wb.active
ws.title = "output"
 
conn = pymssql.connect(server='localhost', user='pyuser', password='test1234', database='mytest')
cursor = conn.cursor()
 
tableName = 'supermarket'
 
# 컬럼이름 얻어오기
def getColName(tName):
  cursor.execute('SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = \'' + tName + '\';')
  row = cursor.fetchone()
  
  cName = []
  while row:
      cName.append(row[0])
      row = cursor.fetchone()
  return cName
 
# 엑셀에 컬럼 저장
def saveColName(cName):
    column_char = 'a'
    for name in cName:
        ws[column_char + '1'= name
        column_char = chr(ord(column_char) + 1)
 
# 컬럼 쿼리 만들기 Category, Food, Price
def makeColumnQuery(cName):
    sCol = ''
    for name in cName:
        sCol = sCol + name + ','
    sCol = sCol[:-1]
    return sCol
 
# 컨텐츠 가져오기
def getTableContent(tName, cName):
    sCol = makeColumnQuery(cName)
    cursor.execute('SELECT ' + sCol + ' FROM ' + tName + '(nolock);')
    row = cursor.fetchone()
 
    column_num = 2
    while row:
        column_char = 'a'  
        for x in range(1len(cName)+1):  #컬럼수 참조하게 변경
            ws[column_char + str(column_num)] = row[x-1]
            column_char = chr(ord(column_char) + 1)
 
        column_num = column_num + 1
        row = cursor.fetchone()
 
# 실행
colName= getColName(tableName)
saveColName(colName)
getTableContent(tableName, colName)
 
 
# test2.xlsx 파일에 저장하기
wb.save("test2.xlsx")
cs

 

 

  해당 내용을 c:\python\code 폴더에 excel_save_function.py로 저장하고(역시 모르면 2교시 참고),  코드를 실행해 본다. 실행 후 폴더 안을 보면 test2.xlsx 파일이 저장됨을 볼 수 있다. 여기서 만든 함수는 나중에, API 호출 하여 엑셀에 저장되는 기능을 만들 때 재 사용 될 예정이다.

c:\Python\code>python excel_save_function.py

 

 

 

[마무리 하면서]
  데이터베이스에서 새로운 테이블을 만들고 코드 중 "tableName = 'supermarket'" 부분의 테이블 이름만 수정하면 해당 테이블의 내용을 가져다 저장할 수 있다(지금 다시 생각해 보니 getTableContent 안에서 saveColName 호출해서 아예 컬럼과 내용을 모두 저장하는 것도 괜찮아 보이긴 한다). 그리고 데이터베이스 내의 테이블 리스트를 모두 얻어오는 쿼리를 이용하면(구글에서 'mssql table name' 으로 검색) 이 함수들을 거의 그대로 이용해서 루프를 만들어 데이터베이스 내의 모든 테이블 내용을 10행식 조회해(select top 10 ...) 엑셀에 저장하게 할 수도 있을 것이다. 물론 그 경우 엑셀에 저장하는 코드 부분은 테이블 끼리 서로 쓰는 영역이 겹치지 않도록 적당히 수정되야 할 것이다. 

 

  여기서 예를 들은 부분이 정말로 함수를 잘 설명했는지는 알수 없지만(사실 상황에 따라 달라져 정답이 정확히 있는거 같지도 않고, 들이는 시간도 고려해 실용성과 재사용성을 고려하는 것이 맞을 듯하다), 어느 정도는 함수를 만드는 도중에 만나는 문제와 만든 후의 모습은 보여 줄 수 있었다고 생각하며 글을 마친다.

 

 

[보충]

  문의가 와서 보니 openpyxl 최신버전에서는 아래 import 문이 에러가 나네요. 지금 보니 사실상 코드안에서 사용하지 않는 부분이라서 아예 빼버림 에러가 발생안합니다

from openpyxl.compat import range

 

 

 

 

2017.3.1 by 자유로운설탕
cs

 

 

posted by 자유로운설탕
2017. 2. 26. 20:00 프로그래밍

  7교시는 엑셀을 다루는 부분에 대해서 얘기하려고 한다. 보통 프로그램에서 나온 결과를 텍스트나 디비로 많이 저장하지만, 엑셀로 저장하게 되면, 여러가지 엑셀의 기능을 이용해서 정렬해 보기도 쉽기 때문에, 나름 장점이 있는 듯 한다.

 

[목차]

0. 왜 파이썬 공부에 구글을 이용하는게 좋은가?

1. 언어를 바라보는 방법. 파이썬을 어떻게 바라봐야 할까?

2. 파이썬 설치와 환경, 버전 선택 하기의 이유.

3. 만들고자 하는 기능을 모르는 조각으로 나눠 조사해 보기

4. 데이터 베이스에서 내용 가져와 출력하기

5. 암호화 모듈을 이용해 암복호화 해보기

6. 퍼즐 조각들을 합쳐보기

7. 엑셀 파일 사용해 보기 -> 부록 : fuction 을 이용해서, 코드 정리해 보기

8. 정규표현식을 왜 사용해야 할까? 언어속의 미니 언어 정규표현식 살펴보기

9. 입력과 결과를 GUI 화면과 연결해 보기

10. Whois API 이용해 보기

11. 웹페이지 호출해 내용 파싱 하기(BeautifulSoup 그리고 한계)

12. 자동화 - 웹 자동화(with Selenium)

13. 자동화 - 윈도우즈 GUI 자동화(with pywinauto)

14. 자동화 - 작업 자동화

15. 수학 라이브러리 살펴보기

16. 그래픽 라이브러리 살펴보기

17. 머신러닝에서의 파이썬의 역활

18. 웹 프로그래밍 - Legacy Web

19. 웹 프로그래밍 - Flask 살펴보기(feat. d3.js)

20. 웹 프로그래밍 - Django 살펴보기

21. 정리 - 이런저런 이야기

 

 

 

[엑셀 모듈 설치]

  언제나처럼 구글에 검색을 시작한다. 'python 3 excel' 라고 검색하면, 제일 위의 결과에 파이썬에서 쓸수 있는 엑셀 모듈들에 대해 정리한 사이트가 나온다.

http://www.python-excel.org/

 

  쭈루룩 5개정도의 모듈이 나오는데, 맨 위에 있는 OpenPyXl 을 써보기로 하자. 모듈을 설치하기 위해서 구글에서 'install openpyxl python3 pip' 로 검색하여 아래의 스택오버플로우 페이지를 참고한다.

http://stackoverflow.com/questions/38364404/how-to-install-openpyxl-with-pip

 

  cmd 창에서 'pip install openpyxl' 를 실행 한다. 특별한 이슈 없이 잘 설치 된다~

c:\Python\code>pip install openpyxl
....
Installing collected packages: openpyxl
  Running setup.py install for openpyxl ... done
Successfully installed openpyxl-2.4.4

 

 

 

 

[샘플 동작 검증]

  그럼 일단 모듈이 잘 돌아간 다는 것을 테스트 하기 위해서 샘플 파일을 찾아본다. 구글에서 'python openpyxl sample' 로 찾으면 첫 페이지 중간 쯤에 아래와 같은 샘플 링크가 있다.

  https://pypi.python.org/pypi/openpyxl

 

  그냥 돌려도 잘 돌아가지만 내용 중 일부가 먼저 쓴 값을 덮는 식으로 만들어져 있어서 한글 주석도 달겸 셀이 겹치지 않도록 일부 수정해 본다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from openpyxl import Workbook
import datetime
 
# 워크북 하나 만들기
wb = Workbook()
 
# 활성 워크시트 선택 하기(뭐 첫번째 것이 선택 된다) 
ws = wb.active
 
# A1 행에 42라는 숫자를 넣는다.
ws['A1'= 42
 
# 현재 글자가 쓰여있는 다음 row 에 1,2,3 이라고 넣는다.
ws.append([123])
 
# 요 부분을 바꿈 A2 로 하면 1,2,3 중 1이 겹쳐 쓰여져서, A3로 수정
ws['A3'= datetime.datetime.now()
 
# 메모리에 있는 워크 북을 실제 엑셀 파일로 저장해 형상화 한다.
wb.save("sample.xlsx")
cs

 

 

 워크 북(workbook), 워크 시크(work sheet)라는 조금 낯설은 용어가 나오지만, 우리가 평소 보는 엑셀을 기준으로 비교해 보면 아주 간단한다. 아래의 그림만 이해한다면 작업은 단순한 인형눈 끼우는 알바 모드가 된다. 엑셀의 표들이 있는 가장 바깥 쪽 부분이 workbook 이며, 엑셀 문서 하나 하나를 얘기한다고 봐도 무방할 듯 하다. 그 안에 들어있는 탭 문서들이 work sheet 이다.

   

  c:\python\code 에 위의 소스 파일을 가져다 excel_sample.py 이름으로 저장하여 실행 한다. (저장하고 실행하는 부분을 모르겠으면 자세히 설명한 2교시에서 보고 오길 바란다.)

c:\Python\code>python excel_sample.py

 

  코드를 실행 시키면 화면엔 아무것도 안 출력 되지만 폴더 안을 보면 아래와 같이 우리가 만든 코드가 실행 되어 sample.xlsx 파일이 만들어져 있다(참고로 테스트를 위해 여러번 실행 할 때, sample.xlsx 엑셀 파일을 열어 놓은 상태면 쓰지 못해서 에러가 난다). 파이썬 코드와 만들어진 값의 상관 관계를 결과 값 그림에 표시해 본다.

 

 

 

 

[문제 나누기 - 컬럼이름 얻어오기]

  그럼 샘플은 실행해 검증해 봤고, 이제 우리가 원하는 기능을 만들어 보려고 한다.  4교시 때 우리가 만들어 보았던 supermarket 테이블을 조회한 내용을 엑셀에 저장해 보려 한다. 그런데 단순히 내용만 저장하면 거의 기존 수업 반복이여서 재미가 덜 하니까. 컬럼 이름을 얻어와서 엑셀의 맨 첫 번째 라인에는 컬럼 명을 적어 좀더 보기 좋게 하려 한다.

 

  어 그럼 테이블의 컬럼 이름을 어떻게 얻어와야 하지? 하는 문제가 생긴다. 구글에서 'mssql table column name' 이라고 검색한다. 제일 첫번째 문서인 아래 문서의 링크를 열어보면, 여러개의 설명들이 있는데, 제일 심플해 보이는 아래 코드를 선택한다. 테이블의 스키마 구조를 저장하고 있는 테이블에서 컬럼이 이름을 가져오는 쿼리이다.

http://stackoverflow.com/questions/1054984/how-can-i-get-column-names-from-a-table-in-sql-server

 

SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.
COLUMNS
WHERE TABLE_NAME = 'name_of_your_table'

 

 

  그럼 저 쿼리안의 'name_of_your_table' 안에 4교시에 만든 'supermarket' 으로 바꾸어서, 잘 돌아가나 검증을 위해 쿼리 분석기에서 실행해 본다. (쿼리 실행 부분을 잘 모르겠으면, 4교시에 자세히 설명했으니 가서 보고 온다).  실행해 보면 우리가 원하는 컬럼 값이 잘 나옴을 볼수 있다.

 

 

 

[문제 나누기 - 루프를 이용해 엑셀에 출력하기]

   또 하나의 해결해야할 부분은 엑셀에 뿌려주는 문제이다. 디비 구조하고 엑셀 구조는 상당히 비슷하기 때문에, 디비를 한줄 읽어서 컬럼 들을 가로로 쭉 써주고, 또 한줄 읽어서 가로로 쭉 써주는 걸 반복하면 되는데, 행이 증가 하는건, 아래 그림과 같이 뒤의 숫자만 1, 2, 3 으로 증가 시켜줌 되는데, 가로로 증가하는건 뒤의 숫자가 고정된 채 문자가 하나씩(A, B, C) 늘어나야 한다. 말로 표현해 보면,"DB의 첫째 행을 가져와 A1~E1 에 저장하고, 다음 행을 가져와 A2~E5 에 저장해야 한다."

 

  for 문을 2개 겹쳐 써야 한다고 대충 감은 든다. 안쪽 for 문은 A1~E1 식으로 숫자는 고정한 채 알파벳만 늘어나게 해주어 한줄을 표시 하는 기능을 하고, 바깥 쪽 for 문은 뒤의 숫자를 하나 증가해 줄을 바꾸어 주어, 다음번 안쪽 for 문이 A2~E2 식으로 다음 줄에 표시하게 해줌 된다. 

 

  그런데 숫자 증가하는건 i = i + 1 하면 될거 같은데,  문자를 어떻게 증가 시킬 지 모르겠다(아스키 코드 어쩌고 하는 식으로 내부적으로 문자가 숫자로 됬다는건 기억이 나긴 한다). 그럼 다른 사람이 문자 증가 문제를 해결한 코드를 찾기 위해 'python increase alphabet' 으로 구글을 조회한다. 위에서 두번째 결과인 아래 페이지를 보면, char = chr(ord(char) + 1) 라는 코드를 알려준다. ord 가 뭘 하는 건지 'python ord' 로 찾아 해석해 보면 ord 는 문자를 아스키 코드로 바꿔주는 함수이기 때문에, 해당 코드는 '문자를 아스키 코드로 변경하여 1을 더하고, 다시 문자로 바꿔준다' 의 기능을 한다.

http://stackoverflow.com/questions/11827226/can-we-increase-a-lowercase-character-by-one

 

 

  그럼 해당 코드를 이용하여 첫 번째 컬럼 명을 출력하는 부분은 아래와 같이 만들어 질 수 있다. (한 행만 찍음 되기 때문에 바깥 쪽 루프는 없어도 된다. 안에 보면 '1' 로 하드코딩 되있다) 

1
2
3
4
5
6
7
8
9
10
11
12
# 컬럼 1번째 표시
column_char = 'a'
# supermarket 의 컬럼들 가져옴
cursor.execute('SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = \'supermarket\';')
 
# 한행 씩 가져오면서
row = cursor.fetchone()
while row:
# 컬럼 문자를 하나씩 증가하면서 해당 행을 넣음.
  ws[column_char + '1'= row[0]
  column_char = chr(ord(column_char) + 1)
  row = cursor.fetchone()
cs

 

  위의 코드가 돌아가면 A1 = row[0](첫번째 컬럼이름), B1 = row[0](두번째 컬럼이름)... 이런식으로 엑셀의 맨 첫줄에 다섯 개의 컬럼 이름이 들어간다.

 

 

  두번 째는 내용을 출력 하는 코드 부분이다. 비슷하지만, 조회된 내용을 한 줄씩 가져오며 내용이 없을 때까지 루프를 돌리는 while 문이 바깥쪽 for 루프 역활을 한다. 아까 구상 했듯이 안쪽 for 문이 한 행(5개 컬럼)을 출력하는 역활을 한다. 처음 1행은 컬럼 이름이 들어가 있을 것이므로, column_num 을 2로 해서 2번째 행부터 내용이 출력되게 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 2번째 행 표시
column_num = 2
# supermarket 테이블의 내용을 가져온다.
cursor.execute('SELECT Itemno, Category, FoodName, Company, Price FROM supermarket(nolock);')
 
# 한 행씩 가져오면서
row = cursor.fetchone()
while row:
# 예전 수동 타자기 처럼, 새로운 줄이 오게 되면, 첫째 셀 a 로 돌아가는 초기 값
  column_char = 'a' 
# 1~5 까지 x 가 변하면서 컬럼 문자, row를 하나씩 늘여 결과를 하나씩 담음. 
# ws['a1'] = row[0], ws['b1'] = row[1], ws['c1'] = row[2]...
  for x in range(16):
    ws[column_char + str(column_num)] = row[x-1]
    column_char = chr(ord(column_char) + 1)
 
# 다음 행을 표시하기 위해 뒤의 숫자 증가       
  column_num = column_num + 1
  row = cursor.fetchone()
cs

 

 

 

 

[해결된 조각 합치기]

  그럼 위의 코드들을 모두 적절히 합치면 아래와 같은 전체 소스가 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#-*- coding: utf-8 -*-
from openpyxl import Workbook
from openpyxl import load_workbook
#from openpyxl.compat import range
import pymssql
 
wb = Workbook()
 
# grab the active worksheet
ws = wb.active
ws.title = "output"
 
conn = pymssql.connect(server='localhost', user='pyuser', password='test1234', database='mytest')
cursor = conn.cursor()
 
 
column_char = 'a'
# supermarket 의 컬럼들 가져옴
cursor.execute('SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = \'supermarket\';')
 
# 한행 씩 가져오면서
row = cursor.fetchone()
while row:
# 컬럼 문자를 하나씩 증가하면서 해당 행을 넣음.
  ws[column_char + '1'= row[0]
  column_char = chr(ord(column_char) + 1)
  row = cursor.fetchone()
 
 
# 2번째 행 표시
column_num = 2
# supermarket 테이블의 내용을 가져온다.
cursor.execute('SELECT Itemno, Category, FoodName, Company, Price FROM supermarket(nolock);')
 
# 한 행씩 가져오면서
row = cursor.fetchone()
while row:
# 예전 수동 타자기 처럼, 새로운 줄이 오게 되면, 첫째 셀 a 로 돌아가는 초기 값
  column_char = 'a' 
# 1~5 까지 x 가 변하면서 컬럼 문자, row를 하나씩 늘여 결과를 하나씩 담음. 
# ws['a1'] = row[0], ws['b1'] = row[1], ws['c1'] = row[2]...
  for x in range(16):
    ws[column_char + str(column_num)] = row[x-1]
    column_char = chr(ord(column_char) + 1)
 
# 다음 행을 표시하기 위해 뒤의 숫자 증가       
  column_num = column_num + 1
  row = cursor.fetchone()
 
# 파일을 실제 저장
wb.save("test.xlsx")
cs

 

  excel_save.py 로 저장하고 실행 하면 아래와 같이 test.xlsx 파일이 만들어 진다.

c:\Python\code>python excel_save.py

 

 

  역시 실행 결과는 안보이지만, test.xlsx 엑셀 파일을 열어보면 아래와 같이 컬럼명과 내용들이 들어가 있다.

 

 

 

[역지사지 - 엑셀 읽어오기]

  마지막으로 저장의 반대인 읽어오기도 위의 식으로 비슷하게 루프를 돌리면서 가져오고 싶은 값을 지정 하면 될 것이다. 쓰기가 충분히 해당 루프를 돌리는 방식을 구현하는 부분을 해결했다고 생각하기 때문에 여기서는 간단하게 읽어와서  A1(Itemno), B2(과자) 값을 출력하는 예제만 봐보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#-*- coding: utf-8 -*-
from openpyxl import Workbook
from openpyxl import load_workbook
from openpyxl.compat import range
import pymssql
 
# 엑셀을 읽어와
wbread = load_workbook(filename = 'test.xlsx')
 
# 이름이 output 인 sheet 를 가져온다.
mysheet = wbread['output']
 
# a1, a2 값을 출력한다
print (mysheet['a1'].value + ' ' + mysheet['b2'].value)
cs

 

  excel_read,py로 저장하고 실행을 하면, 아래와 같이 결과가 나온다.

c:\Python\code>python excel_read.py
Itemno 과자

 

 

 

[마치면서...]

  처음 찾았던 엑셀 모듈 모아놓은 사이트에서 소개했던 openpyxl 외의 다른 모듈의 샘플을 보니 셀을 지정할때, A1, A2 식으로 안하고, (1,1) (1,2) 식으로 좌표식으로 표시하는 모듈도 있다. 인기 많고 유지보수 되는 모듈 중, 각자 쓰기 편하다고 생각되는 모듈을 사용하면 될 듯하다.

 

  그리고 이제와 고백하자면 위의 코드엔 알고도 놔둔 버그가 있다. 아스키 코드를 증가시키는 방법은 A, Z 까지 밖에 안되는데, 엑셀의 가로행은 Z 까지 가면 AA 같이 두자리로 늘어나고 ZZ 담에는 세자리로 늘어난다. 그럼 사실 저 부분도 그 부분을 고려해야 하는데, 테이블 컬럼이 26개(a-z) 이상은 잘 안 쓰일거 같아서 그냥 두었다. 뭐 개인적으로 파이썬은 유틸리티 방식으로 사용하는 편이라서, 26개 이상 필요가 생기면 고치려 했다고 변명 하고 싶다 --; (가로 문자를 증가시키는 로직을 만들던지 위의 좌표 지정 방식의 모듈을 사용해 숫자로만 루프를 돌리는 것도 하나의 해결 방법일 듯 싶다.)

 

  그럼 다음 시간인 '7교시 부록'에서는 살짝 문법 시간으로 가서, 지금 만든 스크립트 샘플을 함수를 이용해 (자신은 별로 없지만) 정리하는 부분을 살펴 보면서 구조화된 코드에 대해 잠시 생각하는 시간을 가져 보려고 한다.

 

 

 

[보충 #1]

  최근 어떤 책을 보다가 발견한건데, openpyxl 에서도 위에 같이 ws['a1'].value 같이 문자 말고,

ws.cell(row=1, column=2).value 로 하는것도 가능하다--; 뭐 위의 코드도 억지로 돌아가긴 하지만, 괜히 어려운 길을 간거 같아서 조금 미안하다^^. 관심 있으신 분은 위의 코드를 아래와 같이 row, column 지정 방식의 코드로 조금만 수정하면 코드가 더 간략해 질것 같다. 숫자로 지정하는 샘플 코드만 아래에 추가 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#-*- coding: utf-8 -*-
from openpyxl import Workbook
wb = Workbook()
 
# grab the active worksheet
ws = wb.active
 
# 예제로 들었던 방식
ws['A1'= 12
 
# 더 편한 방식
ws.cell(row=2, column=2).value = 777 
 
# Save the file
wb.save("sample.xlsx")
cs

 

추가 -> 댓글에서 SSL 이 알려주셔서..

2.5버전 부터는 아래와 같이 값을 바로 넣어도 되네요.

ws.cell(row=2, column=2).value = 777

 

 

[보충 #2]

  문의가 와서 보니 openpyxl 최신버전에서는 아래 import 문이 에러가 나네요. 지금 보니 사실상 코드안에서 사용하지 않는 부분이라서 아예 빼버림 에러가 발생안합니다

from openpyxl.compat import range

 

 

2017.2.28 by 자유로운설탕
cs

 

 

 

posted by 자유로운설탕
2017. 2. 25. 21:55 프로그래밍

  이젠 4교시 때 정리한 mssql 코드와 5교시 때 정리한 암호화 코드를 이용해서 최종 기능을 만들어 보자.

 

[목차]

0. 왜 파이썬 공부에 구글을 이용하는게 좋은가?

1. 언어를 바라보는 방법. 파이썬을 어떻게 바라봐야 할까?

2. 파이썬 설치와 환경, 버전 선택 하기의 이유.

3. 만들고자 하는 기능을 모르는 조각으로 나눠 조사해 보기

4. 데이터 베이스에서 내용 가져와 출력하기

5. 암호화 모듈을 이용해 암복호화 해보기

6. 퍼즐 조각들을 합쳐보기

7. 엑셀 파일 사용해 보기 -> 부록 : fuction 을 이용해서, 코드 정리해 보기

8. 정규표현식을 왜 사용해야 할까? 언어속의 미니 언어 정규표현식 살펴보기

9. 입력과 결과를 GUI 화면과 연결해 보기

10. Whois API 이용해 보기

11. 웹페이지 호출해 내용 파싱 하기(BeautifulSoup 그리고 한계)

12. 자동화 - 웹 자동화(with Selenium)

13. 자동화 - 윈도우즈 GUI 자동화(with pywinauto)

14. 자동화 - 작업 자동화

15. 수학 라이브러리 살펴보기

16. 그래픽 라이브러리 살펴보기

17. 머신러닝에서의 파이썬의 역활

18. 웹 프로그래밍 - Legacy Web

19. 웹 프로그래밍 - Flask 살펴보기(feat. d3.js)

20. 웹 프로그래밍 - Django 살펴보기

21. 정리 - 이런저런 이야기

 

 

 

[들어가면서]

  최종 기능은 테이블에 저장된 평문 문자열을 암호화 한 후 복호화 해서 각각 테이블 내 다른 필드에 저장하고, 이후 전체 내용을 조회해 잘 암,복호화 됬다는 증거로 화면에 표시한다. 그림으로 표시하면 아래와 같다.

 

 

 

[테이블과 원본 데이터 생성]

  일단 테이블을 하나 만든다. 4교시때 만든 'mytest' db에 저장하고, 만약 테이블 생성 하는 부분이 가물가물 하다면, 4교시에 자세하게 설명해 놓았으니 참고하여 생성한다.

1
2
3
4
5
CREATE TABLE [dbo].[play](
    [original] [char](30) NULL,
    [encrypted] [char](200) NULL,
    [decrypted] [char](30) NULL,
)
cs

 

  이후 위의 그림의 A 값을 하나 넣는다.

1
insert play values('secret','','')
cs

 

  내용을 한번 조회해 본다.

1
select * from play(nolock)
cs

 

  아래와 같이 original 에만 우리가 넣은 secret 라는 단어가 들어가 있다. 나머지 2개는 파이썬이 채워줄 예정이다.

 

 

 

[업데이트 쿼리 만들기]

  4, 5교시때 진행한 내용을 되돌아 보면 현재 우리가 모르고 있는 부분 하나는 테이블의 encrypted, decrypted 필드에 원하는 값을 업데이트를 하는 것이다. DB는 복잡하게 보면 많이 복잡하지만 단순하게 보면 파일과 비슷한 면이 있다.(사실 DB는 파일에 저장하는 것을 좀 더 구조적으로 저장하여, 상호 관계를 분석하거나 빠르게 결과를 찾기 위해 만들어진 구조화된 파일 이라고 봐도 될 듯하다.) 파일에 할수 있는 일이 생성(create), 수정(update), 내용, 파일 삭제(delete), 읽기(read) 이 듯, DB안에 들어가 있는 테이블에 할 수 있는 작업도 역시 이 4가지다. 그래서 좀 이 4개의 행동들의 앞 자리들을 따서 조금 고상하게 CRUD 라고 표현하기도 한다. 그럼 4교시때 select 하는 부분은 배웠는데, update 하는 내용은 배우지 않았으니 update 하는 부분을 구글을 통해 찾아보려 한다.

 

  구글에서  'mssql update' 로 검색한다. 한글로 잘 정리된 페이지를 참조해도 좋고, 이전 시간에도 참고했던 아래 techonthenet 페이지를 보자(개인적으로 깔끔하게 핵심만 정리한듯 하여...)

https://www.techonthenet.com/sql_server/update.php

 

  간단히 설명하면 아래와 같다.

1
2
3
4
5
6
UPDATE 테이블
SET 컬럼1 = '값1',
    컬럼2 = '값2' ....
WHERE 컬럼x = '값3';
-- 테이블을 업데이트 하는데, 특정 컬럼에 값3이 들어있는 행을 찾아서, 
-- 컬럼1,2를 새로운 값으로 채운다.
cs

 

  해당 문법을 참조하여 좀 전에 만든 테이블 구조를 업데이트 하는 쿼리를 만들면 아래와 같다.

1
2
3
4
5
-- secret 가 저장된 행을 찾아서 encrypted 컬럼을 암호화된 값으로 업데이트 한다.
update play set encrypted = '암호화 값' where original='secret'
 
-- secret 가 저장된 행을 찾아서 decrypted 컬럼을 복호화된 값으로 업데이트 한다.
update play set decrypted = '복호화 값' where original='secret'
cs

 

 

  해당 코드를 이전 시간의 암호화 한 함수 코드를 호출하는 방식으로 만들면 아래와 같이 된다.

1
2
3
4
5
6
7
8
9
10
# original 필드 조회한다
cursor.execute('SELECT original FROM play(nolock);')
row = cursor.fetchone()
original = str(row[0])
# 암호화 한다.
encrypted = AESCipher(key).encrypt(original)
 
# 암호화한 값 encrypted 필드에 업데이트 한다.
cursor.execute("update play set encrypted = %s where original='secret'", encrypted)
conn.commit()
cs

 

 

  해당 코드를 복호화 한 함수를 호출하는 방식으로 합쳐서 만들면 아래와 같이 된다.

1
2
3
4
5
6
7
8
9
10
# encrypted 필드 조회한다
cursor.execute('SELECT encrypted FROM play(nolock);')
row = cursor.fetchone()
encrypted_select = str(row[0])
# 복호화 한다
decrypted_insert = AESCipher(key).decrypt(encrypted_select)
 
# 복호화 한 값 decrypted 필드에 업데이트 한다.
cursor.execute("update play set decrypted = %s where original='secret'", decrypted_insert)
conn.commit()
cs

 

 

  전체를 조회하는 쿼리는 SQL 샘플과 비슷하지만, 줄 바꿈을 위해 약간 보기 좋게 하기 위해서 문자열 더하기 연산자와(+), 문자열 좌우의 빈 공간을 지우는 함수(strip)를 이용하여 보기좋게 정렬하려 한다. 해당 코드는 아래와 같다.

1
2
3
4
5
6
7
# 모두 조회해서 줄로 나누어('\n') 출력한다. strip() 함수는 양쪽의 공백을 없애준다.
cursor.execute('SELECT * FROM play(nolock);')
row = cursor.fetchone()
while row:
  print ('original  : ' + str(row[0]) + "\n" + 'encrypted : ' 
  + str(row[1]).strip() + "\n" + 'decrypted : ' + str(row[2]))   
  row = cursor.fetchone()
cs

 

 

 

[전체 코드 조합하기]

  그럼 좀 길어 지지만 앞의 암복호화 함수와 합치게 되면 아래와 같이 된다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#-*- coding: utf-8 -*-
# 암호화 코드 출처: http://blog.dokenzy.com/
 
import pymssql
import base64
import hashlib
from Crypto import Random
from Crypto.Cipher import AES
 
# 아마 특정한 블록 사이즈를 채우기 위해서 입력된 값을 임의의 값으로 패딩(채워주기) 하는 코드 인 듯...
BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS).encode()
unpad = lambda s: s[:-ord(s[len(s)-1:])]
 
# 초기화 코드 인듯
def iv():
    return chr(0* 16
 
# 2교시때 설명했 듯이 클래스는 구조를 잘 잡아주는 껍데기 이다.
class AESCipher(object):
 
    def __init__(self, key):
        self.key = key
 
    # 메시지를 암호화 하는 함수 
    def encrypt(self, message):
        message = message.encode()
        raw = pad(message)
        cipher = AES.new(self.key, AES.MODE_CBC, iv())
        enc = cipher.encrypt(raw)
        return base64.b64encode(enc).decode('utf-8')
    
    # 메시지를 복호화 하는 함수
    def decrypt(self, enc):
        enc = base64.b64decode(enc)
        cipher = AES.new(self.key, AES.MODE_CBC, iv())
        dec = cipher.decrypt(enc)
        return unpad(dec).decode('utf-8')
 
# 암호화 키 
key = 'abcdefghijklmnopqrstuvwxyz123456'
 
 
# 커넥션 만들기
conn = pymssql.connect(server='localhost', user='pyuser', password='test1234', database='mytest')
cursor = conn.cursor()
 
# original 필드 조회한다
cursor.execute('SELECT original FROM play(nolock);')
row = cursor.fetchone()
original = str(row[0])
# 암호화 한다.
encrypted = AESCipher(key).encrypt(original)
 
# 암호화한 값 encrypted 필드에 업데이트 하기
cursor.execute("update play set encrypted = %s where original='secret'", encrypted)
conn.commit()
 
# encrypted 필드 조회한다
cursor.execute('SELECT encrypted FROM play(nolock);')
row = cursor.fetchone()
encrypted_select = str(row[0])
# 복호화 한다
decrypted_insert = AESCipher(key).decrypt(encrypted_select)
 
# 복호화 한 값 decrypted 필드에 업데이트 하기
cursor.execute("update play set decrypted = %s where original='secret'", decrypted_insert)
conn.commit()
 
# 모두 조회해서 줄로 나누어('\n') 출력한다. strip() 함수는 양쪽의 공백을 없애준다.
cursor.execute('SELECT * FROM play(nolock);')
row = cursor.fetchone()
while row:
  print ('original  : ' + str(row[0]) + "\n" + 'encrypted : ' 
  + str(row[1]).strip() + "\n" + 'decrypted : ' + str(row[2]))   
  row = cursor.fetchone()
cs

 

  위의 최종 소스를 긁어 c:\python\code 디렉토리에 mix_version.py 라고 파일을 만들고 실행 한다. (역시 실행 방법을 잘 모르겠으면 2교시를 참고한다). 아래와 같이 DB 에 저장 후 필드를 읽어와 화면에 가지런히 표시된다.

c:\Python\code>python mix_version.py
original  : secret
encrypted : F8/7UoVxGVoOxNdlo/0sbf/whobfIAoSVe3QvcbgehI=
decrypted : secret

 

 

 

[디버깅 및 리셋]

  직접 만들어 보는 경우는 중간 중간에 print 문을 통해 변수를 출력하여(물론 리스트와 같은 복합 자료형은 원하는 값을 출력하는 방법에 익숙해야 한다) 해당 값이 정확히 나오는지 확인 해 보는 것이 좋다. 아니면 pycharm 같은 IDE 툴의 디버깅 기능을 이용해도 될 듯 하다. 구글에서 'pycharm 디버깅' 이라고 치면 많은 블로그가 나온다. (뭐 하지만 여러 디버깅 책 들에서 얘기하듯이 디버깅은 머리속으로 구조적으로 하는 거고, print 나 디버깅 기능은 생각한 가설이 맞는지 실제 검증 할때 쓰는 것이라는 것을 잊으면 안된다). 테스트 도중 값을 리셋하기 위해 쓸 수 있는 쿼리를 아래 명시 하였다.(original 값만 빼고 나머지 두 값을 지워준다)

1
2
update play set encrypted ='', decrypted = ''
where original = 'secret'
cs

 

 

 

 

[Database 타입에 대한  간략 브리핑]

  마무리 하기 전에 앞 시간에서 여기서 정리한다고 했으니 database 에 대해 잘 모르는 사람들을 위해 요즘 회자되고 있는 여러가지 database 에 대해서 희미하게 아는 지식으로 간단히 분류하려 한다. 보통 많이 듣는 DB가 관계형 디비인 oracle, mssql, mysql, mariadb, postgreSQL, 빅데이터 라고 부르는 hBase, hive 등, 위치가 조금은 애매한 도큐먼트 디비인 mongodb, 그리고 메모리 디비라고 부르는 redis, elastic search 같은 애들이 있다. 

 

  첫번째 관계형 디비는 보통 ansi-sql 이라는 표준을 따르고 있으나, 각자 자체적으로 확장한 문법들이 많아서 한 SQL 제품에 맞는 쿼리를 다른 쪽에 던지면 보통 미세한 차이땜에 에러가 날 수 있지만, 몇 가지 제품에 쿼리들을 날려보다 보면 큰 틀에서 비슷비슷하다는 생각은 든다.(오히려 많이 차이가 나는 부분은 여러 관리 측면의 부분들이다) 관계형 디비의 장점은 관계의 꽃이라고 할 수 있는 join 기능을 통해 데이터 들 간의 관계를 쉽게 조회할 수 있다는 부분이 있지만 성능 문제 때문에 보통 하나의 서버 안에서만 join 이 가능하다고 보면 된다. 하나의 서버에 데이터를 모두 저장하기 때문에 응답 속도가 빠르지만, 데이터 양이 커지면 서버 용량이 모자라거나 메모리, cpu 에 부하가 걸리는 일도 생길 수 있다. NoSQL 이 나오기 전에 DB 계를 제패했었고 지금도 여전히 빅데이터와는 다른 특성의 데이터 영역들에서 잘 살고 있다고 본다. 참고로 mariadb는 오픈소스였던 mysql이 오라클에 넘어가게 되면서, 호환되는 오픈소스로 갈라진 버전이다. 두 개가 쿼리는 완전 동일했는데 현재는 서로 얼마나 차이가 나는진 잘 모르겠다. 

 

  NoSQL 에서 빅데이터 쪽은 보통 하둡(hadoop)이라는 여러 서버에 데이터를 나눠 보관할 수 있는 hdfs 란 파일 시스템을 기본으로 하는 hbase 디비가 있다. 해당 방식의 장점은 하나의 서버에서 지지고 볶는 관계형 디비와 달리, 토렌토나 컴퓨터들의 파워를 조금씩 나눠 쓰는 여러가지 그리드 형태의 서비스 처럼, 해야될 일을 분산되어 있는 데이터를 저장하고 있는 서버들에게 나눠 주어서 일을 시킨 후, 다시 결과를 모아서 보여줄 수 있는데 장점이 있는 듯하다. 많은 서버가 동시에 일을 나눠 하는 것이 가능해, 용량이 큰 데이터에 대해서 관계형 디비에 비해서 연산하기가 유리하기 때문에 Big데이터용 디비라고 불리는 것 같다. 이러한 분산 작업과 관련되어 맵, 리듀스, 셔플 같은 개념들이 쓰인다. 해당 데이터의 처리 과정이나 결과가 하둡 오리지날 방식에서는 비교적 복잡한 과정을 사용자가 직접 디자인해 줘야 하기 때문에, 해당 부분을 관계형 디비에서 사용하는 방식으로 커버를 씌워 준 것이, Hive(SQL 처럼 작업을 지시) 라고 보면 된다. Hive를 통하면 하둡에 적재된 데이터에 대해서 join 같은 형태의 관계형 작업의 에뮬레이션을 쉽게 지시할 수 있다. 개인적으로 이쪽은 명확히 이해는 못하고 있다고 생각하기 때문에, 아래 링크를 마지막으로 빅데이터 얘기를 마무리한다.

http://blog.acronym.co.kr/312

 

  Mongo 디비는 도규먼트 형식의 대표적인 디비로, 안에 json 형태의 문서를 담을 수 있다. 무엇 보다도 장점인 것 중 하나는 관계형 디비 같은 경우 입력되는 필드가 추가되면 alter table 을 통해 컬럼을 만들거나 수정해 맞춰주지 않으면 에러가 발생하지만(보통 스키마 변경이라고 한다. 컬럼 타입이나 갯수가 변경되면 스키마가 같이 변경되어 줘야 한다.), mongo 디비 같은 NoSQL 은 데이터의 변화에 따라 알아서 스키마를 고무줄 같이 조정하며 맞춰 주어서, DBA의 스키마 관리 역활이 사실 좀 모호해지는 듯 싶다. 그래서 비교적 어플리케이션의 쿼리 호출을 막 바꿀 수 있어 보통 개발자 들한테 인기가 많은 듯도 싶다. 비슷한 타입의 대체 가능한 디비가 뭐가 있는지는 잘 모르겠다.

 

  메모리 디비는 일단 가능한 많은 데이터들을 메모리에 올려 놓는 구조이다. 가능한 많은 데이터를 메모리에 올려 놓고 조회하는 구조로 쿼리에 대해 빠른 응답이 필요한 경우 사용하는 듯 하다. redis 는 키, 밸류 방식의 메모리 디비이다.

 

  Elastic search 디비와 검색엔진이 합쳐진 형태로 대용량 로그 검색이나 검색 엔진 등에서 주로 사용되는 듯하다. 

 

  여담이지만 오픈소스는 사용 비용은 무료지만 보통 통합 관리나, 성능 관리, 보안 등의 기능이 약한 경우가 있어, 그런 부분을 커스터마이즈 하여 확장한 관련 솔루션을 추가로 구입해(외국엔 그런 걸로 잘 사업하며 먹고 사는 기업들도 많은 듯한다) 사용해야 하는 경우도 많기 때문에, 결국 유지 비용이 비슷비슷해지는 경우도 종종 있다고 한다.

 

  그럼 요 정도로 디비 종류 얘기는 마무리하고 혹 잘몰라서 잘못된 얘기를 했더라도 넓은 맘으로 이해해 주길 바라며 글을 마친다.

 

 

 

1
2017.2.25 by 자유로운설탕
cs

 

 

 

posted by 자유로운설탕
2017. 2. 23. 21:33 프로그래밍

  이번 시간에는 3교시때 얘기한 암복호화 하는 샘플을 찾아서 이용해 보려고 한다.

 

[목차]

0. 왜 파이썬 공부에 구글을 이용하는게 좋은가?

1. 언어를 바라보는 방법. 파이썬을 어떻게 바라봐야 할까?

2. 파이썬 설치와 환경, 버전 선택 하기의 이유.

3. 만들고자 하는 기능을 모르는 조각으로 나눠 조사해 보기

4. 데이터 베이스에서 내용 가져와 출력하기

5. 암호화 모듈을 이용해 암복호화 해보기

6. 퍼즐 조각들을 합쳐보기

7. 엑셀 파일 사용해 보기 -> 부록 : fuction 을 이용해서, 코드 정리해 보기

8. 정규표현식을 왜 사용해야 할까? 언어속의 미니 언어 정규표현식 살펴보기

9. 입력과 결과를 GUI 화면과 연결해 보기

10. Whois API 이용해 보기

11. 웹페이지 호출해 내용 파싱 하기(BeautifulSoup 그리고 한계)

12. 자동화 - 웹 자동화(with Selenium)

13. 자동화 - 윈도우즈 GUI 자동화(with pywinauto)

14. 자동화 - 작업 자동화

15. 수학 라이브러리 살펴보기

16. 그래픽 라이브러리 살펴보기

17. 머신러닝에서의 파이썬의 역활

18. 웹 프로그래밍 - Legacy Web

19. 웹 프로그래밍 - Flask 살펴보기(feat. d3.js)

20. 웹 프로그래밍 - Django 살펴보기

21. 정리 - 이런저런 이야기

 

 

 

 

[들어가면서]

  3교시 때 아래까지 얘기를 했었다. 그림의 회색 부분을 위한 코드 샘플을 찾는 중이다. 

 

  "2 번째로, aes256으로 암, 복호화 하기 위한 모듈을 찾아보자. 구글에서 'python 3 aes256' 로 찾으면 처음에 스택오버플로우 글이 나오고, 두번째로 우리나라 분이 정리한 문서가 하나 나온다.

http://blog.dokenzy.com/archives/1997"

 

 

 

 

[암호화 모듈 설치]

  해당 샘플을 이용하기 위해 pycrypto 모듈을 아래와 같이 일단 pip 로 설치하려고 해본다. cmd 창에 아래 명령어로 pycrypto 를 설치한다.  

c:\Python\code>pip install pycrypto

 

  그런데 아래와 같은 에러가 발생한다. 아마도 pycrypto 가 더 이상 유지 보수가 안되거나 해서, 파이썬 공식 저장소에 pycypto 가 빌드된 파일이 없어서 빌드를 하려다가, 빌드에 쓸 적절한 컴파일러가 컴퓨터 내에 없어서 에러가 나는 것 같다. 

 error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": http://landinghub.visualstudio.com/visual-cpp-build-tools

 

  에러에 표시된 해당 페이지로 가면, 아래와 같이 Visual C++ 2015 Build Tools 링크가 나온다.

 

 

  막상 설치하려니 궁금한게 하나 생긴다. 에러에서는 Visual C++ 14.0 버전을 설치하라고 하는데, 2015 버전이 14.0 이 맞을까? 구글에서 'visual studio 2015 version number' 찾아 아래 위키의 내용을 보면 14.0 이 2015가 맞는다고 한다. 그럼 고민 하지 말고, 링크를 클릭해서, Visual C++ Build Tool 을 다운 받아 설치 한다.

https://en.wikipedia.org/wiki/Microsoft_Visual_Studio

 

  설치가 잘 완료된 후 다시 pip 설치 명령을 돌리면 아래와 같이 정상적으로 설치된다.

c:\Python\code>pip install pycrypto
Collecting pycrypto
  Using cached pycrypto-2.6.1.tar.gz
Installing collected packages: pycrypto
  Running setup.py install for pycrypto ... done
Successfully installed pycrypto-2.6.1

 

 

  이쯤 와서 정리해 보면 파이썬에서 모듈을 설치하는 방법은 3가지쯤 있어 보이는데, 첫번째는 pip 를 이용해서 패키지 이름을 지정해 공식적인 저장소에서 다운 받아 가져오는 것, 2번째는 다른 사람들이 빌드해 설치본으로 만들어 놓은 exe 형태의 인스톨 버전을 웹에서 다운받아 설치하는 방법, 세번째는 pip 명령어 뒤에 github 같은 웹경로나 로컬 경로를 지정해 wheel 이라는 확장자의 파일을 이용해 설치한다. wheel이 뭔지 궁금해 구글을 찾아보니 zip 파일의 확장자를 wheel 이라고 바꾸어 저장하는 것으로 아마 그 안엔 이미 컴파일된 모듈이 들어 있어 파이썬이 다운 받아 단지 압축을 풀어 모듈 폴더로 복사 함으로써 모듈이 설치되는 원리 같다. 설치 장소는 c:\Python\Lib\site-packages\ 안이다. 아래 링크의 글을 참고...

https://pypi.python.org/pypi/wheel

 

  ※ 뭔가 환경이 바뀌었는진 모르겠는데, 처음 pycrypto 를 설치 했을때는 비슷한 종류의 에러긴 한지만, 명확히 뭐를 설치하라고는 안나오고, "Unable to find vcvarsall.bat..." 요런 식으로만 나와서 구글에서 'python3 install crypto Unable to find vcvarsall.bat' 게 찾아서, 아래 페이지로 가서 wheel 파일을 설치해 해결했었다. 근데 컴파일러 설치가 정석인 것은 같다.

http://stackoverflow.com/questions/32800336/pycrypto-on-python-3-5

 

[64비트]

pip install --use-wheel --no-index --find-links=https://github.com/sfbahr/PyCrypto-Wheels/raw/master/pycrypto-2.6.1-cp35-none-win_amd64.whl pycrypto
[32비트]

pip install --use-wheel --no-index --find-links=https://github.com/sfbahr/PyCrypto-Wheels/raw/master/pycrypto-2.6.1-cp35-none-win32.whl pycrypto 

 

 

 

[암호화 소스 실행]

  그럼 아래 블로그의 소스를 가져와 보자. 테스트 결과 실행이 잘 되니 내용은 바꿀 필요는 없고, 주석만 조금 제거하고 설명을 추가하도록 하겠다. http://blog.dokenzy.com/archives/1997

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#-*- coding: utf-8 -*-
 
# Python 3.4
# author: http://blog.dokenzy.com/
# date: 2015. 4. 8
 
import base64
import hashlib
from Crypto import Random
from Crypto.Cipher import AES
 
# 아마 특정한 블록 사이즈를 채우기 위해서 입력된 값을 임의의 값으로 패딩(채워주기) 하는 코드 인 듯...
BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS).encode()
unpad = lambda s: s[:-ord(s[len(s)-1:])]
 
# 초기화 코드 인듯
def iv():
    """
    The initialization vector to use for encryption or decryption.
    It is ignored for MODE_ECB and MODE_CTR.
    """
    return chr(0* 16
 
# 2교시때 설명했 듯이 클래스는 구조를 잘 잡아주는 껍데기 이다.
class AESCipher(object):
    """
    https://github.com/dlitz/pycrypto
    """
 
    def __init__(self, key):
        self.key = key
        #self.key = hashlib.sha256(key.encode()).digest()
 
    # 메시지를 암호화 하는 함수 
    def encrypt(self, message):
        """
        It is assumed that you use Python 3.0+
        , so plaintext's type must be str type(== unicode).
        """
        message = message.encode()
        raw = pad(message)
        cipher = AES.new(self.key, AES.MODE_CBC, iv())
        enc = cipher.encrypt(raw)
        return base64.b64encode(enc).decode('utf-8')
    
    # 메시지를 복호화 하는 함수
    def decrypt(self, enc):
        enc = base64.b64decode(enc)
        cipher = AES.new(self.key, AES.MODE_CBC, iv())
        dec = cipher.decrypt(enc)
        return unpad(dec).decode('utf-8')
 
# 암호화 키 
key = 'abcdefghijklmnopqrstuvwxyz123456'
 
# 테스트 용 문자열
message = '한글을 테스트 합니다.'
 
# 테스트 용 문자열이 암호화 됬을 때 만들어 지는 값
_enc = 'gOXlygE+qxS+69zN5qC6eKJvMiEoDQtdoJb3zjT8f/E='
 
# 테스트용 문자열을 암호화 하고, 미리 암호화 된 값을 복호화 하기  
enc = AESCipher(key).encrypt(message)
dec = AESCipher(key).decrypt(_enc)
 
# 암호화 복호화 한 값이 서로 같음을 비교해 증명 true, true 가 나오면 같다고 증명된다.
print(_enc == enc)
print(message == dec)
cs

 

  그럼 위의 파일을 aes_example_in_python.py 로 저장하고 실행 한다. 저장하고 실행하는 방법이 기억이 안나면 2교시 때 자세히 설명한 부분을 다시 보고 오면 된다.

c:\Python\code>python aes_example_in_python.py
ImportError: No module named 'winrandom'

 

 

  실행을 했는데 또 다른 에러가 난다. 에러를 찾아보기 위해 구글에서 'python 3.5 winrandom' 이라고 찾아본다.  
https://stackoverflow.com/questions/24804829/another-one-about-pycrypto-and-paramiko

 

  아래와 같이 설치된 파일에서 nt.py 파일을 찾아서, 아래 문장을 수정해 저장하라고 그런다.
Problem is solved by editing string in crypto\Random\OSRNG\nt.py:

import winrandom -> from . import winrandom

 

  아래 파일을 열어서 내용을 수정해 준다. import winrandom 이 들어간 내용을 수정해 준다. 

  c:\Python\Lib\site-packages\Crypto\Random\OSRNG\nt.py

 

  다시 실행해 본다. 그럼 아래와 같이 True, True 라고 정상적으로 실행되어 결과가 나온다.

c:\Python\code>python aes_example_in_python.py
True
True

 

 

  ※ 수정한 문장에서 . 이 궁금하면 아래 링크들을 본다. 아마도 pycryto 가 2.x 대 버전만 공식적으로 지원 해서 3.x 대와 문법이 안맞아 그런거 같다고는 하는데 확실하진 않다. (2014년 6월이 최종 업데이트 날짜다)

  http://stackoverflow.com/questions/7279810/what-does-a-in-an-import-statement-in-python-mean

  https://wikidocs.net/1418

 

  ※ 새로운 암호화 모듈을 찾아보기 위해 구글에서 'python 3.6 pycrpto' 를 찾아보니, pycryptodome 이라는 모듈이 대체 되었다고 한다. 실제 프로젝트를 할때는 이와 같이 최신으로 유지보수 되는 많이 쓰는 라이브러리를 쓰는게 맞을 듯 싶다.

http://stackoverflow.com/questions/41813030/problems-with-installation-pycrypto-in-python-3-6

https://dzone.com/articles/an-intro-to-encryption-in-python-3

 

 

  그럼 5교시를 마치고, 6교시때는 4교시때 사용했던 MSSQL 와 함께, 5교시때의 암호화를 같이 머지해서, 만들려 그랬던 기능을 만들어 보도록 한다.

 

 

[추가]

파이썬 3.7의 경우는 본문에 있는 한글 에러 문제가 해결되어, pip install pycryptodome 으로 설치해 사용하면 됩니다^^

 

위의 3.5용 소스는 밑에 댓글 달아주신 것 같이 pycryptodome 사용하면 에러가 나서 아래 코드를 참조하세요. 

https://github.com/bjpublic/python_study/blob/master/%EC%98%88%EC%A0%9C%20%EC%86%8C%EC%8A%A4%20%EC%BD%94%EB%93%9C/05%EA%B5%90%EC%8B%9C/pycrypto_aes256_sample.py

 

 

1
2017.2.24 by 자유로운설탕
cs
posted by 자유로운설탕
2017. 2. 18. 19:22 프로그래밍

  4번째 시간이다. 이번 시간에는 지난 시간에 구현을 위해 나눈 기능 중 DB 와의 연결 부분을 구현하기 위해 MSSQL Express DB 서버를 설치하고, 어플리케이션 용으로 이런 저런 세팅 후, 쿼리를 날려서 테이블 내의 값을 가져오려 한다. 아래의 3교시 그림을 기준으로 하면 회색 박스로 표시되 있는 MSSQL Database 항목이다.  MSSQL 설치 및 원격 연결 설정, DB 생성 및 사용자 생성 부분은 이미 아시거나 설치되어 있다면 건너뛰고 보면 되겠다. 풀버전으로만 설치해 보셨던 분들은 express 버전은 조금 다른 듯 하니 설정 부분만 보셔도 될듯 싶다.

 

 

[목차]

0. 왜 파이썬 공부에 구글을 이용하는게 좋은가?

1. 언어를 바라보는 방법. 파이썬을 어떻게 바라봐야 할까?

2. 파이썬 설치와 환경, 버전 선택 하기의 이유.

3. 만들고자 하는 기능을 모르는 조각으로 나눠 조사해 보기

4. 데이터 베이스에서 내용 가져와 출력하기

5. 암호화 모듈을 이용해 암복호화 해보기

6. 퍼즐 조각들을 합쳐보기

7. 엑셀 파일 사용해 보기 -> 부록 : fuction 을 이용해서, 코드 정리해 보기

8. 정규표현식을 왜 사용해야 할까? 언어속의 미니 언어 정규표현식 살펴보기

9. 입력과 결과를 GUI 화면과 연결해 보기

10. Whois API 이용해 보기

11. 웹페이지 호출해 내용 파싱 하기(BeautifulSoup 그리고 한계)

12. 자동화 - 웹 자동화(with Selenium)

13. 자동화 - 윈도우즈 GUI 자동화(with pywinauto)

14. 자동화 - 작업 자동화

15. 수학 라이브러리 살펴보기

16. 그래픽 라이브러리 살펴보기

17. 머신러닝에서의 파이썬의 역활

18. 웹 프로그래밍 - Legacy Web

19. 웹 프로그래밍 - Flask 살펴보기(feat. d3.js)

20. 웹 프로그래밍 - Django 살펴보기

21. 정리 - 이런저런 이야기

 

 

 

 

[들어가면서] 

  개인적인 의견이지만 파이썬을 사용하기 전에 perl(펄) 이라는 언어를 조금 사용하고 있었는데, 파이썬은 perl 이 현대적 스타일을 지니고 다시 태어난 듯한 느낌이 든다. perl 은 리눅스 쉘 환경을 언어로 축약해 놓은 것 같은 재밌는 느낌도 있고, 30년의 오랜 역사를 통해(파이썬도 25년이 됬다고 해서 놀랐다) 방대하게 축적된 라이브러리와 참고 가능한 코드들이 구글에 많아 필요한 기능을 만들기 편하다고 생각했는데(물론 파이썬 보다 축약적 표현을 많이 써서 코드 가독성은 은근 암호문 같아서 확실히 떨어지는 것 같긴 하다), 파이썬을 경험해 보고 비슷하지만 무척 현대적으로 깔끔하다는 느낌을 받았다. 또 파이썬은 프로그래밍 하는 사람의 스타일에 따라 perl 같은 라이트하게 만들어 유틸성으로 사용하는 스크립트형 코딩도 가능하고, java 나 c# 같은 구조적인 언어가 가진 객체 지향이나, 웹의 mvc 같은 구조의 방식을 취할 수도 있어 스타일을 선택해 사용이 가능하도록 스펙트럼을 넓혀 놓은 것 같다. 기회가 닿는 다면 파이썬과 비슷한 느낌이지만 고전성과 정규식의 미가 있는 펄(perl) 이라는 언어도 한번 사용해 보길 추천한다.

 

 

  그럼 잡담은 그만하고(야매지만 할말은 많다 - -;) mssql 을 설치하고 python 으로 필요한 모듈을 설치하고, 샘플 코드를 만들어 보도록 해보자. 참고로 순서는 아래와 같으니 무엇을 하려고 진행하는 건지 의식적으로 생각하면서 따라해 보자

  1) MSSQL 설치

  2) 서비스 설정

  3) 실습용 DB 및 사용자 설정

  4) 테이블 생성과 데이터 입력

  5) python 으로 조회해 보기

 

 

 

 

1) MSSQL 설치

 

  먼저 구글에서 다음과 같이 다운로드 가능한 링크를 찾는다 'mssql 2016 express download' 두 번째로 나오는 아래의 링크에서 SQL Server 2016 express SP1 을 다운로드 한다.

  https://www.microsoft.com/ko-kr/sql-server/sql-server-editions-express

 

 

  설치 파일을 클릭해 설치를 클릭하고, 아래와 같은 화면에서 기본 설정을 선택 한다. 라이선스를 수락하고(요건 간단해서 스크린샷 생략), 기본 설치 위치로 설치한다(스크린샷 생략). 400 메가 정도의 다운로드가 시작되며 설치가 된다(설치가 예전에 비해 간략해진 듯 하다)

 

 

  그럼 아래와 같이 설치가 완료되었다는 화면이 나온다. 그냥 닫지 말고 아래의 SSMS 설치 버튼을 클릭한다(SSMS 는 SQL Server Management Studio의 약자로 SQL 서버 접속용 클라이언트 이다 - 오라클로 따지면 Oracle Developer 나 오렌지, 토드 같은 툴이다. 앞으로 우리는 SSMS 에서 테이블 등을 만들거나 조회해 준비와 검증을 해보고, 이후 파이썬으로 개발을 할 예정이다).

 

 

  아래 페이지로 이동되면, SQL Server Management Studio 16.5.1 을 다운받아 설치 한다. (900 메가 정도 된다. 배보다 배꼽이 더 크다더니...)

  https://msdn.microsoft.com/ko-kr/library/mt238290.aspx

 

 

  다운 받은 파일을 실행하고 아래와 같이 설치를 시작한다.

  자 이렇게 되면 MSSQL 설치는 무사히 완료됬다.

 

 

 

 

2) 서비스 설정

 

  그럼 서비스 설정을 해보자. 원래 개인적으로 이런저런게 막혀서(특히 원격에서 연결이 안되서 좀 헤멨다) 구글을 찾아 해결 했지만, 해당 부분은 파이썬 공부와 좀 거리가 멀기 때문에 선택과 집중을 위해 헤멧던 검색 과정은 생략하도록 하겠다. 설치된 프로그램에서 SQL Server Management studio 를 실행 하자.

 

 

  SSMS 가 뜨면서 "서버에 연결" 하기 창이 나오면, 서버 이름이 비어 있기 때문에(예전엔 디폴트로 표시됬는데...), 드랍박스를 열어서 "더 찾아보기"를 선택한다. 이후에 서버 찾아보기의 로컬 서버 탭에서 내가 설치한 데이터 베이스를 선택 한다.    

 

 

  이제 서버 이름이 나오고 인증을 Windows 인증 상태에서"연결" 버튼을 눌러 연결한다. (SQL Server는 인증 방식이 두 가지가 있는데, 1번째로는 SQL 설치된 서버에서는 Windows 인증으로 id/pass 없이 관리자 계정으로 들어갈 수 있고, 2번째로는 사용자를 생성하거나 SA(System Administrator) 계정을 활성화 시켜서 id/pass 를 넣어서 들어갈 수 있다)

 

 

  그럼 아래와 같이 개체 탐색기 창이 나타난다. MSSQL 을 처음 설치하면 Windows 인증으로만 로그인이 가능하다(아직 활성화되거나 만들어진 사용자가 없기 때문에). 그런데 우리가 만들 파이썬 프로그램은 id/pass 를 넣어서 SQL Server에서 연결해 사용할 것이므로, 해당 id/pass로 로그인 가능한 옵션을 켜줘야 한다. SQL 서버 이름을 선택하고 마우스 오른쪽 버튼을 눌러서 컨택스트 메뉴를 띄운 후 맨 아래 보이는 "속성" 메뉴를 클릭한다.

 

 

  그럼 "서버 속성" 창이 뜬다. 왼쪽 트리에서 보안 항목을 선택하면 서버 인증 옵션이 보이는데, 디폴트는 아래와 같이 Windows 인증 모드가 체크되어 있는데, "SQL Server 및 Windows 인증모드"를 선택 후 "확인" 버튼을 눌러 적용한다. 그럼 SQL 서버를 다시 시작해야 된다는 Alert 이 뜨며 적용이 된다.

 

 

  SQL 서버 재시작은 cmd 명령어로 하거나, 컴퓨터를 재부팅 해도 되긴 하겠지만 시작 > 모든 프로그램에서 Microsoft SQL Server 2016 > 구성도구 > "SQL Server 2016 구성 관리자" 를 실행 한다. 

 

 

 그럼 아래와 같은 "SQL Server Configuration Manager" 창이 뜨는데, 일단 서비스 재시작을 하기 전에 할 일이 하나 추가되었다. 현재 세팅 상태로만 운영하게 되면 SSMS 에서는 id/pass 로 잘 접속이 되는데, python 이나 telnet 으로는 접속이 안되는 일이 발생했다. 구글을 찾아보니 아래와 같은 안내가 있어 추가 적인 세팅을 한 후 서비스 재시작을 하려 한다. 

http://stackoverflow.com/questions/12774827/cant-connect-to-localhost-on-sql-server-express-2012-2016

 

 

  먼저 TCP/IP 를 enable 시킨다. 왼쪽에서 "SQL Server 네트워크 구성" > "SQLEXPRESS에 대한 프로토콜" 을 선택하고, 오른쪽에서 TCP/IP 항목을 더블 클릭 한다. "프로토콜" 탭에서 Enabled 를 "예"로 바꾸어 준다.

 

 

  그리고 동일한 화면의 IP 주소 탭으로 이동해서 맨아래의 "IPALL" 파트에서 "TCP Dynamic Ports" 항목을 지우고, "TCP Port" 항목에 MSSQL 디폴트 port 인 "1433" 을 넣은 후 확인 버튼을 누른다. 그럼 앞서와 마찬가지로 서비스를 재시작 해야된다는 메시지가 나온다.   

 

 

  서비스 재시작을 위해서, 다시 왼쪽에서 "SQL Server 서비스"를 선택하고, 오른 쪽에서 "SQL Server (SQLEXPRESS)" 항목을 클릭 후 마우스 오른 쪽 버튼으로 컨텍스트 메뉴를 띄워서, "다시 시작" 클릭 해, 서비스를 종료했다 다시 시작한다.

 

  자 이렇게 하면 원격 서비스 설정이 완료이다. 이제 부터는 MSSQL 을 사용하기 위해 세팅하는 부분을 진행한다.

 

※ 파이썬을 돌리기 전에 간단히 서버 연결을 확인 하려면, telnet 서비스를 설치하고 cmd 창에서 'telnet localhost 1433' 으로 정상적으로 연결되는지 보면 된다. 텔넷은 아래 링크에서...

  https://opentutorials.org/module/2160/12506

 

 

 

 

3) 실습용 DB 및 사용자 설정

 

  개체 탐색기에서 "데이터베이스" 항목을 선택 후 컨택스트 메뉴를 띄워(이제 마우스 오른쪽 버튼 누르라는 얘기는 생략한다--;), "새 데이터베이스" 항목을 선택한다.

 

 

  데이터베이스 이름을 "mytest" 라고 입력하고(여러분이 좋아하는 다른 이름을 입력해도 되지만, 그러면 앞으로 제공할 소스에서 디비 이름 항목을 여러분 걸로 수정해 주어야 한다). 확인을 누른다.

 

 

 

 그럼 아래와 같이 별로 한것도 없는데, mytest 란 방금 만든 DB가 보이게 된다.

 

 

 

  자 이제 파이썬 프로그램에서 사용할 사용자를 만들어 보자. "보안" > "로그인" 항목을 선택하고, 컨텍스트 메뉴를 띄워 "새 로그인" 항목을 실행한다.

 

 

  "로그인 - 신규" 창이 뜨면, 왼쪽에서 "일반"을 선택 하고, 로그인 이름 "pyuser"(이것도 역시 원하면 취향대로 이름 만드시고, 나중 소스에서 수정!), "SQL Server 인증" 라디오 버튼을 클릭하고, 암호에 "test1234", 암호 확인에 "test1234" 를 넣는다. 공부하는 건데 암호가 만료되면 귀찮으므로(실제 운영시는 정책을 설정해 패스워드를 주기적으로 바꿔 주는게 보안 적으로 맞다) 아래와 같이 "암호 정책 강제 적용" 체크 박스를 끈다.

 

 

  이후 왼쪽에서 "서버역활" 항목을 클릭하고 sysadmin 을 클릭한다.(요 부분도 사실 admin 권한이므로 실제 웹어플리케이션 등 운영시는 권한을 제한하여 사용해야 한다).

 

 

  마지막으로 "사용자 매핑"을 선택하고, 오른쪽에서 "master" 데이터베이스를 선택하여 하단에서 "db_owner" 권한을 체크하고(이것도 "디비의 주인" 역활이기 때문에 운영에서는 과도한 권한임, master 데이터 베이스는 시스템의 여러 설정 값이나, 디비 스키마(디비구조 설계도) 등이 들어 있는데, 나중에 테이블 컬럼명을 얻어오는 코드에서 사용할 예정이다), 또 우리가 만든 "mytest" 데이터베이스를 선택하여 동일하게 "db_owner" 권한을 체크한다.  그리고 하단의 "확인" 버튼을 눌러 사용자 생성을 완료한다. 이렇게 되면 master db와 , mytest db 에 db_owner 권한을 가진, sysadmin 역할을 지닌 pyuser 사용자가 생성된다

 

 

  이후 SSMS 상단의 "파일" > "개체 탐색기 연결" 메뉴를 띄워 인증 부분을 "SQL Server 인증" 으로 바꾸고 우리가 생성한 id/pass 를 입력한다. 이후 "연결" 버튼을 누른다. 

 

 

  이제 개체 탐색기를 보면 2개의 똑같은 연결 항목이 있을 것이다. 항목 제목을 보면 위쪽은 첨에 우리가 Windows 인증으로 연결한 항목이고, 아래쪽은 지금 pyuser 로 연결한 항목이다.

 

 

  자 그러면 초보자들은 헷깔릴수 있으니, 위의 항목을 선택 후 컨택스트 메뉴를 띄워 "연결 끊기" 를 클릭한다.

 

 

  연결이 끊긴 후 개체 탐색기 메뉴를 보면 우리가 2번째 만든 pyuser 에 대한 항목만 하나 남게 된다. 이제 "mytest" DB 항목을 선택 하고, 컨텍스트 메뉴에서 "새 쿼리" 를 선택 한다.

 

 

  그럼 오른 쪽에 쿼리 창이 열리면서 커서가 빤짝빤짝 거린다. 쿼리창 상단에 마우스를 오버해 올려보면, mytest (pyuser (56)) 이라고 현재 사용하는 DB와 사용자 정보가 나온다.

 

 

  마지막으로 쿼리창에 "select SYSTEM_USER" 라고 입력후, 마우스로 드래그해 선택한다. 그리고 F5키 를 누른다(이것은 SSMS 상단 메뉴에서 "쿼리 > 실행" 을 누르는 것과 동일하다). 해당 명령은 등록된 사용자를 보여주는 명령어로 하단 결과 창에 우리가 등록한 "pyuser"가 보인다. (참고로 마우스로 드래그하여 선택하지 않으면 F5를 눌렀을때, 쿼리 창에 있는 모든 쿼리가 다 실행된다.)

 

  이제 MSSQL 을 파이썬에서 이용하기 위한 모든 준비가 끝났다(개인 적으로 관련 스크린샷 만드는 게 은근 힘들었어서, 큰 산을 넘은 듯하다 --;)

 

 

 

 

4) 테이블 생성과 데이터 입력

 

  이제 그러면 DB 에 테이블을 하나 만들어 보자. 구글에서 "mssql table create sample" 라고 검색해 보자. MSDN 설명은 보통 모든 옵션을 다 나열해서 장황하니 아래와 같이 좀 간략한 샘플을 제공하는 페이지를 참고한다.

  https://www.techonthenet.com/sql_server/tables/create_table.php

 

 

  간략히 설명하면 아래와 같다.

1
2
3
4
create table 소유자.테이블이름 (
   "컬럼이름1" " 담을 데이터형태" "각종제약조건(null 여부, primary, default, check 등)"
   "컬럼이름2" .... 
)
cs

 

 

  소유자를 안넣게 되면 현재 사용자의 테이블이 생성되기 때문에 현재 환경에서는 pyuser 가 dbowner 권한 이기 때문에, dbo(db owner)가 무조건 붙게 된다. 담을 데이터 형태는 파이썬과 마찬가지로 int, char, float 등등으로 컬럼에 담을 데이터의 형을 지정하게 된다. 제약 조건은 null, not null, primary, constraint, identity, foreign key, default value 등 여러가지 항목들이 있으나 해당 부분은 MSSQL 뿐만 아니라 표준 SQL 전반에 대한 공통된 내용이므로 "헤드퍼스트 SQL" 이나 잘 정리되 있는 블로그들을 참조한다(차후 개발 보안 관련 글들을 올릴때 SQL 의 전반적인 내용에 대해서 간단히 다룰 예정이긴 하다). 여기서는 null, not null 만 보자. null 은 해당 컬럼에 값을 넣지 않아도 관계없다는 거고, not null 은 꼭 어떤 값이 들어가야 한다는 얘기다.

 

  예를 들어 test 테이블에 no, name, card 라는 컬럼이 있고, card 라는 항목이 nullable 이라면 "1", "홍길동" 이런 식으로 3번째 card 값을 생략하고 한 행을 만들 수 있다(카드가 없는 사람도 있을 수 있기 때문에, 해당 card 라는 컬럼에 데이터가 비어 있는걸 가능하게 하는 것이다). 또 null 이라는 개념은 join 등에서도 해당 되는 대상이 없는 부분을 판단할때 같은 때도 사용하지만, 테이블 만의 개념을 설명하면 위와 같이 단순히 이해하면 된다. 또 데이터형 뒤에 null, not null 을 안 써준 경우 mssql은 기본적으로 null 형태라고 판단하긴 하는데, null 을 명시적으로 쓰는게 명확한 것 같긴하다. null 에 대한 고찰은 구글에서 'null 개념' 이라고 검색하여 아래와 같은 글들을 참고해 본다.

  http://slowalk.tistory.com/2181

 

 

  그럼 위의 지식을 기반으로 우리 실습에 사용할 supermarket 이란 테이블을 만들어 본다.

1
2
3
4
5
6
7
CREATE TABLE [dbo].[supermarket](
    [Itemno] [int] NULL,
    [Category] [char](20) NULL,
    [FoodName] [char](30) NULL,
    [Company] [char](20) NULL,
    [Price] [int] NULL
)
cs

 

 

  자 테이블의 내용을 보면 주인은 DBO(DB Owner = pyuser)이고, 컬럼은 아이템번호(itemno), 종류(category), 과자이름(foodname), 제조사(company), price(가격이다), 모두 비어 있을 수 있는 null 을 지정했고, 아이템번호와, 가격은 int 로 지정했다. 나머지 값들은 20, 30개의 문자로 이루어진 문자열 이다(mssql 은 대소문자 안가린다). 그럼 위의 소스를 긁어서 아까 실행해 놓은 쿼리 창에 붙여 넣고 내용 전체를 선택 후 F5키를 누른다. 하단 결과 창에 명령이 완료되었다고 뜨며, 왼쪽에 우리가 만든 supermarket 테이블이 보이게 된다

 

 

  이후 우리가 만든 테이블을 한번 내용을 조회해 보자. 아래의 소스에서 첫번째 줄인 "select ~ (nolock)" 부분을 복사해서 쿼리 창에 붙여 넣는다. '--' 로 시작되는 줄들은 주석 표시로, 쿼리가 처음 인 분을 위한 설명을 위해 넣었다

1
2
3
select * from supermarket(nolock)
-- 슈퍼마켓 테이블(supermarket) 로부터(from) 모든걸(*) 가져와봐(select) 
-- 다른거랑 부딛치지 말구(nolock)
cs
 

 

  근데 아래와 같이 컬럼 이름만 표시되고 빈 내용만 있다.

 

 

  우리가 아직 테이블만 만들고 데이터는 넣지 않았으니 당연하다. 그럼 데이터를 넣는 방법을 찾아보기 위해서 구글에서 "mssql insert sample"로 검색한다. 아까와 똑같은 techonthenet 사이트의 내용을 참고한다.

  https://www.techonthenet.com/sql_server/insert.php

1
2
3
4
INSERT INTO 테이블
(컬럼1, 컬럼2, 컬럼3)
VALUES
(넣을값1, 넣을값2, 넣을값3);
cs

 

 

  해당 방식을 이용해서 supermarket 에 넣을 쿼리를 만들면 아래와 같다. 역시 아래 소스를 긁어서 쿼리 분석기에 넣고 선택 후 F5키를 누른다(이제 요 긁어서~F5 설명도 익숙해 지셨을 테니 슬슬 생략 하고 "쿼리를 실행한다" 라고 표현한다.) 아래와 같은 4개 메시지가 결과 창에 나오면 성공한 것이다(4개의 데이터 건이 각각 들어갔으므로, 4개 행이 영향을 받았다고 나온다)
  (1개 행이 영향을 받음)
  (1개 행이 영향을 받음)
  (1개 행이 영향을 받음)
  (1개 행이 영향을 받음)
 

1
2
3
4
5
6
7
8
9
10
11
12
insert into supermarket
values (1,'과자', '초코파이', '오리온', 3000)
-- 슈퍼마켓 테이블(supermarket)안에(into) 값들(1,'과자', '초코파이', '오리온')을 넣어줘(insert)
 
insert into supermarket
values (2, '음료수', '크라운 커피', '크라운', 1000)
 
insert into supermarket
values (3, '음료수', '오리온 식혜', '오리온', 1300)
 
insert into supermarket
values (4, '과자', '크라운 산도', '크라운', 500)
cs

 

 

  그럼 아래 쿼리를 다시 실행해 본다.

1
select * from supermarket(nolock)
cs

 

 

  이제 화면과 같이 결과가 잘 나오게 된다. 

 

 

  참고로 아래와 같은 쿼리들도 추가로 돌려서 sql select 문의 여러가지 면들을 느껴보자.

1
2
3
4
5
6
7
8
9
10
11
-- 과자만 보고 싶어(category = '과자')
select * from supermarket(nolock) where category = '과자'
 
-- 1000원 이하(price <= 1000) 상품만 보고 싶어
select * from supermarket(nolock) where price <= 1000
 
-- 결과 중에 상품이름과, 회사만 보고 싶어
select FoodName, Company from supermarket(nolock) 
 
-- 과자이름 가나다 순으로 보고 싶어
select * from supermarket(nolock) order by FoodName
cs

 

  그럼 테이블 생성 및 샘플 데이터 입력 파트가 끝나게 되었다

 

 

 

 

5) python 으로 조회해 보기

 

  여기까지 오기전까지 MSSQL 설치 및, 세팅, 테이블 생성과 데이터 입력 후 조회를 해봤다. 무척 번거롭고 긴 과정이라고 느꼈을 수도 있겠지만, 앞 시간에서 얘기했듯이 4교시 앞 부분의 모든 과정은 python 하고 직접적인 관계가 없다. 어찌보면 일반적인 프로그래밍 요소에 대한 설명을 한 서비스 챕터 개념이다. 반대로 얘기하면 여기서 얻은 mssql(그리고 겪어봄 알겠지만 많이 비슷한 SQL 표준을 따르는 oracle, mysql 같은 관계형 DB 들, 하둡베이스의 hive 등)에 대한 지식들은 다른 언어를 할때도 역시 적절히 응용해 사용할 수 있다. 아마 나중에 lua, java 같은 다른 언어를 할 때도, 지금 알게된 이 지식 때문에, SQL을 호출하는 부분이 좀 더 수월하게 느껴지게 될 것이다. 프로그래밍은 코딩 작업이 아니다. 자기가 아는 구체적인 지식들을 코드라는 수단을 이용해 표현하는 것일 뿐이다. 구현할 주제와 관련된 모든 것을 잘 파악하고 있다면 구현은 비교적 간단해 진다. 그래서 같은 회사안에서도 바쁜척 하면서 설렁설렁 살아가는 프로그래머들도 있다. 요즘 많이 얘기되는 폴리글랏 프로그래밍도 어떤 측면에서는 같은 맥락인 듯 싶다. 새로운 언어를 빨리 배우는 능력은 언어와 연결된 배경을 잘 아는데서 기인할 수도 있다.

 

 

  우선 python 에서 mssql 을 호출하려면 어떻게 해야되는지 알기위해 구글을 검색해 보자. 구글에 "python 3 mssql" 로 검색한다. 맨위에 아래의 링크가 나오게 된다.

  http://www.c-sharpcorner.com/blogs/connect-ms-sql-server-2012-with-python-35

 

 

  내용을 참고해 보면 c# 언어에 대한 가이드긴 하지만 pymssql 이라는 모듈을 사용하고 아래의 명령으로 설치하라고 한다. cmd 창으로 가서 아래 명령어를 복사해 사용한다.

1
pip install pymssql  
cs

 

c:\>pip install pymssql
Collecting pymssql
  Using cached pymssql-2.1.3-cp35-cp35m-win_amd64.whl
Installing collected packages: pymssql
Successfully installed pymssql-2.1.3

 

  정상적으로 설치가 되었다고 문구가 나온다. 그럼 이제는 프로그램 소스에 이용할 관련 샘플을 찾아보자. "pymssql python sample" 이라고 찾으면 제일 위에 공식 홈에서 나온 설명 페이지가 나온다.

  http://pymssql.org/en/latest/pymssql_examples.html

 

 

  해당 페이지의 코드 중에, Basic Features 부분에서 테이블 생성과, 인서트 부분을 제외하고 셀렉트 코드 기준으로 적당히 편집해 가져오면 아래와 같다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
import pymssql
conn = pymssql.connect(server, user, password, "tempdb")
cursor = conn.cursor()
 
cursor.execute('SELECT * FROM persons WHERE salesrep=%s''John Doe')
row = cursor.fetchone()
while row:
    print("ID=%d, Name=%s" % (row[0], row[1]))
    row = cursor.fetchone()
 
conn.close()
 
 
cs

 

 

  샘플 코드의 동작을 확인 하기 위해 일단 다른 코드를 더 추가하지 말고, 해당 코드 그대로 현재 환경과 맟추어서 값을 조정해 넣어본다. where 조건은 없으므로, cursor.execute 의 뒷부분은(Jone Doe) 빼도 될듯 하다. 지금 만든 환경에 맞추면 아래와 같다. 맨위의 uft-8 이 부분은 한글 주석을 위해서 에러가 안 나도록 추가했다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# -*- coding: utf-8 -*-
# 모듈 호출
import pymssql
# DB에 연결함
conn = pymssql.connect(server='localhost', user='pyuser', password='test1234', database='mytest')
# 커서를 만듬
cursor = conn.cursor()
# 커서에 쿼리를 얻어서 실행 시킴
cursor.execute('SELECT Itemno, Category, FoodName, Company, Price FROM supermarket(nolock);')
# 한 행을 가져옴
row = cursor.fetchone()
# 행이 존재할 때까지, 하나씩 행을 증가시키면서 1번째 컬럼을 숫자 2째번 컬럼을 문자로 출력함
while row:
    print("ID=%d, Name=%s" % (row[0], row[1]))
    row = cursor.fetchone()
#연결을 닫음
conn.close()
cs

 

    c:\python\code 디렉토리에 sql_sample.py 이름으로 저장후 실행 시키면 아래와 같은 결과가 나온다. (저장하고 실행하는 부분을 잘 모르겠으면 2교시 때 스크린샷과 함께 자세히 설명했었던 부분들을 다시 참고 한다)

c:\Python\code>python sql_sample.py
ID=1, Name=과자
ID=2, Name=음료수
ID=3, Name=음료수
ID=4, Name=과자

 

 

  그럼 c와 같이 % 로 타입을 지정해 출력하는게 조금 귀찮은 듯해서 소스에서 아래 print 부분만 조금 바꾸어 + 기호를 이용해 결합해 전체 컬럼을 스페이스로 구분된 문자열로 출력해 보자.

1
print (row[0+ " " + row[1+ " " + row[2+ " "  + row[3+ " " + row[4])
cs

 

  근데 실행을 해보니 아래와 같이 에러가 난다. 에러 내용을 보니 첫 번째 itemno 컬럼은 숫자인 int 이고 Category 컬럼은 문자열 이라서, 숫자와 문자열을 합치려고 한다고 에러가 난다.

c:\Python\code>python sql_sample.py
Traceback (most recent call last):
  File "sql_sample.py", line 14, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'

 

 

  아래와 같이 결과 컬럼을 str() 함수로 명시적으로 문자열 타입으로 변경하게 하면 에러가 나지 않는다. (구글에서 'python int to string' 이렇게 입력해 방법을 찾았다)

1
2
3
4
5
6
7
8
9
10
11
12
13
import pymssql
 
conn = pymssql.connect(server='localhost', user='pyuser', password='test1234', database='mytest')
 
cursor = conn.cursor()
cursor.execute('SELECT Itemno, Category, FoodName, Company, Price FROM supermarket(nolock);')
 
row = cursor.fetchone()
while row:
    print (str(row[0]) + " " + str(row[1]) + " " + str(row[2]) + " "  + str(row[3]) + " " + str(row[4]))
    row = cursor.fetchone()
 
conn.close()
cs

 

  다시 실행을 해보면 아래와 같이 최종 결과가 정상적으로 나온다.

 

 

  그럼 위의 코드를 나중에 전체 프로그램을 만들때 다시 사용하기 위해 킾해두고 파이썬에서 DB 호출을 해보는 4교시 시간을 마치려 한다. 다음 시간에는 2번째 요소인 암호화 모듈을 사용해 대상을 암복호화 하는 샘플을 만들어 보려한다. 여러 SQL 종류에 대해서 간단히 얘기하기로 했었는데 너무 글이 길어진 듯해서 6교시 퍼즐 조각들을 합쳐보기에서 언급 하려한다. (별로 깊이는 없으니 많은 기대는 하지 마시고...)

 

 

 

 

2017.2.19 by 자유로운설탕
cs

 

 

 

posted by 자유로운설탕
2017. 2. 13. 20:19 프로그래밍

 3 번째 시간이다. 이제 부터 전에 얘기했던 몇 개의 외부 환경을 사용하는 간단한 파이썬 프로그램들을 만들면서 구글을 통해 문제를 해결하는 예를 보이려고 한다. 이번 예제는 "DB에 암호화 할 평문 값 하나를 넣어놓고, 그 값을 불러와서 암호화 후 다시 복호화를 하고, 해당 값 들을 DB 로 다시 저장 후, 전체 값들을 가져와 화면에 표시"하려 한다. 이러한 부분을 나누어 해결하기(Divide and conquer) 방법을 사용하여 만들어 보자.

 

[목차]

0. 왜 파이썬 공부에 구글을 이용하는게 좋은가?

1. 언어를 바라보는 방법. 파이썬을 어떻게 바라봐야 할까?

2. 파이썬 설치와 환경, 버전 선택 하기의 이유.

3. 만들고자 하는 기능을 모르는 조각으로 나눠 조사해 보기

4. 데이터 베이스에서 내용 가져와 출력하기

5. 암호화 모듈을 이용해 암복호화 해보기

6. 퍼즐 조각들을 합쳐보기

7. 엑셀 파일 사용해 보기 -> 부록 : fuction 을 이용해서, 코드 정리해 보기

8. 정규표현식을 왜 사용해야 할까? 언어속의 미니 언어 정규표현식 살펴보기

9. 입력과 결과를 GUI 화면과 연결해 보기

10. Whois API 이용해 보기

11. 웹페이지 호출해 내용 파싱 하기(BeautifulSoup 그리고 한계)

12. 자동화 - 웹 자동화(with Selenium)

13. 자동화 - 윈도우즈 GUI 자동화(with pywinauto)

14. 자동화 - 작업 자동화

15. 수학 라이브러리 살펴보기

16. 그래픽 라이브러리 살펴보기

17. 머신러닝에서의 파이썬의 역활

18. 웹 프로그래밍 - Legacy Web

19. 웹 프로그래밍 - Flask 살펴보기(feat. d3.js)

20. 웹 프로그래밍 - Django 살펴보기

21. 정리 - 이런저런 이야기

 

 

 

  위에서 하려는 내용을 그림으로 표시하면 아래와 같다.

 

 

 

[문제 나누기]

  그럼 해당 문제를 풀려면 어떻게 해야 할까? 일단 자신이 모르는게 뭔지 정리해야 한다. 해당 문제를 구현하려면 아래와 같은 부분들이 필요하다.

1) 우선 SQL 에서 데이터를 불러오고 저장하는(정확하게 여기서는 업데이트 하는) 방법을 알아야 한다. (적절한 Database 를 설치해야 하고, 해당 Database 에 맞는 SQL 문을 만들어야 한다.)

2) AES256 방식을 사용해 불러온 값을 암, 복호화 해야 하다.

3) 최종으로 디비에 저장된 값 들을 가져와 cmd 화면에 출력해야 한다.

 

 

  일단 SQL 에서 데이터를 불러오고 저장하는 방법을 알아보자. 일반적으로 리눅스에서 돌아가는 MySQL 이 예제로 많이 사용되지만, 윈도우와 제일 친숙하고, 개인적으로 제일 익숙한 MSSQL 을 기준으로 구현을 하도록 하려한다. 나중에 해당 코드를 MySQL 과 Oracle 로 어떻게 변환 할수 있느냐에 대한 예제도 다루어 보려고 한다. MSSQL 은 유료 Database 가 아니냐고 얘기할 수도 있지만, MS 도 많이 개방 적이 되어서 소규모로는 기능제한이 거의 없는 express 버전을 제공한다(이 부분은 오라클도 마찬가지다). 그럼 MSSQL 연결을 지원하는 모듈을 찾기위해 구글에서 'python 3 mssql' 이라고 검색해 본다. 스택 오버플로우 글을 보면 pyodbc 와 pymssql 을 사용하라는 2개의 제안이 보인다.

http://stackoverflow.com/questions/17411362/connecting-python-3-3-to-microsoft-sql-server-2008

http://stackoverflow.com/questions/33326023/python-3-5-using-pymssql

 

  ODBC(Open DataBase Connectivity) 란 mssql, mysql, oracle 등 여러 db 에 대한 호출을, 각 databse 회사에서 윈도우에 지원하는 공통된 드라이버(뭐 API 가 정확한 표현이겠지만)을 통해서, 사용하는 개념이다. 비디오 카드 종류가 틀리더라도, 윈도우 드라이버가 잡히면 프로그램은 비디오 카드 종류만 세팅하면, 공통된 코드로 해당 비디오 카드들을 사용할 수 있는 것과 같은 개념이다. 전문 적인 설명은 아래의 링크를 참고한다.

http://dumaclub.tistory.com/entry/ODBC%EB%9E%80

 

 

  근데 ODBC 로 구현하게 되면, 아래와 같이 ODBC 세팅하는 방법도 소개해야 하고, 그럼 코드와 DB에 연결하는 계정 문자열이 분리되게 되어서, 설명하기가 더 어려워 지기 때문에, ODBC 방식이 아닌 직접 연결을 하는 라이브러리로 설명하려 한다. (사실 연결 방식은 ODBC 든 아니든 연결 계정이 노출된다는 면에서는 보안적으로는 바람직하진 못하다) 그래서 pymssql 모듈을 사용하기로 결정 한다. odbc 를 사용해 본적 없는 심심하신 분은 나중에 pyodbc 모듈을 이용해 구현해 보심 좋을듯 하다.

http://blog.danggun.net/1003

 

 

  2 번째로, aes256으로 암, 복호화 하기 위한 모듈을 찾아보자. 구글에서 'python 3 aes256' 로 찾으면 처음에 스택오버플로우 글이 나오고, 두번째로 우리나라 분이 정리한 문서가 하나 나온다.

http://blog.dokenzy.com/archives/1997

 

  읽어보니 파이썬 3.4에서 돌아간다고 하고, 스택오퍼플로우 글들을 참고해서 만든 코드를 테스트 하는 코드까지 추가되어 자세히 만들어져 있기 때문에 가져다 사용하기 적절해 보인다. 물론 실제로 필요한 프로그램을 만들때는 암호화 라이브러리 같은 경우, 샘플 코드가 인자 값들을 적절하게 세팅하여 사용했는지를 따져보고, 해당 라이브러리가 취약점이 없고 계속 유지보수 되고 있는 지를 따져봐야 할 거이다. 하지만 지금은 학습을 위해 프로그램을 만들고 있는 중이기 때문에, 특별한 문제는 없다고 가정하고 가져와 사용하기로 한다.

 

 

  3 번째는 가져온 내용을 프린트 하는 부분으로 문법 문제와 같지만 DB 에서 가져온 내용을 프린트 하는 부분이므로, DB 조회 샘플을 찾다보면 자연 스럽게 같이 해결 날것 같다.

 

 

 

[여담 - 문법 공부하기]

  여담이지만 개인적인 의견인데, 어떤 언어를 퀵하게 참조해서 사용해야 할때는(예전에 상용 자동화 솔루션을 사용하기 위해 공부 할 때 그랬었다), 메뉴얼이 어느정도 잘 정리되어 있다면 목차위주로 훝으면서 보게되면 언어가 지원하는 기능의 범위를 대충 알수 있게 된다. 예를 들어 파이썬 문법을 훝어 보기 위해서 구글에 'python 3 manual' 을 쳐서 나온 아래의 tutorial 을 한번 관심 있는 항목을 하나씩 예문 위주로 보는 것도 나쁘지 않다. 혹시 지금의 진행이 너무 구현위주로 빠져있어서 중요한 문법 요소들을 놓치는 기분이 들어 불안한 경우 아래 튜토리얼은 꼼꼼히, 나머지 레퍼런스는 대충 함 보셔도 괜찮을 듯하다. (절대 자세히 읽으라는 말은 아니고, 전체적인 흐름 파악 측면에서 보았음 한다.)

https://docs.python.org/3/tutorial/

https://docs.python.org/3/library/index.html

https://docs.python.org/3/reference/index.html

 

 

  그럼 여기서 글을 마무리 하고, 다음 시간에는 mssql 을 설치해서 계정, 테이블 등을 세팅 하고, pymssql 을 사용해서 DB를 조회하는 내용을 다루려고 한다. 그러면서 database 간의 차이에 대한 짧은 지식도 교양 차원에서 얘기하려 한다.

 

 

2017.2.13 by 자유로운설탕
cs

 

 

posted by 자유로운설탕
2017. 2. 8. 21:54 프로그래밍

  두번째 시간이다. 이번엔 파이썬 버전을 선택하는 문제에 대해서 얘기를 해보고 해당 파이썬 버전 및, 실습 환경을 선택한 이유에 대해서 설명 후, 실제 파이썬을 설치하고, 1교시에 나왔던 각 언어요소별 샘플 파일들을 실행해 결과를 보려한다.

 

목차

0. 왜 파이썬 공부에 구글을 이용하는게 좋은가?

1. 언어를 바라보는 방법. 파이썬을 어떻게 바라봐야 할까?

2. 파이썬 설치와 환경, 버전 선택 하기의 이유.

3. 만들고자 하는 기능을 모르는 조각으로 나눠 조사해 보기

4. 데이터 베이스에서 내용 가져와 출력하기

5. 암호화 모듈을 이용해 암복호화 해보기

6. 퍼즐 조각들을 합쳐보기

7. 엑셀 파일 사용해 보기 -> 부록 : fuction 을 이용해서, 코드 정리해 보기

8. 정규표현식을 왜 사용해야 할까? 언어속의 미니 언어 정규표현식 살펴보기

9. 입력과 결과를 GUI 화면과 연결해 보기

10. Whois API 이용해 보기

11. 웹페이지 호출해 내용 파싱 하기(BeautifulSoup 그리고 한계)

12. 자동화 - 웹 자동화(with Selenium)

13. 자동화 - 윈도우즈 GUI 자동화(with pywinauto)

14. 자동화 - 작업 자동화

15. 수학 라이브러리 살펴보기

16. 그래픽 라이브러리 살펴보기

17. 머신러닝에서의 파이썬의 역활

18. 웹 프로그래밍 - Legacy Web

19. 웹 프로그래밍 - Flask 살펴보기(feat. d3.js)

20. 웹 프로그래밍 - Django 살펴보기

21. 정리 - 이런저런 이야기

 

 

 

 

[들어가면서]

  우선 시작에 앞서 처음 프로그램 언어 공부하는게 왜 어려울까에 대한 얘기를 잠시하려 한다. 

 

  "A씨는 php 로 웹 프로그래밍 공부를 시작한다. 책에 나온 문법을 열심히 따라하는데 갑자기 html 이라는 조금 아는 언어가 끼어든다. 테이블을 출력하고, form 을 던지고 하더니, 조금 더 나아가니 db(database) 라는 녀석이 출현을 해서 php 에서 데이터를 저장하고 조회하고 한다. db를 잘 이용하려면 SQL 이라는 언어를 잘 알아야 한다고 한다. 뭔가 동적으로 움직이는 페이지를 만드려면 자바스크립트 라는 또 다르게 생긴 언어도 필요하단다. referer 얘기가 나오면서 http 헤더 얘기도 나오고, get, post 등등이 나오며 점점 내용이 복잡해 진다. html 과 비슷하게 생긴 xml 이라는 언어는 왜 또 책 뒷부분에 찜찜하게 스쳐가듯 언급되는지... 샘플을 그대로 보고 입력해서 실행하는데 에러는 계속 나고, 에러메시지는 이해가 잘 안가서 어떻게 해결하는지도 잘 모르겠다. php 는 참 어려운 언어같다. 남들은 웹프로그램이 제일 쉽다고 하던데 언어하나 배우는게 왜 이렇게 어려운가 싶다."

 

  위의 경험은 처음에 이런저런 언어를 공부한다고 책을 이것저것 잡히는 대로 볼때 마찬가지로 경험했던 현상이다. 첨엔 뭔가 잘 알것 같은 분위기 였는데, 진행하면 할수록 오리무중에 빠져버린다. 그런데 찬찬히 따져보면, 우리가 어려워 하는건 php 언어가 아니다. 언어는 한 측면에서 보면 껍데기에 불과할 수도 있다. php 를 배우면서 평생 처음 보는(또는 불완전하게 아는) db 에 쿼리를 던지는 것은, 말을 막 배우기 시작한 애들이 철학을 논하는 거랑 비슷한 상황이 된다. 중요한 것은 말을 배우는게 어려운게 아니라는 거다. 철학을 이해하지도 못하면서 그 말로 표현하려는게 문제지 말이다. (물론 말 배우는 건 어렵긴 하다;)

 

  프로그래밍 언어는 혼자서 움직이는게 아니라 미리 만들어졌거나 근간이 되는 여러 다른 언어와 시스템 들을 사용한다. 컴퓨터 화면(GUI), 디렉토리, 파일, database, http protocol, javascript, css, ajax, json, xml, 정규식, ftp, webserver, 메모리, 레지스터, 이미지 등등, 게다가 수학 및 물리학 같은 여러 도메인 지식을 포함한 그 밖의 많은 것과 함께 말이다. 그러다 보니 첨에 프로그래밍을 공부하다보면 언어의 본질적인 어려움이 아닌 언어가 접근하고자 하는 대상을 이해 못해 막힐수 밖에 없다. 그렇다고 문법만 공부하면 영원히 현실적인 주제들과 유리되어 버리고, 너무 현실적인 예제로 공부하게 되면, 기초 지식의 부족으로 쉽게 진도가 안나가고 지치게 되버린다. 아님 어찌어찌하게 따라가 마무리를 했더라도 조금 지나게 되면 뭘 했는지도 기억이 가물가물한 허무한 상태가 된다.(물론 형상화 되진 않았지만 작은 경험치가 쌓이긴 한다) 

 

  결과적으로는 적절한 문법 난이도와, 위에 언급한 한 두가지의 외부 접점을 연결시켜 공부하는게 바람직한 방법 같다고 생각한다. 그 배경하에서 왜 해당 언어의 각 문법 요소들이 굳이 언어에 존재해야만 하는지에 대한 필요성을 차근히 느낀다면 단지 외우는 프로그래밍이 아닌 인지하는 프로그래밍이 될 듯하다. 어떤 분야건 중요한건 스스로 마음속에 배우려는 대상에 대해 어떤 모델을 구축해 이해하느냐 이다. 어떤 측면에선 문법은 상세히 몰라도 괜찮다. 구글을 통해서 얼마든 문법은 찾아 이해해 적용하면 된다. 요는 문법 보다는 why? 이다. 책을 고를때도 그런 책을 잘 골라보는 게 좋다. 반대로 얘기하면 한 언어에 국한되더라도 저런 연결된 주제들을 적절한 깊이로 잘 이해한다면 다른 언어에 접근할 때 이미 아는 주제를 어떻게 이용하는가에만 집중할 수 있어 편하게 된다. 어차피 이용하는 방식은 고급언어로 갈수록 비슷해지니까 말이다. 마지막으로 막다른 길에 다달았을때 해당 부분에 대해서 힌트를 제공해 줄수 있는 사람이 있다면 더 좋을 건 없을 듯하다.

 

  사실 이 부분은 프로그래밍 뿐만 아니라 보안이나 테스팅 등 다른 분야를 공부할 때도 마찬가지인 부분 같다. 또 다른 예를 들자면 하둡 같은 빅데이터관련 시스템이나, 머신런닝을 배우고 싶어하는 분들이 리눅스의 기본적인 개념들에 대해서 모르고 있다면 아마 리눅스 서버에 프로그램 세팅하는 순간 부터 이해 안가는 개념들(사용자별 권한, ssh 설정, 환경변수, 모듈 설치, 서버간 동기화, 각종 디렉토리 파일 관련 명령어) 때문에 헤메느라 정작 실제 원했던 하둡 운영에 대해서는 쉽게 다가가지 못하게 될 가능성이 높을 것이다.

 

 

 

[파이썬 2.x vs 3.x 사이의 선택]

  파이썬 공부를 시작하면서 제일 먼저 고민한 부분은 버전 선택이다. 구글에서 "python download" 로 검색해서, 들어가는 파이썬 홈 페이지에서 제일 먼저 보는것이 2.x 대 버전과, 3.x 대 버전의 두개의 다운로드 링크 이다. 현재 기준으로 3.6과 2.7.13 버전이 나타난다.

https://www.python.org/downloads/

 

 밑에 박스 친 링크를 보면 어떤 버전을 선택할지 모르는 사람들을 위해서, 두 개의 버전을 비교한 링크가 있다. 앞으로 적은 부분에 대해 스스로 객관적인 판단을 하기위해 위의 링크의 비교 글을 한번은 살짝 읽어보길 바라며, "파이썬 2 3 선택" 으로 찾은 2개의 한글 페이지들을 소개해 본다.

https://b.ssut.me/python%ec%9d%98-%eb%af%b8%eb%9e%98-python-3%ec%9c%bc%eb%a1%9c-%eb%84%98%ec%96%b4%ea%b0%80%ea%b8%b0/

https://kldp.org/node/129183 

 

  우선 파이썬 2를 옹호하는 사람들은 현재 많은 회사에서 파이썬 2 기준으로 프로그램들이 개발되어 있어 실제 일을 할때 현실적인 버전이고, 외부 모듈들도 대부분 2 는 완벽히 지원하지만, 3 는 지원 안하거나 미지원 되는 모듈들이 많고(이 부분은 정확히 얘기하면 2를 지원하는 모듈들이 더 이상 3를 미지원 하는 문제일 것이다. 코드를 변경할 수고만 감당한다면 같은 기능을 가진 파이썬 3용 새로운 모듈들로 대체될수 있는 가능성도 있다고 본다), 성능 측면이나, 개발과 관련된 기술 문서를 웹에서 찾는데 불리하다는 얘기이다. 파이썬 3를 옹호하는 사람은 파이썬 2는 2020년에 지원이 만료된다는 예정이 있고, 2에서 구조적으로 개선된 여러 언어기능이 파이썬 3에는 있지만, 2는 앞으로 유지보수 정도 수준으로만 개선될 예정이기 때문에 3로 가는게 맞다는 것이다.(아마 파이썬 3의 모듈 호환 문제 같은 경우는 점점 개선될 거 같긴 하다)

 

  저도 원래 시작은 2.7로 시작했다. 근데 mssql db 에서 한글로 된 내용을 가져와 cmd 화면에 표현하려 했는데, 아래와 같이 한글 출력 부분에서 유니코드 변환 에러가 났다.

  구글링을 해서 아래 utf-8 관련 코드를 상단에 추가해서 위의 인코딩 에러는 안났는데, 이후엔 아래와 같이 한글이 깨져보인다.

# -*- coding: utf-8 -*-
import sys
reload(sys)

sys.setdefaultencoding('utf-8')

 

  또 구글링을 해보니 한글 윈도우 cmd 창의 디폴트 코드 페이지가 cp949 라서 그렇다고 해서 아래 명령어를 통해서 cmd 창의 코드 페이지를 utf-8로 변경해 봤다.

>> chcp 65001

 

  이후 다시 샘플을 실행해 보면 아래와 같이 정상적으로 나오긴 한다.

  이렇게 한바탕 헤메며 진을 빼고나서 보니 mssql 등 db와 연동 했을때 파이썬 2 버전의 한글 처리에 대해서 명확하게 구글에 정리된 자료는 없는 듯도 하다.(물론 잘 정리되어 있는데 유니코드 및 한글 인코딩 구조에 대해 까막눈이라서 못 찾았을 수도 있다--;) 그런데 이런 인코딩 오류 부분은 서양 언어가 아닌 동양 언어 국가의 비애인데, 대부분의 프로그래밍 언어나 라이브러리, 유틸리티들이 처음 시작 될때는, 한글, 한자 같이 2byte 언어를 고려한 유니코드 대응 설계를 하지 않는다는 것이다.  많이 유명해지고, 2byte 권 사용자들이 개발자에게 불만을 계속 토해내게 되야, 겨우 유니코드 반영이 되긴 하는데 이 시점이 보통 상당히 늦고, 그나마 불완전하게 땜빵 설계로 되는 경우도 종종 있는 듯 하다. 물론 데이터를 모두 영문으로만 쓰면 괜찮겠지만, 세상일이 어찌될지도 모르고 이렇게 한글이 나올때마다 처리에 신경 써야 되는 부분은 귀찮은 건 사실이다. 좀더 전문적인 파이썬 2.x 의 한글 처리에 대해서는 아래 글을 참고하자.

http://ifyourfriendishacker.tistory.com/5

 

  그래서, 3.x 대 버전을 설치 후 동일한 코드를 돌려봤는데(print 문법 차이 땜에 조금 바꾸긴 했다), 해피하게도 기본적으로 유니코드를 잘 지원해서 위에 언급된 유니코드 관련 코드들을 안 넣어도 한글이 정상적으로 cmd 창이나 엑셀 파일 등에 잘 출력된다. 그래서 주저 없이 파이썬 3를 사용해 학습하는 것을 선택하게 되었다.    

 

 

 

[산 넘어 산 - 학습을 위한 3.x 버전 선택]

  일단 3버전으로 하기로 한 후에도 다시 고민이 생기게 되었다. 최신 버전을 사용하느냐 약간 이전 버전으로 사용하느냐에 대한 문제 였다. 마이너 버전 끼리의 기능 차이는 별로 없을 가능성이 높은 상태에서 최신 버전의 가장 큰 문제는, 모듈 호환성 관련 문제 발생시 아무래도 구글에 참조할 만한 글들이 많지 않을 가능성이 높다. 또 모듈 설치시 편한 pip 명령어를 사용하려면 최신버전은 아무래도 모듈 호환성 업데이트가 안되있을 수 있어 좀 불리하다. 처음 시작하던 때에는 3.5.3 이 최신 이여서 이것저것 막히는 걸 해결하면서 왔는데, 현재는 3.6이 2016년 12월 중순에 나와있긴 했다. 그래도 최신으로 해야 되지 않을까 해서 몇 가지 모듈 테스트를 한 결과 3.5.3에서 잘 설치되는 모듈이 3.6에는 빌드에러가 나서 설치가 안되는 일이 발생했고, 구글에도 딱히 가이드 삼을 문서가 없어서 현 블로그에서 설명은 3.5.3 기준으로 진행하려 한다. 물론 2.x 과의 선택에서도 마찬가지 였지만, 지금 일어나는 호환성 문제는 세월이 지나면 모듈들이 업데이트 되면서 자연스럽게 해결 날 수도 있다.

 

 

 

[파이썬 3.5.3 설치]

  설치에 오기까지 잡담이 많이 길었지만 실제 선택하기 위해 고민한 과정을 보여주고 싶어 그런 것이라고 이해했으면 하고, 이제 버전도 정했으니 설치로 들어간다. 개인 환경은 윈도우즈 10 Home, 64비트 이다. 윈도우즈 7도 호환성은 거의 비슷한 것 같다.

 

  먼저 다운 로드를 받기 위해서 상단 다운로드 그림의 Python for windows 링크를 클릭 하거나, 구글에서 'python 3.5.3 download' 라고 쳐서 젤 처음인 아래 링크를 들어간다.

https://www.python.org/downloads/release/python-353/

  windows x86이 32비트 OS용이고, x86-64가 64비트 용이다. executable installer 가 우리가 보통 생각하는 exe 형태의 설치 파일이다. 저 같은 경우는 64비트이니 아래 파일을 다운 받았다.

Windows x86-64 executable installer

 

다운받은 다음에 실행해 보자

  'Add Python 3.5 to PATH' 를 체크하고, 설치 디렉토리를 정하기 위해서 'Customize installation' 을 클릭하자. path를 추가하는 이유는 어떤 경로에서든 python 명령어가 실행되게 하기 위해서 이다. 설치 디렉토리를 변경하는 이유는 cmd 창에서 다단계의 복잡한 디렉토리 경로로 찾아 들어가기 싫어서 이다^^; 혹 path 개념을 잘 모르신다면 아래의 링크를 참고하자.

http://dvjin.tistory.com/4

 

 

  두 번째 화면은 어차피 다 체크되 있으니 Next 를 그냥 누른다. 앞으로 우리가 자주 보게될 pip 를 설치하는 옵션이 있다.

 

 

  마지막으로 모든 사용자가 사용하도록 체크하고(어차피 혼자쓰는 컴퓨터일 테니), 경로를 c:\python 이라고 간단한게 바꿔주자(디렉토리가 없을테니 Browse 버튼 누르지 말고, 걍 텍스트 박스에서 수정하자). Install 버튼을 누르면 파이썬이 설치 완료될 것이다.

 

 

  그럼 실행을 해보기 전에, 메모장과 cmd창(도스창)에서 실행을 하라고 얘기하는게 왠지 구닥다리 방식 같아서, 구글로 'python ide' 라고 검색해 보니, visual studio 를 이용하는 방법, eclipse 를 이용하는 방법, 그리고 현재는 pycharm 이라는 툴이 많이 쓰이는 거 같다.(프리, 상용) 하지만 IDE 로 설명하게 되면 왠지 조금 더 복잡하게 될거 같기도 하고, 또 처음에는 IDE 를 사용 안하고 cmd 창에서 live 하게 실행해 보는 것도 구조 이해에 도움도 될거 같고(리눅스에서 실행하면 어차피 이런 모드기도 하다), 저도 현재는 텍스트 에디터에 cmd 화면을 이용해 사용하고 있는 형편이고 코드도 클래스니 구조화니 그런건 없어서 파일 하나면 되기 때문에, 예쁜 IDE 사용여부는 아래의 링크를 참고하고, 다른 블로그에도 많긴 하지만 나중 시간에 pycharm 이나 설치하고 사용하는 예를 들까 싶다. 

http://yaraba.tistory.com/173

http://cksl.tistory.com/31

http://noeticforce.com/best-python-ide-for-programmers-windows-and-mac

 

 

  그럼 윈도우즈 10에서 명령 프롬프트(관리자)를 실행 한다. (혹시 더 초보셔서 윈도우즈 10에서 명령 프롬프트 실행을 모르시면, 아래 링크를 참고 한다-좀 10이 이리저리 숨겨놓긴 한듯 하다;) http://slic.tistory.com/410 

  ※ 팁으로 만약 윈도우즈 10 UI 가 넘 화려해서 싫은 저 같은 분은 classic shell 이라는 프리웨어를 설치하시면 익숙한 7 스타일로 시작 메뉴 등을 사용할 수 있다. http://windwaker.net/2096

 

 cmd 창이 뜨면 python 이라고 친다. 아래 같은 특이한 프롬프트의 창이 뜨면 정상적으로 설치한 것이다.

ctrl+z 키를 누른 후 enter 키를 눌러서 원래의 도스창으로 돌아온다.

 

 

 

[샘플들 실행해 보기]

자 그럼 1교시 때 언급한 샘플 파일들을 하나씩 실행해 보자. 

먼저 변수 얘기할 때 만들었었던 친구들의 빵 갯수 더하기 코드를 보자

1
2
3
4
5
breadfriend = [23456]
 
breadfriend_sum = sum(breadfriend, 0.0)
 
print(breadfriend_sum);
cs

 

위의 내용을 복사하여 메모장에 붙여 넣어보자.  c:\python 폴더 밑에 코드들을 저장할 code 라는 디렉토리를 만들자. 메모장 내용을 저장 하면서 파일형식을 '모든 파일'(그래야 원하는 확장자로 저장된다), 이름을 breadcount.py 라고 만들어 놓은 code 라는 폴더에 저장한다. (첨 실행이라서 혹시나 해서 몰라서 아래 스크린샷을 추가한다. 담부터는 힘들어서 말로만 --;)

 

 

  이후 cmd 창에서 c:\python\code 창으로 이동한다. 이 부분도  첨이니까 기초부터 설명하고 스크린샷을 첨부한다. 

이동하는 방법은 처음 cmd 창이 뜨면 c:\windows\system32 폴더일 것이다. 여기서

1) cd / 후 enter(c:\ 로 가게된다)

2) cd python\code 후 enter 하게 되면 프롬프트가 c:\python\code 로 되어 있을 것이다.

(이 부분도 첨이자 마지막 스크린샷)

 

  자 첫코드가 실행되어서 다섯개의 빵의 합인 20이 나오게 되었다. 다시 한번 얘기하지만 지금 이 메모장과 도스창으로 하는 작업이 익숙하지 않다면, 프로그래밍 자체가 아닌 연결되어 있는 배경지식이 필요한 경우이다. 이 경우 가능한 바로 IDE 를 사용하지 말고 지금 하는 구식 방법을 조금 사용해 보는 것도 추천 한다. 헤메면서 얻은 지식은 잊혀지지 않기 때문이다.(단 코드가 복잡해지면 메모장은 한계가 있기 때문에 위의 링크에 추천한 IDE 나 문법을 칼라로 표시해 주는 선호하는 편집기를 쓰는게 낫다.)

 

 

  두번째 부터의 실행 코드 예제는 언어구조 개념에서 설명했던 예들의 코드들을 가져와 보자

 

  두번째 객체 예를 보이는 writefile.py 의 샘플은 아래와 같다.

 

1
2
= open('test.txt''w')
f.write("This is a test")
cs

 

  2줄인 엄청 간단한 코드로, 다른 언어를 하나라도 공부해 본 분들은 한번 쯤 파일 작업 할때 봤던 비슷한 코드 일 거다.(물론 옛날 언어에서는 조금 더 장황하게 써야했다). 코드를 설명하자면 test.txt 라는 이름을 가진 파일 객체를 write(w: 쓰기) 모드로 열어서, 그 안에 "This is a test" 라고 내용을 적는다. 저 open 이라는 명령이 파일 test.txt 라는 이름을 가진 파일 객체를 만든다는 모습을 보여주는 코드라고 생각해 6)번 샘플로 만들어 봤다.

 

  실행은 똑같이 메모장에 붙인 후 c:\python\code 테이블에 writefile.py 라고 저장 후, cmd 창의 해당 폴더에서 python writefile.py 입력 후 폴더안을 보면 "This is a test" 내용이 적힌 test.txt 파일이 생성되어 있을 거다.

 

 

  세번째는 함수 예를 보여주는 function_sample.py 이다.

1
2
3
4
5
6
7
8
def myfn(x):
    if x > 100:
        print("over 100")
    else:
        print("under 100")
        
myfn(120)
myfn(90)
cs

 

  자 파이썬 문법에 대해 공부한건 별로 없지만, 대충 보면 def myfn(x) : 부분으로 함수가 시작되고, 다른 언어에서 { ( 등으로 구분되던 부분은 아무래도 스페이스나 공백으로 구분 되는 듯 하다. 위의 예는 tab 으로 함수 안의 빈 공간을 채워놨다.

 

  예를 들어 밑에 처럼 앞의 if x > 100; 앞의 tab을 2개로 해서 else 와 tab 수를 다르게 한다면 아래와 같은 문법 에러가 난다. 번거로운 경계 문자들을 없앤 대신 만드는 사람이 로직에 맞게 들여쓰기(indentation)를 명확히 맞춰줘야 한다. 

 

1
2
3
4
5
6
7
8
def myfn(x):
        if x > 100:
        print("over 100")
    else:
        print("under 100")
        
myfn(120)
myfn(90)
cs

TabError: inconsistent use of tabs and spaces in indentation

 

위의 첫번째 정상 코드를 실행 하면, 정의한 함수 myfn 에 120 과 90의 값이 넘어가서, 각각 결과가 나오게 된다.  

C:\Python\code>python function_sample.py
over 100
under 100

 

 

  네번째 예는 클래스 내의 method 를 보여주는 upper_method.py 이다.

1
2
3
input = "This is a sample"
result = input.upper()
print(result)
cs

 

  고급언어로 갈수록 string 같이 단순한 문자열을 담는 기본 변수들도 클래스로 정의되어, 해당 변수에 담겨있는 데이터에 대해 클래스에 내장된 유용한 메쏘드를 사용할수 있게 한다. 위의 예에서 고전 언어 같은 경우는 upper 라는 따로 만든 함수에 input 문자열을 넘겨줬어야 했었을 테지만, 파이썬은 input 변수 자체가 upper 라는 메쏘드를 소유하고 있어 스스로 바로 변환을 한다.  

C:\Python\code>python upper_method.py
THIS IS A SAMPLE

 

 

  다섯번째 예는 내부 라이브러리(파이썬의 경우 모듈)을 사용하는 library_sample.py 예이다.

1
2
3
4
import statistics
 
data = [3746]
print (statistics.mean(data))
cs

 

  기본적으로 파이썬 기본 기능에서는 배열의 평균을 한번에 구할 수는 없다. 위에선 import 명령어를 이용하여 기본 통계(statistics) 모듈을 가져온 후, 통계 모듈안에 있는 mean 기능을 이용하여(statistics.mean(data)) 배열의 평균을 구해서 출력 한다. 실행 결과는 아래와 같다.

C:\Python\code>python library_sample.py
5

 

 

  마지막 예는 우리가 이제 것 cmd 창 화면으로만 사용했던 파이썬 기능을 우리가 쓰는 윈도우 창과 같은 GUI 화면에 표시해 주는 예이다. 실제 구현 예는 차후 보겠지만, 맛보기 샘플을 위해서 해야될 일도 적지는 않다. 우선 구글에서 어떤 gui 모듈을 사용할지를 판단해야 한다.

  구글에서 'python gui' 라고 검색을 하고 보다가, 아래의 글을 보게 되었다. 2015년도니 기간도 적당하고, 참고할만 한듯 하다.  

http://insights.dice.com/2014/11/26/5-top-python-guis-for-2015/

 

  이외에도 구글 상위순으로 이런 저런 글들을 보다 보면 제일 많이 나오는게 pyqt 와 wxpython 인거 같다. (너무 과거의 문서가 상위에 나오면 도구 옵션에서 날짜를 1년 정도로 제한하는 것도 좋다) 

 

  그럼 다시 구글에서 'wxpython vs pyqt' 라고 검색을 해보자. 나름 기술 적으로 활발한 토론이 이루어 지는 듯한 reddit.com(클리앙에서 아이폰 탈옥 관련할때 자주 언급되는 곳이다)의 2015년도 글을 하나 보자

https://www.reddit.com/r/learnpython/comments/3pif4k/tkinter_vs_pyqt_vs_wxpython/

 

  대충 보면 파이썬에서 기본제공하는 Tkinter 라는 기능은 배우긴 쉬운데 기능이 넘 단순하고 수동으로 해야되는 일이 많고, pyqt 는 제일 기능은 편하지만, 상업적으로 사용하기엔 라이센스 관계가 복잡하다고 한다(아니라는 댓글도 같이 달려 있다). wxpython 은 어중간 한 위치인가 보다. 일단 저는 어중간한 wxpython 을 선택하게 됬다. 어차피 gui 라이브러라는게 비슷비슷하기 때문에 하나를 감을 잡으면 다른 것들은 적당히 적응할 수 있을 것이라고 생각하고 말이다.(개인적으로 예전에 matlab이라는 언어에서 gui 라이브러리를 좋아해 이런저런 걸 만들었어서 조금은 더 익숙 할 것은 같다)

 

  그럼 python 에서 pip 를 이용해 wxpython 을 설치해 보도록 해보자. pip 는 윈도우의 프로그램 추가 삭제를 cmd 창으로 옮겨놓은 버전이라고 봐도 될듯하다. 리눅스의 apt-get, yum 같이 특정 프로그램을 해당 명령어를 이용해 설치하겠다고 정의하면 (아마) 의존성 까지 고려해서 한번에 설치하거나 삭제해 주는 프로그램 이다. 설치된 패키지는 python 폴더내의 tools 폴더안에 들어가게 된다.  https://ko.wikipedia.org/wiki/Pip

 

  일단 파이썬 3를 wxpython 이 지원할지 안할지 모르는 상태니, 구글에서 'python3 wxpython pip' 이라고 쳐보자. 그런데 첫번째 글을 읽어보니, wxpython 은 2.x 대만 지원하고 3.x 를 지원하기 위해서, wxpython Phoenix 이란거로 모듈이 분리되었다고 한다.

  http://stackoverflow.com/questions/28029760/wxpython-in-python-3-4-1

 

  스택오퍼플로어에 가이드 된데로 cmd 창에서 아래 명령어를 실행시킨다.

pip install -U --pre -f https://wxpython.org/Phoenix/snapshot-builds/ wxPython_Phoenix

 

 뭔가 한참 진행되더니 성공적으로 설치되었다고 뜬다. happy case 다.

Installing collected packages: six, wxPython-Phoenix
Successfully installed six-1.10.0 wxPython-Phoenix-3.0.3.dev2812+b3485d4

 

  그럼 이제 GUI 창을 맛보기로 띄워주는 wx_sample.py 내용을 보자

1
2
3
4
5
6
7
8
9
import wx
 
app = wx.App()
 
window = wx.Frame(None, title = "wxPython Frame", size = (300,200)) 
panel = wx.Panel(window) 
label = wx.StaticText(panel, label = "Hello World", pos = (100,50)) 
window.Show(True) 
app.MainLoop()
cs

 

  얘는 어쩔 수 없이 코드가 조금 길다. 간단히 설명하면 저 복잡한 구조는 우리가 매일 쓰는 윈도우 어플리케이션의 구조와 연관되어 있다. 설치한 wxPython 라이브러리를 가져오고(import wx )-> 어플리케이션 객체를 하나 만든다(ws.App)-> 어플리케이션 객체안에 작은 윈도우를 하나 만들고(wx.Frame)-> 윈도우 창에 넣을 판데기 공간(패널)을 하나 만든다(wx.Panel)->  패널안에 넣을 Hello World 텍스트를 하나 넣고(wx.StaticText)-> 윈도우를 우리한테 보여준다(window.show)-> 이후 이벤트를 수령해 사용자 동작에 반응한다(app.MainLoop).

<google: 'wxpython app mainloop' 참고>

 

  실행을 하면 아래와 같이 Hello World 가 가운데 있는 작은 윈도우 창을 보여준다.

C:\Python\code>python wx_sample.py

 

 

  이렇게 해서 또 생각보다 길어진 2교시가 끝났다. 다음 시간에는 만들어 보고자 하는 기능을 하나 정하고 어떻게 구글을 이용해 해당 기능을 나누어 조사하여 원하는 기능을 만들지에 대한 부분을 진행할 예정이다.

 

 

[추가]

파이썬 3.7의 경우는 pip install wxpython 으로 설치하면 됩니다^^

 

 

2017.2.12 by 자유로운설탕
cs

 

 

posted by 자유로운설탕
2017. 1. 30. 19:06 프로그래밍

  파이썬이라는 언어에 대해 공부해야지 하면서 차일 피일 미루던 중, 우연한 기회에 파이썬을 공부하고 싶어하는 지인에게 주기적으로 정리해서 알려주게 되었다. 해당 공부를 진행을 하면서, 정리한 내용들을 블로그에 올리려고 한다. 물론 인터넷에 잘 정리되 있는 좋은 강의는 많지만, 대부분이 이미 글 쓴 분의 잘 정리된 지식을 따라가는 것이기 때문에, 과거 경험에 따르면 시간이 지나면 유효하지 않아서 따라하기 힘든 경우도 있고, 강의를 따라하거나, 확장된 시도를 해보다가 이해되지 않는 에러를 만나서 '망연자실' 상태에서, 공부를 포기하는 경우도 많을 듯하다.

 

  프로그래밍 공부에서 꼭 필요한 건 막다른 상황을 만나 이곳 저곳을 헤메면서 문제를 해결하면서 배우는 시행착오 과정이라고 본다. 그 과정에서 겪게된 문제 해결을 위한 여러가지의 시행착오는 나중에 비슷한 문제를 만났을 때, 부드럽게 문제를 해결하게 해주는 내적인 패턴이 된다.

 

  그래서 많은 파이썬 관련 강의가 있겠지만, 체계적인 문법이나, 좋은 유지보수를 위한 구조같은 측면은 제외하고, 처음 시작부터 구글을 찾아 헤메며 시행 착오를 거치며 원하는 기능을 만들게 되는 과정을 보여주려고 한다. 스스로가 프로그래밍를 잘 한다고 하긴 힘든 실력이라고 생각하지만, 오히려 전문 프로그래머 보다는 필요에 의해 기능의 구현에만 포커스를 두고 언어들을 사용하는 입장이기 때문에 좀 더 초보자 입장에서 설명하기 쉽지 않을까 생각해 본다. 얘기를 해나가면서 가능한 해당 방식이나 코드를 선택하게된 연유를 설명하면서 진행하려 한다.

 

 

0. 왜 파이썬 공부에 구글을 이용하는게 좋은가?

1. 언어를 바라보는 방법. 파이썬을 어떻게 바라봐야 할까?

2. 파이썬 설치와 환경, 버전 선택 하기의 이유.

3. 만들고자 하는 기능을 모르는 조각으로 나눠 조사해 보기

4. 데이터 베이스에서 내용 가져와 출력하기

5. 암호화 모듈을 이용해 암복호화 해보기

6. 퍼즐 조각들을 합쳐보기

7. 엑셀 파일 사용해 보기 -> 부록 : fuction 을 이용해서, 코드 정리해 보기

8. 정규표현식을 왜 사용해야 할까? 언어속의 미니 언어 정규표현식 살펴보기

9. 입력과 결과를 GUI 화면과 연결해 보기

10. Whois API 이용해 보기

11. 웹페이지 호출해 내용 파싱 하기(BeautifulSoup 그리고 한계)

12. 자동화 - 웹 자동화(with Selenium)

13. 자동화 - 윈도우즈 GUI 자동화(with pywinauto)

14. 자동화 - 작업 자동화

15. 수학 라이브러리 살펴보기

16. 그래픽 라이브러리 살펴보기

17. 머신러닝에서의 파이썬의 역활

18. 웹 프로그래밍 - Legacy Web

19. 웹 프로그래밍 - Flask 살펴보기(feat. d3.js)

20. 웹 프로그래밍 - Django 살펴보기

21. 정리 - 이런저런 이야기

 

 

 

 

0. 왜 파이썬 공부에 구글을 이용하는게 좋은가?

 

  일단 구글에서 파이썬에 대해서 검색했을때, 한국 자료와 영문 자료의 비율을 한번 봐보도록 해보자.

1) http://www.google.co.kr/ 로 이동해서 검색어에 '파이썬' 이라고 치고 검색 버튼을 누른다.

--> 약 92만건 정도가 나온다.

 

2) 한국에서 누군가 영문으로 누군가 적을 수도 있다고 가정하고, python 으로 검색하고 오른쪽의 '도구'을 눌러서, 한국어 페이지만 선택해 본다.

--> 47만건 정도가 나온다. 두개를 합치면, 140만 건 정도가 나온다고 보면 된다.

 

3) 그럼 최종으로 영문 페이지에서 검색하게 되면 어떨까. 영문 페이지만 검색하는 방법은 아래 블로그에 있듯이 주소창에서, &lr=lang_en 인자를 추가해 검색하면 된다. (검색인자 조작은 여기선 주제와 별 상관 있는 부분이 아니니 궁금하면 아래 페이지를 보심 된다) 

http://channelofchaos.tistory.com/60

--> 1억 1천만건 정도가 나온다. (러프하게 보면 한글 페이지의 99배 정도이며 하나의 페이지에 파이썬, python 두 가지가 모두 있을 수도 있으니 1.2% 라고 해도 100배 정도라고 가정할 수 있을 듯 하다.) --> 아 근데 이러고 보니 python 이 비단뱀이라는 뜻도 가지고 있다. python program 이라고 찾으면 3천 1백만건 정도 나온다. python snake 는 1천 3백만건 된다. 비단뱀도 무시할 수 없으니 대충 비율대로 8천 만건 정도라고 하자 --;

 

  물론 페이지의 양이 컨텐츠의 질을 보장한다고 할순 없겠지만, 파이썬에 대해 한글로 정리된 페이지만을 찾아보는 것은 수많은 자료 중 약 1~5%만을 살펴보는 것이다. 또 대부분의 기술적 지식은 외국에서 시작되서 국내의 엔지니어 들이 한글로 풀어 소개하기 전까지는 어느정도 텀이 있는 부분도 고려해야 한다. 그래서 찾으려는 검색 결과가 제대로 안나온다면 반드시 적절한 영문으로 변환하여 검색하여 내용을 찾아봐야 한다. 그래서 프로그래밍을 하고 싶어하는 사람은 영어는 열심히 해서 원하는 내용은 훝어서 읽어 볼 수 있는 정도가 되는게 좋다. 그리고 요즘은 유튜브 같은 동영상으로 기술적인 부분을 안내하는 사람도 많기 때문에 듣기도 포함해서 기술적인 부분을 익힐 때 필요한 언어가 되는거 같다. 저도 듣는건 잘 못해서 매번 아쉽다.

 

  또 구글의 장점 중 하나는 사람들이 가장 많이 링크한 자료가 맨 첫 페이지에 나올 가능성이 높다는 거다. 그래서 적절한 검색어로 찾는다면 특히 파이썬 같이 유명한 프로그래밍 분야에서는 1, 2 페이지에서 원하는 힌트를 찾아낼 수 있다. 해당 부분은 추후 실제 예제를 진행 해가면서 느껴 보기로 하자.

 

 

 

1. 언어를 바라보는 방법. 파이썬을 어떻게 바라봐야 할까?

 

  프로그래밍 언어라는 것은 복잡하게 보면 한 없이 복잡하지만, 복잡하지 않게 보면 한없이 단순한 구조이기도 하다.

 

 

  위의 그림을 보자. (각 영역에 보면 사이사이에 확장자가 .py 라는 파이썬 샘플로 만든 파일이 표시되어 있는데, 해당 부분은 2교시 때 실제로 파이썬을 설치해 본다음에 하나씩 실제로 실행해 보도록 하는 시간을 가지려고 한다.)

 

  먼저 입력을 나타내는 1)번 영역을 보자 . GUI, DOS, FILE, Packet... 이 보인다. 프로그램에서 입력이라는 것은 다양한 형태로 이루어진다. 우리가 지금 블로그를 보고 있는 브라우저도 하나의 프로그램이고, 구글의 창에 'python' 이라는 검색어를 넣고, '검색' 버튼을 누른 것도 구글이라는 프로그램에 python 이라는 인자를(물론 다른 여러가지 인자들도 있겠지만) 전달한 것이다. 이렇게 입력이라는 것은 우리가 쓰는 윈도우즈 응용 프로그램 처럼 비주얼한 인터페이스를 제공할 수도 있고(GUI - Graphic User Interface), 도스 화면에서 수행하는 프로그램에 인자를 전달해 줄수도 있고, 새로운 워드 문서를 열듯 워드 문서 파일을 선택할 수도 있고, 카카오톡 같은 프로그램 들도 결국은 우리가 입력한 문장이나, 사진 등이, 통신 패킷에 들어가 프로그램 사이를 돌아다니는 입력으로 된다고 볼수도 있다.

 

 

  2)번 영역은 변수이다. 예를 들어 우리가 머리속에서 덧셈을 하는 과정을 생각해보자. 2+3 을 더한다고 하자. 물론 과정이 너무 빠르게 지나갈 수 있으므로, 우리가 좀더 덧셈을 못하는 다섯살 짜리 아이라고 상상해 보자. 먼저 2라는 글자가 머리에 떠오른다 + 기호가 생각나고 3 이라는 숫자가 생각난다. '두개를 더함 5네' 라고 생각 한다. 우리가 숫자를 생각한 순간에 뭐라고 설명할 순 없지만 머리속의 칠판에 2라는 숫자가 형상화 된다. 컴퓨터는 그런 형상화를 메모리(램, 하드디스크, 레지스터)라는 기억 공간안에 한다. 자 그럼 프로그램 언어에서도 2+3= 이라고 적을 수도 있다. 자 그런데 단순한 숫자의 연산보다는 친구가 빵을 2개 가지고 있고, 내가 3개 가지고 있는 부분을 프로그램에 표시하고 싶다. 어 근데 친구와 내 빵이란 것을 구분하고 싶은데 어떻게 해야 할까. bread_friend 를 친구가 가진빵이라고 하고, bread_me 를 내가 가진 빵이라고 표시하고 거기에다 수량을 넣는 방법이 있다. 그게 프로그램에서 얘기하는 변수라는 개념이다. 친구가 빵을 2개 가지고 있을땐 bread_friend 에 2를 넣음 되고, 3개 가지고 있으면, bread_friend 에 3개를 넣음 된다. 프로그램 언어로 형상화 하면 아래와 같다.

1
2
3
4
5
6
7
bread_friend = 2  (친구가 가진 빵 2개)
 
bread_me = 3  (내가 가진 빵 3개)
 
bread_we = bread_friend + bread_me (우리 빵 = 친구가 가진빵 + 내가 가진 빵)
 
화면에 출력 => bread_we 
cs

 

  약간 억지 상상이 끼었지만 별로 틀리진 않다. 프로그래밍 언어는 항상 사람의 생각을 자연스럽게 기술해 표현하고(다만 영문권에서 주로 만드니 영어 기준으로), 그 표현을 좀더 효율적인 구조로 나타내기 위해서 계속 노력하며 변하고 있기 때문이다. 자 그럼 3)번 데이터 영역도 자연스럽게 설명된다. 프로그램의 어느 공간안에 변수라는 개념을 형상화 시킨 방을 만들고, 그 안에  데이터(숫자, 글자 등등 우리가 프로그램에 넣어 이용하고 싶어하는 값들..)를 넣게 된다. 그리고 그 값들을 '+' 같은 도구 장치(사실 + 기호는 '연산자'라고 공식적인 용어로 얘기하긴 한다) 들을 이용하여, 이리저리 굴려 가공한다.

 

 

  4) 자료구조는 이렇게 생각해 보자, 단순하게 하나의 값을 넣는 변수들을 이리저리 정교하게 조합하다 보면 꽤 귀찮은 상황도 생길수 있다. 예를 들어 아래의 더하기 상황에서 친구가 10명이라고 가정하자. 그럼 변수를 bread_friend1, bread_friend2,... bread_friend10로 10개를 만들어야 할까? 좀더 편하게 많은 친구들을 표시할수 있는 방법이 있음 어떨까? '배열(array)이라는 자료구조가 그런걸 해결하기 위해 옛날에 만들어 졌다(정확하게 여기에서는 리스트-list 이다). bread_friend 라는 공통 인자를 가지고 그 뒤에 (괄호로 쌓여진)숫자로 구분할 수 있게 되었다.

1
2
3
4
bread_friend[1= 2
bread_friend[2= 3
...
bread_friend[10= 4
cs

 

  근데  이것도 10줄이 되어 귀찮으니 조금 더 간단한게 값을 넣을 수 있을까?

1
bread_friend = [23, ....., 4
cs

  위와 같이 하면 알아서 프로그램이 bread_friend[1], bread_friend[2], ... 순으로 알아서 넣어주기도 한다.

 

  그럼 조금 더 나아가 리스트를 더 할때도 bread_friend1 + bread_friend2 +... 하지말고,

'빵 값을 더해라 (친구 빵 리스트)' 이런 식으로 명령을 보낼 수도 있을까? ㅇㅇ 당연히 가능하다. 아래 그러한 예제를 설명한 글에서 실제 구현 예를 참조하자.

http://mwultong.blogspot.com/2007/02/python-list-array-sum-average-mean.html

 

  자 그럼 여짓것 설명한 내용을 총괄해서 5명의 친구의 빵을 더해 화면에 출력하는 프로그램은 아래와 같다. (2교시 때 파이썬 설치하고 실행하는 법을 알게 되면, 예제 내용을 bread.py 파일로 만들어 실행해 5개의 값을 더한 결과인 20이 출력되는 것을 보려한다)

1
2
3
4
5
breadfriend = [23456]
 
breadfriend_sum = sum(breadfriend, 0.0)
 
print(breadfriend_sum);
cs

 

  어 근데 가운데 줄의 코드가 왠지 받아들이기 어색하다. 굳이 sum 이라는 기능안에 넣는 식으로 하지 말고, breadfriend.sum() 이렇게 하면 계산해 주면 좀 더 직관적이지 않을까? 라고 생각이 든다. 구글을 찾아보니, 파이썬 자체는 그런 문법 방식을 지원하지 않는거 같지만, 나중에 아마 머신러닝쪽 얘기할때 살짝 언급하게될 numpy 라는 수학 모듈(모듈은 나중에 10번 영역 에서 설명한다)을 사용하면 그렇게  호출할 수도 있는거 같다. 밑의 블로그를 참고하자~

http://yceffort.com/archives/298

 

  자 그럼 단순한 변수에서 자료구조가 필요한 이유를 대충은 정당화 한 것 같으니, 아래 링크의 글을 한번 훝어 읽으라고 소개하는 것으로 자료구조 파트 설명은 마친다. 파이썬의 자료구조는 어떤 것이 있는지만 알고 필요할때 가져다 써야 머리가 덜 아플듯 하다. (단 문법은 대충 넘어가도 어떤 경우 사용하는게 좋은지에 대한 개념은 확실히 짚는게 구조적인 기억력에 도움이 된다,)

http://felixblog.tistory.com/54

 

 

  5) 프로그래밍 언어책을 보다보면 뭔가 약간 확 와닿지 않는 복잡함을 주는 요소들이 있다.  클래스, 인터페이스, 델리게이트(위임)... 이런걸 막 섞어서 효율적인 구조라고 소개하는 디자인 패턴이라고 하는 주제를 설명하는 책도 있고 말이다. 그 중의 가장 주범인 5) 클래스 - 객체 지향 언어라는 말을 만들어 내게 된 가장 중심적인 개념이다. 유투브의 디자인 패턴 설명하던 분은 설계도라고 설명 했는데, 약간 설계도보단 좀더 덜 적합한거 같지만, DNA 구조 라고 보면 어떨까 싶다. 사람이나 생물들이 해당 모양으로 성장하는데에 대한 정보는 DNA 구조에 담겨있고, 그 정보들이 구현되고 나면 사람(뭐 프로그램에서 말하는 인스턴스나, 객체)이 된다고 봐도 될듯하다. RPG 게임을 하면서 마법사를 선택한 순간, 내가 선택한 "설탕" 캐릭은 마법사 클래스의 DNA를 물려받아, 체력이 약하고, 마나 재생속도는 빵빵하고, 스킬 트리는 전격 스킬 쪽을 밟게되는 특성을 지닌 객체(캐릭터)로 만들어 지는 거라고 생각해보자. 이런 부분을 상상하게 되면 6)번 객체(Object) 까지 슬쩍 끼워 설명하게 된 듯하고, 유투브에서 아래의 강의나 '클래스 객체 인스턴스' 라고 검색해서 열심히 설명해 놓은 동영상 강의들을 보자. 고백하지만 명확히 잘 모르기도 하고 요 부분은 이번 강의에서는 곁가지 요소들이다. 

https://www.youtube.com/watch?v=8B2Wxks5Sig

 

  9)번 메쏘드는 마법사로 얘기하면 버프를 걸거나, 파이어볼 스킬을 쏘거나 하는 액션이 구현된 부분이고, 나중에 설명한 7) 함수(function) 가 객체 내부에 구현된 버전이라고 봐도 될듯 하다. 이 부분은 별로 자신없는 부분이니 언젠가 객체 지향과 패턴 개념에 대해 쉽게 정리해 전달 할수 있는 수준이 되길 기대하며 요 정도로 얼버무리려 한다.

 

 

  7) 번은 함수(fuction) 또는 서브루틴(sub-routine)이라고 하는 부분이다(언어마다 표현하는 말이 좀 다르다) 한글로 해석하면 각각 '기능', '루틴의 밑(또는 부수적으로)에 있는 로직' 이정도 이다. '루틴'이라는 것은 route 에서 나온 말로  '특정한 길을 따라 가는 행위' 같은 것이라고 볼수 있을 거다. 이 두 가지의 용어가 비슷한 개념을 설명하기 위해서 만들어 낸 말이라는 것을 이해해보자. (뭐 서브루틴은 인자가 없고, 함수는 인자가 있다고 구분 될 수도 있지만 그것은 무시하자). 함수를 얘기할때 붙어다니는 용어들은 '재사용', '중복코드 제거' 라는 말이다. 

 

  아까 여러명의 친구를 간단히 표시하기 위해 배열을 사용했 듯이, 코드 중에 자꾸 반복되어 나오는 부분을 묶어서 다른 구역 에다 격리해 옮겨놓고, 메인 코드에서 반복하여 호출해서 사용하는 개념이다. 회사로 얘기하면 각 팀에 복사기를 하나씩 놓았던 회사가 하나의 복사기만 OA 공간에 놓고 사람들이 같이 사용하게 하는 것도 일종의 함수개념 일 수 있다. 게임회사에서 개발자 풀을 만들어 놓고 각 게임별 그룹이 그때 그때 요청해서 원하는 리소스를 얻을 수 있게 하는 것도 개발자 풀이라는 함수를 재사용 하는 거라고 볼수 있다. 복사기 문제로 돌아가서 그렇게 되면 각 팀별 복사기 수십개를 신경 쓰지 않아도 되고, 최신형 복사기로 변경하고 싶을 때에도 공용인 1개의 복사기만 변경시키면 되기 때문에 유지보수가 쉬워진다. 대신 많이 써서 고장이 자주 나지 않겠냐고 할수도 있겠지만, 그건 현실의 사물 얘기고 코드상의 로직은 일반적으로 많이 호출해 쓴다고 고장이 나진 않을테니 괜찮다. 그래서 이런 공용 기능인 함수는 요청하는 쪽에서 요청을 하면서 (복사하길 원하는 자료 등)원하는 부분들을 얘기해야 하고(요것이 함수의 입력 인자이다), 자료가 복사되면 나온 결과물을 찾아가야 한다. 물론 복합기에서 팩스 보내기 같은걸 하는 경우는 실제 찾아갈 결과물은 없을 수 있다. 요게 함수의 리턴값으로 마찬가지로 값이 있을 수도 없을 수도 있다).

 

 

  8) 제어 로직은 변수나 자료구조, 함수등에 모두 적용할 수 있는 요소다. 영어의 단어 뜻과 사용법이 거의 일치 한다.

for (리스트의 첫번째~마지막 까지 인자를 찾는 동안),

while(리스트의 마지막 인자가 될때 까지),

case(리스트의 인자가 내가 지정한 조건들에 맞을 경우),

if(리스트의 세번째 인자가 10일 경우)와 같이 적용되거나,

또는 함수에 적용되어 if(함수의 리턴값이 1일 경우) 같이 원하는 조건을 넣는 것이라고 보면 될 듯 하다.

 

 

  10) 라이브러리, 모듈은 남이 미리 만들어 놓은 기능을 사용하는 것이라고 본다. 일반적인 사람들이 만드는 프로그래밍 이라는 것은 어찌보면 결국은 남이 만들어 놓은 기능 등을 추상적으로 꾸미는 일이라고 볼수도 있을 것 같다. 파이썬에도 GUI, 엑셀, web, 통계, 머신러닝, 이미지. text 처리 등등 세계의 수많은 프로그래머 들이 만들어 놓은 모듈이 있다.

 

  예를 들어 엑셀을 다루는 기능을 만든다고 하자. 만약 우리가 엑셀 파일의 구조까지 분석하여, 하나하나 다 약속된 구조대로 값을 불러오거나 저장하는 로직을 만든다고 하면 아마 시간도 오래 걸리겠지만, 엑셀 구조를 분석할 능력이 없어 구현 자체를 못할 수도 있다. 그런데 공개된 엑셀 모듈을 사용하면 아래와 같이 간단하게 엑셀파일을 만들수 있다.

1
2
3
4
5
6
7
엑셀 모듈을 가져온다.
 
엑셀 모듈안에 있는 클래스를 이용해서 엑셀 객체를 하나 만든다.  
 
엑셀 객체 안에 시트를 만들고, A1 열에 'hello' 라고 넣는다.
 
메모리의 엑셀 객체를 test.xls 라는 문서로 디스크에 저장한다.
cs

(해당 코드의 대한 시연은 7교시때 진행 예정이다.)

 

  근데 왜 굳이 라이브러리나 모듈을 굳이 프로그램에 import(가져오기) 시켜야 할까? 모든 기능을 다 한꺼번에 가져오면 프로그래밍 편집 및 실행 환경이 아주 무거워 지기 때문에, 필요한 순간에만 명시적으로 포함 시켜 만들면 선택한 라이브러리들만 내가 만든 프로그램과 같이 메모리로 올라가 실행되어 효율적으로 메모리 공간들을 사용할 수 있기 때문이다(뭔가 다른 숨은 설계적 이유도 많을 듯해서 별로 설명에 자신은 없긴하다^^).

 

  여튼 특히 파이썬 같은 사람들이 많이 사용하는 프로그램 언어를 사용할때 필요한 기능이 있다면, 구글을 잘 찾아보자. 사람들이 많이 사용하는 기능이라면, 이미 모듈로 존재하고 있을 것이다.(게다가 소스까지 공개해서 무료로...) 물론 해당 모듈을 그대로 사용하거나, 소스를 수정해서 좀더 좋게 개선해 사용하거나, 내 프로그램에는 적합하지 않아 새로 만들어 사용하는 것은 개발하는 사람 맘이긴 하지만, 일반적으로 유지보수가 계속 되고, 검증된 모듈이 있다면 그 것을 사용하는 것이 현명한 듯 싶다.

 

  요즘은 프로그래밍 환경도 SNS 처럼 연결 지향적인 환경이 되어가고 있는 듯하다. 좋은 연결고리를 많이 찾는 센스를 가진 사람이 쉽게 문제의 해결방법을 찾을 수 있는 것 같다. 또 다른 측면으로 얘기하면 결국 자신의 똑똑해 보이는 결과물들의 많은 부분이 수많은 사람들이 만든 기존 코드들의 도움으로 지지된 다는 것을 인지함으로서, 거인의 어깨위에 편하게 서 있는 자신을 느끼게 되어, 스스로에 대해 겸손해지고, 현재의 자신에 멈추지 않고 계속 노력할 수 있게 될 것 같다.

http://legacy.www.hani.co.kr/section-009100003/2004/11/009100003200411261636092.html

 

 

  11) 출력. 출력은 입력과 같은 형태의 윈도우 프로그램 화면, 도스화면, 파일, 이미지, 패킷등으로 자유롭게 표현될 수 있다. 이는 이러한 출력이 다른 프로그램의 입력이 될수도 있다는 것을 의미하기도 한다. 리눅스의 명령어 들이 파이프로 연결되어 출력을 다른 명령어의 입력으로 전해 주듯, 요즘의 많은 프로그램들은 자신의 출력을 다른 프로그램에서 사용하는 것을 염두해 두고 만들어 지는 것 같기도 하다.

 

  마지막으로 아주 초보 분은 할수 없지만 어느정도 코드에 익숙한 사람들도 다른 사람이 만든 소스들을 보다보면 이해가 안되게 복잡한 코드를 만나서 자신의 지적 능력에 좌절을 가질 수도 있을듯 싶다. 그런데 개인적으로 생각하기에 어려워 보이는 코드를 만나는 것은(물론 내부에 담겨 있는 도메인 지식이 부족한 경우나, 패턴을 이해못해 그런건 예외지만) 그 코드를 만든 사람이 문제를 명확히 이해하지 못하거나 타인을 생각하는 객관적인 코딩을 하지 못한 결과물이라고 생각한다.. 해답을 찾아 구글을 돌아다니다가 이해 못할 어려운 코드와 설명을 만나면 자격지심을 가지지 말고 살포시 무시하는 것도 정신건강에 좋다. 

 

  그럼 파이썬이라는 언어를 어떻게 바라보는냐 하는 부분에 대한 설명을 마치고, 다음 시간에는 강의를 위해 파이썬의 특정 버전과 sql 등을 선택하게된 사유를 설명하고, 실제 파이썬을 설치하고 위의 각 언어 구조들의 샘플 파일들을 실제 시연 하는 시간을 가지려고 한다.

 

 

 

2017.2.5 by 자유로운설탕
cs

 

 

 

 

 

posted by 자유로운설탕
prev 1 next