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

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

'프로그래밍'에 해당되는 글 25

  1. 2022.09.04 [책소개] 버그 정글을 헤쳐 가기 위한 테스터 지침서
  2. 2019.02.04 구글로 공부하는 파이썬 - 부록 (IIS, Apache 로 Flask 돌리기)
  3. 2018.03.02 [책 출간 안내] 구글로 공부하는 파이썬25
  4. 2017.08.19 구글로 공부하는 파이썬 - 21교시 (정리 - 이런저런 이야기)
  5. 2017.08.06 구글로 공부하는 파이썬 - 20교시 (장고 - Django 살펴보기)
  6. 2017.07.20 구글로 공부하는 파이썬 - 19교시 (Flask 살펴보기 - Feat. D3.js)3
  7. 2017.07.02 구글로 공부하는 파이썬 - 18교시 (Legacy Web)
  8. 2017.06.18 구글로 공부하는 파이썬 - 17교시 (머신러닝에서의 파이썬의 역활)
  9. 2017.05.21 구글로 공부하는 파이썬 - 16교시 (그래픽 라이브러리 살펴보기)
  10. 2017.05.06 구글로 공부하는 파이썬 - 15교시 (수학 라이브러리 살펴보기)
  11. 2017.04.19 구글로 공부하는 파이썬 - 14교시 (작업 자동화)
  12. 2017.04.07 구글로 공부하는 파이썬 - 13교시 (윈도우즈 GUI 자동화 with pywinauto)12
  13. 2017.03.26 구글로 공부하는 파이썬 - 12교시 (웹자동화 with Selenium)6
  14. 2017.03.19 구글로 공부하는 파이썬 - 11교시 (웹페이지 파싱 with Beautifulsoup)2
  15. 2017.03.11 구글로 공부하는 파이썬 - 10교시 (Whois API 사용해 보기)2
  16. 2017.03.05 구글로 공부하는 파이썬 - 9교시 (GUI 프로그램 만들어보기)
  17. 2017.03.04 구글로 공부하는 파이썬 - 8교시 (정규표현식 소개)
  18. 2017.03.01 구글로 공부하는 파이썬 - 7교시 부록 (함수로 정리하기)3
  19. 2017.02.26 구글로 공부하는 파이썬 - 7교시 (엑셀 다뤄보기)3
  20. 2017.02.25 구글로 공부하는 파이썬 - 6교시 (해결된 조각 합치기)
  21. 2017.02.23 구글로 공부하는 파이썬 - 5교시 (암호화 모듈 사용)8
  22. 2017.02.18 구글로 공부하는 파이썬 - 4교시 (데이터 베이스 연결)5
  23. 2017.02.13 구글로 공부하는 파이썬 - 3교시 (문제 나누기)
  24. 2017.02.08 구글로 공부하는 파이썬 - 2교시 (버전 선택 및 샘플 실행)
  25. 2017.01.30 구글로 공부하는 파이썬 - 1교시 (언어를 바라보는 방법)4
2022. 9. 4. 19:54 프로그래밍

지인이 새로 책을 냈다고 그래서, 관리가 안되 방문이 드문 블로그 이긴 하지만 간단히 책 소개를 하려고 한다.

예전에 테스트를 직업으로 가졌던 시절만 하더라도(벌써 10년이 조금 더  넘은듯 하다), 임베디드와 관련된 테스트를 하게 되는 것은 일반적으로 경험하기 힘든 영역이였던 것 같은데, 요즘은 모바일 기반으로 중계되는 여러 현실 비즈니스 및 퀵보드 같은 사물 인터넷 기반의 사업 때문에, 소프트웨어 자체가 아닌 연결된 사물의 특성 및 주어진 환경과 같이 테스트 설계 적으로 고려해야 할 부분들이 더 많이 늘어난 것 같다. 추가로 IT쪽의 핫한 추세였던(요즘은 기업들도 몇년 전처럼 맹목적인것 같진 않지만) AI 및 자율주행 등과 같이 센서와 데이터를 통한 자동적인 규칙의 해석이라는 측면에서, 처음부터 요구사항에 의해 개발된 제품에 기반하여 이루어졌던 테스트 영역에 대해서 변화가 생긴 측면도 있는 것 같다.

 

그 하나하나의 주제에 대해서는 각각의 산업들의 히스토리 및 자체 기술이 있고, 해당 기술적인 측면은 업무적 경험과 공부를 통해 하나하나 쌓아갈 수 밖에 없는 것 같긴하지만, 여러 산업 분야에서 다양한 측면으로 경험했던 저자의 배경을 기반으로 전체적인 테스팅 쪽 산업의 모습을 정리해 주는 것에 이 책의 강점이 있는 것 같다. 이미 QA 팀이 오래 동안 성숙되온 커다란 회사는 나름의 철학 등을 가지고 있겠지만, 아마도 그 연혁이 짧거나 겸업의 개념으로 그 직무를 유지해온 회사들은 여전히 여러가지 오해 속에서 헤메면서 QA와 테스팅 업무를 수행하고 있을 것 같다. 이 책은 그렇게 헤메는 사람들에게 업계 전체의 표준과 객관적인 시각으로 자신의 위치를 바라보게 하는 참고 자료가 될 수 있을 것 같다. 개인적으로 개발이나 보안이나, 테스팅 모두 같은 기반을 가진 관점만 다른 분야이긴 것 같긴 하지만 말이다.

 

책을 읽으면서 프로세스와 기법적으로 해석 되어지는 테스팅 전반의 사전적 지식들을 기존의 실무적 경험과 당위성을 기반으로 쉽게 엮어 설명하려고 하는 저자의 노력을 엿볼 수 있었으며, 프로젝트 관리 경험과 컨설팅 경험이 풍부한 저자의 관점에서 테스팅 산업 전반에 대한 숲을 관찰할 수 있게 해준다. 테스팅에 대한 의미와 경향, 버그 및 품질에 대한 이해, 테스팅 계획 및 프로세스, 케이스 작성, 수행, 결과보고, 결함의 원인에 대한 고찰, 팀에 대한 관리, 여러 테스팅 업무 포지션에 대한 현실적으로 주어진 환경 및 장 단점을 이 책을 통해서 경험해 볼수 있다.

 

다만 한가지 실제 개발 코드 예제에 기반하여, 세부적인 다양한 테스팅 설계에 대한 예제 및 자동화 예제가 있다면 좀 더 좋았겠다는 생각이 들지만, 해당 부분은 현재 책과는 다른 방향의 영역이며, 현재도 국, 내외에서 그런 종류의 지식을 디테일하게 설명하는 테스팅 관련 책을 찾아보기는 어렵다 생각하므로, 저자의 다음 책의 서브 주제로 확장되어 발간되기를 기대하는 바램을 가지며 소개를 마쳐본다. 

posted by 자유로운설탕
2019. 2. 4. 22:31 프로그래밍

  보안글이 어느정도 정리되면(아직 갈길은 좀 멀어보이긴 하지만--;;) 그 동안에 개인적으로 업데이트된 부분들을 기반으로 파이썬 부록 편을 몇 개 작성하고 싶었는데, 마침 그 부분 중 하나에 대해서 콕 찝어 문의해 주신 "독자(닉네임)"님이 있으셔서 간단하게 정리를 해보려고 한다. 물론 구글을 잘 찾으면 있긴 한 내용이지만, 아무래도 처음 하게되면 개념도 잘 안잡히고, 버전이 달라서 헤멨었던 경험이 있어서 한번 더 모아 정리하는 것도 웹 공해는 안될 것 같았기 때문이다. 기본적으로 책 기준인 파이썬 3.6.4 에 IIS 7.5(윈도우즈 10 기준), Apache 2.4 를 기준으로 세팅해 보려고 한다.

 

 

 

 

1. 들어가면서

  플라스크(flask)던 장고(django)든 굳이 IIS, Apache 같은 웹서버와 연결해야 하는 이유는 예제에서 보였던 CMD 창에서 띄우는 방식은 CMD 창이 닫혀 버리면 웹사이트 자체로 사라져 버린 다는 것 때문이다. 특히 사용자가 로그 아웃을 하게 되면 모든 CMD 창은 사라지기 때문에, 다른 사람이 해당 페이지를 원격에서 접속해서 지속적으로 사용할 수 없게 된다. 물론 서비스 형태로 등록해 본다던가 하는 꽁수는 있을 것 같긴한데, 아무래도 정상적인 절차는 예전 플라스크 시간에 얘기했던 WSGI(Web Server Gateway Interface) 방식을 사용해, 사용자 요청은 안정적인 웹 서버에서 전담하고, 로직 처리만 WSGI 를 통해서 파이썬으로 전달해 처리하여 다시 웹 서버를 통해서 사용자 브라우저에 전달해 주는 방식일 것이다. 윈도우즈 쪽에서는 Fast-CGI 라고 보통 얘기하는 듯하고, 아파치 쪽에서는 WSGI 라고 얘기하는 거 같은데, 뭐 두가지 사이에 설계적 차이는 있다고는 하는데, 딱히 선택의 여지도 없으니까 무시해도 될 갭인 것은 같다.

 

[불곰님 블로그 - WSGI, WAS, GgI 이해]

https://brownbears.tistory.com/350

 

 

 

 

2. Windows 10 + IIS 설정 하기

 기본적으로 구글에 "windows 10 IIS flask" 라고 검색하면 몇 개의 링크가 나오는데, 몇 몇 시행착오를 겪어본 결과 가장 현재 시점에 맞춰 간략히 잘 정리된 페이지는 아래의 링크들 같다. 이 블로그의 글은 해당 글들을 여기서 연재했던 샘플에 맞춰 좀더 쉽게 풀어 정리한 버전이라고 봐도 될듯 하다.

 

[github - Python Flask on IIS with wfastcgi]

https://gist.github.com/bparaj/ac8dd5c35a15a7633a268e668f4d2c94 

 

[Medium.com - Deploying Python web app (Flask) in Windows Server (IIS) using FastCGI]

https://medium.com/@bilalbayasut/deploying-python-web-app-flask-in-windows-server-iis-using-fastcgi-6c1873ae0ad8

 

  간단하게 흐름을 설명하자면, 예전 설명글들을 보다보면, 여러가지 IIS  환경 변수를 직접 설정해야했는데(사실 좀 이런 오래된 리눅스 스타일의 하나하나 설정하는 작업은 요즘엔 좀 번거롭게 느껴지는 일 같기는 하다), wfastcgi 는 모듈이 점점 업데이트 되면서 wfastcgi-enable.exe 파일을 같이 제공해서, 해당 작업들을 한꺼번에 자동으로 해준다. 이 후 우리는 적당히 웹서버를 하나 만들고(디폴트 웹서버를 써도 되고), 웹서버에 대한 설정 파일인 web.config 에 연결된 fastcgi 환경과 연결된 파이썬 게이트웨이 파일을 정의하는 작업을 하면 된다. 여기서는 19교시에서 진행한 DB에서 가져온 테이블을 보여주는 "myweb.py" 파일을 기준으로 설명한다.

 

 

2.1 IIS 설치하기

  18교시 레거시웹 편을 참조하여 IIS 를 설치하고 기본적인 동작을 확인 한다. 추가로 하나 더 해야 할 일이 있는데, "Windows 기능 켜기/끄기" 에서 "인터넷 정보 서비스" > "World Wide Web 서비스" > "응용 프로그램 개발 기능" > "CGI" 를 추가로 선택해 설치해 줘야 한다(그래야 fastcgi 기능을 쓸수 있다).

 

 

2.2 관리자 권한으로 cmd 창 실행

  뒤의 설정 파일 때문에 관리자 권한으로 cmd 창을 실행해야 된다. 아니면 권한 에러가 난다.

 

 

2.3 wfastcgi 설치

C:\Python\code>pip install wfastcgi
Collecting wfastcgi
...
Successfully installed wfastcgi-3.0.0

 

 

2.4 설정 자동화 파일 실행 하기

C:\Python\code>wfastcgi-enable
구성 커밋 경로 "MACHINE/WEBROOT/APPHOST"에서 "MACHINE/WEBROOT/APPHOST"의 "system.webServer/fastCgi" 섹션에 구성 변경을 적용했습니다.
"c:\python\python.exe|c:\python\lib\site-packages\wfastcgi.py" can now be used as a FastCGI script processor

 

  이렇게 설정하면 다른 설명 글들에서 얘기하는 여러가지 IIS fastcgi 설정이 자동으로 된다. 

 

 

2.5 IIS 루트 폴더내에 파일 복사하기

  19교시에서 예전에 예제로 만들었던 SQL 서버에서 데이터를 가져와 HTML 테이블로 표시하던 myweb.py 파일과 templetes 폴더안에 있는 myweb.html 파일을 IIS 루트 디렉토리인 c:\inetpub\wwwroot 에 복사한다(관리자 권한으로 해야된다). 아래와 같은 구조가 될것 이다.

1
2
3
4
5
wwwroot/
    myweb.py
    templetes/
        myweb.html
        web.config
cs

 

 

2.6 web.config 수정하기

  이제 IIS 의 사이트별 config 파일인 web.config 파일을 아래와 같이 수정하자. 관리자 권한이 있어야 수정되기 때문에 메모장을 관리자 권한으로 실행해서 해당 파일을 읽어 들여 수정해야 한다.

 

 대충 내용을 보면, 사용자 요청을 처리할 핸들러를 정의하고(<handler> 섹션), 핸들러 메인 파일이나, 루트 패스, 로그 위치 등(<appSettings> 섹션)을 설정한다. WSGI_LOG 폴더를 실습용 폴더인 c:\python\code\ 안으로 잡은거는 기본 로그 폴더인 c:\inetpub\logs 폴더가 기본적으로 IIS 사용자 권한으로 쓰기가 안되서 권한 있는 폴더로 잡은 것이다(좀 이상한건 IIS 로그 폴던데 원래 이런건진 모르겠다--;). 만약 logs 폴더를 사용하고 싶다면 해당 폴더의 속성의 보안 권한에서 IIS_IUSRS 사용자에게 쓰기 권한을 주면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <system.webServer>
        <handlers>
            <remove name="Python27_via_FastCGI" />
            <remove name="Python34_via_FastCGI" />
            <add name="Python FastCGI" path="*" verb="*" modules="FastCgiModule" scriptProcessor="C:\Python\python.exe|C:\Python\Lib\site-packages\wfastcgi.py" resourceType="Unspecified" requireAccess="Script" />
        </handlers>
        <httpErrors errorMode="Detailed" />
    </system.webServer>
 
    <appSettings>
        <!-- Required settings -->
        <add key="WSGI_HANDLER" value="myweb.app" />
        <add key="PYTHONPATH" value="C:\inetpub\wwwroot" />
        <add key="WSGI_LOG" value="C:\python\code\my_app.log" />
    </appSettings>
</configuration>
cs

 

 

2.7 기능 위임 부분 수정 하기

  아마도 이 오류는 windows 10 타입(Home, Pro 나 정책 설정?)에 따라서 다른거 같긴한데, 현재 실습 환경인 Windows 10 Home 에서는 위와 같이 모든 설정을 다하고 "http://localhost/sqltable" 을 실행하게 되면, 아래와 같은 에러가 발생한다.

 

HTTP 오류 500.19 - Internal Server Error
요청된 페이지와 관련된 구성 데이터가 잘못되어 해당 페이지에 액세스할 수 없습니다.

이 구성 섹션은 이 경로에서 사용할 수 없습니다. 섹션이 부모 수준에서 잠겨 있는 경우 이 오류가 발생합니다.

 

  구글을 찾아보면 c:\windows\system32\inetsrv\config\applicationHost.config 에서 핸들러의 겹쳐쓰기(overide) 모드를 allow 로 바꾸라던데, 막상 저 폴더에 가면 해당 파일이 없어 좀 당황을 했다. 조금 더 다른 문서를 찾다보니 이제는 저 파일을 바로 수정하려 할 수는 없고 관리화면에 기능 위임 부분을 수정하라길래, IIS 관리 모듈로 들어가 보니 아래와 같이 왼쪽 트리에서 가장 위의 항목을 선택하면 오른쪽의 관리 섹션에 기능 위임 아이콘이 있다.

 

  해당 아이콘을 더블 클릭해 들어간 후, 아래와 같이 처리기 매핑 항목을 "읽기" 에서 "읽기/쓰기" 로 바꾸어 준다.

 

 

2.8 샘플 실행해 보기

  이제 설정이 완료된 듯 하니 "http://localhost/sqltable" 이라고 치면 아래와 같이 IIS 환경을 통해서 flask 페이지가 나오게 된다(첨엔 많이 신기했음;).

 

 

 

 

3. Windows 10 + Apache 설정하기

  다음으로는 아파치에 설정하는 부분을 똑같이 해보려 한다. 아파치 설정하는 부분은 다른 시리즈 글인 "구글로 공부하는 보안 3교시" 뒤쪽을 보면 PHP 설정하는 곳에 놓여있다. 거길 그대로 따라해서 "It Works!" 샘플페이지 까지 봤다고 가정하고 연결해서 쓴다(PHP 설정도 그대로 해도 된다. 단 현재 설정을 config 파일에 하게되면 php 해석기는 안 돌아가긴 한다)

 

  구글을 찾아보니 아래 2개의 사이트를 주로 참조하면 될듯 하다.

 

[ZEIZ님 블로그 - Windows 10 64bit에서 Apache 64bit에 Flask with Python 3.6 배포]

https://zeiz.com/post/158725706804/windows-10-64bit%EC%97%90%EC%84%9C-apache-64bit%EC%97%90-flask-with

 

[STOREHUBS 사이트]

https://www.storehubs.com/Blog/deploy-python-flask-application-apache-windows-server/

 

 

3.1 mode_wsgi 모듈 설치하기

C:\Python\code>pip install mod_wsgi
Installing collected packages: mod-wsgi

 

  다른 환경에서는 VS 컴파일러 에러가 나서 wheel 파일을 설치하기도 한거 같은데, 아마 실습을 쭉 해왔다면 이미 깔려있어서 괜찮은 것 같다. wheel 파일을 설치해도 되고, VS 컴파일러를 설치해도 되는거 같다. 뭐 일단 잘 되니 패스.

 

 

3.2 mod_wsgi.cp36-win_amd64.pyd 파일을 복사

  mod_wsgi 모듈을 설치하게 되면 파이썬의 모듈 폴더안에 c:\Python\Lib\site-packages\mod_wsgi\server\mod_wsgi.cp36-win_amd64.pyd 파일이 설치되는데, 이게 IIS 에 있던 wfastcgi.py 와 비슷한 역할을 한다고 보면 될듯 하다. 뭐 참조 사이트에 있는 것처럼 직접 해당 경로를 지정하도 되지만 경로가 조금더 단순해 설정이 쉬워 보이길 바라는 맘에서 mod_wsgi.cp36-win_amd64.pyd 파일을 c:\Apache24\modules 에 복사한다. 그 안을 보면 다 .so 확장자 파일만 있는데, 예전엔 mod_wsgi 도 .so 확장자로 지원했다가 요즘은 .pyd 확장자로 지원하는 거 같다(요 부분도 좀 그래서 구글을 참조하다보면 헷깔린다)

 

 

3.3 파이썬 용 폴더 만들고 파일 복사하기

  IIS 와 비슷하게 기존에 세팅한 httpd 폴더에 설정해도 될듯 하나, php 도 나중에 다시 써야하고, flask 웹서비스용 폴더를 한번 분리해 보고 싶어서 c:\test 폴더를 만들고, IIS 때와 마찬가지로 myweb.py 파일과 templetes 폴더안에 있는 myweb.html 파일을 복사한다. 아래와 같은 구조가 될것 이다.

1
2
3
4
test/
    myweb.py
    templetes/
        myweb.html
cs

 

 

 

3.4 WSGI 게이트웨이 파일 호출 하기

  mod_wsgi 설계 구조상 IIS 처럼 해당 파일을 직접 매칭 시키지는 못하는 것 같고, 중계하는 게이트웨이 파일을 하나 만들어야 한다. 설명에는 .wsgi 확장자로 만들긴 하지만 .py 로 만들어도 괜찮길래 "gateway.py" 란 파일로 c:\test 폴더에 만들어 본다. 내용을 보면 application 이란 호칭으로 myweb.py 파일 안에 있는 app 라는 이름의 플라스크 객체를 가져온다. 

1
2
3
4
import sys
 
sys.path.append('c:/test/')
from myweb import app as application
cs

[gateway,py]

 

  폴더 구조를 보면 아래와 같이 하나의 파일이 추가되었다.

1
2
3
4
5
test/
    gateway.py
    myweb.py
    templetes/
        myweb.html
cs

 

 

3.5 config 파일 수정하기

  이제 IIS 에서 햇던 것처럼 이것저것을 수정해야 되는데 결론적으로 아파치 설정 파일인 httpd.config 파일만 수정하면 된다. 해당 파일을 열어 맨 아래에 아래 값들을 추가한다. 여러 글들에 보면 글마다 옵션이 좀 다른데, 하나씩 빼보면서 샘플에서 에러가 안나는 최소 값으로 추려봤다.

 

<httpd.config 에 추가>

# 사용하려는 test 폴더에 모두 허용하는 것으로 설정한다(운영에서는 뭔가 고민할 부분이 있을 수도)

<Directory "c:/test/">
    Require all granted
</Directory>

 

# 복사했던 모듈 파일을 읽어오고

LoadModule wsgi_module modules/mod_wsgi.cp36-win_amd64.pyd
# flask 게이트웨이 파일 위치를 알려준다

WSGIScriptAlias / "c:/test/gateway.py"

 

 

3.6 아파치 재시작

  아파치를 재 시작해야 config 파일이 반영되므로 예전 글에서 설명한 트레이 아이콘 이용해 재시작한다. 뭔가 수정이 잘못됬음 재시작시 에러가 날텐데 해당 경우는 c:\Apache24\logs\error.log 로그를 보고 원인을 해결해야 한다.

 

 

3.7 동작 확인해보기

  "http://localhost:9999/sqltable" 를 호출해보면 IIS 때와 같은 테이블 결과를 볼 수 있다.

 

 

 

 

4. 에러 추적 및 웹서버 구성 시 차이점

  마지막으로 WSGI 방식으로 서버를 구성하면서 CMD 창에서는 잘 돌아가던 코드가 안돌아갔거나 구성 설계를 바꾸거나 해야될 필요가 있던 부분에 대해서 얘기하려 한다.

 

 

4.1 에러 추적

  저렇게 연결했을때 제일 속터지는 상황이, CMD 창에서는 잘 실행이 되는데 fastcgi나 mod_wsgi 를 연결하게 되면 에러가 날때 이다. 에러 원인을 찾아 해결하는 것도 힘든 일이지만 처음 만나게 되면 일단 화면에서 아래와 같이 에러의 상세 내용 없는 500 대 화면만 보게 되기 때문이다. 

Internal Server Error

The server encountered an internal error or misconfiguration and was unable to complete your request.

 

  아파치 같은 경우는 앞에서 얘기한 c:\Apache24\logs\error.log 를 보게 되면 우리가 CMD 창에서 봤던 에러를 똑같이 볼 수 있는 것 같고, IIS 의 경우는 따로 남기는 에러는 없는 것 같아(아시면 댓글 좀_ _;), 아래의 스택오버플로우 글에서 제시한 코드를  app = Flask(__name__) 뒤에 추가해서 500에러 메시지를 보이게 하는게 현재까지로는 제일 난 것 같다.

 

[스택 오버플로 - python flask login form throws 500 error on iis]

https://stackoverflow.com/questions/31804800/python-flask-login-form-throws-500-error-on-iis

1
2
3
4
5
6
7
8
9
10
11
12
app = Flask(__name__)
 
@app.errorhandler(500)
def internal_error(exception):
    app.logger.exception(exception)
    file_handler = RotatingFileHandler('C:\inetpub\wwwroot\logs.log''a'1 * 1024 * 102410)
    file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'))
    app.logger.setLevel(logging.INFO)
    file_handler.setLevel(logging.INFO)
    app.logger.addHandler(file_handler)
    app.logger.info('microblog startup')
    return render_template('500.html'), 500
cs

 

 

  해당 코드를 넣으면 아래와 같이 나름 상세한 에러가 브라우저 화면에 보이게 된다.

Error occurred: Traceback (most recent call last): File "c:\python\lib\site-packages\flask\app.py", line 2292, ..... , in make_response 'The view function did not return a valid response.

 

 

4.2 CMD 개발과 웹서버 연결시 차이점.

  불행하게도 잘 돌아가던 flask 코드를 IIS 나 Apache 와 연결하게 되면 에러가 발생하는 경우가 종종 생기는 것 같다. 개인적으로 기억 나는 일은 SQLite 를 사용하는 경우와 flask-excel 이라는 엑셀 다운로드 모듈을 사용하는 경우였다. SQLite는 IIS 에서 돌리니 기본적으로 웹서버 계정에 로컬 파일을 쓰는 권한이 없어서 에러가 났고, flask-excel 은 excel 초기화 함수가 if __name__ == "__main__": 안에 정의해 놓으니 WSGI 모듈이 해당 코드를 무시해 버려서 초기화 작업이 안되서 에러가 나게 됬다.

 

  Flask 로 샘플 페이지를 만들다 보면, 웹프로그램이라는걸 깜빡 잊고 일반 파이썬 어플리케이션 처럼 파일도 읽고 쓰면서 개발하게 될 경우가 많은데, 막상 웹 서버로 배포하려 하다보면, 기본적으로 로컬에 있는 파일을 왠만하면 접근 못하도록 막아놓은 웹 서버 설계를 무시하고(보통 보안적으로 이렇게 되는 부분이 안전하기도 하다) 로컬 파일을 읽고 쓰는 방식으로 만드는 게 맞나 하는 생각이 들게 된다. 그래서 SQLite 쓰는 부분을 DB 서버 자체가 권한을 관리하는 일반 SQL 로 변경하거나 하는 일도 필요할 수 있다. 이게 좀 자유롭게 취향대로 쓸 수 있는 플라스크의 조심스러운 측면인것도 같긴하다.

 

  뭐 이런 연결방식에 따른 코드나 설계의 차이는 시행착오를 겪으면서 하나하나 다른 사람들의 경험에도 도움을 받으면서 해결 할 수 밖에는 없을 듯 싶긴하다.

 

 

 

 

5. 마치면서

  뭔가 간략히 정리하려 했는데, 이왕 정리하는거 하다보니 꽤 긴 글이 되긴 했다. IIS 와 아파치 설정 부분이 다른 글에 이미 정리되어 있어서 그나마 다행이라고 생각되는 상황이였다. --; 아마 Flask, WSGI, IIS, Apache 버전이 서로 계속 변하면서 또 상황은 바뀔 수도 있겠지만, 여기서 설명한 개념만 잘 잡고 있다면 그리 많이 변하는 건 없을 거 같긴하다. 그럼 이렇게 파이썬 게릴라 포스팅을 마치려고 한다~

 

 

2019.2.4 by 자유로운설탕
cs

 

 

 

 

 

posted by 자유로운설탕
2018. 3. 2. 23:37 프로그래밍

   안녕하세요. 블로그에 연재했던 "구글로 공부하는 파이썬" 글이 출간 제의를 받아 책으로 만들어 졌습니다.

 

  책의 경우 조금은 장점을 더해야 할 듯 해서, 아래의 내용들이 추가 및 수정 되었습니다.

  • 현재 최신 버전인 파이썬 3.6.4 로 업데이트 하여 진행(5월인가 3.7 정식 버전이 나오는 것으로 알고 있습니다. 그때까지는 일단 최신이겠네요;)
  • 작년말 업데이트 된 장고 2.0 으로 진행(장고 1.11 과 비교하면 라우팅 부분의 코드가 조금 변경되었습니다)
  • 4장 SQL 부분에 MySQL, 몽고디비, 오라클, Sqlite3 설치 및 세팅, 조회 예제 추가
  • 각 장의 뒤에 예제에 나온 문법 요소들을 설명하는 미니문법 섹션 추가
  • 3.6 버전 pymssql 한글 깨짐 현상에 의해 pyodbc 사용 예제로 변경
  • 자잘한 예제 및 구성 변경(Plotly 오프라인 예제, 작업 자동화 예제 간략화, d3.js 에러에 대한 장고 어플리케이션 측면에서의 해결 코드 추가 등)
  • 3가지 무료 편집기(PyCharm, Visual Studio Code, Atom) 설치 및 예제 실행을 위한 세팅 방법 부록으로 추가
  • 모호한 내용을 다듬거나, 링크로 소개했던 부분을 내용으로 구성하여 업데이트 함(잘못 설명한 내용들은 블로그에도 곧 업데이트 할 예정 입니다)

 

   계약 관계로 책의 내용을 블로그에 업데이트 하진 못하지만, 답변에는 제약이 없도록 출판사 쪽에 양해를 얻은 상태 입니다. 해당 부분 이해해 주시길 바라며, 혹시 블로그를 참조해서 진행하시다가 막히시는 경우 언제라도 댓글로 문의해 주세요^^.

 

   그럼 이런 글을 올리는게 좀 민망스럽긴 하지만, 모쪼록 이해 부탁 드립니다.

 

 

posted by 자유로운설탕
2017. 8. 19. 23:28 프로그래밍

  이제 개인적으로 소프트웨어를 바라 보는 관점에 영향을 주었던 글을 하나 소개하면서, 구글로 공부하는 파이썬 시리즈를 마무리 하려 한다.

 

 

[목차]

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. 정리 - 이런저런 이야기

 

 

[추가]

현재 모바일에서 코드(colorscript 이용)가 나온 후에 글 색깔이 흐려져서 읽기가 어렵네요. 티스토리 개편하고 현재 스킨이 너무 예전것이라서 그런가도 싶긴한데, 수정이 쉽진 않을것 같아서 일단 웹에서는 정상적으로 보이니 웹을 이용해 주세요. 조만간 수정해 보도록 하겠습니다.

감사합니다.

 

 

[The five orders of ignorance]

  저도 몇년전에 누군가에게 소개 받았던 글이긴 하지만, 'Communications of the ACM' 저널에 실렸던 'The five orders of ignorance(무지의 5계층)' 라는 소프트웨어 개발에 대한 글을 소개하려 한다(아래 URL 안의 링크를 클릭하면 pdf 로 된 전체 글을 다운받을 수 있다).

http://www.corvusintl.com/CACM002-5OI.htm

 

  약간의 개인적인 해석을 추가해 요약하자면 아래와 같다.

 

  인류가 가지고 있는 지식을 저장하는 매체는 DNA, 뇌, 하드웨어, 책을 거쳐 현재는 소프트웨어라는 형태가 생겨나게 되었다. 소프트웨어는 뇌처럼 공간이 제한되어 있거나, 쉽게 변하지 않고, 책처럼 수동적이지도 않으며(뭐 읽는 사람에게 감성과 상상력을 일으킨다는 측면에서는 그렇지 않을지도 모르지만...), 하드웨어나, DNA 처럼 느린 변화를 가지거나, 유연성이 부족하지도 않다.

 

  우리가 소프트웨어 제품을 만든다는 것은, 특정한 제품 자체를 만드는것이 아닌 우리가 알고 있는 지식들을 저장하는 매체를 구현하는 행위라는 것이다. 그래서 소프트웨어의 잘 알려진 지식들은 프레임워크나, IDE 를 통해서 자동으로 코드를 만들어 낼 수 있는 반면에, 다른 특정한 부분들은, 직접 해당 지식을 획득하여 구현해 내야 한다.

 

  예를 들어 현재의 파이썬 시리즈를 다 읽은 시점에서, 새로운 파이썬 프로그램을 만든다고 해보자. 그 중에 어떤 부분들은 우리가 이미 알고 있는 지식을 재사용하여 쉽게 구현할 수 있으며, 만약 새로운 프로그램의 주요한 부분들이 기존에 지식들과 유사하다면, 정말 쉽게 짧은 시간에 새로운 프로그램을 만들 수 있을 것이다. 그런데 만약에 우리가 현재 다루지 않았던 기능을 가진 프로그램을 만들려 한다면 해당 분야에 대한 지식을 추가로 획득해야 한다. 추가적인 지식이 필요한 경우인 경우라도 다른 유사한 프로그래밍 경험을 통해서, 해당 지식의 영역(예를 들어 쓰레드 구현)을 경험해 본적이 있다면 프로그래밍 언어의 차이점만을 잘 극복한다면 백지에서 시작하는 것보다 좀더 쉽게 원하는 지식을 획득할 수 있을 것이다. 아마도 최악의 경우는 어떻게 해당 부분을 해결해야 할지에 대한 방법이 전혀 감이 잡히지 않는 경우일 것이다.

 

  이런 관점에서 볼때 소프트웨어 개발은 지식을 획득하는 활동인 동시에, '무지(ignorance)' 를 줄이거나 제거하는 활동으로 바라볼 수 있다. 이 글에서는 이러한 측면에서 무지의 계층을 5단계로 나누어 제시한다.

  • 0 계층 - 무지의 결핍(Lack of Ignorance) : 무언가를 오랬동안 경험해 왔기 때문에 그것에 대해 잘 아는 상태이다. 오랫동안 보트를 타봤다면 어느 정도 항해에 대해서 잘 안다고 얘기할 수 있는 것처럼 말이다.
  • 1 계층 - 지식의 결핍(Lack of Knowledge) : 무언가를 모른다는 것을 정확히 알고 있는 상태이다. 예를 들어 러시아어를 못한다는 것을 안다면, 러시아어를 공부하기 위해 학원을 다니거나 책을 봐야한다는 것을 명확히 알수 있다.
  • 2 계층 - 의식의 결핍(Lack of Awareness) : 무엇을 모른다는 것 자체를 인지하지 못하고 있는 상태이다. 무엇을 모르는지 자체를 모르기 때문에, 무엇을 해야 하는지도 모르는 상태 이다.
  • 3 계층 - 프로세스의 결핍(Lack of Process) : 인지하지 못하는 무지를 알수 있게 해주는 수단 자체가 없는 상태를 얘기한다. 그것은 특정한 프로세스의 부재 일수도 있고, 특정한 환경의 부재일 수도 있다.
  • 4 계층 - 약간 유머 같긴 하지만, 무지의 5계층 자체를 모르는 단계라고 한다. 이 글을 읽게 되면 이 단계에서 벗어나는 셈이라고 한다.

 

  개인적으로는 이 글에 공감이 가서 소프트웨어를 바라보는 관점이 많이 바뀌게 되었고 설명은 하기 어렵지만 왠지 좀더 대상을 편하게 바라보게 되었다. 소프트웨어에 대한 여러 다양한 기술 업무들이 비슷하게 무지를 줄이고, 숨어있는 무지들을 찾아내는 활동이라는 생각이 들었으며, 결국 소프트웨어에 담겨있는 내용은 지식이기 때문에, 소프트웨어이외의 다른 분야들의 좋은 접근 방법이나 개념들도 무형적 지식이라는 측면에서 비슷하게 연결이 되는게 아닐까하는 생각도 들었다. 스타크래프트의 'Black Sheep Wall' 처럼 무지의 영역을 한순간에 밝혀주는 마법의 치트키는 없겠지만, 자신이 모르는 필요한 부분들을 계속 발견하고 해당 영역들을 채우는 것이, 소프트웨어에 관한 직업을 가져가면서 스트레스를 덜 받는 방법이 아닌가 싶다.

 

 

 

 

[마무리 하면서]

  이 글의 목적은 읽는 사람을 파이썬에 대해서 어느 수준까지 끌어가는 것은 아니다(개인적으로 그럴만한 능력도 안된다고 생각하고...). 오히려 프로그래밍이란 생각보다 모호한 일이며, 다른 사람들이 미리 만들어 놓은 수많은 지식과 접근법들을 조합하여 문제를 해결하는, 정답이 여러개이며 완벽한 정답은 없는 선택적 영역이라는 것을 얘기하고 싶었다. 또한 코드를 만드는 과정에서 무언가가 막혔을때, 검색 엔진을 이용해 문제를 찾아서 해결하는 과정에서 일어나는 '헤멤'이라는 활동을 실제로 보여주고 싶기도 했다(뭐 다른 사람은 다른 방식으로 헤멜 수는 있을 것 같다). 그리고 조금 욕심을 내자면 각 주제 영역에 대해서 스스로 공부할 수 있게, 조금 먼저 길을 걸어본 입장에서 해당 지식들에 대한 접근 방법을 안내를 하고 싶었다. 얼마나 해당 부분을 충족 시켰는지 자신은 없지만, 목표로 했던 이야기들은 다 한 것 같아서 여기서 일단 글을 멈추려고 한다. 다들 너무 부담 갖지는 말고 천천히, 하지만 꾸준히 공부 하시기를 바란다.

 

 

 

 

2017.8.20 by 자유로운설탕
cs

 

  

 

 

posted by 자유로운설탕
2017. 8. 6. 00:30 프로그래밍

  이번 시간에는 앞 시간의 플라스크(flask) 시간에 이어서, 파이썬을 대표하는 웹 프레임워크로 알려져 있는 장고(Django) 를 살펴보는 시간을 가지려고 한다. 플라스크를 살펴볼 때와 비슷한 방식으로 공식 메뉴얼을 기준으로 전체적인 장고의 구조에 대해서 살펴보고, 플라스크에서 구현했던 2개의 예제(MSSQL 테이블 표시, d3.js 그래프 그리기)를 장고 환경에서 똑같이 구현하는 과정을 보여 줌으로서, 플라스크와는 어떤 다른 측면들이 있는지를 설명하려고 한다. 

 

 

 

[목차]

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. 정리 - 이런저런 이야기

 

 

 

 

[들어가면서]

  우선 시작하기 전에 혹시 19교시의 플라스크(flask)에 대해서 보지 않은 상태라면, 먼저 해당 내용을 보고 오기를 바란다. 장고를 살펴본 결과 플라스크와 대비되는 부분을 짚어가며 설명하면 효율적이라는 생각이 들었다. 플라스크에서 개념을 설명했던 url routing(url 라우팅), static files(정적 파일), rendering templates(템플리트 랜더링)의 개념은 거의 동일 하게 사용되기 때문이다. 또 템플릿 소스도 일부만 수정해 그대로 사용할 계획이다. 이번 시간에는 ORM, MVT(MVC), ADMIN 등 장고에 특화된 개념만 추가로 설명할 계획이다. 개인적으로 플라스크를 살펴볼때 보다 2~3배 정도의 시간이 장고에 소요됬었고, 플라스크를 이해한 상태라면 장고도 비교적 접근하기 수월하다는 생각이 들었다.

 

 

[한글 문서 여부]

  역시 플라스크와 비슷하게, 구글을 찾다보면 아래의 한글 페이지가 있긴한데, 제목까지만 다 번역되어있고, 실제 내용은 튜토리얼 까지만 번역되어 있다. 다만 튜토리얼에 전체 구조를 이해시키는 핵심적인 얘기들이 많으므로, 튜토리얼을 한글로 보며 대충 돌아가는 상황을 파악후, 영문 문서들을 그담에 보길 권장한다.

https://django-document-korean.readthedocs.io/ko/master/

 

  또 다른 튜토리얼 레벨의 괜찮은 한글 문서는 구글에서 'django sample' 로 찾으면 나오는 아래의 장고걸 사이트 문서이다. 개인적으로 봤을때 체계적으로 잘 정리되어 있어 처음에 개념을 잡는데 도움이 됬었다. 파이썬 버전도 3.5 버전 기준이라 문법에 위화감도 없다.

https://tutorial.djangogirls.org/ko/django_start_project/

 

  그리고 마지막으로 영문 예제 사이트는 아래 사이트가 괜찮았다. 여기도 마찬가지로 3.5.x 버전이고, 위의 3개 사이트의 내용을 보면 대충 장고 사이트가 어떻게 돌아가는지에 대해 전체적으로 감이 잡히기 시작할 것이다.

https://scotch.io/tutorials/build-your-first-python-and-django-application

 

 

[Django Documentaton 보기]

  구글에서 'django document' 라고 검색하면 아래의 문서가 나오는데, 현재 최신이 1.11 버전(이게 '일점 일일' 버전이 아니라 '일점 십일' 버전이다. 첨에 이것땜에 구글을 검색하때 1.7, 1.8 같은 미래 버전이 왜 있지 하고 좀 헷깔렸다--;)의 공식 메뉴얼 페이지를 볼수 있다.

https://docs.djangoproject.com/en/1.11/

 

  'First steps' 안에 있는 Overview, Installation, Tutorial 에는 장고를 이용해 사이트를 세팅하는 것부터, 라우팅(URLconfs. 라고 보통 말한다)을 설정하는 법(urls.py), 모델을 만들어 해당 구조를 데이터베이스 및 어드민 기능과 싱크(migration) 시키는 법, 뷰와 템플릿을 사용하여 표시하고, 폼을 전송하여 받아 처리하고, 정적(static) 파일들을 설정하고 접근하는 법에 대한 전체적인 흐름을 보여준다.

 

  'The model layer' 에서는 모델을 상세하게 다루어, 모델에 대해 어떻게 클래스로 정의하고, 정의된 모델들을 지원하는 메쏘드들을 이용해 쿼리를 요청해 값을 가져오고(QuerySet), 데이터베이스와 모델을 어떻게 싱크시키고(Migrations), 모델을 벗어난 커스텀 쿼리를 데이터베이스에 어떻게 날리며(Raw SQL), 데이터베이스별로 모델을 적용하는데 필요한 여러가지 참고사항과 주의사항들을 얘기한다.

 

  'The view Layer' 에서는 어떻게 라우팅을 구성 하며(URLconfs), 어떻게 요청(request)을 하고받아서, 모델에 대한 검색을 지원하는 QuerySet 를 이용하여 결과를 가져와서, 어떻게 응답을 하는지(Requsest and response objects), 파일 업로드를 구현하는 법(File upload), 내장된 뷰(generic view)를 이용하는 방법(Built-in display view) 등을 다룬다.

 

  'The template layer' 에서는 템플릿에서 여러가지 장고에서 지원하는 템플릿 지원 로직들을 사용하여 표현하는 방법을 얘기한다(for 라든지 url 이라든지 여러 문법들을 사용하는 방법을 얘기하는데, 앞의 플라스크와 비슷하지만 조금 더 확장된 기능이라고 보면 될듯 하다) 
 

  이후 'Forms' 에서 폼을 넘기고 처리하는 부분을, 'The Admin' 에서 모델과 연관되어 자동으로 업데이트 되는 관리 페이지를 커스터 마이즈해 사용하는 부분을, 'Security' 에서 잘 알려진 보안 이슈들에 대응하는 설계를 구현하는 방법을, 'Common Web application tools' 에서 웹사이트를 개발하면서 주로 만나게 되는 세션, 캐싱, 스태틱 파일, 사이트맵 등등에 대한 구현을 지원하는 기능들을 얘기한다. 그 외의 섹션에서는 유니코드라든지, 로케일이라든지, 개발, 테스팅 방법이라든지 하는 여러 내용들을 다루고 있다.

 

 

  위에서 얘기했던 내용들을 이해한대로 그려보면 아래의 그림과 같다. 브라우저가 폼이나 API를 통해 요청을 하면, 장고의 웹 모듈이(WGSI - 메뉴얼에 이 기능은 테스트에만 사용하고 운영시에는 아파치 등을 연동해 쓰라고 명시되어 있다) 요청을 받아, 라우팅(URLConfs) 기능을 통해 해당되는 뷰의 콜백 함수에 전달한다.

 

  모델은 ORM(Object-relational mapping)이라는 패턴 기법을 이용해 데이터베이스를 가상의 프로그래밍적 객체로 모델링하여 정의하고, migrate 명령어를 이용해, 실제 데이터베이스에 테이블을 만들거나, 수정하여, 장고와 데이터베이스 사이의 구조를 싱크(뭐 migration 이 한 방향의 의미긴 하지만 넓게 보면 싱크 개념인것 같다) 시킨다. 또한 데이터 베이스에 SQL 문을 날리듯, 모델에서 QuerySet 이라는 검색용 메써드를 제공해, ORM 객체로부터 데이터를 조회해오게 한다(select, where, order by, join 등을 실제 비슷하게 구현한다. 앞에 진행했던 시간을 생각해 보면, 'Panda' 가 가상의 메모리 객체(dataframe)를 만들어서 비슷한 행동을 했었다).

 

  양 쪽을 싱크하는 과정에서 어드민에서 필요한 몇몇 테이블도 데이터베이스 안에 들어가게 되고, Admin 쪽에는 모델에서 정의한 ORM 객체들을 살펴보고, 데이터를 넣거나, 수정하거나, 지우거나 하는 관리 행동을 할수 있는 기본적인 인터페이스가 자동으로 싱크되어 구현된다.

 

  이러한 모델에서 만들어진 ORM 개체들은 뷰에서 QuerySet 을 이용하여 호출되어, 사용자가 요청한 조건에 적합하도록 가공되며, 템플릿과 조합되어 동적인 UI를 생성해 제공되거나, Json 응답 등으로 템플릿과 상관없이 독립된 형태로 사용자에게 응답을 줄 수 있다. 템플릿은 기본적으로 HTML 베이스로 구성된 응답(response)을 위한 기본 문서이며 여러가지 장고에서 제공되는 템플릿용 지원 기능 들과 CSS, Javascript 같은 스태틱 파일들을 이용하여 적절한 UI 를 구성하여 사용자에게 처리결과를 보여 주게 된다. 

 

  위의 그림중에 대부분의 요소들은 앞의 레거시 웹이나, 플라스크(flask) 살펴보기 시간에 얘기했던 주제들이거나, 추후 메뉴얼을 보면서 자세히 항목들을 살펴봐야 될 주제들 같아서, 이 시간에는 ORM 과 그에 연관된 주제들(QuerySet, Migration), 그리고 장고의 뼈대를 구성하는 MVT 라는 패턴 구조에 대해서 설명한 후 나머지 플라스크와의 자잘한 차이는, 실제 샘플을 구현하면서 중간 중간 얘기하려 한다.

 

 

[ORM]

  ORM 은 앞에서도 얘기했지만, 데이터베이스의 테이블의 구조와 관계를 클래스와 프로퍼티(속성), 메쏘드를 이용해, 객체의 관계로서 모델링 하는 기법이다. 구글에서 'orm pros and cons' 나 'orm 장단점' 이라고 검색하면, 아래와 같은 많은 페이지가 나오면서 난상 토론을 보게 되는데,

 

https://stackoverflow.com/questions/494816/using-an-orm-or-plain-sql

https://stackoverflow.com/questions/35955020/hibernate-orm-framework-vs-jdbc-pros-and-cons

https://gs.saro.me/#!m=elec&jn=718

http://layered.tistory.com/entry/ORM%EC%9D%80-%EC%95%88%ED%8B%B0%ED%8C%A8%ED%84%B4%EC%9D%B4%EB%8B%A4-ORM-is-an-antipattern

https://okky.kr/article/286812


 

  ORM 사용에 대한 판단은 각각 하는게 맞겠지만, 몇 가지 생각해 봐야할 것 같은 부분들이 있다. 우선 1번째는 'ORM' 은 'ODBC' 나 Selenium 의 'webdriver' 처럼 원소스 멀티유즈를 표방한다. 그러다 보니, 같은 모델이 여러 데이터베이스와 100% 호환된다는 보장은 사실 힘들것 같다(메뉴얼의 'The model layer' 의 뒷 부분이 이런 차이들과, 모델로서 해결하기 힘든 경우 raw sql 을 사용하는 부분들을 가이드 하고 있다). 데이터베이스마다 미묘한 문법차이나, 설계 차이가 있을 수 있고, 데이터베이스 버전 별로 100% 호환이 되도록 충분히 지원하고 테스트 되었다고 보기 힘들 수 있다. 마치 셀레늄에서 webdriver 의 종류에 따라 서로 다른 브라우저 끼리의 동작이 미묘하게 달랐던 것 처럼 말이다. 그래서 가능한 ORM 을 쓸 경우, 해당 프레임워크가 가장 기본으로 지원하는 데이터베이스를 사용하는게 업데이트도 계속 지원되고, 호환이 안되어 곤란한 일이 안 생길 것 같다(예를 들어 Django 는 PostgreSQL, MySQL, Oracle, Sqlite3 를 공식적으로 메뉴얼에서 다룬다).

 

  2번째는 ORM 이 데이터베이스에서 구조와 성능을 위해서 지원하는 주요 기능 요소들을 충분히 모델상에서 포함하고 있는지에 대해서이다. 장고에서 기본적으로 하나의 모델 객체는 하나의 실제 테이블에 마이그레이션이되어 매칭 되는 구조인데, 실제 복잡한 사이트에서 효율적인 설계가 해당 방식으로 100% 이루어질 수 있는지를 체크해 봐야한다(현실에서는 수많은 테이블이 서로 join 등으로 엃혀있는 관계를 가진 경우가 많아서, ORM 모델로 구현시 가독성이나, 복잡도가 감당할만 한지 등을 따져봐야 할 것 같다)

 

  3번째는 레거시 데이터베이스를 사용하는 경우이다. 관계형 데이터베이스의 특성상 많은 테이블들이 서로 연관을 가지면서 파편화 되있을 가능성이 많은데, 해당 부분을 현재의 모델로 흡수하여 구현하게 되면, DB프로시저 등을 생산해서 추가적인 중계가 필요할 수도 있고, 그런 경우엔 장고의 ORM 에서 구현한 장점 중 하나인 마이그레이션 기능을 원활히 사용할수 없을 가능성이 높아진다. 

 

  4번째는 DBA 인력들과의 협업이다. 개발 쪽에서 독자적으로 데이터베이스를 관리하고 책임지는 구조라면 모르겠지만(뭐 요즘 유행인 DevOps 등의 조직에선 모르겠다), DB 관련 팀 쪽에서 데이터베이스의 성능, 테이블 스키마 관리 등을 책임 지고 있다면, 만들어진 모델을 검증하기 위해서, DBA 인력이 장고의 ORM 을 구현한 클래스들과 QuerySet 구성을 이해하고, 해당 부분이 실제 현재의 데이터베이스에 어떤 영향을 주는지 모델의 변경이 생길때마다 매번 검증을 해야하는 상황이 생기게 되는데, 이런 방식이 가능한 시나리오인지는 의문이 생긴다.

 

  마지막으로는, 장고 쪽에 마이그레이션 기능을 위해, 데이터베이스의 스키마를 자동으로 바꿀 수 있는 권한을 주는 부분이 보안적으로 적절한가에 대한 고려와, Admin 의 요구사항이 경험적으로 단순히 모델 객체에 데이터를 넣거나 편집하는 것으로 단순하게 이루어지진 않기 때문에 자동화된 어드민이 실제 얼마나 유용할까에 대한 의문이다.

 

  결론적으로 적절히 우호적인 환경에서는 장고의 ORM 을 사용해 데이터베이스를 마이그레이션 하여 관리하는 것도 좋지만, 해당 경우 발생할 여러가지 반대 급부를  생각해 봐야 하며, ORM 을 사용하는 것 자체가 개발자에게 데이터베이스를 덜 이해해도 된다는 면죄부가 되는 것은 아니라는 것을 얘기하고 싶다. 오히려 개인적인 생각에는 ORM 을 사용해 모델을 설계해서 테이블과 싱크 시키고 싶다면, 장고의 모델과 QuerySets, 마이그레이션 쿼리, 객체지향 설계를 잘 이해하고, 동시에 사용하는 데이터베이스의 여러 성능 요소를 결정하는 미묘한 특징들에 대해서 잘 이해해야지만 충분히 규모가 커져도 유지보수가 가능한 좋은 설계가 나오지 않을까 싶다. 뭐 그냥 개인적인 생각이라는 것을 꼬리로 단다.

 

 

[MVT]

  MVT(Model, View, Template) 패턴은, 기존에 많이 쓰이는 용어가 MVC 이니 두 개를 비교해 보면서 살펴보도록 하자, 구글에서 'mvc vs mvt' 로 검색하면 아래의 페이지가 나온다.

https://stackoverflow.com/questions/6621653/django-vs-model-view-controller

 

If you’re familiar with other MVC Web-development frameworks, such as Ruby on Rails, you may consider Django views to be the controllers and Django templates to be the views.

 

  해당 내용 중 위와 같은 내용이 있는데, MVC 의 model 은 장고에서 설명한 구조와 거의 같이 데이터베이스와 매핑되는(사실 어떻게 보면 웹 프로그램이라서 그렇지 원래 ORM 은 굳이 매핑되는 대상이 데이터베이스일 필요는 없는것 같다) 부분을 얘기하고, 컨트롤러(controller)는 장고나 플라스크의 url 요청을 받아 해당되는 함수에 연결해 주는 라우팅 부분을 얘기한다. 그리고 뷰(View)는 실제 모델로부터 데이터를 받아서 보여주는 역활을 한다.

 

  그래서 장고의 경우 위의 그림에서 그렸듯이, 뷰에 URLconfs 기능이 있기 때문에 MVT의 뷰는 컨트롤러 개념을 가지고 있다고 하고(사실 뷰에서 템플릿을 사용안하고 바로 응답값을 줄수도 있기때문에, 템플릿과 뷰 개념을 같이 가지고 있다고 보는것도 맞을듯 싶다), 실제 상으로 템플릿에서 모든 결과를 보여주기 때문에 MVT 의 템플릿은, MVC 의 뷰와 같다고 말하는 것이다. 사실 좀 말장난 같은 요소가 있으며, 기능이 어느 편에 붙었는지에 상관없이 전체적인 기능 요소들은 거의 동일 하므로, 두 개가 사실상 같은 개념이라고 봐도 무방할듯 싶다(언어로 따짐 사투리 관계인데 뭐가 표준어인지는 모르겠다^^;).

 

 

[사전 준비 - Django 설치]

  파이썬 3를 지원하므로 pip 명령어로 설치하면 된다.

c:\Python\code>pip install django
Collecting django
...
Successfully installed django-1.11.4

 

 

 

 

[DB 에서 데이터 불러와 HTML 테이블로 보여주기]

  그럼 본격적으로 장고를 이용해서 앞의 시간에 플라스크로 구현해 봤던, 4교시에서 만들었던 예제를 응용한, MSSQL 데이터베이스에서 데이터를 불러다 HTML 테이블 형태로 웹에 표시하는 부분을 구현해 보기로 하자. ORM 모델을 무시하고, 4교시와 비슷하게 pymssql 모듈을 사용하여 모델을 무시하고 호출하는 것도 가능하겠지만, 그러면 장고의 모델 부분을 살펴 볼수 있는 기회가 없어지기 때문에 ORM 을 최대한 이용하도록 구현을 해보려고 한다.

 

 

[장고의 모델에서 MSSQL 을 지원하게 해주는 모듈 찾기]

  우선 공식 메뉴얼에는 MSSQL 지원에 대해서 언급된 내용이 없으므로, 구글에서 'django mssql' 이라 검색해서, 아래의 페이지를 찾는다.

https://django-mssql.readthedocs.io/en/latest/

 

  스토어드 프로시저(stored procedure)도 호출할 수 있고, 메뉴얼이랑 사용법등이 그런데로 괜찮은듯 한데, 지원하는 버전을 보니, django 1.8 까지 지원한다. 적용하면 돌아갈것 같긴한데 이젠 지원이 끊긴듯 해서 찜찜하다. 그래서 다시 다른 페이지를 찾다보니 아래 레딧 페이지에 'django-pyodbc-azure' 를 쓰라는 조언이 있다.

https://www.reddit.com/r/Python/comments/4iq7zb/django_19_with_mssql_as_backend/

 

  그래서 다시 구글을 검색해, 해당 모듈의 깃허브 페이지로 가니, 장고 1.11 최신버전과 MSSQL 2016 까지 지원해 주는, 현재 활발히 유지되고 있는 모듈이 있다.

https://github.com/michiya/django-pyodbc-azure

 

 

  그럼 설치 가이드에 있는 것처럼, pyodbc 와 django-pyodbc-azure 를 각각 설치해 보자(ODBC 개념은 4교시때 간단히 설명했다)

c:\Python\code>pip install pyodbc
Collecting pyodbc
...
Successfully installed pyodbc-4.0.17


c:\Python\code>pip install django-pyodbc-azure
Collecting django-pyodbc-azure
...

Successfully installed django-pyodbc-azure-1.11.0.0

 

 

 

[이미 만들어진 스키마를 장고 모델로 가져오는 방법 찾기]

  이제 모듈이 설치되었는데, 모듈이 잘 동작하는지 보려면, 장고 모델을 만들어서 마이그레이션으로 MSSQL 데이터베이스와 싱크를 해야될 것 같다. 그런데 다시 supermarket 테이블에 대한 모델을 만들어, 마이그레이션을 시키고 다시 장고의 어드민이나 SQL Management Studio 를 통해 데이터를 넣고 하는게 귀찮기도 하고, 기존 레거시 데이터베이스를 사용하는 경우 모델을 어떻게 구성하나도 궁금하기도 해서 구글에 'django pre existing database' 라고 검색해 아래의 메뉴얼 페이지를 찾았다. 근데 다행히도 설정파일에 연결문자열만 맞춰 놓으면, manage.py 의 inspectdb 명령어를 사용하면, 데이터베이스의 테이블을 자동으로 읽어와서,  해당 되는 테이블 구조에 맞는 model.py 파일을 만들어 준단다. 그럼 조금 뒤에서 모델을 만들때 이 기능을 사용하기로 해본다.

https://docs.djangoproject.com/en/1.11/howto/legacy-databases/


 

[프로젝트 만들기]

  일단 앞의 플라스크와 마찬가지로 가상환경을 쓸 필요는 없을 듯해 virtul env 설정은 생략하고, 앞의 장고걸의 예제들을 따라해 보다보니 설정 문제인지 현재 환경에서(windows 10) 일부 명령어 실행에 에러가 나서, 첨에 소개한 아래의 영문 설명 페이지를 기준으로 virtual env 설정 하는 부분과 migration 하는 부분만 제외하고 적당히 따라해 보기로 했다.

https://scotch.io/tutorials/build-your-first-python-and-django-application

 


  먼저 프로젝트를 생성한다. 아래의 명령어를 치면, c:\python\code 밑에 djangoweb 이라는 프로젝트를 만든다. 뭐랄까 GUI 메뉴를 사용하는 방식은 아니지만, 이제 부터는 비주얼 스튜디오나 이클립스 등으로 프로젝트를 만드는 느낌으로 따라오면 된다.
c:\Python\code>django-admin startproject djangoweb

 

  이렇게 되면 메뉴얼에도 나오지만 아래와 같이 c:\python\code 폴더 아래 djangoweb 이란 폴더가 생성되면서, 아래와 같은 서브폴더와 파일 구조를 가지게 된다.

1
2
3
4
5
6
7
djangoweb/
    manage.py
    djangoweb/
        __init__.py
        settings.py
        urls.py
        wsgi.py
cs

 

  뭐 각각의 파일이 모두 나름의 역활은 있지만, 자세한건 메뉴얼을 보고, 현재 시간에 의미 있는 요소들만 얘기하면,

1) mamage.py - 어플리케이션(프로젝트 안에서 실제 돌아가는 프로그램 모듈 1개를 얘기한다)을 만들거나, 데이터베이스에 모델 정보를 마이그레이션 하거나 하는 등의 여러 프로젝트의 관리에 필요한 기능들을 모아논 장고 전용의 작은 파이썬 프로그램이라고 보면 된다.

2) settings.py - 프로젝트 전반에 필요한 설정들을 저장한 파일. 데이터베이스 연결 문자열, 사용하는 어플리케이션들의 정의, 로케일, 스태틱 파일 경로, wsgi(web server gateway interface - 사용자로부터의 웹 요청을 처리하는 모듈) 지정 등등이 들어가 있다고 보면 된다/

3) urls.py - 1차 라우팅 설정이 들어 있다. 1차라고 하는 이유는 나중에 어플리케이션을 만들면 또 그 안에 라우팅 설정이 또 있다. 들어온 요청을 각 어플리케이션에 분배하는 1차 라우팅 단계라고 보면 된다.

 

 

[프로젝트 안에 어플리케이션 만들기]

  그 담에는 어플리케이션을 만들기 위해서, 'cd' 명령어를 이용하여 새로 만들어진 'djangoweb' 폴더 안으로 이동하여 'supermarket' 테이블에서 조회해서 보여줄 어플리케이션을 생성하는 명령어를 실행 한다(뭐 복잡해 보인다고 생각할지 모르지만, 어차피 장고를 사용하려면 정해져 있는 방식이라서 그대로 따라해야 되는 부분이다).

c:\Python\code>cd djangoweb
c:\Python\code\djangoweb>python manage.py startapp supermarket

 

  이 후에는 내부의 폴더에 supermarket 폴더가 생기며 안에 담긴 파일들은 아래와 같다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
djangoweb/
    manage.py
    djangoweb/
        __init__.py
        settings.py
        urls.py
        wsgi.py
    supermarket/
        __init__.py
        admin.py
        apps.py
        migrations/
            __init__.py
        models.py
        tests.py
        views.py
cs

 

  역시 이번 시간에 주로 의미가 있는 파일들만 언급하면,

1) models.py - ORM 모델들이 클래스로 정의되어 있는 파일이다. 현재는 빈 어플리케이션을 만들었기 때문에 안을 보면 내용이 비어있다.

2) views.py - 뷰가 정의되어 있는 파일이다.

-> 위의 생성된 리스트를 보면 좀 이상하다라고 생각할수 있는데, 'templates' 폴더나, 어플리케이션 용 'urls.py' 파일은 안보인다. 뒤에서 보겠지만, 해당 파일은 수동으로 생성해 주어야 된다.

 

 

[프로젝트 파일 설정에 어플리케이션 추가]

  가이드에 나와 있는데로, 프로젝트의 셋팅 파일에, 새로 만든 supermarket 어플리케이션을 인식할 수 있도록 추가해 주자. c:\Python\code\djangoweb\djangoweb\settings.py 파일에서 아래 부분을 추가해준다. 

1
2
3
4
5
6
7
8
9
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'supermarket',
]
cs

 

 

[프로젝트 라우팅에 어플리케이션 쪽 라우팅 추가]

  그 다음엔 '프로젝트의 urls.py' 를 수정해 요청이 들어왔을때, 요청의 처리를 'supermarket 어플리케이션' 쪽에 위임하도록 해보자. c:\Python\code\djangoweb\djangoweb\urls.py 파일에서 아래 import 문과, supermarket.url 항목 부분을 추가해 준다. url 쪽이 플라스크와 하나 다른건 8교시때 배웠던 정규표현식으로 파싱을 한다는 것이다. 플라스크 처럼 사용하려면 '^원하는문자열$'(시작과 끝 사이에 원하는 문자열만 있음) 패턴으로만 url 을 정의해 쓰면 될듯 하다. 아래에서는 ^(시작하자마자) 후에 바로 supermarket.urls 로 라우팅 되기 때문에 결국 supermarket 쪽으로 전체 라우팅 제어권을 넘기는 것과 마찬가지가 된다. 

1
2
3
4
from django.conf.urls import include
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^', include('supermarket.urls')),
cs

 

 

[어플리케이션 라우팅 파일 만들기]

  자 그럼 이제 프로젝트에서 supermarket 어플리케이션으로 던진 경로를 해석해주는 어플리케이션 라우팅 부분이 있어야 한다. c:\Python\code\djangoweb\supermarket\urls.py 을 메모장에서 만들어서 아래의 내용을 넣어 utf-8 인코딩으로 저장한다(혹시 한글 주석을 달려면 utf-8로 파일이 저장되어야 장고 실행시 에러가 안 난다). 안의 내용은 일단 아직 뭐가 뭔지 잘 모르기 때문에, 위의 튜토리얼의 예제 내용을 그대로 넣어보자. 웹 서버 루트 경로(^$-시작과 끝 사이에 아무 값도 없음)를 호출했을 경우, 앞서 잠시 얘기한 장고에서 제공하는 generic view 를 사용하여 보여주는 형태 이다(뭐 장고가 잘 돌아가는지만 보려 하는거니 상세한 문법은 넘어가기로 하자)

1
2
3
4
5
6
from django.conf.urls import url
from supermarket import views
 
urlpatterns = [
    url(r'^$', views.HomePageView.as_view()),
]
cs

 

 

[어플리케이션 뷰 파일에 샘플 뷰 추가하기]

  자 그럼 이제 어플리케이션에서 해당 되는 라우팅에 대한 샘플 뷰를 만들어 보자. c:\Python\code\djangoweb\supermarket\views.py 파일에서 아래의 내용을 추가한다. 대충 문법을 보면 generic view 중 TemplateView 라는 것을 이용해 index.html 이라는 템플릿 파일을 지정했다.

1
2
3
4
5
6
7
from django.shortcuts import render
from django.views.generic import TemplateView
 
# Create views
class HomePageView(TemplateView):
    def get(self, request, **kwargs):
        return render(request, 'index.html', context=None)
cs

 

 

 

[어플리케이션 뷰 파일에 해당 되는 템플릿 만들기]

  마지막으로 템플릿 파일을 만들면 된다. c:\Python\code\djangoweb\supermarket\ 폴더에 templates 폴더를 생성한다(c:\Python\code\djangoweb\supermarket\templates\). 이후 해당 폴더안에 c:\Python\code\djangoweb\supermarket\templates\index.html 을 만들어 저장한다. 역시 한글 주석을 위해서는 utf-8 인코딩으로 저장하자.

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>장고샘플</title>
    </head>
    <body>
        <h1>동작함</h1>
    </body>
</html>
cs

 

 

[샘플 페이지 실행]
  그럼 다시 c:\Python\code\djangoweb 경로에서 아래의 명령어를 실행해 보자(manage.py 를 실행 시켜야 해서 실행 경로가 맞아야 한다)

c:\Python\code\djangoweb>python manage.py runserver
Performing system checks...
You have 13 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
...

Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
[05/Aug/2017 17:30:55] "GET / HTTP/1.1" 200 222

 

  뭐 기본 기능에 대한 마이그레이션이 안됬으니 어쩌니 얘기가 나오긴 하지만, 해당 부분은 무시하고 일단 웹페이지를 띄워, http://127.0.0.1:8000/ 을 호출하면 아래와 같이 웹페이지가 동작함을 볼 수 있다.

 

 

  참고로 현재 까지의 파일 구조는 아래와 같다. 추가된 파일과 폴더들은 색으로 표시했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
djangoweb/
    manage.py
    djangoweb/
        __init__.py
        settings.py
        urls.py
        wsgi.py
    supermarket/
        __init__.py
        admin.py
        apps.py
        migrations/
            __init__.py
        models.py
        tests.py
        templates/
            index.html
        urls.py
        views.py
cs

 

 

[MSSQL 연결 문자열 추가하기]

  MSSQL 연결 문자열을 추가하기 위해(앞의 'django-pyodbc-azure' git 페이지의 문서를 참고했다), 프로젝트 내의 c:\Python\code\djangoweb\djangoweb\settings.py 을 열어 DATABASE 설정 부분을 디폴트인 sqlite3 에서 MSSQL 로 수정한다.  
<원본>

1
2
3
4
5
6
DATABASES = {
    'default': {
        'ENGINE''django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}
cs

 

<수정>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
DATABASES = {
    'default': {
        'ENGINE''sql_server.pyodbc',
        'NAME''mytest',
        'USER''pyuser',
        'PASSWORD''test1234',
        'HOST''localhost',
        'PORT''1433',
 
        'OPTIONS': {
            'driver''ODBC Driver 13 for SQL Server',
        },
    },
}
cs

 

 

[모델 자동으로 만들기]

  앞에서 봤던 레거시 테이블을 자동으로 만드는 inspectdb 명령어를 이용하여 model.py 파일을 만들어 본다.

https://docs.djangoproject.com/en/1.11/howto/legacy-databases/

 

 

  일단 제대로 연결 문자열이 설정됬나 보기 위해서 아래의 명령어를 실행해 본다. 잘은 모르겠지만, 대충 출력된 내용을 보니 해당 데이터베이스안에 기존에 만들어 놓은 'Play' 와 'supermarket' 테이블을 가져오고, 필드 이름들도 정상적으로 파싱해 가져오는거 같다.
c:\Python\code\djangoweb>python manage.py inspectdb
# This is an auto-generated Django model module.
....

class Play(models.Model):
...

    class Meta:
        managed = False
        db_table = 'play'


class Supermarket(models.Model):
    itemno = models.IntegerField(db_column='Itemno', blank=True, null=True)  # Field name made lowercase.
...
    price = models.IntegerField(db_column='Price', blank=True, null=True)  # Field name made lowercase.

    class Meta:
        managed = False
        db_table = 'supermarket'

 

 

 그럼 models.py 파일을 만들어 내기 위해서, 아래의 명령어를 실행한다. inspectdb 를 하여 출력된 내용을 리다이렉션('>') 을 이용해서 기존의 자동 생성된 models.py 파일을 덮어쓰기 한다.

c:\Python\code\djangoweb>python manage.py inspectdb > supermarket\models.py

 

 

  확인을 하고 싶으면 c:\Python\code\djangoweb\supermarket\models.py 파일을 열어보면, 테이블의 구조가 아래와 같이 model 파일로 자동으로 만들어 진다. 내용을 보면 우리가 클래스는 배운적이 없지만 걍 내용을 담는 박스라고 생각하자. 클래스 안에 각 디비의 컬럼들이 정의 되어 있는데, 앞의 소문자로 된 itemno, categoty 등이 나중에 QuerySet 등에서 명시되어 사용되게 된다. 

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
# This is an auto-generated Django model module.
# You'll have to do the following manually to clean this up:
#   * Rearrange models' order
#   * Make sure each model has one field with primary_key=True
#   * Make sure each ForeignKey has `on_delete` set to the desired behavior.
#   * Remove `managed = False` lines if you wish to allow Django to create, modify, and delete the table
# Feel free to rename the models, but don't rename db_table values or field names.
from __future__ import unicode_literals
 
from django.db import models
 
 
class Play(models.Model):
    original = models.CharField(max_length=30, blank=True, null=True)
    encrypted = models.CharField(max_length=200, blank=True, null=True)
    decrypted = models.CharField(max_length=30, blank=True, null=True)
 
    class Meta:
        managed = False
        db_table = 'play'
 
 
class Supermarket(models.Model):
    itemno = models.IntegerField(db_column='Itemno', blank=True, null=True)  # Field name made lowercase.
    category = models.CharField(db_column='Category', max_length=20, blank=True, null=True)  # Field name made lowercase.
    foodname = models.CharField(db_column='FoodName', max_length=30, blank=True, null=True)  # Field name made lowercase.
    company = models.CharField(db_column='Company', max_length=20, blank=True, null=True)  # Field name made lowercase.
    price = models.IntegerField(db_column='Price', blank=True, null=True)  # Field name made lowercase.
 
    class Meta:
        managed = False
        db_table = 'supermarket'
 
cs

 

 

[마이그레이션 - 일단 생략]

  굳이 여기서 쓸것도 아니고, 모델과 데이터베이스를 계속 싱크 시킬 것도 아니기 때문에 마이그레이션 명령을 돌리는건 생략 하지만, 저도 에러 땜에 이것저것 찾아보다 한번 돌려봤다. 마이그레이션 명령어를 실행하게 되면 아래와 같이 어드민 관련 테이블이나, 장고에서 지원되는 세션 같은 기능에서 사용하는 기본 테이블들이 데이터베이스안에 만들어지게 된다. 실제로 안돌려도 되니 참고만 하자(뭐 돌려도 상관은 없지만 말이다).

c:\Python\code\djangoweb>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying admin.0001_initial... OK
....
  Applying auth.0008_alter_user_username_max_length... OK
  Applying sessions.0001_initial... OK

 

 

[inspectdb 를 통한 마이그레이션 성공 여부 확인 하기]
  실제 페이지를 모두 만들어서, 만들어진 모델의 Queryset 이 정상으로 돌아가는지 확인 하려면, 만드는 도중에 또 다른 실수를 할지 모르므로, 실제 사이트를 만들기 전에 아래의 장고걸 사이트에서 나온데로, 장고쉘 기능을 이용해서(파이썬 쉘과 비슷하다고 보면 된다), 모델이 잘 생성됬는지 확인해 본다.

https://tutorial.djangogirls.org/ko/django_orm/

 

 

일단 쉘을 실행 하자.

c:\Python\code\djangoweb>python manage.py shell

 

  잘 실행되서 '>>>' 프롬프트가 나오면 아래의 명령어를 넣어 슈퍼마켓 모델을 가져오고, 슈퍼마켓 모델에서 모든 내용들을 다 가져와 보자

>>> from supermarket.models import Supermarket
>>> Supermarket.objects.all()
  File "C:\python\lib\site-packages\sql_server\pyodbc\base.py", line 545, in execute
    return self.cursor.execute(sql, params)
django.db.utils.ProgrammingError: ('42S22', "[42S22] [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]\ufffd\ufffd \ufffd\u0338\ufffd 'id'\ufffd\ufffd(\ufffd\ufffd) \ufffd\u07f8\ufffd\ufffd\u01fe\ufffd\ufffd\ufffd\ufffd\u03f4\ufffd. (207) (SQLExecDirectW)")

 

 

  흠 그런데 이상한 SQL 에러가 난다. 근데 에러 파일의 위치를 보면('pyodbc\base.py') 장고쪽은 아니고, pyodbc 쪽 에러같아서 mssql 모듈쪽은 이상없는것 같아 일단 안심은 된다. 어떤 에러인지 찾기 위해서 구글에서 'pyodbc 42S22 error' 라고 검색해서 아래 페이지를 찾아서 보니, 컬럼이 없는 경우의 에러인 것 같다. 그러고 보니 위의 에러에서 'id' 라는 컬럼이름 같은 항목이 보인다. 

https://stackoverflow.com/questions/36202976/pandas-with-pyodbc-nan-error-42s22-error-attribute-qnan-not-found-31

 

 

  그런데 supermarket 테이블에는 'id' 라는 컬럼 자체를 만든적이 없으니 이상하다고 생각하던 중, 예전에 장고 메뉴얼을 읽다가, 마이그레이션 설명 쪽에서 모델에 primary 키(겹치지 않는 유일한 값을 가진 필드 속성)가 없으면, 자동으로 테이블에 'id' 라는 이름으로 자동으로 숫자가 증가하는 primary 키를 만든다는 내용을 스쳐가듯 본 듯한 기억이 났다. 해당 테이블에는 당연히 primary 키 설정이 없었었고, 그래서 장고가 마이그레이션한 테이블처럼 id 필드가 무조건 있으리라 생각하고, 데이터베이스에 select 쿼리를 보낼 때 'select id, ...' 를 하여 에러가 났나보다.

https://docs.djangoproject.com/en/1.11/topics/db/models/

https://docs.djangoproject.com/en/1.11/topics/db/models/#automatic-primary-key-fields

 

 

  그럼 2가지 해결책이 있을 듯 하다. 하나는 현재의 supermarket 테이블에, 자동으로 숫자가 증가되는(auto increment) id 필드를 추가하는 방법이 있고, 다른 하나는 기존 supermarket 테이블을 삭제하고 다시 만들면서, 특정한 필드를 primary 키로 지정하여 생성하는 것이다(개인적으로 두 방식 모두 해봤는데 둘 다 잘 해결되긴 했다). 여기선 간단하게 가기위해서, supermarket 테이블에 id 컬럼을 하나 추가하자. 구글에서 'mssql add column autoincrement' 라고 검색하여 아래 페이지를 찾았다.

https://stackoverflow.com/questions/4862385/sql-server-add-auto-increment-primary-key-to-existing-table

 

     ALTER TABLE dbo.YourTable
     ADD ID INT IDENTITY

 

  아래의 명령어를 4교시에 설명한 MSSQL Managment Studio 를 실행해 쿼리 입력 창에서 mytest db 를 대상으로 실행한다(SSMS 사용법이 기억이 잘 안나면 4교시를 참고한다)

1
2
3
4
5
6
7
8
9
10
use mytest
go
ALTER TABLE dbo.supermarket
   ADD id INT IDENTITY
 
ALTER TABLE dbo.supermarket
   ADD CONSTRAINT PK_supermarket
   PRIMARY KEY(id)
 
select * from dbo.supermarket(nolock)
cs


  명령 실행후 supermarket 테이블을 셀렉트 한 내용을 보면 아래와 같이 숫자가 증가하는 id 값이 추가로 생겼다.

 

 

  이후 다시 장고 쉘에서 같은 명령어를 입력해 보면 정상적으로 결과를 가져온다. 값이 아니라 오브젝트 자체를 가져오기 때문에 안의 내용은 표시되진 않지만, 설치한 mssql 용 장고 모듈이 잘 동작되는 것을 확인 했으니 이제 실제 코드를 만들면 될듯 하다.
>>> from supermarket.models import Supermarket
>>> Supermarket.objects.all()
<QuerySet [<Supermarket: Supermarket object>, <Supermarket: Supermarket object>, <Supermarket: Supermarket object>, <Supermarket: Supermarket object>]>

 

 

  ※ 참고로 기존 테이블에 id 를 추가하는 게 싫어서 테이블을 재생성 하고 싶으면, 아래와 같은 itemno 에 primary 키 속성이 있는 테이블을 만들고, 데이터를 다시 채워 넣음 된다(물론 그전에 'drop table' 명령어로 기존 supermarket 테이블은 지워야 한다)

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


 

[supermarket 용 url 만들기]

  아까 샘플 url 을 만든것 처럼,  c:\Python\code\djangoweb\supermarket\urls.py 파일에 아래와 같이 supermk url 을 추가 한다.

1
2
3
4
5
6
7
from django.conf.urls import url
from supermarket import views
 
urlpatterns = [
    url(r'^$', views.HomePageView.as_view()),
    url(r'^supermk$', views.supermk),
]
cs

 

 

[supermarket 용 view 만들기]

  마찬가지로 c:\Python\code\djangoweb\supermarket\views.py 파일의 기존 내용의 마지막에, 아래의 supermk 뷰를 추가한다(따라오시다 헷깔리시는 경우는 나중에 전체 소스를 맨뒤의 부록 섹션에 첨부할테니 그걸 참고하시기 바란다). 예전 플라스크에서 구현한 것과 비슷한 구조로 Supermarket 모델에서 모든 값을 가져오고(supers = Supermarket.objects.all()), 이후에 가져온 데이터를(supers)을 super.html 과 같이 랜더링 한다.   

1
2
3
4
5
from .models import Supermarket
 
def supermk(request):
    supers = Supermarket.objects.all()
    return render(request, 'super.html', {'supers': supers})
cs

 

 

[supermarket 용 template 만들기]

  마찬가지로 c:\Python\code\djangoweb\supermarket\templates\super.html 파일을 메모장으로 utf-8 인코딩으로 저장해 만들면서 아래의 코드를 넣는다. 역시 플라스크와 비슷하게

'{{ }}' 와 '{% %}' 를 사용하여 루프를 돌리면서 <td> 태그 안에 각 컬럼 값을 넣게 된다(템플릿 엔진이 같은가도 싶다).

1
2
3
4
5
6
7
8
9
10
11
<table border="1" cellpadding="5" cellspacing="5">
{% for super in supers %}
   <tr>
      <td>{{ super.itemno }}</td>
      <td>{{ super.category }}</td>
      <td>{{ super.foodname }}</td>
      <td>{{ super.company }}</td>
      <td>{{ super.price }}</td>   
   </tr>
{% endfor %}
</table>
cs

 

 

[supermarket 페이지 결과 보기]

  아까 runserver로 웹서버를 실행한 상태로 두었다면 소스의 변경사항이 자동으로 반영되었을 테고(플라스크도 이랬다), 종료했다면 아래의 명령어를 다시 쳐서 실행한다) 

c:\Python\code\djangoweb>python manage.py runserver

 

  이제 브라우저를 띄워 http://127.0.0.1:8000/supermk 를 실행 하면, 아래와 같이 플라스크로 구현한 것과 비슷한 화면을 볼수 있다.

 

  현재까지의 트리는 아래와 같다. 추가된 파일은 색으로 표시했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
djangoweb/
    manage.py
    djangoweb/
        __init__.py
        settings.py
        urls.py
        wsgi.py
    supermarket/
        __init__.py
        admin.py
        apps.py
        migrations/
            __init__.py
        models.py
        tests.py
        templates/
            index.html
            super.html
        urls.py
        views.py
cs

 

 

 

 

[D3.js 에서 Json 데이터 URL 를 호출해 웹으로 그래프 보여주기]
  이 예제를 만들기 위해 플라스크에 있는 예제를 전환하면서 또 한바탕 헤메긴 했었다. 헤멘 부분이 플라스크와 장고의 차이를 보여주기 때문에 일부는 말로 설명하고 일부는 해결 과정을 보여주려고 한다.

 

 

[d3.js 샘플 페이지 찾아보기]

  일단 플라스크 때 구현한 코드를 장고로 옮기려면 두 가지가 필요하다. 첫번째는 d3.js 를 사용하는 템플릿 코드의 일부를 장고에 맞추어 변경해야 할 것 같고, 또 d3.js 에서 호출했던 json 데이터를 반환하는 URL 을 장고로 구현해야 한다.

 

  구글에서 'django d3.js json' 을 찾아서 아래 페이지를 보니 2개의 힌트가 있다. 첫번째는 d3.js 에서 url 을 호출 하는 방식이다. urlconfs 파일에서 url 을 정의 할때 'name' 속성을 이용해 정의하고, d3.js 에서 'url' 문법을 이용해 경로를 호출 한다. 또 json 데이터를 만들어 내는 play_count_by_month 함수는 QuerySet 을 이용해 결과를 가져와서 JsonResponse 함수를 이용해 json 응답을 생성한다(JsonResponse(list(data), safe=False)). 'safe=False' 옵션이 있는 이유는, JsonResponse는 dictionary 형태의 데이터만 기본적으로 중계하고, 다른 데이터 형일 경우는 'safe=False' 옵션을 넣어야만 형변환 에러가 안난다. 

https://stackoverflow.com/questions/26453916/passing-data-from-django-to-d3

 

 

  좀더 자세히 json 을 반환하는 것을 살펴 보려고 추가적인 페이지도 찾아보았다.  JsonResponse 를 사용하는게 1.11 버전에서는 적절해 보인다.

https://simpleisbetterthancomplex.com/tutorial/2016/07/27/how-to-return-json-encoded-response.html
https://docs.djangoproject.com/en/dev/ref/request-response/#jsonresponse-objects

 

 

[urlconf 파일 수정]

  우선 c:\Python\code\djangoweb\supermarket\urls.py 파일에 아래와 같이 'data' 와 'd3sample' 경로를 추가한다. 'data' url에는 아까 샘플에서 봤듯이 name 속성이 추가됬다.

1
2
3
4
5
6
urlpatterns = [
    url(r'^$', views.HomePageView.as_view()),
    url(r'^supermk$', views.supermk),
    url(r'^data$', views.data, name='data'),
    url(r'^d3sample$', views.d3sample),
]
cs

 

 

[view 파일 수정]

  다음으로 c:\Python\code\djangoweb\supermarket\views.py 파일을 수정한다. data 함수 안의 내용은 거의 플라스크때 json 형태로 만든 샘플 데이터를 최종으로 JsonReponse 에 넘기는 변경만 했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import json
import numpy as np
from django.http import JsonResponse
def data(request):     
    x = np.array(['2017-07-10''2017-07-11''2017-07-12''2017-07-13''2017-07-14'])
    y = np.array([58.1353.9867.0089.7099.00])
 
    myData = json.dumps([{"date": x[i], "close": y[i]} for i in range(5)])
    return JsonResponse(myData, safe=False)
 
 
 
def d3sample(request):
    return render(request, 'd3sample.html', context=None)
cs

 

 

[templete 파일 생성]

  메모장을 열어서 예전 플라스크 때의 코드를 복사해 json URL 호출 하는 부분만 샘플에서 참고한 내용을 기준으로 변경 후에, 인코딩을 utf-8 로 하여, c:\Python\code\djangoweb\supermarket\templates\d3sample.html 파일로 저장한다. 

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
<!DOCTYPE html>
<meta charset="utf-8">
<style> <!-- 그래프 요소들의 스타일 지정 -->
body { font: 12px Arial;}
path { 
    stroke: steelblue;
    stroke-width: 2;
    fill: none;
}
.axis path,
.axis line {
    fill: none;
    stroke: grey;
    stroke-width: 1;
    shape-rendering: crispEdges;
}
 
</style>
<body>
 
 
<!-- 라이브러리 로딩. 내부에서 돌리려면 다운받아서 static 폴더에서 읽어와야 할듯 -->    
<script src="http://d3js.org/d3.v3.min.js"></script>
 
<script>
// 그래프 좌표 공간 설정
var margin = {top: 30, right: 20, bottom: 30, left: 50},
    width = 600 - margin.left - margin.right,
    height = 270 - margin.top - margin.bottom;
 
// 그래프 범위
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
 
// 축 정의
var xAxis = d3.svg.axis().scale(x)
    .orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
    .orient("left").ticks(5);
 
// 그래프 선 정의
var valueline = d3.svg.line()
    .x(function(d) { return x(d.date); })
    .y(function(d) { return y(d.close); });
    
// 캔버스 객체 생성
var svg = d3.select("body")
    .append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
    .append("g")
        .attr("transform"
              "translate(" + margin.left + "," + margin.top + ")");
 
// 2017-07-01 식으로 데이터를 해석하게 지정함
var parseDate = d3.time.format("%Y-%m-%d").parse;
 
// 전달 받은 데이터를 이용해서 그래프를 그린다.
var callback = function (data) {
 
    data.forEach(function(d) {
        d.date = parseDate(d.date);
        d.close = +d.close;
    });
 
    // 실데이터에 맞춰 그래프 범위 지정
    x.domain(d3.extent(data, function(d) { return d.date; }));
    y.domain([0, d3.max(data, function(d) { return d.close; })]);
 
    // 선 그리기.
    svg.append("path")
        .attr("class""line")
        .attr("d", valueline(data));
 
    // x축 그리기?
    svg.append("g")
        .attr("class""x axis")
        .attr("transform""translate(0," + height + ")")
        .call(xAxis);
 
    // y 축 그리기?
    svg.append("g")
        .attr("class""y axis")
        .call(yAxis);
 
};
 
 
// django 에서 만든 http://127.0.0.1/data 를 호출하여 json 데이터를 가져와 callback 함수 호출
d3.json("{% url "data" %}",  callback);
 
</script>
</body>
cs

 

 

[에러를 만나다]

  그런데 브라우저를 열어 http://127.0.0.1:8000/d3sample 을 호출하여 보니 그래프가 표시되지 않는다. 피들러를 띄워 확인해 보니 d3sample url 이 호출되고, 이후 /data url 까지 잘 호출한다.

 

 

 

  그래서 json 형태로 넘어오는 데이터를 확인하기 위해 브라우저에서 http://127.0.0.1:8000/data url 을 호출해보니, 파일을 저장하라는 창이 뜬다. 뭔가 이상하긴 하다. 해당 json 파일을 c:\python\code 폴더에 저장해서 메모장으로 열어 보니, 아래와 같이 (") 문자앞에 역슬래시(\) 가 들어가 이스케이프 처리가 되어있다(아마 여기서는 안전하지 못한 코드의 경우 JsonResponse 메써드가 방어를 하는 차원인거 같긴하다). 

1
2
3
4
5
"[{\"close\": 58.13, \"date\": \"2017-07-10\"}, 
{\"close\": 53.98, \"date\": \"2017-07-11\"}, 
{\"close\": 67.0, \"date\": \"2017-07-12\"}, 
{\"close\": 89.7, \"date\": \"2017-07-13\"}, 
{\"close\": 99.0, \"date\": \"2017-07-14\"}]"
cs

 

 

  그래서 브라우저에서 F12키를 눌러서, 예전에 배운 IE 개발자 도구를 열은 후, 'F5'키를 눌러서 http://127.0.0.1:8000/data 페이지를 다시 로딩 한다.  개발자 도구창에서 '콘솔' 탭을 클릭한 후 동그란 !표 아이콘을 클릭해 보니, 아래와 같이 개체가 상이하여 루프 메서드를 실행할 수 없다는 자바스크립트 에러가 난다.

 

 

  에러 메시지 밑의 라인 링크('d3smaple (75,5)')를 클릭하면, 해당되는 소스 라인에 가게 되는데, 아무래도 json 데이터를 받아온 내용이 담겨있는 data 변수가 기존 플라스크 때와는 다르게 json 형식으로 잘 해석이 안 된것 같다(아마 위의 \" 이스케이프 처리 때문인 것 같긴하다)

 

 

  그래서 자바스크립트 안에 'document.write(data)' 구문을 넣어 data 변수를 뿌려보니 아래와 같이 파일로 저장했을때는 보였던 escape 문자(\) 는 d3.json 함수를 통과하면서 없어진듯 하고, 정상적으로 뿌려짐을 볼수 있다(해당 디버그 코드들은 최종 샘플에 주석처리해 놓았으니 참고만...). 문법적으로 json 형태는 맞는거 같아, 그렇다면 왠지 이스케이프 문자를 d3.json 이 처리는 해줬지만, json 데이터라고 생각을 안해 변환을 안해 준거 같다는 생각이 든다. 

1
2
// d3.json 으로 받아온 값을 담고 있는 data 변수의 값을 찍어봄
document.write(data)
cs

 

 

 

  그래서 data 변수의 타입을 확인해 보기로 했다. 구글에서 'javascript print typeof' 로 검색하니, 아래의 페이지에서 type 을 문자열로 반환하는 함수를 얻게 된다.

https://stackoverflow.com/questions/7390426/better-way-to-get-type-of-a-javascript-variable

1
2
3
4
5
6
7
8
// 데이터 타입을 string 형태로 반환하는 함수
function typeOf (obj) {
  return {}.toString.call(obj).split(' ')[1].slice(0-1).toLowerCase();
}
 
// 에러추적 2 : data 의 형태를 뿌려보니,string 이라고 나옴
var datatype = typeOf(data);
document.write(datatype);
cs

 

 

  데이터 타입을 확인하니 'string' 이라고 나온다.

 

 

   JsonResponse 에서 " 문자를 이스케이프 처리하는게 확실히 문제는 문제인거 같다. 처음에는 json object 를 dict 로 변환해 볼까를 이리저리 궁리했지만 샘플의 json object 는 2차원이고, dict 는 1차원이라서 뭔가 억지로 변환하여 문제를 풀면 d3sample.html 코드도 이것저것 바꿔줘야 할 것 같아서 망설이다가 data 변수의 타입이 string 이지만 실제 내용은 json 데이터 형태인건 맞으니, string 을 json 객체로 변환하면 결과대로 나오지 않을까 하는 생각이 들었다. 구글에 'javascript string to json' 이라고 찾아서, 아래의 페이지를 찾는다. 해당 코드를 이용하여 변환 후에, 데이터 타입을 화면에 뿌려본다.

http://jekalmin.tistory.com/entry/string%EC%9D%84-json%EC%9C%BC%EB%A1%9C-json%EC%9D%84-string%EC%9C%BC%EB%A1%9C-%EB%B3%80%ED%99%98

1
2
3
4
5
// string 을 json 형태로 변환하여 뿌려보니, list 라고 나옴
// 확인 후 변환 코드는 남겨두어, 넘어온 data 값을 변환하여 사용함.
data = JSON.parse(data);
var datatype = typeOf(data);
document.write(datatype);
cs

 

  위와 같이 변환한 후 데이터 형을 다시 확인해보니 array 라고 나온다. 지금 보니 이래서 구글에의 샘플 페이지의 view 함수의 JsonResponse 호출부분에서 데이터를  list 형태로 바꾼건가 싶기도 하다. 뭐 여튼 변환하는 코드만 주석 처리 하지 않고 남겨두어 사용해 본다.

 

 

   최종 코드는 아래와 같고, 디버깅 코드 등으로 변경한 내용은 다른 색으로 표시해 놓았다. 브라우저의 개발자 도구를 잘쓰면 이렇게 덕지덕지 덜 넣어도 될듯하긴 하다. 보시면 결국 넘어온 데이터를 'data = JSON.parse(data);' 를 이용해 json array 형태로 바꾸어줌 간단히 해결나는 상황이다(밑의 typeof 함수는 디버깅 용이다). 해당 내용으로 d3sample.html 내용을 수정해 준다.

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
<!DOCTYPE html>
<meta charset="utf-8">
<style> <!-- 그래프 요소들의 스타일 지정 -->
body { font: 12px Arial;}
path { 
    stroke: steelblue;
    stroke-width: 2;
    fill: none;
}
.axis path,
.axis line {
    fill: none;
    stroke: grey;
    stroke-width: 1;
    shape-rendering: crispEdges;
}
 
</style>
<body>
 
 
<!-- 라이브러리 로딩. 내부에서 돌리려면 다운받아서 static 폴더에서 읽어와야 할듯 -->    
<script src="http://d3js.org/d3.v3.min.js"></script>
 
<script>
// 그래프 좌표 공간 설정
var margin = {top: 30, right: 20, bottom: 30, left: 50},
    width = 600 - margin.left - margin.right,
    height = 270 - margin.top - margin.bottom;
 
// 그래프 범위
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
 
// 축 정의
var xAxis = d3.svg.axis().scale(x)
    .orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
    .orient("left").ticks(5);
 
// 그래프 선 정의
var valueline = d3.svg.line()
    .x(function(d) { return x(d.date); })
    .y(function(d) { return y(d.close); });
    
// 캔버스 객체 생성
var svg = d3.select("body")
    .append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
    .append("g")
        .attr("transform"
              "translate(" + margin.left + "," + margin.top + ")");
 
// 2017-07-01 식으로 데이터를 해석하게 지정함
var parseDate = d3.time.format("%Y-%m-%d").parse;
 
// 전달 받은 데이터를 이용해서 그래프를 그린다.
var callback = function (data) {
 
    // 에러추적 1 : 데이터를 뿌려보니 \ 문자도 제거되고, 정상적인 json 문법인거 같음
    // document.write(data)
    
    // 에러추적 2 : data 의 형태를 뿌려보니,string 이라고 나옴
    // var datatype = typeOf(data);
    // document.write(datatype);
 
    // string 을 json 형태로 변환하여 뿌려보니, list 라고 나옴
    // 확인 후 변환 코드는 남겨 넘어온 data 값을 변환하여 사용함.
     data = JSON.parse(data);
    // var datatype = typeOf(data);
    // document.write(datatype);
 
    // 첨에 이런 에러가 남 (개체가 'forEach' 속성이나 메서드를 지원하지 않습니다.)
    data.forEach(function(d) {
        d.date = parseDate(d.date);
        d.close = +d.close;
    });
 
    // 실데이터에 맞춰 그래프 범위 지정
    x.domain(d3.extent(data, function(d) { return d.date; }));
    y.domain([0, d3.max(data, function(d) { return d.close; })]);
 
    // 선 그리기.
    svg.append("path")
        .attr("class""line")
        .attr("d", valueline(data));
 
    // x축 그리기?
    svg.append("g")
        .attr("class""x axis")
        .attr("transform""translate(0," + height + ")")
        .call(xAxis);
 
    // y 축 그리기?
    svg.append("g")
        .attr("class""y axis")
        .call(yAxis);
 
};
 
 
// flask 에서 만든 http://127.0.0.1/data 를 호출하여 json 데이터를 가져와 callback 함수 호출
d3.json("{% url "data" %}",  callback);
 
// 데이터 타입을 string 형태로 반환하는 함수
function typeOf (obj) {
  return {}.toString.call(obj).split(' ')[1].slice(0-1).toLowerCase();
}
 
</script>
</body>
cs

 

 

  이후 브라우저를 열어 http://127.0.0.1:8000/d3sample 를 호출하면 플라스크 때와 같이 그래프가 정상적으로 출력된다.

 

 

 참고로 앞의 코드에서 d3sample.html 이 아닌, 뷰 파일 쪽을 고치려면, 딕션너리 데이터가 리스트 안에 담긴 형태로 만들어서, JsonResponse 함수로 전달하면 됩니다.

 

 

[마무리 하면서]

  이렇게 해서 플라스크에서 구현했던 예제들을 장고에서 구현해 보면서, 장고란 프레임워크의 여러가지 면들에 대해 살펴보았다(덤으로 javascript 도 약간 배웠다). 생각보다는 장고는 DB에 마이그레이션도 해주고, 어드민도 자동 생성하는 듯, 마법사 모드에 가까운 부분도 있는것 같으며, 확실히 플라스크 보다는 체계적인 구조를 갖춘 듯 하다. 좀 뭐랄까 사용하는 규칙이 엄격하다고 할까... 장고를 사용하는게 괜찮아 보인다면 이제부터 메뉴얼을 찬찬히 훝어보거나, 관련 책을 하나 사서 보거나, 구글을 검색하면서 필요한 부분을 찾아 공부하면 될듯 싶다. 3섹션동안 웹 쪽을 다루긴 했지만 파이썬 기능에 초점을 두었기 때문에, 웹 프로그래밍 책들에서 많이 다루는 게시판이나, 로그인, 파일 업로드, CSS 등으로 화면 꾸미기와 같은 주제들은 다루지 않았기 때문에, 초보시라면 공부해야 될 부분들은 앞으로도 많을 것이다. 여기서는 legacy web, flask, django 세 가지가 각각 나름대로 비슷하면서도 다른 배경을 가지고 살아왔다는 것을 이해 한다면 성공일 것 같다. 이렇게 해서 legacy web 에서 시작하여 flask 와 django 를 살펴본 파이썬의 웹 프로그래밍에 대한 여정을 마치려 한다. 나름 처음 생각 했던것 보다는 잘 정리된 듯도 싶다 --;

 

 

 

[부록]

  전체 코드의 트리는 아래와 같고 소스들은 따라하시다 꼬일때를 대비해서, 참고하시라고 압축 파일로 첨부했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
djangoweb/
    manage.py
    djangoweb/
        __init__.py
        settings.py
        urls.py
        wsgi.py
    supermarket/
        __init__.py
        admin.py
        apps.py
        migrations/
            __init__.py
        models.py
        tests.py
        templates/
            index.html
            super.html
            d3sample.html
        urls.py
        views.py
cs

 

 

[첨부 zip 파일]

djangoweb.zip

 

 

 

 

 

2017.8.7 by 자유로운설탕
cs

 

  

 

 

 

 

 

posted by 자유로운설탕
2017. 7. 20. 15:44 프로그래밍

  이번 시간에는 파이썬 웹 프레임워크로 많이 쓰이는 경량화 웹 프레임워크라고 불리우는 플라스크(flask) 를 살펴보는 시간을 가지려고 한다. 메뉴얼을 기반으로 전체적인 플라스크의 구조에 대해서 살펴보고, 지난 legacy web 시간에 구현했었던 DB의 테이블을 조회해 HTML 테이블로 표현하는 예제, Javascript 계의 matplotlib 이라 할수 있는 D3.js 와 결합하여 json 데이터를 가져다가 그래프를 그려주는 예제, 마지막으로 matplotlib 을 이용해서 파이썬 코드 기반의 그래프를 생성하여 HTML 문서에 포함 시키는 총 예제 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. 정리 - 이런저런 이야기

 

 

 

 

[들어가면서]

  우선 시작하기 전에 얘기하고 싶은 것은, 프레임워크란 부분에 너무 많은 기대는 하지는 말라고 하고 싶다. 프레임워크란 해당 분야의 좋은 관행(best practice)들과, 지원용 라이브러리들을 모아 놓은 범용적인 틀 같은 것이여서 개발에 대한 방법론, 자주 쓰는 기능 라이브러리, 안전한 설계 이슈 등 많은 부분에 도움을 줄 수 있겠지만, 그 범용적인 부분이 현재 자기가 만들고자 하는 특정한 프로그램에 적합 하다는 보장은 못해주며, 자신이 만드려는 프로그램에 맞게 커스터마이즈 하기 위해서는 많은 노력이 추가로 들어가게 될 것 이다. 마치 집을 짓는데, 기본 재료와 반듯한 땅, 숙련된 기술공을 제공해 준다 해도, 해당 지원 부분이 내가 원하는 집을 짓는데 필요한 디테일의 전부는 절대 못 된다는 것이다. 해당 프레임워크가 지원하는 언어를 충분한 깊이로 이해하고, 해당 분야(웹, 디비, 시스템, 빅데이터 등등)에 대해 충분히 이해한 상태에서 사용해야, 사용하려는 프레임워크에 대한 이해도도 깊어지고, 적절하게 스스로나 해당 프레임워크가 의도한 대로 사용이 가능할 것이다. 추가로 비슷하거나 상이한 다른 프레임워크들에 대해서도 핵심을 잘 이해하고 있다면, 선택한 프레임워크의 장, 단점에 대해서 객관적으로 바라보고 선택에 대한 트레이드오프를 잘 따져볼 수 있을 것 같다

 

 

[한글 문서 여부]

  Flask 에 대한 메뉴얼 문서는 구글을 찾다보면 한글문서가 있긴 한데, 현재 0.12 버전이 나온상태에서, 파이썬 3 지원이 안됬던 0.11 개발기간 당시의 버전 문서의 번역본이고, 번역이 완전히 다 되진 않은 상태라서, 처음 접할때 전체적인 맥락에 대한 살펴보기 용으로만 쓰라고 권하고 싶다. 또 구글에서 검색하다보면 나오는 간단한 예제들을 구현해 놓은 여러 한글 블로그들도 많으니, 본격적으로 영문 문서를 보기 전에 미리 사전 지식을 쌓아놓으면 좀 더 읽기 수월해 질 듯 하다.

http://flask-docs-kr.readthedocs.io/ko/latest/index.html

 

 

[Flask vs Django]

  그럼 우선 파이썬에서 보통 많이 얘기되는 두 개의 웹 프레임워크를 한번 비교해 보자. 구글에서 'flask vs django' 라고 넣고 검색해 보면 아래와 같은 여러 비교한 페이지들이 나온다.

 

[플라스크와 장고 비교 글 - 이 사람은 플라스크를 더 편하다고 생각한다.] 

https://www.codementor.io/garethdwyer/flask-vs-django-why-flask-might-be-better-4xs7mdf8v

[플라스크, 장고, 피라미드 비교 - 밑에 것은 번역 글]

https://www.airpair.com/python/posts/django-flask-pyramid

http://kmc5500.tistory.com/162

 

  대충 요약해 보면, flask 는 웹 프레임워크가 필요한 최소한의 기능만 제공 하고, 나머지는 외부 모듈이나, 개발자에게 구현을 하도록 유도하는 편이라고 하고, Django 는 중, 대규모의 사이트를 목적으로 만들어져 일반적인 웹사이트 개발에 필요한 풀 패키지를 지원하는 편이라고 한다. 글 들에서도 나오지만 판단은 각자 스스로 하는게 맞을 것 같고, 이번 시간에는 flask 가 표방하는 최소한의 웹 프레임워크가 어떤 의미인지 살펴보도록 하겠다.

 

 

[Flask Documentaton 보기]

  구글에 'flask documentation' 이라고 검색하면 아래의 최신 0.12 버전의 프레임워크에 대한 설명이 있는 링크를 얻을 수 있다.

http://flask.pocoo.org/docs/0.12/

 

  예전 수학 라이브러리 볼때 처럼, 쭉 목차를 살펴 보면, 먼저 'User Guide' 에는 Installation(설치) 부분이 있고(개인적으로 여러 버전을 운영할 일은 없을 듯 해서 vitualenv는 고려하지 않았다),  Quick Start 섹션에 기본적으로 기본적인 flask 웹 기동이나, url 을 파싱해 해당되는 파이썬 함수에 전달하는 라우팅, css 나 img 같은 정적인 파일에 접근하는 방법, 템플릿을 꾸며서 원하는 웹 화면을 보여주는 방법, request 된 데이터를 받아 처리하는 방법 등이 설명되어 있다. 그리고 Tutorial 에는 세팅 방법과, DB 를 조회해 화면에 보여주는 예제가 있는 것 같고, Templates 에는 여러가지 템플릿 제어 방법에 대한 설명, 한참 아래의 Patterns for Flask 에는 여러가지 플라스크로 웹을 구성하는 권장 기법들이 있다. 그 밑의 'API Reference''Additional Notes' 는 위의 사용자 가이드가 익숙해 졌을 때 추가적인 정보를 보기 위해 살펴보는게 맞을 거 같다(개인적으로 flask 를 둘러보면서 예제를 구현해 보려고 했을때 진행했던 흐름이다).

 

 

[Routing]

  실용적으로 보이는 프레임워크긴 하지만, 실제 웹코드 구현에 들어가기 전에 legacy 하고 차이가 나는 부분에 대해서 설명 후에 들어가려 한다(혹시 웹 프로그래밍에 대해서 익숙하지 않은 상태라면 앞 교시인 Legacy Web 파트를 꼭 읽어 보시고 오길 권한다). 먼저 routing 와 static files 에 대해서 생각해보자. 현재 가상현실, 증강현실이 주목받는 세상이지만, 컴퓨터 자체가 어느 정도는 해당 개념들의 표본이 아닌가 싶다. 컴퓨터 내의 파일이라는 개념에 대해 우리는 동영상, 그림파일, 문서파일, 음악파일 등을 자연스럽게 사용해와서 실체화된 것이라고 생각하지만, 실제로는 메모리나 디스크 상에 구분된 숫자에 불과하다는 사실은 맞을 것이다. 그 정보를 운영체제가 해석을 해서, 폴더나 파일로 구분하여 인식하고, 응용 프로그램이 해당 데이터를 전달 받아 우리가 볼 수 있도록 화면에 출력하거나, 소리로 출력하여 노래를 듣거나, 문서를 보거나 하는 것일 것이다. 일례로 오피스가 설치되 있지 않은 컴퓨터라면 doc, xls 파일은 아무 의미가 없는 파일일 것이다. 예컨데 특정한 처리를 해주는 로직을 만나야지만 우리가 파일이라고 믿는 것들이 의미가 생긴다는 것이다.

 

  비슷하게 우리가 legacy web 에서 'test.asp', 'hey.php' 같은 파일들이, 웹 프로그램 확장자를 가진 파일이여서, IIS, Apache 같은 웹 서버에서 해당 파일이 실행이 된다고 믿어왔던 것도 어떤 측면에서는 관념적인 것에 불과할지도 모른다. 역으로 얘기하면 어떤 이름과 확장자를 가진 URL 의 호출이 웹서버에 주어질 때, 그것을 어떻게 해석하냐는 폴더와 파일이라는 물리적인 요소에 달려있는게 아니라, 웹 서버가 요청을 어떻게 해석을 하냐는 논리적인 요소에 달려있다고 볼수 있다 

 

  이 부분이 최근의 웹 서버 모듈에서 볼수 있는 'routing' 이라는 개념이다. 클라이언트가 url 주소에 hey.php 를 요청하든 hey 를 요청하든, 웹 서버 모듈만 지원을 한다면, 해당 파일이라는 형태에 국한되지 않고 해석을 해,  파일 경로를 찾은 후 처리하는 대신에 특정한 로직(함수)으로 직접 전달할 수 있다. 즉 파일과 디렉토리 기반으로 움직이던 웹 서버의 동작을, URL 경로 규칙에 기반한 함수와의 연결 로직으로 추상화(혹은 일반화) 시켰다고 봐도 될 듯 싶다(그래서 개인적으로 확장자를 가진 웹에만 익숙하다가, 어느날 확장자가 없는 처음 웹을 만났을 때 웹서버 내에서 url 에 해당하는 실제 파일의 경로를 찾을 수가 없어서 당황했었던 기억이 난다). 결국 이렇게 되면 기존 웹에서 의미가 있었던 디렉토리와 파일명, 확장자는 모두 의미가 없는 껍데기가 된다. 이러한 URL 과 내부 기능과의 직접적인 연결을 하는 방식을, 네트워크에서 패킷을 적절한 경로로 안내하는 라우터의 역활을 차용해서 routing 이라고 명명한 것 같다.

 

 

[Static Files]

  같은 맥락에서 보면 이제 프로그램 확장자 파일이 아닌 css, jpeg 같은 정적인 파일에 대해서도 기존 웹과 같이 url 기반의 디렉토리와 파일이름 경로로 접근하기는 좀 힘들어 지게 됬다. 왜냐면 이젠 웹서버 모듈은 기존 웹과 같이 웹루트 폴더 기준의 트리 구조로 된게 아니라, 기본으로 라우팅이 되는 논리적 레벨의 매핑이기 때문이다. 그러한 논리적 레벨의 매핑을 특정 디렉토리를 기반으로한 물리적(이것도 앞에서 얘기했듯이 넓게보면 가상이긴 하지만) 매핑으로 잠시 변환하는 기능이, static files 이라고 보면 될것 같다. (flask 에서는 dynamic web, static web 이라는 표현으로 설명한다)

 

 

[Rendering Templates]

  예제를 구현하기 필요한 마지막 개념은 Rendering Templates 섹션이다. 사실 template 란건 특정 UI 의 재사용을 위한 개념으로 많이 사용되기는 하는데, flask 에서는 꼭 재사용이 아니더라도 UI 를 표현하기 위해선 하나의 템플릿(UI 를 표현한다는 측면과 재사용 적인 측면의 의미를 동시에 가졌다고 볼 수 있을 듯 싶다)을 사용해야 된다고 생각하면 된다. dynamic web 인 flask 의 어플리케이션 모듈 쪽에서 표현에 사용할 데이터를 준비한 후, 해당 데이터를 지정한 템플릿에 연관해서 처리를 한다. 이러한 구조가 HTML 과 어플리케이션 로직을 분리하기 위해서 라고 볼 수도 있겠지만, 사실 웹 자체의 베이스가 동적이여서 페이지 개념이 없어졌기 때문에, 최종적으로 페이지를 동적 프로그램 코드내에서 생성하게 되면, 이번엔 반대로 프로그램 코드에 UI코드가 섞이게 되어 의미가 없게 되니, 어쩔수 없이 최종으로 UI 를 표시하는 부분을 템플릿이라는 개념으로 다시 떼어낸 것도 같다. 마치 Javascript 와 HTML 을 event 속성이 연결하였듯이, 템플릿 또한 순수한 html 코드는 아니고, 앞의 ASP 의 <% %> 코드와 비슷하게, 전달된 데이터들을 템플릿 사이에 적절히 끼워주는 방식으로 구현된다(해당 작업을 렌더링이라고 표현하는 듯 하다).

 

 

[사전 준비 - Flask 설치]

파이썬 3를 지원하므로 pip 명령어로 설치하면 된다.

c:\Python\flaskweb>pip install flask

Collecting flask
....
Installing collected packages: flask
Successfully installed flask-0.12.2

 

 

  c:\python 폴더에 flaskweb 폴더(이 폴더 이름은 다른 아무 이름이나 된다)를 만든다. 다시 flaskweb 폴더내에 templates 폴더(이 폴더 이름은 약속된 이름이기 때문에 꼭 이 이름으로 만들어야 된다)를 만든다.

 

 

 

 

[DB 에서 데이터 불러와 HTML 테이블로 보여주기]

  첫번째 예제로 4교시에서 만들었던 예제를 응용해, MSSQL Server 에서 데이터를 불러 HTML 테이블로 출력하는 예제를 보이려고 한다. 여기에서 기본적인 routing, template 를 다루는 코드가 나오니 예제로 개념을 익히면 된다. 18교시와 마찬가지로 4교시에 만든 supermarket 테이블과 python 코드를 그대로 재사용 하려고 한다(4교시를 안해보셔서 환경이 없는 분들은 MSSQL Server 설치와 테이블 생성을 하시고 오셔야 한다).

 

 

[App Code 구현]

  해당 방식 구현을 위해 구글에서 'flask db to html table' 이라고 검색해서, 아래 3개의 페이지를 얻었다.

[DB 데이터를 템플릿에 넘기는 방식을 볼수 있음 - 템플릿 출력 부분 코드가 명확히 표현되 있진 않다.] 

https://stackoverflow.com/questions/29525758/data-from-sqlite-to-an-html-table-in-a-flask-page

[템플릿 출력 하는 코드가 명확히 나옴]
https://stackoverflow.com/questions/42040379/creating-an-html-table-with-database-values-in-flask

[메인 페이지에 SQL 초기화와 조회 관련 코드를 어떻게 배치할까에 대한 힌트]
https://stackoverflow.com/questions/38540256/flask-python-mysql-how-to-pass-selected-data-though-a-for-loop-and-return-it

 

 

  앞의 사용자 메뉴얼의 튜토리얼을 읽어 전체적인 분위기를 파악 후, 아래 3개의 코드들을 참조하고, 4교시에 만들어 놓았던 파이썬 코드를 결합시킨 최종 코드는 아래와 같다. 간단히 설명하면, flask 는 웹서버와 웹어플리케이션 기능을 같이 실행하는데(운영 단계에선 아파치 등과 연계하는게 맞을 듯은 싶다), 127.0.0.1 의 포트 5000번으로 서비스가 된다. 'sqltable' 경로가 호출되어 'showsql()' 함수가 시작이 되면, 지정된 SQL 문을 실행하여, templates 폴더에 있는(flask 의 몇 안되는 미리 약속되 있는 폴더이다) 'myweb.html' 파일과 DB 에서 가져온 전체 결과값(fetchall)을 지닌 'rows' 변수를 (아마 list)를 이용해서 렌더링 하여(rendering) 결과를 표시한다. 아래 코드는 결과를 표현해주는 템플릿 코드가 아직 만들어지지 않았기 때문에 아직은 반쪽의 코드라고 볼 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from flask import Flask
from flask import render_template
import pymssql
 
# MSSQL 연결 하기 
conn = pymssql.connect(server='localhost', user='pyuser', password='test1234', database='mytest')
cursor = conn.cursor()
 
# flask web 실행
app = Flask(__name__)
 
# 'sqltable' 이라는 URL 인자를 'showsql' 이라는 함수로 연결한다. 
@app.route("/sqltable")
def showsql():
    # SQL 문을 실행하여 supermarket 테이블에서 데이터를 가져온다.
    cursor.execute('SELECT Itemno, Category, FoodName, Company, Price FROM supermarket(nolock);')
    # 가져온 모든 데이터를 mytable.html 파일과 함께 랜더링 하여 표현한다.
    return render_template('myweb.html', rows = cursor.fetchall())
 
# 이 웹서버는 127.0.0.1 주소를 가지면 포트 5000번에 동작하며, 에러를 자세히 표시한다. 
if __name__ == "__main__":
    app.run(host='127.0.0.1',port=5000,debug=True)
cs

 

  위의 코드를 메모장에 넣고, 파일형식을 '모든파일', 인코딩을 'utf-8' 로 선택하여, c:\python\flaskweb 폴더에 myweb.py 로 저장한다.

 

 

[Templates Code 구현]

  다음은 myweb.html(이름은 호출하는 .py 파일의 이름과 달라도 무방하다) 템플릿 파일이다. 뭔가 전 시간의 ASP 코드 흐름과 비슷하지 않나 싶다. '<%' 대신 '{%' 로 파이썬 코드임을 표시하고, 안의 문법이 'vbscript' 대신 '파이썬' 문법인 차이같다.  사용자 메뉴얼을 보면 해당 템플릿을 표현하는 방식은 외부 모듈인 'Jinja2' 라는 템플릿 모듈을 차용했다라고 나온다. 대충 로직을 살펴 보면 테이블 외형을 뿌려주고('<table>태그') SQL 조회 결과를 한 줄씩 루프를 돌면서(for row in rows), '<tr>' 태그를 뿌려주고, 그 안에서 다시 해당 줄의 컬럼들을 선택하며 돌면서(for data in row), '<td>' 태그안에 그 값('{{data}}')을 넣어준다. 해당 부분은 ASP 코드랑도 비슷하고, 예전의 7교시 엑셀 시간에 배웠던 엑셀 파일로의 출력과도 비슷한 루프 구조를 가진다.

1
2
3
4
5
6
7
8
9
<table border="1" cellpadding="5" cellspacing="5">
{% for row in rows %}
    <tr>
    {% for data in row %}
        <td>{{ data }}</td>
    {% endfor %}
    </tr>
{% endfor %}
</table>
cs

 

  해당 코드를 역시 메모장에 복사하여, c:\python\flaskweb\templates 폴더에 myweb.html 로 저장한다(html 에 한글을 넣으려면 utf-8 인코딩으로 저장해야 에러가 안난다.)

 

 

  그럼 모든 코드가 구현되었고 c:\python\flaskweb\ 으로 이동하여, 아래와 같이 myweb.py 파일을 실행 한다.

c:\Python\flaskweb>python myweb.py
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 288-594-455
 * Running on
http://127.0.0.1:5000/ (Press CTRL+C to quit) 

 

  이후 브라우저를 열어 http://127.0.0.1:5000/sqltable 이라고 치면, myweb.py 상에서 sqltable 에 해당하는 showsql() 함수를 실행하여(일부러 독립된 요소라는 것을 보여주기 위해 이름들을 다 다르게 했다), 조회한 데이터를 templates 폴더에 있는 myweb.html 와 함께 랜더링 하여 아래와 같이 html 테이블을 보여준다(지금은 자연스럽게 보이지만 여기까지 올때까지의 몇 번의 시행착오 과정은 생략했다). 이 예제를 통해 기존 파이썬 로직들이 flask 라는 웹 서버 겸, 어플리케이션 모듈 프레임워크를 통해서 웹형식으로 표현되게된 흐름(flow)을 캐치하셨음 한다.

 

 

 

 

[D3.js 에서 Json 데이터 URL 를 호출해 웹으로 그래프 보여주기]

  D3.js 는 앞에서도 얘기했지만, 자바스크립트 쪽의 matplotlib 같은 시각화 라이브러리이다. 인기가 많은 편인 듯 해서, 디자인 강화에 중점을 둔 c3.js 등의 d3.js 기반의 라이브러리들도 있는 듯 하다. 해당 라이브러리의 컨셉은 목적상 matplotlib 과 역시 비슷하다(사실 모든 시각화 라이브러리가 비슷 한듯 하다). csv 등의 파일이나, 데이터를 반환하는 api 형태의 url 부터 json 데이터를 가져와서, 데이터 형을 잘 맞춰서, 원하는 그래프를 그려주는 라이브러리 함수에 공급한다. 그럼 보통 svg 형식으로 그래프를 그려(브라우저에서 HTML 문서안에 백터 그림을 나타내는 표준으로, svg와 canvas 두 가지 표준이 있다) 브라우저에 표시해 준다.

 

 

[App Code 구현]

  해당 페이지를 만들어 보기 위해 구글에서 'flask d3', 'd3 simple example', 'd3 simple date' 를 조회해서 아래의 3개의 페이지를 참고했다.

[전체적인 개념]
https://github.com/dfm/flask-d3-hello-world

[실제 동작 하는 코드]
http://bl.ocks.org/d3noob/b3ff6ae1c120eea654b5

[xxxx-xx-xx 형식의 날짜 데이터를 D3 에서 파싱하기 위해서]
https://stackoverflow.com/questions/13654609/draw-d3-simple-line-chart-with-an-array

 

 

  해당 페이지들의 예제들은 데이터 생성 로직이 조금 복잡하여, 주제에 집중하기 위해 17교시 머신러닝 예제 만들때 처럼 아래의 간단한 numpy 데이터를 임의로 만들었다.

1
2
3
   # 데이터 지정
    x = np.array(['2017-07-10''2017-07-11''2017-07-12''2017-07-13''2017-07-14'])
    y = np.array([58.1353.9867.0089.7099.00])
cs

 

 

  위의 예제들을 조합해서 정리한 프로그램쪽 코드는 아래와 같다. routing 경로가 2개로 늘어났다는 점만 제외하면, 나머진 다 앞에서 다루어 봤던 코드들이다. 조금 낯설은 코드는 json.dumps 명령어를 이용해서, json 데이터를 만들어 내는 부분이다.

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
import json
import flask
import numpy as np
 
app = flask.Flask(__name__)
 
# d3sample 을 호출했을때의 템플릿 설정
@app.route("/d3sample")
def showsample():
    return flask.render_template("d3sample.html")
 
# D3에서 가져갈 data url을 호출하면 반환할 json 데이터 만들어 내기 
@app.route("/data")
def data():
     
    # 데이터 지정
    x = np.array(['2017-07-10''2017-07-11''2017-07-12''2017-07-13''2017-07-14'])
    y = np.array([58.1353.9867.0089.7099.00])
 
    # 리스트를 json 데이터로 변환
    return json.dumps([{"date": x[i], "close": y[i]}
        for i in range(5)])
 
# 앞과 비슷한데 조금 틀려만 보임
if __name__ == "__main__":
    port = 5000
    app.debug = True
    app.run(port=port)
cs

 

  위의 코드를 메모장에 넣고, 파일형식은 '모든파일', 인코딩은 'utf-8' 로 선택하여, c:\python\flaskweb 폴더에 myweb_d3.py 로 저장한다(json.dumps 코드의 결과가 궁금 하시면 'http://127.0.0.1/data' 를 브라우저에서 호출해 본다).

 

 

[Templates Code 구현]

  그 다음은 랜더링에 사용할 d3samlpe.html 템플릿 파일이다. 안의 코드는 html 코드 보다는 d3.js 라이브러리를 사용하기 위한 자바스크립트 코드로 가득 차있다. 코드의 흐름을 보면 맨 아래 'd3.json("/data", callback)' 함수에서 'http://127.0.0.1/data' 경로를 호출하여 'json 형태의 데이터'를 얻어와서, 'callback' 함수에 넘겨준다. 'callback' 함수에서는 넘어온 데이터를 'd3.js 라이브러리' 함수에 입력하여 'svg 그래프'를 그린다. 상세한 코드들은 혹시 해당 라이브러리를 이용할 일이 있음(어차피 그래프 종류가 많아 각각 쓰임을 이해해야 한다) 이해하면 되고 여기선 flask 를 설명하는 목적이니 대충 주석과 흐름만 보자.

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
<!DOCTYPE html>
<meta charset="utf-8">
<style> <!-- 그래프 요소들의 스타일 지정 -->
body { font: 12px Arial;}
path { 
    stroke: steelblue;
    stroke-width: 2;
    fill: none;
}
.axis path,
.axis line {
    fill: none;
    stroke: grey;
    stroke-width: 1;
    shape-rendering: crispEdges;
}
 
</style>
<body>
<!-- 라이브러리 로딩. 내부에서 돌리려면 다운받아서 static 폴더에서 읽어와야 할듯 -->    
<script src="http://d3js.org/d3.v3.min.js"></script>
 
<script>
 
// 그래프 좌표 공간 설정
var margin = {top: 30, right: 20, bottom: 30, left: 50},
    width = 600 - margin.left - margin.right,
    height = 270 - margin.top - margin.bottom;
 
// 그래프 범위
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
 
// 축 정의
var xAxis = d3.svg.axis().scale(x)
    .orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
    .orient("left").ticks(5);
 
// 그래프 선 정의
var valueline = d3.svg.line()
    .x(function(d) { return x(d.date); })
    .y(function(d) { return y(d.close); });
    
// 캔버스 객체 생성
var svg = d3.select("body")
    .append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
    .append("g")
        .attr("transform"
              "translate(" + margin.left + "," + margin.top + ")");
 
// 2017-07-01 식으로 데이터를 해석하게 지정함
var parseDate = d3.time.format("%Y-%m-%d").parse;
 
// 전달 받은 데이터를 이용해서 그래프를 그린다.
var callback = function (data) {
    data.forEach(function(d) {
        d.date = parseDate(d.date);
        d.close = +d.close;
    });
 
    // 실데이터에 맞춰 그래프 범위 지정
    x.domain(d3.extent(data, function(d) { return d.date; }));
    y.domain([0, d3.max(data, function(d) { return d.close; })]);
 
    // 선 그리기.
    svg.append("path")
        .attr("class""line")
        .attr("d", valueline(data));
 
    // x축 그리기?
    svg.append("g")
        .attr("class""x axis")
        .attr("transform""translate(0," + height + ")")
        .call(xAxis);
 
    // y 축 그리기?
    svg.append("g")
        .attr("class""y axis")
        .call(yAxis);
 
};
 
// flask 에서 만든 http://127.0.0.1/data 를 호출하여 json 데이터를 가져와 callback 함수 호출
d3.json("/data", callback);
 
</script>
</body>
cs

 

  해당 코드를 역시 메모장에 복사하여, c:\python\flaskweb\templates 폴더에 utf-8 인코딩으로 d3sample.html 로 저장한다(한글 주석이 있어 utf-8 인코딩이 아니면 rendering 과정에서 에러가 난다).

 

 

  그럼 모든 코드가 구현되었고 c:\python\flaskweb\ 으로 이동하여, 아래와 같이 myweb_d3.py 파일을 실행 한다.

c:\Python\flaskweb>python myweb_d3.py
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 288-594-455
 * Running on
http://127.0.0.1:5000/ (Press CTRL+C to quit)

 

  이후 브라우저를 열어 http://127.0.0.1:5000/d3sample 이라고 치면, myweb_d3.py 상에서 d3sample 에 해당하는 showsample() 함수를 실행하여, d3sample.html 템플릿을 호출하면, 해당 템플릿 내에서 http://127.0.0.1:5000/data URL 을 호출하여, json 데이터를 받아서 D3.js 라이브러리를 이용해서 브라우저 화면에 아래와 같이 그래프를 출력하게 해준다(뭐 HTML 페이지에서 자바스크립트 d3.js 라이브러리를 이용해서 그래프를 그리는 것도 rendering 이라고 표현해도 된다).

 

 

  앞에서 설명한 방식으로 실제 동작하는지 확인하기 위해 이전 시간에 배운 피들러를 띄워 페이지 호출을 관찰해 보면, 아래와 같이 /d3sample 과 /data 가 차례로 호출되는 것을 볼 수 있다. 앞의 /d3sample 은 브라우저 주소창에서, 뒤의 /data 는 템플릿 랜더링 과정에서 호출한 것이다.

 

 

 

 

[matplotlib 그래프를 웹 페이지에 보여주기]

  마지막 예제는 위와 비슷한 데이터를 matplotlib 으로 그리고, HTML 페이지안에 해당 그림을 <img> 태그 형태로 삽입하는 예제이다.

 

 

[App Code 구현]

  예제를 구현하기 위해 구글에서 몇가지 샘플을 실행해 봤는데, python 2.x 대의 예제라서 라이브러리가 안 맞아 안 돌아 가거나, 3.x 대 예제인데, 실제로 에러는 안 나는데 이미지는 안 나오는(엑박표시) 경우가 많았었다. 몇 개의 예제를 검토해 본 바로는, 보통 두 가지 방식으로 구현 되는데, 첫째는 그려진 이미지를 static 폴더에 img 파일로 실제 물리적으로 저장한 후, html 템플릿 페이지를 띄워, 해당 이미지를 html 템플릿 내에 static 형식으로 포함하는 방법이 있고, 둘째는 <img> 태그 경로에 이미지를 생성하는 flask url 을 지정하여, 이미지를 마임(MIME) 데이터로 전달받아 브라우저에서 표시해 주는 방식이 있다. 여튼 결과적으로 'python 3 flask matplotlib html' 라고 검색해서 아래 2번째 방식으로 구현하는 예제를 찾았다.

http://dataviztalk.blogspot.kr/2016/01/serving-matplotlib-plot-that-follows.html

 

 

  해당 코드를 기반으로 데이터 생성만 간략화한 버전이 아래와 같다.

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
from io import BytesIO
from flask import Flask, render_template, send_file, make_response
import flask
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
import numpy as np
import matplotlib.pyplot as plt
 
app = flask.Flask(__name__)
 
# mypic 을 호출하면 mypic.html 로 렌더링 한다.
@app.route('/mypic')
def mypic():
    return flask.render_template("mypic.html")
 
# matplotlib 그래프 파일을 생성하여 소켓 통신으로 보내준다. 
@app.route('/plot')
def plot():
 
   # 그림판 준비
    fig, axis = plt.subplots(1)
    
   # 데이터 준비
    y = [1,2,3,4,5]
    x = [0,2,1,3,4]
 
    # 그리기
    axis.plot(x,y)
    canvas = FigureCanvas(fig)
    
    # 그려진 img 파일 내용을 html 랜더링 쪽에 전송한다.
    img = BytesIO()
    fig.savefig(img)
    img.seek(0)
    return send_file(img, mimetype='image/png')
 
 
if __name__ == '__main__':
    port = 5000
    app.debug = True
    app.run(port=port)
cs

 

  위의 코드를 메모장에 넣고, 파일형식은 '모든파일', 인코딩은 'utf-8' 로 선택하여, c:\python\flaskweb 폴더에 myweb_mat.py 로 저장한다.

 

 

[Templates Code 구현]

  그 다음은 mypic.html 템플릿 파일이다. 내부 코드는 엄청 간단해서 이미지 태그를 만들면서, 이미지 소스(src) 위치를 '/plot' 으로 지정한다. 그럼 http://127.0.0.1:5000/plot 을 읽어오며 실행되게 되어 해당 데이터가 MIME 데이터로 html 쪽에 전달되어 결합된다(사실 이런 방식은 처음 보는 거라서 좀 신기하긴 하다).

1
2
3
4
5
6
7
8
9
10
<html>
  <head>
    <title>image</title>
  </head>
  <body>
    matplotlib 으로부터 만들어진 이미지
    <p>
    <img src="/plot" alt="Image Placeholder">
  </body>
</html>
cs

 

  해당 코드를 역시 메모장에 복사하여, c:\python\flaskweb\templates 폴더에 utf-8 인코딩으로 mypic.html 로 저장한다.

 

 

  그럼 모든 코드가 구현되었고 c:\python\flaskweb\ 으로 이동하여, 아래와 같이 myweb_mat.py 파일을 실행 한다.

c:\Python\flaskweb>python myweb_mat.py
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 288-594-455
 * Running on
http://127.0.0.1:5000/ (Press CTRL+C to quit)

 

 

  이후 브라우저를 열어 'http://127.0.0.1:5000/mypic' 이라고 치면, myweb_mat.py 상에서 'mypic' url 에 해당하는 'mypic()' 함수를 실행하여, 'mypic.html' 템플릿을 호출하면, 해당 템플릿 내에서 'http://127.0.0.1:5000/plot' 경로를 호출하여, MIME 데이터로 이미지 스트림을 받아서 그림을 표시해 준다. 

 

 

 

 

 

[마무리 하면서]

  이렇게 해서 flask 프레임워크에 대해 간단히 살펴보는 시간이 끝났다. 'routing', 'dyanamic vs static web(static files)', 'rendering templetes' 가 주요 키워드인것 같다. 이전 시간에 legacy web 을 길게 설명하고, 이 시간에 flask 와 legacy web 을 비교하면서 얘기했던 방식이, 쉬운 이해에 도움이 되면 좋겠다. 여러 파이썬 웹 프레임워크에 대한 비교 글들의 끝에서 언급되지만 프레임워크의 선택은 개인의 일하는 스타일의 취향과, 구현하려는 목표에 해당 프레임워크가 추구하는 방향이 얼마나 적합한가에 따라 달라질 것이다. 실제 구현하려다 보면, 전체 구현하려는 대상에 비교해 프레임워크가 지원해 주는 기능은 정말 꼭 필요한 최소한의 기능 밖에 없는 것도 같긴 하다(물론 essential 한 부분이긴 하지만...). 

 

   추가로 d3.js 를 연계한 예제에서 봤듯이 웹의 많은 기능이 자바스크립트 기반에서 움직이기 때문에, 파이썬 로직으로 모든걸 해결하려는 것보다는, 파이썬 웹 어플리케이션 모듈 쪽에서는 데이터를 가공하여 제공하고, 실제 웹 쪽 UI 구현은 자바스크립트 라이브러리를 이용해 구현하면 효율이 좋은 경우도 많을 것 같다(웹의 1/3 쯤은 자바스크립트의 세상이고, d3.js 를 이해하기 위해서는 javascript 와 그래픽 라이브러리에 익숙하면 유리하기 때문에, 웹 전체에 연관되는 분야를 공부해 균형을 맞춰 놓는 것도 중요한 듯 하다). 다행인 점 하나는 'd3.js' 예제에서 봤듯이, 파이썬에서 배웠던 'matplotlib' 같은 목적이 비슷한 라이브러리를 사용해본 경험이, 많은 부분에서 비슷하게 적용 된다. 지금 이 시간이 기반이 되어 다음 시간인 Django 프레임워크에 대해서도 적절하게 설명할 수 있게 되길 바라며, Flask 살펴보기 시간을 마친다.  

 

 

 

 

2017.7.21 by 자유로운설탕
cs

 

  

 

 

 

 

 

 

 

 

posted by 자유로운설탕
2017. 7. 2. 19:54 프로그래밍

  이번 시간에는 파이썬 웹 프레임워크인 Flask, Django 를 살펴보기 전에 웹을 구성하는 기초적인 부분들에 대해서 살펴보려고 한다. HTML, CSS, Javascript, Web Server(IIS), Web Language(ASP), Ajax 에 대해 개념을 간단히 설명하고, 간단한 예제를 만들어 시연해 보면서, 일반적인 웹 환경이 어떻게 구성되어 있는지를 살펴보며, 다음 시간에 얘기할 비교적 최신 개념인 MVC(model view controller)나 Url Rewriting(Routing) 설명을 위한 사전 지식을 쌓아 놓으려 한다. 다만 위 하나하나의 분야는 이렇게 블로그 한 챕터에 담기에는 각각 수 권의 책으로 따로 분리해야 할 만큼 넓은 분야라서, 가볍지만 필요한 개념은 이해할 수 있을 정도로 설명을 진행해 보려고 한다. 만약 기존에 ASP, PHP, JSP 등으로 웹 페이지를 만들어 봤던 분들은 이번 장은 대충 넘겨보거나, 생략하여도 될듯 하다.

 

 

 

[목차]

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. 정리 - 이런저런 이야기

 

 

 

 

[들어가면서]

  왜 파이썬 공부 관련 글에서 하나의 챕터를 따로 빼내 HTML, Javascript, CSS 등의 다른 웹 언어와 지금은 유행이지난 스크립트형 웹 프로그래밍 언어인 ASP 를 언급하려 하냐면, 다음 시간을 위해 Flask 와 Django 를 살펴보다보니, 처음 웹을 접하는 사람들이 접근하기는 꽤 어려운 구조라는 생각이 들었기 때문이다. 아마 Django 같은 프레임 워크를 이해하기 위해서는 기존 Legacy Web 에 대한 지식은 기본으로 갖춘 상태에서, Url Rewriting, MVC 같은 비교적 최신의 개념들에 대한 이해가 추가적으로 선행되야 할 듯 싶다. 물론 파이썬도 어느정도 익숙해 졌다고 가정하고 말이다.

 

  다른 웹 프로그래밍 언어 등에서 기존 웹을 어느정도 경험해 본 사람들은 해당 쪽 방식과 접근 방식이 다른 부분 위주로 비교해 가면서 적응하면 되겠지만, 웹 프로그래밍 세상에 처음으로 들어온 사람들은 해당 프레임워크를 배울때 웹의 일반적인 지식들이 한꺼번에 같이 쏟아져 들어오기 때문에, 아마도 뭐가 프레임위크에 대한 얘긴지, 뭐가 일반적인 웹 기술에 대한 얘기인지 혼란에 빠질 듯 싶다. 또 해당 방식이 기존 방식에 대해 어떤 장단점을 가지고 있는 건지를 알지 못하고 맹목적으로 받아들일 수도 있다. 앞의 시간하고 비교하면 웹 자동화 프레임워크인 selenium 을 사용하고 싶은데 웹 동작을 담당하는 HTML 이나 자바스크립트를 이해 못한 상태에서 막연히 배우는 것보다도 더 힘들지 않을까 싶다. 

 

  또한 구조적인 프레임워크는 분명히 여러 장점을 가지고 있는건 맞지만, 초보자의 입장에서 봤을때는 ASP 와 같은 Legacy Web Language은 아무래도 URL 이 바로 웹 페이지 파일 자체와 일치되는 1:1 관계의 직관성을 제공하여 접근하기가 좀더 쉽지 않을까 싶다. 또한 ASP, PHP 같은 스크립트 언어들은 컴파일 과정이 없이 바로 결과를 볼수 있어서 .NET 이나 JAVA 같은 컴파일 형 언어보다는 에러를 쉽게 만나고 수정해 볼수 있다(개인적으로는 수많은 에러를 만나고 해결하는 과정이 프로그램을 배우는데는 아주 중요한 경험이라고 생각한다). 그래서 flask 나 Django 에 대한 컨셉 설명을 용이하게 해보기 위해 앞서 머신러닝 챕터 진행 전에 수학, 그래픽 라이브러리를 소개하여 분리할 수 있는 개념을 떼어낸 것과 비슷한 일을 시도하려고 한다.

 

 

[IIS, ASP 에 대해서]

  참고로 여기서 잠시 다루는 웹 스크립트 언어인 ASP 는 현재 글을 진행하는 환경인 윈도우즈10 홈 버전이면 프로그램 추가/제거를 이용해서, IIS(Internet Information Server-아파치 같은 윈도우즈쪽 MS 웹 서버임)를 설치해서 비교적 간단히 사용이 가능하다. ASP 는 PHP, JSP 와 거의 기능적으로 비슷하다고 봐도 될듯 하다(뭐 두 언어는 계속 발전해 와서 .NET 으로 전략적으로 마이그레이션 하면서 버려진 ASP 와는 갭이 크다고 말하시는 분들도 있겠지만, 개인적으로 생각하기에는, 서로들 좋아 보이는 점들을 한참 차용했기 때문에 일반적인 기능 범위는 비슷하고 문법 측면만 틀리다고 생각한다. 물론 어떻게든 비슷하게 구현을 할수 있다는 얘기지, 더 이상 새로운 문법 구조나 라이브러리가 지원 되지 않기 때문에 난이도가 같다는 것은 아니다).

 

  지금 생각하면 좀 낯설지만 예전엔 위의 3개 언어가 웹프로그래밍 언어의 패권을 다투기 위해 경쟁하던 시대도 있었었다. 지금은 뭐 더 많은 웹프로그래밍 언어들이 경쟁하는 춘추전국시대에 있는듯 하며, 사실 고수준 언어가 많은 부분을 모듈화 해서 관리해 주지만, 어떤 언어를 쓰냐보다는 어떻게 설계하여 쓰느냐가 더 중요한듯도 싶긴한다.

 

  윈도우즈 7의 경우는 그때의 MS 라이센스 정책의 방향 땜에 홈 버전에서는 IIS 설치가 안되고, 프로페셔널 버전에서만 지원되니, 혹 윈도우즈 7 홈 환경으로 강좌를 따라오고 있는 분이라면, 눈으로 코드 흐름만 살펴 보셔야 할듯 싶다. 개념을 설명하기 위해 관련 코드를 만든것이기 때문에 그러셔도 무방하다(의사코드 대신 ASP 를 사용했다고 봐도 좋을듯 싶다)

 

 

 

 

[웹은 어떻게 동작하는가?]

  웹 브라우징은 기본적으로 아래 그림과 같이 브라우저와 웹 서버가 중심이 되어 일어나는 행위이다. 웹서버는 우리가 많이 아는 IIS(ASP, .NET), 아파치(PHP), 톰캣(JSP) 부터 node.js(Javascript), 파이썬 자체 웹서버 등 다양하다. 브라우저 주소창에 웹페이지 주소를 입력하거나, 또는 결제창에서 결제 버튼을 누르거나, 특정 페이지에서 다음 버튼을 누르거나 할때, 브라우저가 웹 서버에 명시적으로 요청을 보낸다. 해당 요청은 패킷이라는 조그만 신호 단위에 담겨서, 네트워크 카드를 통해서, 인터넷 세상으로 나가게 된다. 

 

  인터넷 세상에서는 라우터와 스위치라는 장치를 통해서 해당 되는 주소(정확하게는 DNS 서버를 통해 얻어온 IP)가 가리키는 사이트로 이동되게 된다. 그럼 해당 서버는 그 요청을 받아서, 포트에 대기(listen)하고 있는 있는 웹 서버(예를 들면 아파치) 프로그램에게 전달하게 된다. 해당 웹 서버 프로그램은 해당 요청의 form 요소등에 대해서 프로그래밍 로직을 적용하여 DB의 내용을 조회하거나, 저장하거나 한후 최종 처리 결과를 HTML 형식으로 꾸며 사용자 브라우저에게 다시 보내준다. 사용자 브라우저는 해당 정보를 구조<tag>에 맞게 적절히 해석하여 사용자에게 그래픽 적인 웹 페이지 화면으로 보여준다. 브라우저에서 특정한 옵션을 설정하는 경우 브라우저와 네트워크 카드 사이에서 웹 프록시 형태의 프로그램이 패킷을 중계하는 일도 있는데, 그게 우리가 사용해본 fiddler 같은 HTTP 패킷을 보는 툴의 동작 원리이다.

 

 

 

 

[웹을 구성하는 언어들]

  웹에서 사용되는 언어들은 보통 어느 측면에서 사용되느냐에 따라 클라이언트 언어(브라우저)와 서버(웹 서버 프로그램) 언어로 나눠볼 수 있다. 클라이언트 언어는 HTML, CSS, Javascript, AJAX 같은 언어로 이루어져 있고, 서버 언어는 JAVA, .Net 같이 컴파일이 되어 동작하는 언어와 ASP, PHP, JSP, Python 같은 스크립트 형태(사실 이것도 실시간 컴파일이라고 봐야된다. 그리고 PHP 도 컴파일 해서 사용이 되는 것 같기도 하고, JSP 도 뒷단은 자바 class 파일을 호출하는 경우도 많은 듯 하니 사실은 구분이 조금 묘하긴 하다)로 이루어진 언어로 이루어져 있다(요즘은 Javascript로 동작하는 서버 환경인 Node.js 가 나오는 등 서버와 클라이언트 언어라는 절대적인 구분이 점점 모호해 져가는 듯은 하다. 파이썬도 웹과 시스템 양쪽에서 쓸수 있듯이 말이다). 또 DB쪽 언어인 SQL(Structed Query Lanauage) 언어도 있다. 브라우저는 클라이언트 언어들을 이용해 사용자의 액션에 반응하거나, 그래픽적인 화면 UI을 보여주고, 사용자의 입력들을 받아 form 이나 json 등에 담아서 서버 쪽으로 전달한다. 서버 언어는 전달된 클라이언트의 데이터들을 미리 작성된 프로그램 로직에 맞춰 처리하여, DB에 저장하거나 하며, 이후 클라이언트 언어 형태로 브라우저에게 적절히 응답을 주게된다.

 

 

  HTML(Hyper Text Markup Language) 은 우리가 매일 클릭하는 링크(hyperlink)와, 페이지 구조를 담고 있는 태그(markup)로 이루어진 언어이다. CSS(Cascade Sytle Sheet)는 초기 HTML 로부터 디자인 속성들을 따로 분리해낸 언어라고 볼 수 있다. Javascript는 초창기의 정적인 HTML위에 event 속성과의 협업을 통한 사용자와의 상호작용으로 생명을 불어 넣어주었다고 할수 있으며, HTML을 개념적으로 구조화한 DOM(Document Object Model) 객체를 이용하여 HTML 요소들을 조작한다. 우리가 웹에서 보는 모든 동적인 동작들이 Javascript 의 출현 덕분에 일어난다고 보면 되며, 파이썬과 비슷할 정도로 다재다능하고 복잡한 언어이며, Node.js 의 출현 덕분에 서버 쪽 언어로도 사용되게 됬다. Ajax(Asynchronous JavaScript and XML)는 자바스크립트로 만들어진, 멈춰진 HTML 페이지 뒤에서 리퀘스트를 날릴 수 있는 라이브러리 묶음이라고 생각하면 될것 같고, HTML 의 <form> 을 이용하지 않고도 json, xml, text 등의 데이터 형태를 이용하여 브라우저 뒤에서 비동기적으로 통신하는 것을 지원한다.

 

  서버쪽의 JAVA나 .NET 등의 컴파일 언어의 경우 사용전 빌드 과정이 꼭 필요하고, 초기 공통 바이너리 로딩 등에 부하가 걸린다고 하지만, 일반적으로 초기 로드 동작이 끝나면 스크립트 형식의 언어보다는, 메모리를 이용해 좀 더 자원을 효율적으로 공유한다고 한다. SQL(Structed Query Language) 은 MSSQL, Oracle, MySQL 등에 쿼리를 날리는 공통 표준으로 실제로는 SQL서버 종류별로 문법이 조금씩 차이는 있으며, 사용자의 요청에 따라 서버 쪽 프로그램에서 데이터를 조회하거나 저장하는 데 사용한다.

 

 

 

 

[HTML 살펴보기]

  HTML 은 아래의 그림 처럼, 하이퍼링크와 태그로 이루어진 언어이다. Markup 은 문서의 활자나 구조를 잡아주는 것을 얘기하는데, HTML 에서도 비슷하게 구조를 잡아주는 요소의 의미를 가지게 된다. HTML 은 밑의 로봇 그림처럼 헤더(header)와 바디(body)라는 것을 가지게 되는데(HTML5 에는 푸터-footer도 있긴 하던데, 어찌 봄 전체 구분 구조자체가 많이 바뀌었으니 여기서는 무시하자), 헤더에는 문서에 대한 여러가지 배경정보(제목, 작성자 등)들이 들어가고, 바디안에 우리가 실제 브라우저에서 보는 화면들이 들어간다고 보면 된다.

 

  그림을 보면 <html> 태그안에 <head> 와 <body> 태그 쌍이 있고, 제목(<title>)을 좀 큰 글자(<h1>)로 보여주고, 한칸을 띈후(<p>), '본문' 이라고 적힌 HTML 문서가 브라우저에서 열리면 해당 정의된 대로 화면에 표시되는 것을 볼 수 있다.

 

 

  브라우저가 HTML 을 해석하는 것은 사실 이미지뷰어 프로그램이나 메모장이 하는 일과 비슷하다. 이미지 뷰어 프로그램이 읽은 이미지에 대해 이미지 종류, 압축방식(jpeg, png 등), 좌표와 색정보에 따라서 화면에 뿌려주거나, 메모장이 텍스트 파일내에 있는 문자, 줄바꿈 기호, 탭(우리 눈에는 글자들이 탭으로 구분된 것으로 보이지만, 실제의 텍스트 파일 내부에는 아스키 코드 09 같은 특별한 기호로 사실 정의되어 있다)을 해석해 우리에게 보여주듯, 브라우저가 HTML 형태의 정보를 받으면 헤더, 바디에 있는 여러 태그 정보들을 분석해서, 화면에 우리가 볼 수 있도록 표시해 주는 것이다(이를 HTML 랜더링 이라고 말한다) 

 

 

 

 

  그럼 HTML의 모든 태그를 다 볼순 없으니 대표적인 몇개만 살펴보자

 

[1- TABLE 태그]

  11교시에서 잠시 다루었지만, 테이블은 아래와 같은 기본 구조를 가진다. 가장 바깥은 <table> 태그로 쌓여 있고, <th> 는 맨위에 있는 제목 필드라고 보면 되고(테이블에서는 옵션 태그라 없어도 무방), <tr> 은 엑셀의 row 같이 테이블의 한 행을 나타내고, <td> 는 하나의 입력 칸인 셀(cell)을 얘기한다. 그래서 테이블의 구조는 <table> 태그 안에 줄을 나타내는 <tr> 태그들이 쭉 있고, 각 <tr> 태그 안에 칸을 나타내는 <td> 태그들이 들어가 있는 단순한 구조이다. 근데 이 단순한 구조로 이것저것 다양한 형태의 테이블을 만들거나 페이지의 구조를 잡다보니 은근 분석하기 어려울 정도로 복잡해 질 때도 많다.

 

 

 

  가장 간단한 구조의 테이블 예제는 아래와 같다. <table> 태그가 맨 밖에 있고, 선(border) 굵기가 1 사이즈를 가진다. <th> 태그안에 제목인 '과자'와 '초콜릿'이  들어있고, 줄을 나타내는<tr> 태그가 두개 있는데, 하나에는 '파이, 카카오45%' 가, 나머지 하나에는 '머랭, 카카오100%'  가 각각 <td> 태그안에 나눠 담겨 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<table border=1>
  <tr> 
     <th>과자</th>    
     <th>초콜릿</th>  
  </tr>  
  <tr>    
     <td>파이</td>
     <td>카카오45%</td>
  </tr>  
  <tr>   
     <td>머랭</td>
     <td>카카오100%</td>  
  </tr>
</table>
cs

 

  c:\python\code 폴더에, 파일형식을 '모든 파일'로 선택하고 table.html (또는 table.htm) 이라고 저장한다. 이후 탐색기에서 해당 파일을 더블클릭해 실행하면 브라우저가 뜨면서 아래와 같이 지정한 테이블이 표시된다.

 

 

  테이블에는 아래와 같은 스타일을 나타내는 속성들이 있다(속성 중 주요한 일부만 표시함). HTML을 복잡하게 생각하지 말고, 우리가 많이 쓰는 워드나, 한글 등에서 작성하는 문서를 브라우저가 이해할 수 있게 태그로 표현한다고 생각하면 된다. 글자를 크게하거나, 오른쪽 정렬을 하거나, 표의 색을 정하거나, 셀의 여백 값을 정하거나 하는 부분들을 아래와 같은 태그 내 속성에 넣어 넣어서 해결한다고 생각해 보면, 속성이 이렇게 많은 이유를 이해할 수 있을 것이다.

 

  그럼 일부 속성을 사용해 보자. 속성 이름들은 <table> 같은 하나의 태그에서만 독점해 쓰이는게 아니고, 비슷하게 속성을 정의할 다른 태그들이 있다면 동일한 이름으로 사용된다. 아래에서는 <th> 태그 내에 배경색(bgcolor : 색은 'yellow' 와 같은 예약된 영어이름이나, '# + 16진수 숫자' 을 이용해 R, G, B 로 표현 가능하다)을 입히고, '파이'와 '머랭'가 들어간 셀의 사이즈를 200 pixel로 늘이고, 특히 '파이'가 들어간 셀은 가운데 정렬을 한다.  

1
2
3
4
5
6
7
8
9
10
11
12
13
<table border=1>
  <tr>
     <th bgcolor=#FF22CC>과자</th>    
     <th bgcolor=yellow>초콜릿</th>
  <tr>    
     <td width=200px align = center>파이</td>
     <td>카카오45%</td>
  </tr>  
  <tr>   
     <td width=200px>머랭</td>
     <td>카카오100%</td>  
  </tr>
</table>
cs

 

  위와 마찬가지로 c:\python\code 폴더에 table2.html 에 저장하여, 브라우저로 실행하면 아래와 같다. 

 

 

  이번엔 맨 마지막에 <tr> 행을 하나 추가하며, 내부의 두개의 셀을 합쳐보자. 해당 역활을 해주는 속성이 'colspan'(column span:컬럼 폭)이다. 이것을 2라고 해주면 위의 테이블을 기반으로 해서 2개의 셀을 세어서, 밑에 하나로 합쳐 표시해 준다(처음 테이블을 만들어 이것저것 해보면 colspan, rowspan{위아래합치기} 개념이 복잡한 테이블에서는 조금 헷깔리긴 했었다)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<table border=1>  
  <tr>
     <th>과자</th>    
     <th>초콜릿</th>  
  </tr>  
  <tr>    
     <td>파이</td>
     <td>카카오45%</td>
  </tr>  
  <tr>   
     <td>머랭</td>
     <td>카카오100%</td>  
  </tr>
  <tr>   
     <td colspan=2>비고:살찌는거조심</td>
  </tr>
</table>
cs

 

   c:\python\code 폴더에 table3.html 에 저장하여, 브라우저로 실행하면 아래와 같다. 

 

 

 

[2- FONT 태그]

  다음으로 <font> 태그는 아래와 같다. 해당 태그 사이에 들어가 있는 문장의 색, 크기, 폰트 등을 정의 한다(역시 워드의 글자 스타일을 생각해보면 된다).

1
2
3
4
5
6
<html>
  <body>
    <font size="5" color="blue">첫번째 폰트</font>
    <font face="궁서체" color="green">두번째 폰트</font> 
  </body>
</html>
cs

 

  c:\python\code 폴더에 font.html 에 저장하여, 브라우저로 실행하면 아래와 같다. 

 

 

 

[3- Form 태그]

  세 번째 태그인 폼(<form>) 은 사용자가 입력한 데이터를 서버로 전송하기 위한 요소이다. 우리가 검색 페이지에서 검색어를 넣고 '검색하기 버튼'을 누르거나, 여러 결제 옵션을 선택하고 '결제하기 버튼'을 눌렀을때, 우리가 입력하거나 라디오버튼 등으로 선택한 값들을 서버 쪽으로 묶어 전송하는 역활을 하는 태그가 <form> 이다. 서버 쪽으로 데이터를 날릴때는 HTML 전체 데이터가 아니라 이 <form> 안에 담긴 데이터만 날아간다(물론 이 설명 부분은 요즈음에 와서는 json 이나 xml 을 데이터 형식으로 주로 쓰는 AJAX 와 같은 비동기 방식이나, .net의 viewState 같은 새로운 전송 역활을 하는 형식들이 생겨서 예전같이 절대적이진 않는듯 하다).

 

 

  밑의 그림에 나타난것 처럼 폼은 <form> 이라는 태그로 감싸져 있고, 그 안에 여러가지 사용자의 입력을 받는 태그들이 들어가게 된다(이 부분도 윈도우 GUI 화면 요소들을 떠올리면 쉽게 이해갈 것이다. <form>은 윈도우즈의 다이얼로그 박스와 비견 될듯하다). 폼안에 들어가는 태그는 txt 형태의 필드, 입력값을 와일드 카드로 가려주는 password 형태의 필드, 라디오 버튼, 체크 박스, 셀렉트 박스 등의 다양한 요소 들이 있다. 사용자가 type="submit" 으로 속성이 지정된 버튼을 누르게 되면, 폼의 action 속성에 지정된 URL이 호출되며 폼내 정보들이 전달된다. 

 

  아래의 소스는 위의 그림의 소스를 옮겨놓은 것이다.

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
<form name=“basic" action="http://127.0.0.1/demo_form_action.asp" method="get">
이름: <input type="text" name="myname">
<br><br>
아이디: <input type="text" name="id">
<br>
패스워드: <input type="password" name="pwd">
<br><br>
<input type="radio" name="address" value="seoul">서울
<br>
<input type="radio" name="address" value="busan">부산
<br><br>
<input type="checkbox" name="hobby" value="gym">취미는 헬스
<br>
<input type="checkbox" name="hobby" value="book">취미는 독서
<br><br>
한달 용돈 : 
<select name="pocketmoney">
   <option value="100000">10만원</option>
   <option value="200000">20만원</option>
 </select>
<br><br>
<input type="submit" value="전송"> 
 
</form> 
cs

 

  c:\python\code 폴더에 form.html 로 저장하여 브라우저로 띄워보자. '전송'버튼을 누르면 지금은 없는 페이지를 호출 하기 때문에, 에러가 나긴 할 것이다.

 

  그럼 피들러를 이용해 샘플 페이지에서 전송 버튼을 눌렀을때 폼이 어떻게 날아가는지 실제로 봐보도록 해보자(피들러는 10교시 때 설치 및 기초 사용법을 설명했다). 밑의 피들러 그림을 보면 왼쪽 url 항목에 우리가 지정했던 demo_form_action.asp 파일이 있고(상대 경로로 지정했기 때문에 앞의 도메인과 폴더 부분은 127.0.0.1의 루트 폴더 그대로 이다), 파일이름 뒤에 물음표와 함께 form 안에 담겨있는 태그 요소들이 name 속성을 기준으로 'myname=Hello', 'id=freesugar' 식으로 값이 어사인되어 전송되는 것이 보인다(예전에 웹페이지 파싱 시간에 잠시 얘기했지만 폼 및 폼 내부의 태그들은 name 속성을 기준으로 구별된다).

 

 

  조금 더 보충해서 설명하면, 우리가 주소창에 주소를 입력하여 구글 웹사이트에서 특정 웹 폴더내에 있는, 특정 파일(test.html)을 요청하여 가져오는 것처럼, 페이지내에서 submit 버튼을 눌렀을 때는 폼 태그 내의 action 에 정의되어 있는 URL 경로를 호출하면서 form 안에 지정된 값을 모두 모아서 전송을 한다(해당 부분은 브라우저가 알아서 해준다)

 

 

 

[4-EVENT]

  event 속성의 설명은 여기서 진행하진 않고 Javascript 와 뗄수 없는 관계니 뒤쪽 Javascript 섹션에서 설명 하려고 한다.

 

 

 

[HTML 마무리]

  그럼 이런 다양한 HTML 태그들과 속성들은 어떻게 접근해야 될까? 추천 하는 방법은 '헤드 퍼스트 html' 같은 가벼운 책을 한권 읽어보거나(개인적으로 헤드퍼스트 시리즈가 있으면 워밍업 용으로 먼저 본다. 대신 안의 낱말 맞추기나 퀴즈는 시간도 걸리고 쪽지시험 같아서 잘 안 푸는 편이다. 다만 저자가 다 다르기 때문에 시리즈 별로 품질이 차이가 좀 있다). HTML 책에 돈을 들이기 아까운 분은, 아래의 w3school 사이트의 샘플을 보거나, 구글 검색을 통해 필요한 태그를 조금씩 봐도 된다. 어차피 웹 프로그래밍 공부를 하다보면 태그는 계속 찾아 볼수 밖에 없다.

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

 

  하지만 아마도 초보자 분은 w3school 나 구글에서 뭘 봐야할지를 모를 것 같기 때문에, 책이나 웹상의 HTML 관련 블로그나 관련 무료 강의를 보기를 추천한다(대신 이 방식은 a b c 로 진행되는 경우가 많아서 따라가다가 지칠 수도 있다). 그리고 무엇보다 중요한 것은 어느정도 알 것 같은 느낌이 들면, 직접 원하는 UI의 웹 페이지를 만들어 보면서 벽에 부딫치고, 해결해 보는 것이 좋다.

 

  HTML 은 브라우저로 모든 소스를 볼수 있기 때문에, 디자인이 좋은 페이지의 궁금한 요소들을 뜯어 보는 것도 좋다(11교시에서 설명한 브라우저 개발자 도구의 '요소보기'는 웹의 보고싶은 부분을 뜯어보는데 아주 편리한 도구이다). 어느 정도 공부를 해서 잘 안다고 생각해도 막상 웹페이지를 만들어 보거나, 현실 웹의 소스를 보기 힘들도록 꼬아놓은 자바스크립트, CSS, HTML 을 보게되면 한숨이 나올때가 있을 것이다. 쉽게 안되는게 아쉽지만 이런 공부는 'no pain, no gain' 이기 때문에 어쩔수 없다.

 

  HTML 은 4.01 표준과 5.0 이 있는데, 5.0 은 정적인 4.01 환경에서 좀더 동적인 웹을 위한 확장 킷이라고 봐도 될것 같다(개인적으로 게임의 확장팩 같다고 생각한다). 그래서 일단 4.01 위주로 공부한 후 5.0 내용을 보는 것이 좀 더 효율적일 것 하다(초보자분이 HTML 공부한다고 HTML5 책을 덜컥 사버리면 아마 맨붕이 올지도 모른다). HTML 태그가 워낙 잡다한게 많기 때문에(MS워드의 잘 안쓰는 잡다한 기능들과 같다고 보면 된다) 먼저 공부를 추천하는 기초 html 요소들을 아래에 정리해 놨으니 참고 하시길 바란다. 개인적으로 아래 정도만 알고 조금 헤메보면  beautifulsoup 같은 웹 라이브러리를 사용해 일반적인 웹페이지의 HTML을 파싱할 정도는 될 거라고 생각한다 . 물론 다음에 언급할 자바스크립트와 CSS는 HTML과 실과 바늘의 관계라고 볼수 있어 크롤링 등을 위해 페이지에 대한 분석을 잘 하고 싶다면 세 가지 언어를 비슷한 레벨로 수준을 맞춰 놓는게 좋다(어찌봄 원래 하나일 걸 3개로 나눴다고 봐도 된다). 거기다 웹프로그래밍 언어까지 얹어 배우게 되면(아마 자연스럽게 DB도 배우게 될테고) 웹 기술에 대한 전체적인 기초 그림이 완성이 된다고 생각한다.

 

[HTML 추천 태그 및 개념]

  • 기본구조용: <head>, <body>, <br>, <table>, <font>, <a>, <b>, <h1>~<h6>, <hr>, <i>, <p>, <title>, <meta>, <ol>, <ul>, <li>,
  • 프레임 태그: <frame>, <frameset>, <iframe>
  • 이미지 관련: <image>, <map>, <area>
  • 미묘한 구조의 확장: <div>, <span>
  • 외부와의 연결: <object>, 폼: <form>, <input>, <textarea>, <select>, <option>
  • 주석: <!-- -->
  • URL, 절대경로, 상대경로
  • (form 에 관련된) get, post 인자 개념
  • (자바스크립트를 배우는 초입인) event 속성

 

 

 

 

[CSS]

  CSS 는 문법으로 세세히 들어가면 무척 복잡해지는거 같긴 하지만, 간단하게 컨셉만 얘기하면, HTML 에서 각 태그의 디자인 속성들을 독립시켜 읽고 쓰거나, 관리하기 편하게 만든 것이라고 생각한다. 이렇게 무언가를 분리시켜 관리가 편하게 만드는 것은 다음에 나올 MVC 나 객체지향 프로그램, 함수 같은 요소의 공통점인것 같다.  아무래도 복잡히 꼬인 실타래 코드 보다는, 정리되고 분리되어 명확한 코드가 파악도 잘 되고 유지보수도 쉬울 테니까 말이다. 

 

 

  예를 들어 아래와 같은 HTML 코드가 있다면, 단순한 기본 모양의 테이블 이겠지만,

1
2
3
4
5
6
7
8
9
10
<table> 
  <tr>     
     <th>과자</th>    
     <th>초콜릿</th>  
  </tr>  
  <tr>    
     <td>파이</td>
     <td rowspan=2>카카오45%</td>
  </tr>  
</table>
cs

 

  아래와 같은 <style> 태그 안에 담긴 CSS 형식으로 <table>, <td>, <th> 의 디자인 속성을 정의한 파일이 있다면,

1
2
3
4
5
6
7
8
9
10
11
<style>
table, td, th
 {
 border:1px solid green;
 }
 th
 {
 background-color:green;
 color:white;
}
</style>
cs

 

  위의 두 개의 서로 다른 코드를(HTML+CSS) 합쳐서, 아래와 같이 하나의 html 파일로 만들면 서로 독립된 HTML 과 CSS 가 같이 연합해 동작하게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<style>
table, td, th
 {
 border:1px solid green;
 }
 th
 {
 background-color:green;
 color:white;
}
</style>
 
<table> 
  <tr>     
     <th>과자</th>    
     <th>초콜릿</th>  
  </tr>  
  <tr>    
     <td>파이</td>
     <td rowspan=2>카카오45%</td>
  </tr>  
</table>
cs

 

  c:\python\code 폴더에 css.html 로 저장하여 브라우저로 열어보자. <style> 태그안에 CSS 형식으로 정의한 스타일들이, 공간적으로는 분리되어 있는 HTML 코드에 적용되어 아래와 같이 꾸며진다(물론 이 부분은 브라우저가 소스를 파싱하여 적용해주는 것이다).

 

 

  위와 비슷한 표현을 하는 HTML 코드 형식으로만 이루어진 아래의 코드와 비교해 보면, 디자인 속성의 분리라는 점이 얼마나 코드를 깔끔하게 정리해 주는지 볼수 있다. 만약 복잡한 HTML 페이지의 디자인을 수정 시 아래처럼 각각 태그마다 디자인이 정의된 코드를 수정하는 것보다는, 위의 CSS 스타일로 분리된 코드를 수정하는 편이 좀 더 쉽고, 편리할 것 같지 않은가 싶다. CSS 는 HTML 의 디자인 작업을 개념적으로 분리시키고, 중복 코드를 제거해 쉽고 명확하게 만들어 준 측면이 있는 것 같다(HTML5 에는 아래 대부분의 디자인 속성을 안 쓰고 CSS 스타일을 사용하게 하는듯 하다).

1
2
3
4
5
6
7
8
9
10
<table border="1"> 
  <tr>     
     <th border="1" bgcolor="green"><font color=white>과자</font></th>    
     <th border="1" bgcolor="green"><font color=white>초콜릿</font></th> 
  </tr>  
  <tr>    
     <td border="1">파이</td>
     <td rowspan=2 border="1">카카오45%</td>
  </tr>  
</table>
cs

 

 

[CSS 마무리]

  CSS 에는 많은 디자인 요소들이 있고, class 를 지정하여 특정한 디자인을 선택해 적용하거나, css selector 같은 주제도 있다.  자세한 부분은 관련 블로그나 책을 한권 훝어 보는 걸 권장한다.

 

 

 

 

 

[Javascript]

  자바스크립트를 설명하려면, HTML 파트에서 설명을 뒤로 미뤘던, HTML 과 자바스크립트를 연결 해주는 요소인 '이벤트(event)' 에 대해 설명해야 한다. 이벤트는 아래 그림과 같이 윈도우즈 프로그램을 움직이게 하는 이벤트 개념이, 브라우저 내의 DOM 객체를 대상으로 구현된 것으로 봐도 될듯 한다. 윈도우즈 운영체제에서 사용자의 키보드, 마우스의 움직임이 어떤 프로그램 창의 어떤 사용자 컨트롤에서 발생했는 지에 따라 이벤트를 발생시켜 처리를 한다면, 브라우저 내에서도 사용자들의 여러 키보드, 마우스 액션이 HTML 페이지내 DOM 의 어떤 요소에서 일어났는지에 따라서, 해당되는 이벤트를 일으켜 자바스크립트를 이용해 처리하게 만드는 구조이다. 아래에 종종 볼수 있는 HTML 이벤트들을 정리해봤다.

 

 

  해당 이벤트가 동적인 웹을 구성하는데 어떤 역활을 하는지 처음 보는 분들은 감이 안 잡힐 듯도 싶어서, 대표적인 적용 예들을 밑에 표시했다(구글이 onchange 인지, onkeyup 일지는 잘 모르겠다^^). 밑의 예에서 유추해 보면 웹에서 UI가 사용자 동작에 따라서 반응하는 부분은 대부분 이런 이벤트+자바스크립트의 도움으로 이루어진다고 보면 될 것이다.

 

 

[자바스크립트 예제 1]

  그럼 간단한 자바스크립트 예제를 2개만 보자. 아래의 코드를 간단히 설명하면 하단에 input box가 두개 있고, 박스 내를 클릭하면 'onfocus' 이벤트가 발생 하며, 위쪽 input box 의 이벤트는 배경을 노란색으로 바꾸어주는 setSytle1 자바스크립트 함수에, 아래쪽 input box 는 파란색으로 바꿔주는 setStyle2 함수에 연결되어 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<html>
 
<head>
  <script>
     function setStyle1(x)
     {
       document.getElementById(x).style.background="yellow";
     }
 
     function setStyle2(x)
     {
       document.getElementById(x).style.background="blue";
     }
  </script>
</head>
 
<body>
 
<p>Color Change</p>
First name: <input type="text" id="fname" onfocus="setStyle1(this.id)"><br>
Last name: <input type="text" id="lname" onfocus="setStyle2(this.id)">
 
</body>
</html>
cs

 

  위의 파일을 c:\python\code 폴더에, colorchange.html 이라고 저장하고, 더블 클릭해 브라우저로 열어본다. 로컬 파일에서 자바스크립트가 돌아가려 하기 때문에, '차단된 콘텐츠 허용' 경고가 브라우저 하단에 뜰텐데, 이 경우는 딱히 위험한건 아니니 클릭해서 허용을 해줘야 자바스크립트 코드가 동작한다.

 

  처음엔 둘다 하얀 입력 박스인데, 각각 클릭하여 포커스를 주고 나면 아래와 같이 색이 바뀌게 된다.

 

 

 

[자바스크립트 예제 2]

  2번째는 조금 더 복잡한 예제를 해보자. 쇼핑몰 들에서 종종 볼수 있는 메뉴에 마우스를 오버하면 해당 하위 메뉴가 뜨는 예제이다. 이번엔 과정을 보여주기 위해 처음부터 전체 코드를 제시 하지 않고 HTML(원래는 css 와 구분되는게 더 낫겠지만), Javascript, event 각각의 코드를 소개하고 이후 합쳐서 동작을 보려고 한다.

 

  먼저 디자인을 나타내는 HTML 코드이다. 애니매이션이 1초에 수십장의 그림을 사람에게 연속으로 보여줘서 실제 움직이는 것처럼 속이는 것처럼, 자바스크립트도 비슷하게 여러 트릭을 통해 사람을 눈을 피해 표현하는 경우들이 많다(개인적으로 별로 우아하게 느껴지는 코드는 아니다). 아래를 보면 맨 위의 id 가 'mainCate' 인 <td> 태그가 상위 메뉴인 '과자' 를 보여주는 셀이고, 그 안을 보면 id 가 'subCateMenu' 인 '파이와 머랭' 정보를 담고 있는 <div> 태그가 하나 들어가 있는데, 속성들을 잘보면 뒤 쪽에 숨김 속성(display:none;)이 있다. 그래서 첨에는 <div> 태그 안에 있는 '파이와 머랭' 은 안보이고, 보는 사람의 눈에는 상위 메뉴인 '과자'만 보이게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<table width="100" border="0" cellpadding="0" cellspacing="0">   
    <tr><td align="left" id="mainCate">
       <div id="subCateMenu" style="width: 260px; position: absolute; margin-left: 120px; border: 
3px solid rgb(100, 200, 100); padding: 10px; z-index: 10000; display: none; background: rgb(255, 255, 255);">
          <table width="100%" border="0" cellspacing="0" cellpadding="0">
              <tr>
                 <td>                          
                       <div style="width:115px; border-bottom:1px solid;"><a href=“test1">파이</a></div>
                       <div style="width:115px; border-bottom:1px solid;"><a href=“test2">머랭</a></div>     
                </td>
              </tr>
          </table>
         </div>
        과자
        </td>
    </tr>
</table>
cs

 

 

  다음으로 동적인 움직임을 구현해 주는 자바스크립트 코드를 보자. showMenu 와 showSubCateMenu가 함수가 있는데, showMenu 가 하위메뉴가 나타날때, 상위메뉴인 '과자'가 들어있는 셀의 색을 바꿔주는 역활을 하고(backgroundColor), showSubCateMenu 가 숨겨놓은

'파이와 머랭'이 들은 <div> 태그를 보여준다(disaplay=""). 나머지 2개의 hide 계열 함수들 view의 반대의 역활을 해서 원래 상태로 돌려주는 역활을 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script language="javascript">
<!--
  function showMenu(td){
    td.style.backgroundColor = "#444444";
    td.style.color="#ffffff";
  }
 
  function hideMenu(td){
    td.style.backgroundColor = "#ffffff";
    td.style.color="#555555";
  }
 
  function showSubCateMenu(i) {
    document.getElementById("subCateMenu").style.zIndex = 10000;
    document.getElementById("subCateMenu").style.display = "";
  }
 
  function hideSubCateMenu(i) {
    document.getElementById("subCateMenu").style.display = "none";
  }
 
//-->
</script>
cs

 

 

  여기까지 오더라도, 마지막에 빠진 고리가 있다. 지금으로서는 HTML 하고 자바스크립트가 서로의 존재를 모른다는 것이다. 이것은 앞에서 얘기했던 'event' 요소가 연결해준다. '과자'가 들어있는 <td> 태그 안에 넣을 이벤트들은 아래와 같다. 이렇게 되면 마우스를 '과자' 셀위에 올리면(onmouseover) show 계열 함수들을 실행해 메뉴를 보여주고 색을 바꾸며, '과자' 셀을 벗어나면(onmouseout) hide 메뉴를 사용해서 원복한다.

1
2
onmouseover="javascript:showSubCateMenu(); showMenu(this);" 
onmouseout="javascript:hideSubCateMenu(); hideMenu(this);"
cs

 

 

  그럼 위의 HTML, Javascript, event 세 가지 코드를 결합한 최종 코드는 아래와 같다.

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
<script language="javascript">
<!--
  function showMenu(td){
    td.style.backgroundColor = "#444444";
    td.style.color="#ffffff";
  }
 
  function hideMenu(td){
    td.style.backgroundColor = "#ffffff";
    td.style.color="#555555";
  }
 
  function showSubCateMenu(i) {
    document.getElementById("subCateMenu").style.zIndex = 10000;
    document.getElementById("subCateMenu").style.display = "";
  }
 
  function hideSubCateMenu(i) {
    document.getElementById("subCateMenu").style.display = "none";
  }
 
//-->
</script>
 
 
<table width="100" border="0" cellpadding="0" cellspacing="0">   
    <tr><td align="left" id="mainCate" onmouseover="javascript:showSubCateMenu(); showMenu(this);" 
onmouseout="javascript:hideSubCateMenu(); hideMenu(this);">
       <div id="subCateMenu" style="width: 260px; position: absolute; margin-left: 120px; border: 
3px solid rgb(100, 200, 100); padding: 10px; z-index: 10000; display: none; background: rgb(255, 255, 255);">
          <table width="100%" border="0" cellspacing="0" cellpadding="0">
              <tr>
                 <td>                          
                       <div style="width:115px; border-bottom:1px solid;"><a href=“test1">파이</a></div>
                       <div style="width:115px; border-bottom:1px solid;"><a href=“test2">머랭</a></div>     
                </td>
              </tr>
          </table>
         </div>
        과자
        </td>
    </tr>
</table>
 
cs

 

  c:\python\code 에 menu.html 로 저장한 후, 브라우저로 열어본다. 아래와 같이 '과자' 가 들어간 셀 부분에 마우스를 올리거나 내렸을때 서브 메뉴인 '파이와 머랭'이 모양이 나왔다 사라지는 메뉴 동작을 볼수 있을 것이다.

 

  그리고 위에서 자바스크립트 코드에 나온 document.getElementById("subCateMenu") 같은 부분들은 자바스크립트가 HTML 코드에 접근할때 쓰는 DOM(Document Object Model)의 개념이 들어가 있다. 우리가 앞에서 beautifulsoup, 이나 selenium 을 사용할때 의식은 안 했지만 자연스럽게 해당 개념을 이용했다고 볼수 있다.

 

 

 

[Javascript 마무리]

  그럼 마지막으로 자바스크립트를 공부하려면 어떻게 할까? 개인적으로 자바스크립트는 깊이 들어가게 되면 파이썬하고 비슷한 깊이의 복잡도를 가진다고 본다(서버 쪽으로 눈을 돌리면 node.js 같은 서버 언어로도 사용되고 있고 말이다). 그래서 되도록 처음엔 너무 깊은 쪽으로는 가지 않도록, 쉬운 책이나 블로그를 보면서 개념을 잡은 후, 이후 웹 프로그래밍 공부를 하면서 궁금한 부분을 만났을때마다 구글 등을 찾아서 개념을 이해하는 것을 추천한다. 자바스크립트 라이브러인 jQuery 를 많이 사용하긴 하나, 실제 웹페이지들은 jQuery 와 일반 자바스크립트 두 가지 코드가 공존하고 있기 때문에 jQuery 는 응용편이라고 생각하고 접근하는 게 나을듯 싶다. 

 

  일단 기초가 잡히면 무엇을 모르는지와 공부해야할 방향을 스스로 알수 있게 되고, 집밥 백선생에서 요리하기 전에 재료를 섞음 어떤 맛이 될지 상상해 보라는 말 같이, 무언가를 덥석 구체적으로 습득하는 것도 좋지만, 그 전에 이것을 습득하게 되면 어떻게 될까를 잠시 생각해 보는 것도 나쁘진 않은듯 하다. 공부할수 있는 시간은 한정되어 있기 때문에, 가야될 방향을 정확하게 잡는것도 중요하다. 사실 이 강의의 의미도 구체적인 구현 지식의 전달 보다는 각 주제에 대한 접근방식과 개념을 전달하는 것이라고 생각하고 있다. 

 

 

 

 

 

[Web Server]

  이제 슬슬 후반부로 들어간다. 웹 프로그래밍을 공부하려면, 기본적으로 웹 서버의 존재를 이해해야 한다. 웹서버는 단순하게 얘기하면 특정 포트로 요청이 오기를 기다리고 있는 서비스 프로그램이다. 사용자(또는 다른 프로그램일수도 있고)로부터 요청이 들어오면, 적당한 프로그램적 처리를 한 후, 다시 요청한 쪽에 HTTP 형태로 결과를 돌려준다. 웹 서버는 사실 2개의 모듈로 나뉘어져 있다고 생각하는 게 좋다. HTTP 통신을 받거나 응답해 주는 순수 웹 모듈과, 받은 데이터를 특정한 언어에 기반해서 처리해 주는 프로그래밍 모듈로 구성되어 있다고 볼수 있다.

 

  예를 들어 순수 아파치 웹서버는 PHP 와 HTML 을 처리할 수 있는 반면, 아파치-톰캣은 아파치에 톰캣 프로그래밍 모듈이 얹어져 있어 JSP 파일의 처리가 가능하다(심지어 성능이 얼마나 나올지는 모르겠지만, IIS 에도 PHP 모듈을 설치해 PHP 웹서버로 사용할 수도 있다). 파이썬도 flask 같은 샘플을 보면 샘플 프로그램을 띄울때 웹서버가 같이 실행되어 프레임워크 뒤로 숨겨져 있어서 잘 안보이이긴 하지만, 웹서버 모듈이 있어야지만 사용자의 요청에 응답할 수 있다. flask 나 Django 도 아파치와 연동해서, 클라이언들과 주고받는 처리는 검증된 아파치 서버가 해주고, flask 나 django 는 뒤에서 웹어플리케이션 모듈로서만 동작하게도 할수 있는거 같다(이렇게 보면 모든 웹서버의 꿈이 어떤 언어라도 연결해 중계해 주고 싶은것인가 싶기도 하다).  

 

 

 

[IIS 설치]

  그럼 다음 섹션에서 ASP 를 돌려보기 위해서 윈도우 10에서 기본으로 지원하는 IIS 를 설치해서 샘플페이지를 하나 호출해 보자.

 

  먼저 IIS를 설치해 보자. '윈도우키+x' 를 누른후 옆 쪽에 나타나는 메뉴에서, '프로그램 및 기능' 메뉴를 선택한다(설치 부분을 설명하기 위해 잠시 전 uninstall 을 했지만, 이전 시간에서 소개한 예전 윈도우 스타일로 시작메뉴를 보여주는 'classic shell' 을 깔았을 경우는 '제어판>프로그램>프로그램및 기능' 으로 가면 된다).

 

  왼쪽에서 '윈도우즈 기능 켜기/끄기' 를 선택한다.  

 

  이후 인터넷 정보 서비스에서 1) 'World Wide Web 서비스' 를 체크하고, 2) 하위 메뉴에서 '응용 프로그램 개발기능>ASP' 를 선택한다. 3) 그리고 웹 관리 도구' 도 체크한다(나머진 그냥 디폴트로 두면 된다). 체크가 다 되었음 확인 버튼을 누른다. 잠시 기다림 IIS 가 설치가 된다.

 

 

 

['Hello ASP' 샘플 페이지 실행]

  이제 메모장으로 샘플 ASP 파일을 하나 만들건데, 기본 사용자 권한으로는 IIS 의 웹루트 폴더인 c:\inetpub\wwwroot\ 폴더에 파일을 쓰지 못하게 되어 있기 때문에, 메모장을 관리자 권한으로 실행해야 된다. '윈도우+x' 키를 눌러서, 왼쪽 메뉴에서 '검색'을 선택한다. 검색 창이 나오면 '메모장' 이라고 찾는다. '메모장' 아이콘이 나오면 마우스 오른쪽 버튼을 눌러서 컨텍스트 메뉴를 띄워 '관리자 권한으로 실행' 을 선택한다. (매번 이게 귀찮다면 wwwroot 폴더 '속성'의 '보안탭'에서 현재 로그인한 사용자에게 '모든 권한'을 주면 파일 저장이 가능해진다) 

 

  이후 메모장에 아래와 같이 입력한다.

1
2
3
<%
   Response.Write "Hello ASP"
%>
cs

 

  해당 내용을 c:\inetpub\wwwroot 폴더에 파일형식을 '모든 파일'로 하여 'test.asp' 로 저장한다. 이후 브라우저를 띄워 주소창에 http://localhost/test.asp 를 입력한다(localhost 나 127.0.0.1 은 현재 컴퓨터의 주소를 나타낸다). 그럼 아래와 같이 ASP 로 만든 간단한 문서가 IIS의 ASP 모듈에게 해석되어 브라우저에게 전달되어 화면에 보이게 된다.

 

 

 

 

[ASP 로 DB 조회하여 HTML 테이블로 출력하는 샘플 만들기]

  그러면 ASP 웹페이지를 하나 만들어 보자. 데이터베이스의 테이블을 조회 해서 결과를 화면에 HTML 테이블 형태로 뿌려주는 페이지를 만드려고 한다. 여러모로 시간을 아끼기 위해서^^; 4교시때 설치 및 세팅해 놓았던 SQL Server 와 그때 만들어 놓은 supermarket 테이블을 그대로 이용한다(4교시를 안보신 분들은 4교시에 소개된 MSSQL Server 설치 및 테이블 생성을 하시던지, 귀찮으시면 의사코드라 생각하시고 눈으로만 보고 동작을 이해하셔도 된다).

 

  4교시에 만든 테이블은 아래와 같은 테이블 이였다. 4 or 7교시 때 해당 테이블의 내용을 가져오는 파이썬 코드를 만들어 봤었는데, 그때 로직을 기억을 더듬어 보면 1) 먼저 DB 정보를 입력해 연결하고, 2) 각 행을 루프를 돌며 하나씩 읽어오면서, 3) 화면에 프린트를 하거나 엑셀로 저장했다. ASP 코드도 사실 이와 엄청 비슷한 흐름을 가진다(어차피 비슷한 스크립트 언어이기도 하고, 언어들은 서로 영향을 받아 유사한 경향이 있기 때문이다. 코드를 비교해 보시면 파이썬 코드쪽이 좀더 현대적이여서 흐름이 세련된 느낌을 받을 것이다). 작성할 ASP 코드에서는 1) DB 정보를 입력해 연결하고 2) 각 행을 하나씩 읽어 오면서, 3) HTML 테이블을 출력하면서 <td> 태그안에 해당되는 컬럼을 하나씩 넣어준다. 

 

 

 

[ASP 샘플 코드 만들어 보기]

  그럼 ASP 를 공부하는 시간은 아니기 때문에 만들어진 예제를 바로 보자. 원래는 웹프로그래밍 예제라고 하면, 사용자가 입력한 내용을 폼으로 전송해, 해당 값을 서버에서 받아 SQL 조건에 조합해 넣어 특정 itemno 의 상품을 조회해 오거나 하는 등의 예제가 좀더 현실적이겠지만, 단순함과, 이전에 만든 파이썬 코드와 비교할 수 있도록 하기 위해 전달 받는 인자가 없이 테이블의 모든 정보를 조회해 오는 방식의 예제를 만들었다.

 

  ASP, PHP, JSP 같은 스크립트 코드를 처음 보시면 낯설수도 있겠지만, ASP 에서는 <% %> 안에 든 내용이 VBscript 문법의 순수 ASP 코드이고, 코드를 보면 거의 파이썬으로 구현했던 예제와 비슷하게 진행이 된다. ASP 를 배울 것은 아니니 코드의 상세 문법을 보지 말고 주석 위주로 흐름만 보길 바란다(참고로 ASP 는 대소문자를 안가린다).

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
<%@ Language=VBScript %>
 
<%
   ' 연결 문자열 정의
   strMyTest = "Provider=SQLOLEDB; Data Source=localhost; Initial Catalog=mytest; User Id=pyuser; Password=test1234"
   Set objConn = Server.CreateObject("ADODB.Connection")
   objConn.Open strMyTest 
 
   ' 실행할 SQL 정의
   strSQL = "select itemno, category, foodname, company, price from supermarket s(nolock)"
 
    ' 쿼리 실행 하여 결과 얻어옴
   Set rtnRow = objConn.Execute(strSQL)
%>
 
 
<html>
   <head>
      <title>supermarket</title>
   </head>
 
   <body>       
      <p>supermarket 상품</p>
      <table border=1>
         <tr>
            <td>번호</td>
            <td>카테고리</td>
            <td>종류</td>
            <td>상품이름</td>
            <td>가격</td>       
         </tr>    
 
<%
   'DB 에서 조회한 행이 끝이 아니라면 루프를 돌리면서 각 컬럼을 <td> 태그안에 끼워 넣는다.
   Do while Not rtnRow.EOF 
%>
 
         <tr>
            <td><%=rtnRow("itemno")%></td>
            <td><%=rtnRow("category")%></td>
            <td><%=rtnRow("foodname")%></td>
            <td><%=rtnRow("company")%></td>
            <td><%=rtnRow("price")%></td>
         </tr>
                        
<%
   'rsList의 내용을 다음 결과 행으로 이동하며 Do 문을 반복한다.
   rtnRow.MoveNext
   Loop
%>
 
      </table>
</body>          
 
<%
   '열었던 연결 닫기
   objConn.Close
   Set objConn=Nothing
%>
cs

 

  그럼 위의 내용을 샘플파일과 비슷하게 관리자 계정으로 실행한 메모장에 붙여넣기 하여 c:\inetpub\wwwroot 폴더에 'supermarket.asp' 이름으로 저장을 하자. 이후 브라우저 주소창에서 http://localhost/supermaket.asp 을 호출한다. 그럼 아래와 같이 DB에서 supermarket 데이터를 가져와서 HTML 테이블로 정리하여 보여주는 페이지가 나오게 된다. ASP 가 처음이신 분은 처음 파이썬으로 DB 를 조회했을 때처럼 조금은 신기한 느낌을 받지 않을까 싶다.

 

 

  참고 1:  여기서 잠시 위의 코드의 구조를 보면, 우리가 배운 HTML 과 ASP 의 프로그램 코드가 하나의 파일에 섞여 있어서, 보기 조금 힘든 것 같지 않는가 싶다(뭐 익숙해짐 편한면도 있긴하다). 나중에 Django, JAVA 나 .NET 같은 언어에서 사용하는 MVC 개념을 통해, 마치 CSS를 통해 디자인 코드를 분리해 낸 것처럼, 이런 레거시 웹 프로그램에 섞여있는 프로그램 코드와 HTML 코드를 개별 요소로 분리해 다루려는 시도를 하게 된다.

 

  참고 2: 현재 기본 설정으로는 에러가 발생시 어떤 에러가 났는지 상세하게 보여주지 않는다. SQL 에러같은 상세한 에러를 보기를 원하는 경우는 아래의 웹사이트를 참고하여 세팅한다.

http://ooz.co.kr/172

 

  참고 3: 그리고 하나 더 노파심에 얘기할 것은, 위의 예제 코드는 소스를 간단히 만들기 위해서, SQL Injection(DB에 공격자가 임의의 쿼리를 삽입할 수 있는 웹취약점) 에 노출되어 있다. 저 방식으로 페이지를 만들어서는 안된다^^; 궁금하신 분은 구글에 'sql injection defense asp' 라고 찾아보심 된다. 요점은 stored procedure 같이 preparedstatement 타입으로 쿼리를 호출하고, 입력 데이터들을 validation 하고, 어플리케이션 DB계정의 권한을 최소화 하고 등등 이다. 현대 웹이 좋은 이유 중 하나는 이러한 패턴을 프레임워크 자체에서 막아주는 경우가 많다.

 

 

 

 

[Ajax]

  그럼 오늘의 마지막 주제인 Ajax에 대해 얘기해 보자. 개인적으로 모든 현대의 세련되고 편리한 웹페이지들은 이 Ajax 의 활약으로 이루어진게 아닌가 싶다. 구글의 검색어 추천이나, 여러 사이트 들의 사용자의 액션에 반응하는 부드러운 UI 의 이면에는, 웹 브라우저의 뒤에서 열심히 데이터를 요청해 나르고 있는 Ajax 가 존재한다. Ajax(Asynchronous JavaScript and XML) 의 비동기적이라는 의미는, 아마도 기존 웹 페이지 간의 명시적인 호출을 동기적이라고 가정했을때, 페이지가 정지해 있는 상태에서 뒷단에서 Ajax 라이브러리를 이용해 데이터를 교환하는 행위를 비동기적이라고 바라보는 관점인 것 같다. Ajax 는 자바스크립로 구현된 라이브러리로 단순하게 봐도 괜찮을 듯 싶다.

 

 

  Ajax 의 간단한 예는 아래 그림의 구글 검색어 추천 기능이다. 사용자가 'python' 이라고 입력하는 동안 계속 추천하는 검색어를 바꿔가면서 보여준다.

 

  해당 추천 검색어가 나오는 과정을 fiddler 로 살펴 보게되면, 아래와 같이 www.google.co.kr 도메인에서 /complete/search? 페이지를 호출하는 6개의 요청('p' 'y' 't' 'h' 'o' 'n' 각각 입력에 따라 총 6개의 요청이 날아간다)을 볼수 있다. 그 중 맨 마지막 요청 항목을 클릭해 보면, 폼안의 'q' 인자 안에 우리가 입력했던 'python' 이라는 글자가 넘어가고, 밑 쪽 보면 결과값으로 'python' 에 해당하는 추천 검색어들이 'json 데이터 형식'으로 담겨 응답으로 오게 된다. 브라우저는 해당 json 내의 값을 적당히 풀러서, HTML 페이지의 DOM 개체에 넣어서, 위의 그림과 같은 추천 검색어 들을 보여주게 된다.

 

 

 

[Ajax 샘플 코드 만들어 보기]

  그럼 실제로 간단한 Ajax 샘플을 하나 만들어보자. 우선 전송되는 데이터 타입은 json 이 아닌 평문으로 간단하게 구현 하려고 한다(ASP 가 좀 구식언어이고, .NET 이 나오면서 업데이트가 없이 버려져서, json 같은 최신 데이터 구조를 파싱하기가 까다로운 점도 이유이다). 아래 코드에 최대한 주석을 달아 놓았다(// 는 자바스크립트 주석이고, <!-- --> 는 HTML 주석이다).

 

  위쪽의 자바스크립트에는 Ajax 리퀘스트가 정의되어 있다(처음 보는 분들은 조금 사용하는 구조가 낯설어 보일테지만, 처음 Ajax 를 만든 사람이 저렇게 사용하라고 정해 놓은거라서, 파이썬에서 SQL 연결 할때 사용하는 모듈의 문법대로 구현 해야하는 것과 같다고 보면 된다. jQuery 등의 라이브러리를 사용하면 또 그 쪽에서 시키는 스타일대로 하면 된다). 하단 HTML 에 정의된 사용자가 입력한 <input> 값을 읽어와서, ajax_sub.asp 페이지를 호출(폼의 action과 비슷하다고 보면 된다)하면서 'no' 인자에 사용자 입력값을 넣어서 보내고, 결과가 반환되어 오면 그 값을 아래 HTML 에 정의된 <span> 태그에 innerHTML 속성을 이용해서 넣는다

 

  하단 HTML 에는 버튼 속성을 가진 <input> 태그가 있는데, 자바스크립트의 getMenu 함수가 onclick 이벤트를 통해 연결되어 있다. 사용자가 번호를 입력하고 버튼을 클릭하면, getMenu 함수가 실행되면서 Ajax 요청을 실행하는 도미노 형태의 구조이다.

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
<%@ Language=VBScript %>
 
<script>
function getMenu() {
   var xhttp;
   // 사용자가 입력한 값을 id 를 통해 가져온다.
   var menuNo = document.getElementById("menuNo").value;
   
   // 새로운 ajax 요청을 만든다
   xhttp = new XMLHttpRequest();
   // 요청에 대해 응답이 정상으로 올때까지 기다려서 
   xhttp.onreadystatechange = function() {
      if (xhttp.readyState == 4 && xhttp.status == 200) {
         // span 태그내에 응답으로 온 텍스트 값을 살짝 끼워 넣는다.
         document.getElementById("menuName").innerHTML = xhttp.responseText;
      }
   }
   // 실제 요청하는 페이지는 ajax_sub.asp 페이지 이고, get 인자로 no 에 사용자가 입력한 값을 넣는다.
   xhttp.open("GET""ajax_sub.asp?no="+menuNotrue);
   xhttp.send();
}
</script>
 
<html>
   <head>
      <title>ajax 샘플</title>
   </head>
<body>
   <table>
      <tr>    
         <td> 메뉴 번호: </td>
         <!-- 사용자가 입력하는 값 -->
         <td width=120> <INPUT id="menuNo" size="10" type="text" value=""> </td>    
           <td width=200> 
               <!-- 버튼을 누르면 getMenu 함수를 실행 한다 -->
               <input type="button" value="해당되는 메뉴 찾기" onclick="getMenu()">
               <!-- 나중에 응답 값을 끼워 넣을 span 태그. 첨에는 아무 내용도 없다 -->
               : <span id="menuName"></span>
           </td>
         </tr>
   </table>         
</body>
</html>
cs

 

  앞의 ASP 예제들과 마찬가지로 해당 코드를 메모장에 붙여놓고, c:\inetpub\wwwroot에 ajax_main.asp 라고 일단저장한다.

 

 

  그 다음은 위의 페이지에서 호출하는 대상인 ajax_sub.asp 페이지를 만들어야 한다. 보통 이 호출 당하는 쪽은 데이터만 받고 보내면 되기 때문에 API 같은 형태로 많이 구현 되어있다. 구현한 코드는 아주 간단하다. 넘어온 no 값을 받아서(사용자가 입력한 값이다), 1 이면 'pizza', 2 이면 'pasta', 그 이외의 숫자면 'drink' 를 반환(response) 해준다(참고로 값이 없을 때의 에러처리는 안되서, 값을 안 넣음 아무 결과도 넘어오지 않는다).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%@ Language=VBScript %>
 
<%
    ' request 값 받기
   menuNo = request("no")
    
   ' 넘어온 메뉴 번호에 해당하는 메뉴이름을 반환해 준다..
   Select Case menuNo
      Case 1
         Response.Write("pizza")
      Case 2
         Response.Write("pasta")
      Case Else
         Response.Write("drink")
   End Select
%>
cs

 

  해당 코드도 메모장에 붙여놓고, c:\inetpub\wwwroot에 ajax_sub.asp 라고 저장한다. 

 

 

  이후 브라우저를 열어서 http://localhost/ajax_main.asp 라고 주소창에 입력한다. 아래의 화면이 나오면 메뉴번호에 1을 넣고, '해당되는 메뉴찾기' 버튼을 클릭하면, 페이지 뒤에서 Ajax 기능을 이용해 ajax_sub.asp 에 인자를 넘겨 데이터를 조회해서, 'pizza' 를 받아 옆에 표시하게 된다.

 

 

  Ajax 호출하는 과정을 피들러로 살펴보게 되면 앞에 봤던 구글 검색어 추천과 비슷하게, 숨겨진 호출(request)이 보인다. 이제 Ajax 를 통한 비동기적인 호출이 어떤 원리로 일어나고, 어떻게 보이는 건지 대충 감을 잡으시리라 생각한다.

 

 

 

 

[마무리 하면서]

  그럼 이렇게 되어 가벼운 깊이지만 꽤 길게 진행됬던 Legacy Web 편을 마치려 한다(개인적으로 소주제별 균형을 맞추기 가장 힘들었던 시간 중 하나일듯 싶다). 뒤돌아 보면 자잘하게 많았던 HTML(form, event), CSS, Javascript, Web Server, Web Language, Ajax 같은 기초 요소들이 우리가 보고 있는 웹을 지탱하고 있다는걸 볼수 있었다. 이런 'Legacy Web' 에 대한 기초가 잘 잡혀 있다면 파이썬이든, 다른 언어로든 웹프로그래밍을 공부할때 배워야 할 주제들이 한결 가벼워지게 된다고 생각한다. 앞에서 얘기했던 프로그래밍을 배울 때의 외적요소들을 미리 알고 있을 때처럼 말이다.

 

  혹시 위의 주제들을 잘 모르는 상태에서 파이썬으로 만드는 웹을 공부하고 싶다면, flask 나 Django 프레임워크를 공부하는 중간 중간에 꼭 각각의 주제에 대한 쉬운 책 한권 정도는 보기를 추천한다. 개인적으로 프레임워크는 웹의 구성요소들을 잘 배치할 수 있도록 돕는 껍데기 역활에 불과하지 않는가도 싶다. 다음 시간에는 이런 Legacy Web의 요소들이 파이썬의 Flask 라는 프레임워크에 어떻게 녹여져 소개되고 있는지, 가벼운 맘으로 체크해 보도록 해보자.

 

 

 

 

2017.7.15 by 자유로운설탕
cs

 

 

posted by 자유로운설탕
2017. 6. 18. 15:49 프로그래밍

  이번 시간에는 머신러닝에 대한 이런저런 생각들을 얘기하고, 이전 시간에 배웠던 numpy, scipy, 그리고 요즘 주목 받고 있는 텐서플로우(tensorflow) 라이브러리를 이용해서, 머신러닝 계의 구구단 이라고 할수 있는 최소제곱법(Least Square Fit)으로 데이터에 맞는 직선을 추정하는 샘플 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. 정리 - 이런저런 이야기

 

 

 

 

[들어가면서]

  여러 웹사이트와 블로그들을 돌아다녀본 결과, 보통 두 가지 입장이 눈에 많이 뜨이는 것 같다.  한 측면은 수학이나 통계학에 대한 경험을 보유하고 있는 상태에서, R이나 매트랩 등의 연구 목적의 툴을 쓰다가, 좀더 일반적인 머신러닝 프레임윅에 관심을 가져 길을 가다보니, 파이썬 같은 범용 프로그래밍 언어를 해야될 필요성을 느끼게 되어 본격적으로 프로그래밍의 세계에 발을 들여 놓게 되는 경우 이다.

 

  다른 한 측면은 프로그래밍, DB, 보안, 시스템 등의 기술적인 업무 등을 하는 입장에서 머신러닝 책이나 강의를 찾아 보다보니, 원리를 설명하는데 사용하는 선형대수, 통계 그리고 RNN 같은 낯선 이론들을 만나게 되고, 책이나 강의를 이것저것 봐도 머신러닝 라이브러리를 작동 시키는 부분은 대충 따라 할 수 있을 것 같은데, 해당 도메인에 대한 부족한 이해 때문에 전체적인 그림이 그려지지 않고, 어떤 원리로 그런 일들이 가능한지, 어떤 데이터를 가져다 어떻게 가공해야 하는지 대해서는 여전히 모호한 상태임을 느끼게 되어, 수학이나 통계 분야에 대한 공부를 시작하는 경우일 것이다(저같은 경우는 후자 그룹에 속하는 경우인거 같다).

 

  그런데 사실 그렇게 가지지 못한 지식에 대한 대한 니즈나, 동경에서 공부를 시작한다고 해도, 만족할 만한 끝은 좀처럼 잘 보이지 않게 된다. 왜냐하면 그 동안에 관심이 없었던 상대의 영역의 본질에 접근하기는 쉽지 않기 때문이다. 다른 분야의 예를 들자면 캐릭터를 잘 만들거나 그림을 잘 그리기 위해서 3D 모델링 프로그램, 일러스트레이터, 페인터 등의 그래픽 관련 프로그램을 마스터 하려는 것과 비슷한 접근이라고 느껴진다. 아무리 해당 툴의 메뉴얼을 달달 외고, 기능을 잘 이해해서 쓴다고 해도, 그러한 행위의 본질이라고 할수 있는 미학적, 공간적인 감각을 기르는 경험들이 부재한다면, 그러한 노력이 올바른 결실을 맺긴 힘들다고 본다.

  

  마찬가지로 현재 올렸던 글들을 계속 읽어오신 분들은 공감하셨겠지만, 프로그래밍 이란 것은 단지 가시적으로 보이는 코드란 측면에 국한된 지식은 아니다. 외국어를 배울때 그 나라의 문화를 같이 이해해야 자연스럽고 맥락에 맞는 표현을 할수 있게 되는 것처럼, 머신러닝을 받치고 있는 프로그래밍 지식은 파이썬이란 언어 자체에서 출발하여 해당 언어가 동작하는 OS, 네트워크 등의 여러 주변환경과, 데이터를 담는 빅데이터 시스템 등 기술 생태계 전체가 포괄적으로 엮여 있다고 생각한다. 그래서 스펙트럼이 넓을 뿐만 아니라, 인기가 좋아 급속히 성장하며 확장되는 기술 영역들이 의례 그렀듯이 자료들이 초보자들에게 그다지 친절하게 정리되어 있진 않고 계속 변하기 때문에 접근 난이도가 높은 편인 것 같다. 물론 서로 다른 문화가 그렇듯이, 서로 다른 분야 사이에도 자신의 분야의 경험을 기반으로 유추할 수 있는 비슷한 공통 개념들도 많긴 하지만 말이다.

 

  잘은 모르지만 반대로 프로그래밍 영역과 비슷하게 짐작해 보면, 수학이나 물리, 통계 등의 분야도 단순히 보이는 이론과 수학적 지식들이 전부는 아닐 것 같다. 그 학문을 오래 접했던 사람들만이 가질 수 있는 특유의 사고 방식과 문제에 대한 접근법, 데이터와 숫자를 보는 시야나 감을 얻는다는 것은 단순히 통계나 선형대수 책을 공부하는 것과는 좀 많이 다른 일이 아닌가 하는 생각이 든다. 

 

  그래서 어떤 측면에서는 한 사람이 모든 부분을 다 잘 알순 없기 때문에, 효율성을 위해서는 데이터를 다루는 직군, 모델과 알고리즘을 다루는 직군, 프로그램을 관리하는 직군을 따로 나누어 조직해야 한다고 하는 주장도 있지만, 뭐 그런 경우라도 서로서로 상대방이 하는 일을 이해하면서 일을 하면 효율적이기도 하고, 이해 타산을 버리고 순수하게 공부하는 현 시점에서는 무시하기로 하자. 이 시간엔 조금이라도 머신러닝이라는 분야가 정밀한 숫자, 연관된 수학, 복잡한 통계이론을 차지하고도 합리적인 활동으로 보일 수 있는지 생각해보자.

 

 

 

 

[머신러닝이 하는 일에 대해 상상해보기]

  우선 어떻게 기계가 학습을 할수 있다는 것을 직관적으로 이해할 수 있을까? 기계가 학습을 한다는 것은 기계가 사람처럼 알고리즘을 만드는 작업, 즉 프로그래밍을 할수 있다는 얘기다. 예전부터 자동으로 프로그래밍을 짜는 일은 인간의 창조적인 능력의 한 부분이라고 믿어왔고, 여러가지 쉽고 자동화된 프로그래밍 툴을 만들려는 시도가 많이 실패 해 온것으로 알고 있는데, 모두 인간의 자만이였을뿐일까? 개인적인 생각으로는 기계가 학습하여 프로그래밍을 한다고 하는 부분은 사람이 프로그래밍을 하는 부분과는 다르면서도 동일한 모순적인 측면이 있다고 본다.

 

 

  예를 들어 하나의 프로그래밍 로직에 대해 생각해 보기 위해, '1교시 언어를 바라보는 방법'에서 보았던 아래 그림을 다시 한번 보자.

 

  당연한 얘기 같지만 모든 목적을 가진 프로그램은 입력과, 출력을 가지고 움직인다(실행 시점에 인자가 전달되지 않는 프로그램도 실행 시 시스템과 입력과 출력을 하고 있다고 볼수 있을 것이다). 그럼 프로그래머가 작성한 프로그램이 내부적으로 입력에 항상 3을 곱한 후 1을 더 해서 결과를 보여주는 프로그램이라고 해보자. 초등학교 수학식으로 나타내면 'y=3x+1' 일 것이다. 파이썬 식으로 나타내면 아래와 같다.

1
2
3
4
.... 입력으로 x 가 들어옴.
 
= 3x + 1
print (y)
cs

 

  근데 우리가 해당 로직을 이해할 수 없는 상태에서(곱하기가 엄청 고차원 적인 수학이라고 가정해 보자), 사람들이 해당 프로그램을 사용해서 쌓인 충분히 많은 입력-출력 쌍 데이터가 있다고 해보자. 예를 들면 (1, 4), (4, 13), ... (10000, 30001) 같은 형태일 것이다. 해당 데이터를 관찰해 보면 결과 데이터의 경우는 입력 데이터에 사람이 만든 어떤 프로그램 로직(여기서는 x*3 +1 )이 들어가 만들어지는 것이라고 볼수 있을 것이다.

 

  그럼 반대로 만약 어떤 임의의 시스템이 x 입력을 받아 y를 출력을 나타내는 것을, 위의 수집한 데이터에 대해서 최대한 높은 '옳음'으로 처리할 수 있다면, 해당 시스템의 속이 어떻게 생겼는지 상관없이, 해당 시스템은 사람이 작성한 위의 프로그램과 근사적으로(어쩌면 극한적으로) 동일한 자동화 프로그램이라고 할 수 있을 것이다(모든 사칙연산 데이터를 그대로 흉내내는 머신러닝 모델을 상상해 보자. 사람이 만든 계산기와 비슷하다 볼수 있지 않겠는가?).  

 

  즉 데이터 측면만을 보면 충분한 규모의 객관적으로 수집된 입력과 출력 데이터가 있고, 그 안에 현상을 왜곡하는 가짜 데이터가 무시할 만큼만 존재하고(또는 모델내에 그러한 데이터를 걸러내 무시하는 안전장치가 있어도 되고), 그 데이터들이 뭔가 사람이나 현상의 의미있는 활동을 나타낼 가능성이 있다면, 해당 데이터에는 사람이 논리적으로 파악하긴 힘들 수도 있지만, 그 데이터 쌍들을 만들어낸 로직을 포함하고 있다고 볼수 있지 않을까 싶다. 

 

 

  충분한 규모의 객관적인 입력과 출력 데이터 쌍은 비즈니스, 데이터 분석과, 빅데이터의 도움으로 이루어 진다고 보면 될것 같고, 가짜 데이터의 제거 부분은 비즈니스, 데이터 분석, 노이즈에 강한 모델 등을 포함해 모든 동원할수 있는 방법이 다 포함 될수 있을 것 같고, 입출력 데이터들 자체에 숨어있는 로직이나 패턴을 적절한 필터를 써서 참기름 짜듯 뽑아내는 것이 여러 머신러닝 알고리즘과 모델의 앙상블이라고 생각해보면 어떨까 싶다.

 

  그래서 사람이 가진 논리적 기술로 데이터를 가공하지 않고, 머신러닝 알고리즘(물론 이것도 넓은 범위에선 논리인것 같다)이 데이터 안의 로직이나 패턴을 추출해 내는 과정을 '기계학습'이라고 명명 지은게 아닌가 싶다. 로직을 짜내는 과정에서, 해당 머신러닝 필터는 데이터에 커스터 마이즈된 특정한 모양(모델이 데이터에 커스터마이즈 된 부분을 얘기하는지, 데이터에 독립된 필터를 얘기하는지는 좀 아리송 하긴 하긴 하지만. 현재로서는 왠지 모델 자체에 데이터의 숨은 로직의 특성도 포함되는거 같다)을 가진다. 이후 이 특정한 모양으로 커스터 마이즈된 필터를 향후 들어오게 되는 같은 타입의 새 데이터에 적용하게 되면, 사람이 프로그래밍 하지 않아도 기존 결과 데이터와 비슷한 로직의 영향을 받은, 결과 데이터를 만들어 내게 되지 않을까 싶다.

 

  그 후 일반적으로 만들어진 모델을 검증하기 위해서, 보통 전체 데이터셋을 트레이닝셋과 검증셋으로 적당히 나누어 트레이닝셋으로 훈련을 시킨 후에 검증셋으로 실제 경기를 진행 하게 한다. 트레이닝 셋은 일종의 원어민 회화 이고, 검증셋은 실제 다른 외국인에게 배운걸 시도 하는거라고 생각해봄 이상할까--; 

 

 

 

 

[몇가지 문제 들]

  머신러닝이 입력과 결과 데이터 사이에 숨어있는 로직을 뽑아내는 작업이라는 것이 어느정도 맞다는 가정에서, 몇가지 머리가 아픈 모호한 문제들이 발생할 수 있다.

 

 

  1번째 문제는 'garbage in garbage out' 이다. 어떤 우수한 머신러닝 모델이라도 좋은 데이터를 공급해 주어야 좋은 알고리즘을 추출해 준다는 것이다. 정신건강의학과 의사가 상담을 하는데, 환자가 거짓말만 늘어 놓는다면 어떻게 될까? 아무리 실력있는 의사라도 환자의 마음의 병의 원인에 대해서 잘못된 판단을 하게 될것 이다(물론 정말 뛰어나다면 거짓말을 한다는 것 자체를 눈치채고 진실을 얘기하도록 유도할 수도 있겠지만^^). 그럼 이러한 가짜 데이터들은 왜 들어 가게 될까?

 

  우선 충분히 신뢰할 만큼 데이터 양이 많지 않거나 특정 군으로 편향될 수도 있다. 우리가 매번 선거 시기에 듣는 여론 조사용 표본 데이터의 중요성이다. 게임센터를 들어가는 학생 10명의 의견을 듣고 우리나라 전체 학생이 어떤 생각을 가지고 있다라고 말하는 것은 의미 없을 것이다. 근데 사실 현실적으로 충분히 좋을 정도라는 표현은 참 추정하기 힘든 일인거 같다. 충분히 좋을 정도의 개발, 충분히 좋을 정도의 테스트 같이, 잘은 모르지만 통계적으로 의미 있는 충분한 데이터 라는 것도 약간은 바라보는 관점에 따라 애매한 영역이지 않을까 생각해 본다.

 

  다른 원인은 노이즈이다. 만약 누군가 사실을 들키는 것이 창피해서 속마음과는 다른 가짜 선택을 했다면? 데이터를 취합하는 과정에서 잘못된 정보들이 우연히 섞여졌다면? 쇼핑몰에서 연령대별 분석을 하는데 회원들의 아이디들을 가족이 모두 공유해서 사용하고 있다면?(뭐 예를 들어서 회원제인 코스트코 같은데는 지인이 부탁한 물건을 사다주는 사람도 많을거 같다) 해커가 들어와 정보를 본인에게 유리하게 몰래 변경했다면? 해당 데이터의 형태가 애초부터 랜덤적인 선택 요소가 표함되어 있다면? 등등.. 다양한 일들이 생길 수도 있다. 또는 아래의 알렉사 기사처럼 TV 에서 나온 음성 같은 외부 테이터를 잘못 받아들여 엉뚱한 판단을 하게 될지도 모른다.(뭐 홍채나 얼굴 인식이 사진으로 된다든지 하는 것도 비슷한 경우일 것이다) 

http://thegear.co.kr/13718

 

   

  2번째 문제는 데이터 무더기 에서 학습을 위해 제공하기 위해 골라낸 요소들이, 정말 해당 현상을 제대로 설명하는 인자인가 하는 문제이다. 어떤 인자는 혼동만 주는 필요 없는 인자일 수도 있고, 어떤 중요한 인자는 빠져있을 지도 모른다. 최악의 경우는 현재 수집을 안하고 있거나, 현실적으로 수집을 못하는 요소일 지도 모른다. 데이터를 제대로 이해 못하고 머신러닝 모델에게 전달한다는 것은, 사람이 비즈니즈 룰도 이해를 못하고 프로그래밍을 하는 것과 그닥 차이가 없을 것이다. 로직은 돌아 갈테지만 아마도 아무 의미도 없거나 재앙일 것이다.

 

  또는 데이터의 범위를 잘못 잡은 학습을 할수도 있다. 예를 들의 앞의 예가 아래와 같은 로직으로 동작하는데, 현실로 수집가능한 데이터가 10000 까지 였다면, 해당 데이터로 학습된 모델은 10000 을 넘어가는 데이터를 만날때 커다란 재앙을 안겨줄 지도 모른다(버퍼 오버플로우 처럼 말이다).

1
2
3
4
if input <= 10000:
    우리가 수집한 데이터(0~10000)가 적용된 로직
else:
    새로운 타입의 데이터가 생성됨
cs

 

  비슷하게 여론 조사 결과 조작이나, 통계의 여러 부작용 처럼, 데이터의 어떤 집합, 성질을 선택 하느냐에 따라서 의도된 답을 선택하거나 실제와는 다른 결과를 보여줄 수도 있을 듯 싶다. 

 

 

 3번째 문제는 학습된 모델을 실제 현실에 적용할수 있느냐 하는 문제이다. 자율자동차와 같이 운전자의 보호냐 보행자의 보호냐를 선택하는 문제일 수도 있고, 99.9999% 의 정확도라도 false-positive(안전하다고 판단했지만 실제로는 위험함) 가능성이 존재하는 한 실수에 대한 윤리적 문제가 일어날 수 있다. 예로서 머신러닝 기반의 의료검사를 신뢰 하다가 드문 케이스 때문에 암을 놓쳤다고 해보자. 시스템의 정확도가 엄청 높고, 해당 경우의 데이터가 트레이닝 데이터에 포함되지 못한 정말 운이 없는 경우기 때문에 어쩔수 없다라고 할수 있겠는가? 이런 분야라면 의사가 먼저 체크 후 괜찮다고 판단한 환자를 머신러닝으로 2차 체크 하여, 의사의 실수를 줄이는 방식으로 이용하는 등의 사람들이 납득할 수 있는 합리적인 기술 적용 프로세스가 필요하다. 그래서 어떤 사람들은 머신러닝을 기존 기술의 대체제가 아닌 보완제의 관점에서 접근하기도 한다.

 

 

  마지막 문제는 아마도 모델의 오차와 별개로 우리가 가지고 있는 데이터 자체가 제공된 시점부터 이미 실제 진실과는 조금은 차이가 날 수 있을지 모른다는 것이다. 사람들의 해당 데이터를 만들게된 모든 이유를 다 이해한다면 모르지만, 그런 전지전능함을 가지지 못한 우리는 데이터에 담긴 fact 만을 믿어, 중요한 사실을 놓칠지도 모른다. 마치 우리가 매일 만나는 사람이 겉모습과는 다른 속마음을 가질 수 있는 것과 비슷하게 말이다. 또한 해당 진실은 해당 시간대에서만 유효했을 수도 있다. 시간이 흐름에 따라 다른 영향을 받을 수도, 또한 아예 해당 데이터는 시간에 따라 변화를 하는 데이터일 수도 있으니 말이다.

 

 

  그래서 알파고가 바둑이나 스타크래프트 같은 분야를 선택한건 아주 영리한 선택이라고 생각한다. 현실의 데이터가 왜곡되듯이 바둑기사가 불리하다고 바둑알을 속이거나 바둑판을 엎는 일 따위는 없을 테니까 말이다. 또한 바둑과 같은 테이블 게임은 경우의 수는 무한대일지 모르겠지만, 바둑 룰과 선의 공간, 그리고 승리라는 목적으로 닫힌 세상이라는 부분은 그 무한대 성을 많이 제한해 주며, 데이터의 무결성을 보장하는데 오픈된 분야보다 많이 유리하다고 생각한다(게임이란건 사람의 이해 가능성을 전제로 성립될 수 있기 때문에 무한에 가깝다곤 하지만 어째든 제한은 있다고 본다. 다만 해당 부분은 효율적인 승리의 관점에서 제한된다는 거지, 바둑의 철학적 관점에 대한 제한을 얘기하는 것은 아니다). 그것은 다음에 도전할 지도 모른다는 스타크래프트 같이 현재 머신러닝이 강세를 보이고 있는 게임 분야도 비슷할 듯 싶다. 아무리 자유도가 높더라도 게임 분야는 사람이 흥미를 가지도록 방향이 제한되고, 추출해야될 로직들이 머물고 있는 데이터들에게 비교적 안전한 닫힌 세상이라고 생각한다(뭐 그렇다고 쉽다는 얘기가 아니라 real world 에 비해서 상대적으로 그렇다는 의미다. 그리고 부루마블 같은 주사위와 황금열쇠라는 랜덤요소를 가지는 게임 같은건 좀 다른 문제일 것이다). 어떤 기사의 마지막에서 살짝 언급했듯이 알파고의 승리의 숨은 주역은 뒤에 적절한 장르와 데이터를 선택하고, 적절한 알고리즘으로 학습시킨 과학자 들이라는 말이, 물위를 헤엄치는 오리의 바쁜 발을 떠올리게 된다.

 

 

 

 

[기술적인 미래]

  이왕 이것저것 얘기를 한 김에, 앞으로의 머신러닝 기술이 어떻게 될까도 추측해 보자. 빅데이터 분야의 진행을 기반으로 추측해보면, 처음에 나온 하둡이 hive 나 pig 같은 좀더 쉬운 작업을 가능케 하는 프레임윅들로 감싸지듯이, 현재 같이 하나하나 이해하며 초기 값 및 모델, 제어 요소들을 조정해, 좀더 빠르고 높은 정확도의 결과를 얻으려 하는 부분은 점차 통합되고 자동화된 로직으로 대체될 가능성이 높을 듯 하다. 군웅할거중인 머신러닝 프레임윅들도 한두개의 강력한 범용 프레임 윅과, 몇개의 특수 프레임윅들로 정리되지 않을까 생각한다(안 쓰는 애들은 버려지니...). 여튼 머신러닝 기법을 사용하는 방법은 점점 쉬워지며 블랙박스로 감싸질 것 같다. 이런 부분이 많은 강의 하는 분들이, 관련 수학을 깊게 몰라도 머신러닝을 배우는덴 무리가 없다고 강조하는 측면인 듯 싶다. 우리가 기계공학을 몰라도 자동차를 운전할수 있듯이 말이다.

 

  물론 머신러닝 분야의 알고리즘 개발에 발을 담그고 있는 분들이나, 좀더 디테일하며 컨트롤을 원하는 분들은 껍데기 안쪽을 다루기 위해서 좀 더 깊게 이해하려 하는게 맞을 수도 있지만, 어느 정도 깊이로 이해하려 하는지는 좀 생각해 볼 문제 같다. 깊이 문제도 수학식이나 통계 공식의 논리적 정확성의 증명에 너무 집착하다보면 원래 그 식이 얘기하려고 했던 이미지나, 본질을 놓칠수도 있으니까 말이다(공부하기 싫어서 하는 얘긴 아니다^^). 수학은 연산보다는 그 연산이 무엇을 의미하는지를 이해하는게 더 중요한 듯도 싶다. 그리고 아마도 커다란 메이저 회사들이 필요한 프로그래밍 기술들을 머신러닝 프레임윅 안 쪽에 쉬운 인터페이스로 구현해 놓음으로서 현재의 프로그래밍 성격의 장벽은 점점 낮게 될 것 같다. 물론 어려운건 마찬가지 겠지만 현재보다 상대적으로 쉬울수 있다는 얘기긴 하다. 또한 개인이나 자그마한 기업들은 유용한 데이터 자체를 수집하기 힘들수도 있기 때문에 일반적인 분야에 대해서는 1+1 상품처럼 데이터와 학습된 머신러닝을 같이 묶어서 제공하는 부분들도 늘어날 것 같다. 지금 한참 관심을 받고 있는 음성이나 이미지 인식 분야 같은 인류의 공공재 분야에서 말이다.

 

 

  그리고 반대로 생각해 보면 미래에도 개선되기 힘들 것 같은 부분들이 있다. 우리가 비즈니스를 이해하고, 적정한 테이터를 디자인해 수집하고, 학습에 필수적인 값들을 뽑아내고, 머신러닝이 추출해낸 반투명한(뭐 RNN 같은 신경망 이론인 경우는 가중치 들이 왜 그런지 사람은 이해하기 힘들 듯 하다) 로직들이 맞는지를 이해하고, 오차의 본질을 이해해 모델을 조정하는 능력이다(원석을 감정하는 능력이라고 할까..). 또 문제를 파악하고 적절한 기법(알고리즘과 모델)을 선택하는 것도 오랜기간은 수학적, 통계적 기법을 잘 아는 사람들의 손을 대부분 필요로 하지 않을까 싶다. 물론 프레임 윅들이 여러가지 시각화 툴을 통해, 해당 작업을 많이 도와줄 것은 같지만 말이다. 또 아마 지금의 장미빛 전망의 끝이 지나면, 자동화된 프로그램이 못하는 분야에 대해 다른 해결을 원하는 새로운 장르가 생겨날 것도 같다(어차피 현재의 로직으로도 해결 못하는 문제는 많으니까. 왠지 머신러닝은 로직을 벗어난 로직이라 그러한 부분을 일부 해결해 줄것은 같다). 

 

  현재 트레닝셋과 검증 셋으로 나누어, 리그레이션 테스트와 비슷하게 신뢰도를 확인 하는 방법은, 뭔가 약간 너무 낙관주의적인면(출처가 같은 트레이닝과 검증 데이터가 무조건 옳다고 가정하는 측면에서)도 있다고 느껴지기 때문에, 수집을 포함한 전체적인 데이터 케어 관점에서 추출된 로직에 대해 테스트 및 검증 하거나, 구성된 시스템의 여러 부분과 보안적인 측면을 검토하는 검증 활동 영역도 활성화 되지 않을까 싶다. 다만 머신러닝으로 추출된 로직은 프로그래머가 만든 로직과 다르게 명확히 알수 없으므로(그래서 앞에서 반투명이라고 표현했다), 모순적으로 알고리즘과 모델을 이해해야 최소한의 검증이 가능하므로 기존에 일반적인 테스팅이나 보안 활동을 하던 사람들이 접근하기는 장벽이 높은 분야 같기도 하다.


  결과적으로 머신러닝은 데이터를 기반으로 현대의 많은 과학 분야가 그렇듯이 완전한 해에 대한 실용적인 근사로 나아가는 로직이다(퍼펙트할수 있는 예외는 일부 게임과 같이 닫힌 세상의 데이터 일 것이다). 프로그래머들이 프로그래밍 언어를 통해 기호로 분리해 놓은 논리 기호들을 녹여 다시 새로운 기호를 만드는 느낌이라고 할까? 그래서 머신러닝을 바라볼때는 수학이나 통계학을 기호의 학문이 아닌 알고리즘을 구성할 레고의 블록(블랙박스적 도구)으로 바라보는 접근법도 어떨까 싶다. 실제로 우리가 파이썬을 할때도 모든 세세한 라이브러리의 내부 로직까지 모르면서 사용하는 것과 비슷하게 말이다.

 

  왠지 헤메고 있는 사람을 더 복잡하게 만드는 어설픈 얘기들을 늘어놨는지도 모르겠지만^^;, 이런저런 블로그를 돌아다니다가 알게되어 읽어봤던 몇 가지 괜찮았던 머신러닝의 '주변영역'을 건드리는 책들을 소개하면서 생각의 나래를 마무리 하려 한다. 혹시 위의 생각에 조금은 일리가 있다고 생각하시는 분들은, 아래의 책들과 함께, 아래 링크의 유투브의 유명한 '모두를 위한 딥러닝 강좌' 를 보시고, 이후 스스로 자기가 갈 공부 방향을 선택하시면 될것 같다.

https://www.youtube.com/playlist?list=PLlMkM4tgfjnLSOjrEJN31gZATbcj_MpUm

 

  • 틀리지 않는 법 : 수학적 사고의 힘 (현실을 심리학, 경제학으로 해석하는 대신 수학으로 해석해 보며 수학적 접근법의 한계도 얘기 한다. 은근 그 과정에서 머신러닝의 한계 또한 생각해 보게 되는 듯 하다)
  • 화이트헤드의 수학이란 무엇인가(앞의 수학 라이브러리의 복소수 파트에 대해 아이디어를 얻은 책이다. 왜 이런 수학 장르가 생겨나게 됬을까를 찬찬히 생각하게 하는 책 같다. 수학과 별로 안 친한 저와 비슷한 레벨이면 이해가 안가서 살짝 넘겨야 되는 수학적 개념들이 좀 있다)
  • 마스터 알고리즘 (머신러닝에 대한 얘기다. 해당 분야를 오래 경험해온 전문가가 세상에는 5종류의 머신러닝에 대한 접근방식들이 있고, 그들을 조화시킬 궁극의 알고리즘이 있을수 있다는 얘기를 한다. 이 책을 읽게 되면 머신러닝 책들이 왜 그렇게 여러 주제의 잡학 사전처럼 구성되어들 있는지 이성적이면서 감성적으로 수긍이 되게된다)
  • 헤드 퍼스트 통계학 (통계를 직관적으로 설명하려는 시도를 한다. 뒤로 갈수록 직관성이 사라지고 수학만 남는 아쉬움이 있다--;)
  • How to solve it (어떻게 문제를 풀어나가느냐에 대한 책이다. 문제 풀이에 대한 접근 방법을 얘기하는 책이고, 학생들에게 어떻게 수학을 가르치느냐에 대해 얘기하는 책이지만, 어려워서 이해가 안가는 문제들이 종종 있음에도 읽을 가치가 있다. 이 수업의 방향이 이 책 영향을 좀 받은듯 하다^^;) 
  • CODE 코드 : 하드웨어와 소프트웨어에 숨어 있는 언어(이건 프로그래밍 공부가 의미 없게 느껴지는 분들을 위해서 덤으로~ 불빛 신호에서 컴퓨터가 만들어지기 까지의 과정을 설명한다. 중간에서 길을 잃어 필름이 끊기더라도 읽어볼 가치가 있다고 본다. 비 전공자한테는 생각보다 어려울지도 모르지만, 해당 책의 저자가 비전공자도 읽을 수 있도록 노력해 쓴 책이고 파이썬 공부를 하고자 하는 의지 정도만 있음 가능할 듯 싶다.)

 

[몸풀기 - 공학 라이브러리의 푸리에 변환]

   머신러닝을 하다 갑자기 이름도 낯선 푸리에 변환이란 것이 나오게되서 이상하게 생각할지 모르겠지만, 이 라이브러리를 소개하는 이유는, 머신러닝 라이브러리가 특별한 형태의 라이브러리라는 생각을 버리게 하고 싶어서 이다. 그리고 지금 소개하는 라이브러리를 잘 보면 왠지 머신러닝 라이브러리랑 닮아있다는 느낌도 있어서 이다. 그래도 맘에 안든다면 opencv 라는 이미지 라이브러리(정확하게 얘기함 컴퓨터 비전 쪽 라이브러리)를 살펴보는 부록 챕터라고 생각해도 좋다.

 

  푸리에 변환은 보통 전자기파 쪽에서 많이 쓰는 이론으로, 저도 수학적으론 잘 모르지만, 모든 주기를 가지는 신호는 사인과 코사인의 합으로 분리해 나타낼 수 있다는 이론이다(우리가 쓰는 스마트폰의 통신들도 이런 이론들 땜에 가능하게 됬다) 그러다보니 분리된 사인, 코사인 함수들의 주기를 보면, 해당 신호가 어떤 주파수(1초에 몇번 지그재그로 움직이나. 2.6G 와이파이의 경우 1초에 2억 6천번 파형이 움직인다)들을 포함하고 있는지 알게된다. 뭐 주기가 없는 신호도 무한대의 주기를 가졌다고 가정해서 변환하는 신묘함도 있다.

 

  근데 묘하게 이걸 그림에 적용하면, 그림에 분포되 있는 명암 요소에 따라서 주파수 영역으로 변환할 수 있다(사실 정확히 설명하기 위해 이리저리 사이트 들을 찾아봤는데, 그림의 평균적인 명암값 으로부터의 차이를 가지고 판단하는지, 경계 값과의 변화정도 인지 기준을 잘 모르겠다. 아시는 분은 댓글에 좀 --;). 여튼 그렇게 그림에 푸리에 변환을 적용해 정리하면, 그림과 동일한 2차원 영역의 주파수 그림이 나오는데, 중심 부분에 가까울 수록 그림에서 주로 면이라고 얘기하는 색이 비슷한 공간속의 점들의 정보를 가지고 있고, 중심에서 멀어질 수록 그림의 외곽선 이라고 부르는 경계를 구분해 주는 점들의 정보를 가지고 있다. 

 

  그래서 그림을 읽어와 푸리에 변환을 해서 나온 네모난 주파수 평면에서, 가운데 원 영역의 값을 0 값으로 덮어 지워버리면(보통 마스킹 한다고 한다. 밑의 그림 처럼 까맣게 정보를 없앴다), 그림의 외곽선 정보만이 남는 효과를 가져오게 된다(약하게 삭제하면 sharpening effect 가 되는듯 하다). 그것은 뭐 포토샵 같은 전문 프로그램에선 더 정밀한 로직을 쓰겠지만, 이 로직을 적용하면 어떤 그림을 적용하든 동일한 윤곽선 추출 효과를 가져온다(마치 학습된 머신러닝 로직이 데이터에 동일한 효과를 가져오는 것처럼 말이다)

 

 

 

  그럼 예제를 보자(요건 머신러닝 예제가 아니라 구글을 헤맨 과정은 생략한다^^). 일단 컴퓨터에 있는 사진 등을 복사해도 좋고 적당한 이미지가 없다면 무료 사진 사이트인 https://pixabay.com/ 로 가서 이미지를 하나 다운 받아서, c:\python\code 폴더에 mypic.jpg 라고 저장한다.

 

  그리고 opencv 를 설치해야 되는데 불행하게도 python 3.5 버전은 pip 인스톨이 안되는 듯하다. https://www.solarianprogrammer.com/2016/09/17/install-opencv-3-with-python-3-on-windows/ 사이트에서 안내하는 데로, http://www.lfd.uci.edu/~gohlke/pythonlibs/ 사이트로 가서, opencv_python-3.2.0-cp35-cp35m-win_amd64.whl 를 다운받아(가끔 파일이 이름이 바뀌어서 그런데 cp35 가 파이썬 3.5란 의미이고, amd64가 64비트 윈도우이다.) c:\python\code 에 넣고, 아래와 같은 명령어로 whl 파일을 설치했다. whl 파일이 무언가는 예전 시간에 설명했다~

(참고로 어떤 블로그 보다보니 python 3.6 버전에서는 pip 로 그냥 설치가 되는걸 보긴 했다)

 

c:\Python\code>pip install opencv_python-3.2.0-cp35-cp35m-win_amd64.whl
Processing c:\python\code\opencv_python-3.2.0-cp35-cp35m-win_amd64.whl
Installing collected packages: opencv-python
Successfully installed opencv-python-3.2.0

 

 

 그럼 예제를 위해 만든 소스를 구글에서 'python opencv fft low pass filter image' 라고 찾아서, 돌려보다 보니 python 2를 기준으로 만든거라서 나누기 코드 부분에서 문제가 생겨서(나누기 후 결과를 integer 로 취급할거냐 float 로 취급할거냐가 버전이 올라가면서 달라졌다), 아래의 사이트를 찾아, 코드를 조금 수정했다.

[소스 사이트]

http://docs.opencv.org/3.0-beta/doc/py_tutorials/py_imgproc/py_transforms/py_fourier_transform/py_fourier_transform.html

 

[에러 메시지와 해결 사이트]

TypeError: slice indices must be integers or None or have an __index__ method 에러

https://stackoverflow.com/questions/28272322/typeerror-slice-indices-must-be-integers-or-none-or-have-an-index-method

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
import cv2
import numpy as np
from matplotlib import pyplot as plt
 
# 이미지를 읽어옴
img = cv2.imread('mypic.jpg',0)
 
# 푸리에 변환 하고 이런저런 맞춤
= np.fft.fft2(img)
fshift = np.fft.fftshift(f)
magnitude_spectrum = 20*np.log(np.abs(fshift))
 
# 작은 마스크를 만들어서 푸리에 변환한 영역의 가운데를 지워 버림(하이패스 필터)
rows, cols = img.shape
crow,ccol = rows//2 , cols//2
fshift[crow-30:crow+30, ccol-30:ccol+30= 0
f_ishift = np.fft.ifftshift(fshift)
 
# 다시 이미지로 역변환
img_back = np.fft.ifft2(f_ishift)
img_back = np.abs(img_back)
 
# 그림으로 보여줌.
plt.subplot(131),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(132),plt.imshow(magnitude_spectrum, cmap = 'gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.subplot(133),plt.imshow(img_back, cmap = 'gray')
plt.title('Image after HPF'), plt.xticks([]), plt.yticks([])
 
plt.show()
 
print ("ok")
 
cs

 

 

  언제나 처럼 c:\python\code 폴더에 'python opencv_fft_sample.py' 란 이름으로, 파일형식은 '모든 파일'을 선택하고,  인코딩은 'utf-8' 로 저장하고 실행한다.

c:\Python\code>python opencv_fft_sample.py
ok

 


  위와 같이 그림이 세개 나오는데 왼쪽이 원본 이미지, 가운데의 빛이 가운데 모인듯한 이미지가 푸리에 변환을 통해 주파수 영역으로 변환된 이미지, 오른쪽이 주파수 영역의 가운데 데이터를 삭제하여(코드안에 로직이 있다). 다시 이미지로 복구하였을때, 주로 경계선 요소들만 남기고 평면 요소들이 사라진 경우이다.

 

  보시면 푸리에 변환이 왠지 머신러닝과 비슷한 느낌이 들지 않는가?(머신러닝은 사실 데이터를 적절히 해석해 내부 특징을 얻을 수 있는 모든 알고리즘 들을 가져다 쓴다고도 할수 있다) 차이라면 데이터에서 로직을 추출하진 않는다. 하지만 데이터를 넣으면(디지털 이미지 또한 좌표 기반의 데이터 이다) 임의의 처리를 해서 데이터에 특정한 특징을 노출해준다는 부분에서 왠지 비슷하다는 느낌이 들어 굳이 이 시점에 소개했다. 그럼 실제 머신러닝 로직으로 가보자.

 

 

 

 

[최소제곱법 이란]

  많은 머신러닝 및 데이터 분석 책에서, 제일 처음에 나오는게 회귀분석이고(과거의 데이터를 기반으로 미래의 데이터를 예측함), 그 중 가장 먼저 나오는게 최소 제곱법 같다(이후는 최우추정법, 베이즈 같은 낯선 이론들이 나온다). 디테일한 의미를 다 알진 못하지만 개념적으로만 근사한다면, 해당 컨셉은 제공되는 데이터들로부터 가장 가까운 어떤 선(직선 or 곡선 : 사실 직선은 곡선의 한 형태 일 것이지만)을 찾기위해서, 선과 데이터 들의 수직 거리를 제곱한 값들의 합이 제일 작아지는 선을 나타내는 방정식의 상수들을 찾는 것이다. 즉 직선이라고 가정한다면 y = ax + b 의 a, b 값을 계속 특정 방향으로 조금씩 변화해 가면서, 각 점과의 거리를 제곱한 합이 제일 작아지는 지점을 찾는다(엑셀의 추세선이라고 봐도 될것 같다). 뭐 자세한 내용은 관련 책이나 블로그 등에서 보시라고 하고 살짝 넘어간다 --; 

    

 

 


[numpy 에서의 최소제곱법 구현]

  원래 numpy 는 계획에 없었는데, 찾다보니 있어서 다른 라이브러리와 비교를 위해서 넣게 되었다.구글에서 'linear least square fit python' 라고 찾아 아래의 페이지를 찾는다.

https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.lstsq.html

 

  이후 적당히 편집하여, y = 3x + 1 의 공식에 맞도록 x, y 값을 바꿔 넣었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import numpy as np
import matplotlib.pyplot as plt
 
# x, y 좌표 지정(y = 3x + 1), 방정식 모델 지정
= np.array([1257])
= np.array([471622])
= np.vstack([x, np.ones(len(x))]).T
 
# 선형대수 라이브러리의 least squre 호출
slope, intercept = np.linalg.lstsq(A, y)[0]
print("기울기:", slope, ", 절편:", intercept)
 
# 기존 값을 점으로, 찾은 기울기를 선으로 그린다.
plt.plot(x, y, 'o', label='Original data', markersize=10)
plt.plot(x, slope*+ intercept, 'r', label='Fitted line')
plt.legend()
plt.show()
cs

 

  앞 시간의 예제들을 시연해 봤다면 이미 라이브러리들을 설치되어 있을테니(아니라면 설치 방법은 15, 16교시를 참고한다), c:\python\code 폴더에 'numpy_least_sample.py' 이름으로, 파일형식은 '모든 파일'을 선택하고,  인코딩은 'utf-8' 로 저장하고 실행한다(지금보니 utf-8 로 저장하면 한글 주석이 달리는 경우에도 상단에  '#-*- coding: utf-8 -*-' 를 안 넣어도괜찮다). 정상적으로 해를 찾아 기울기 3, 절편 1 을 찾게 된다.

 

c:\Python\code>python numpy_least_sample.py
기울기: 3.0 , 절편: 1.0

 

 

 

 

[scipy 에서의 최소제곱법 구현]

  이번에는 과학 라이브러리인 scipy 를 이용해 보자. 구글에 'linear least square fit python scipy' 라고 찾아 아래의 2개 페이지를 찾아 적당히 믹스 했다. 전체적인 흐름은 numpy 와 거의 같다고 보면 될 듯 하다.

http://www2.warwick.ac.uk/fac/sci/moac/people/students/peter_cock/python/lin_reg/
https://docs.scipy.org/doc/scipy-0.19.0/reference/generated/scipy.stats.linregress.html

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
 
# 데이터 지정
= np.array([1257])
= np.array([471622])
 
# 데이터에 맞는 값 찾기, Slope: 기울기, Intercept: 절편
slope, intercept, r_value, p_value, std_err = stats.linregress(x,y)
print ("Slope and intercept", slope, intercept)
print ("R-squared", r_value**2)
 
# 데이터를 점으로, 찾은 선과 같이 화면에 표시
plt.plot(x, y, 'o', label='original data')
plt.plot(x, intercept + slope*x, 'r', label='fitted line')
plt.legend()
plt.show()
cs

 

  c:\python\code 폴더 'scipy_least_sample.py' 이름으로, 파일형식은 '모든 파일'을 선택하고,  인코딩은 'utf-8' 로 저장하고 실행한다.

c:\Python\code>python scipy_least_sample.py
Slope and intercept 3.0 1.0
R-squared 1.0

 

  뭐 결과가 같으니 이미지도 동일하다.

 

 

 

 

[tensorflow 에서의 최소제곱법 구현]

  마지막으로 tensorflow 인데, 운이 좋게도 금년에 텐서플로우가 64비트 윈도우 버전의 python 3.5 까지 지원하게 되서 시연이 가능하게 됬다. 근데 뭐 아직 윈도우즈는 텐서플로우에게 관심의 바깥 같은 느낌이 좀 들어서, 텐서플로우를 사용하는 사람들은 보통 리눅스나 맥환경에서 많이 쓰는 것은 같다. 

 

  먼저 pip 를 이용해서 텐서플로우를 설치한다(꼭 윈도우 64비트에, 파이썬 3.5 대 버전이여야 가능하다!. 구글을 찾다 보니 새로 빌드를 해서 32비트에 설치해 쓰는 사람들도 있다고는 한다)

c:\Python\code>pip install tensorflow
Collecting tensorflow
  Downloading tensorflow-1.2.0-cp35-cp35m-win_amd64.whl (21.2MB)
...
Successfully installed backports.weakref-1.0rc1 bleach-1.5.0 html5lib-0.9999999 markdown-2.2.0 protobuf-3.3.0 tensorflow-1.2.0 werkzeug-0.12.2 wheel-0.29.0

 

 

  이후 구글에 'tensorflow least squares matplotlib' 라고 검색하여 아래의 두 페이지를 찾았다.

https://gist.github.com/tomonari-masada/ed2fbc94a9f6252036eea507b7119045
https://github.com/aymericdamien/TensorFlow-Examples/blob/master/examples/2_BasicModels/linear_regression.py

 

  위의 페이지의 예제가 좀더 간단하게 보이지만 텐서플로우에서 생성된 변수(x, y_)를 matplotlib 의 넘겼을때 plot 에서 타입 에러가 난다. numpy 리스트 형태를 넘겨야 괜찮다. 그리고 두번째 예제가 좀 모범답안 같이 전체적인 사용 플로우를 잘 보여주는거 같아서 두번째 예제에서 훈련 후 검증 테스트를 하는 코드 부분만 제거하고, 적당히 편집하면 아래와 같다. (상세 로직은 어차피 텐서플로우에서 하라는데로 하는거니, 전체적인 코드의 흐름을 보자 )

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
import tensorflow as tf
import numpy
import matplotlib.pyplot as plt
rng = numpy.random
 
# 파라매터들 변수 조정 수치, 전체 실행 수, 몇 번마다 화면에 로그를 보여주는지
learning_rate = 0.01
training_epochs = 1000
display_step = 50
 
# 훈련용 데이터 지정
train_X = numpy.asarray([1257])
train_Y = numpy.asarray([471622])
n_samples = train_X.shape[0]
 
# 텐서플로우 변수들 만들기
= tf.placeholder("float")
= tf.placeholder("float")
 
= tf.Variable(rng.randn(), name="weight")
= tf.Variable(rng.randn(), name="bias")
 
# 모델 만들기 'y = Wx + b' 를 정의한다.
pred = tf.add(tf.multiply(X, W), b)
 
# least square 공식을 이용하여 최소값을 만들 요소를 지정한다. 
cost = tf.reduce_sum(tf.pow(pred-Y, 2))/(2*n_samples)
 
# 기울기를 보정하는 경사하강법이란것을 사용하고, 비용을 최소화 하는 방향으로 학습 한다.
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)
 
# 초기화?
init = tf.global_variables_initializer()
 
# 텐서플로우 기동
with tf.Session() as sess:
    sess.run(init)
 
    # 데이터 넣기
    for epoch in range(training_epochs):
        for (x, y) in zip(train_X, train_Y):
            sess.run(optimizer, feed_dict={X: x, Y: y})
 
        # 50번 마다 로그 뿌려서 찾는 값 변화를 보여 줌.
        if (epoch+1) % display_step == 0:
            c = sess.run(cost, feed_dict={X: train_X, Y:train_Y})
            print("Epoch:"'%04d' % (epoch+1), "cost=""{:.9f}".format(c), \
                "W=", sess.run(W), "b=", sess.run(b))
 
    # 완료 되면 결과 출력
    print("Optimization Finished!")
    training_cost = sess.run(cost, feed_dict={X: train_X, Y: train_Y})
    print("Training cost=", training_cost, "W=", sess.run(W), "b=", sess.run(b), '\n')
 
    # 찾은 결과 그래프로 보여주기
    plt.plot(train_X, train_Y, 'ro', label='Original data')
    plt.plot(train_X, sess.run(W) * train_X + sess.run(b), label='Fitted line')
    plt.legend()
    plt.show()
cs

 

    c:\python\code 폴더에 'tensorflow_least_sample.py' 이름으로, 파일형식은 '모든 파일'을 선택하고,  인코딩은 'utf-8' 로 저장하고 실행한다.

 

c:\Python\code>python tensorflow_least_sample.py
Epoch: 0050 cost= 0.031498514 W= 3.08722 b= 0.53246
Epoch: 0100 cost= 0.023787469 W= 3.07587 b= 0.593684
Epoch: 0150 cost= 0.017966598 W= 3.06593 b= 0.64688
Epoch: 0200 cost= 0.013570149 W= 3.0573 b= 0.693111
Epoch: 0250 cost= 0.010249456 W= 3.0498 b= 0.73329
Epoch: 0300 cost= 0.007741358 W= 3.04328 b= 0.768208
Epoch: 0350 cost= 0.005847037 W= 3.03761 b= 0.798554
Epoch: 0400 cost= 0.004416286 W= 3.03269 b= 0.824928
Epoch: 0450 cost= 0.003335610 W= 3.02841 b= 0.847849
Epoch: 0500 cost= 0.002519361 W= 3.02469 b= 0.867768
Epoch: 0550 cost= 0.001902884 W= 3.02146 b= 0.88508
Epoch: 0600 cost= 0.001437244 W= 3.01865 b= 0.900125
Epoch: 0650 cost= 0.001085538 W= 3.01621 b= 0.913202
Epoch: 0700 cost= 0.000819894 W= 3.01409 b= 0.924565
Epoch: 0750 cost= 0.000619259 W= 3.01224 b= 0.934442
Epoch: 0800 cost= 0.000467722 W= 3.01064 b= 0.943025
Epoch: 0850 cost= 0.000353270 W= 3.00925 b= 0.950485
Epoch: 0900 cost= 0.000266819 W= 3.00804 b= 0.956967
Epoch: 0950 cost= 0.000201530 W= 3.00698 b= 0.962601
Epoch: 1000 cost= 0.000152215 W= 3.00607 b= 0.967497
Optimization Finished!
Training cost= 0.000152215 W= 3.00607 b= 0.967497

 

  데이터를 보면 1000번을 찾으면서 50번마다 화면에 현재 상태를 출력하라고 했으니 (1000/50) 번 해서 20개의 로그가 화면에 표시된 것을 볼수 있다. Epoch(찾기 반복 횟수), cost 및 optimizer(해 찾기의 목표가 되는 기준 값과, 기준 값을 어떻게 사용할 것인지. 여기서는 최소제곱값 함수가 가장 작아지는 값을 기울기 하강법으로 찾는다). W(기울기) b(절편) 값의 변화를 보면 점점 횟수가 늘수록 기울기와 절편 값들이 의도했던 3과 1로 수렴해 가는 것을 볼 수 있다.

 

  이렇게 비교해 보면 뭔가 전문적인 머신러닝 라이브러리들은 위의 numpy, scipy 같은 일반 선형대수 라이브러리 처럼 결과만 그냥 보여주기 보다는, 모델을 지정하고(pred = tf.add(tf.multiply(X, W), b)), 잘된 학습의 판단기준도 정하고(cost = tf.reduce_sum(tf.pow(pred-Y, 2))/(2*n_samples)), 학습 과정을 살펴보며(로그) 조정하는 듯 체계적인 접근 방식을 유도하고 있다. 뭐 제가 모르는 다른 기능 들도 많겠지만, 꼭 개발쪽의 유닛테스트 프레임윅이 하는 역활과 비슷한 느낌이 든다(저런 프레임윅들은 관련 기능과 함께 조금 논란의 여지는 있지만 'best practice' 를 동시에 제시한다)

 

  matplotlib 으로 그린 그래프 화면은 원래 정답(3, 1)과 비교함 소수점 약간 차이라서, 앞의 2개 그래프와 구분이 안되서 생략했다. 설명의 일관성을 위해 정답을 제한해 놓은게 맘에 안드신다면, 아래와 같이 소스에서 train_X, train_Y 의 배열 값들을 임의의 값으로 바꾸거나 배열 데이터 갯수를 늘여보면 좀더 점들의 중심에 위치하고자 하는 선의 모양을 볼 수 있을 듯 하다. 머신러닝 및 텐서플로우에 이론과 기능에 대한 더 깊은 내용들은 관련 커뮤니티의 강좌나 책을 참고하시면 될듯 하다.

1
2
train_X = numpy.asarray([135912])
train_Y = numpy.asarray([45101813])
cs

 

 

 

[마치면서]

  자 그럼 마무리를 위해 먼 길을 돌아 처음 목적으로 돌아갈 시간이 됬다. 머신러닝에서의 파이썬의 역활은 무엇일까? 개인적인 생각으로는 numpy, panda, matplotlib 과 같이 입출력 데이터를 선, 후처리 할 수 있는 기능을 머신러닝 라이브러리들에게 무료로 제공해 주며, 더 나아가서 세계 공용어인 영어의 역활과 비슷하게 머신러닝과 관련된 여러 관련 라이브러리들이 서로 대화와 협력을 나눌 수 있도록 중재하는 역활을 한다고 하고 싶다. 아마 다른 언어에 의해서 뒤집어 지긴 힘들듯한 이런 중요성 때문에 머신러닝을 배우려는 분들은 꼭 파이썬을 조금씩, 하지만 꾸준히 관심을 가지는게 맞을 듯 싶다.

 

  그리고 다시 한번 얘기하지만 머신러닝 라이브러리는 최근 화려한 스포트라이트와 지원을 받고 있어 그렇지, 어쩌면 원래는 일반 공학 라이브러리와 같은 평범한 라이브러리 출신 일지도 모른다. 그래서 아마도 현재 유행되는 머신러닝 장르 이외에도 데이터의 특별한 측면을 노출해주는 기존의 평범한 알고리즘들이 재조명되어 새로운 머신러닝의 장르를 열수도 있을 것이다. 

 

  또한 앞의 푸리에 변환 라이브러리의 사용과 같이, 사용자가 가진 라이브러리의 동작 원리와 적용 대상에 대한 이해의 깊이에 따라서 그 유용성이 달라지게 된다. 그러므로 머신러닝 프레임윅들의 사용법이나 트레이닝, 오버피팅, 모델, 학습율, 초기값, 오차 등의 방법론들에 집중하는 것도 좋지만, 그것은 어쩌면 그림자만을 쫓는 행위일수도 있으므로, 파이썬을 공부할 때와 마찬가지로 그 기술의 배경이 되는 여러가지의 주변 요소들에 대해서도 교양이라고 생각하고 꾸준히 관심을 가지도록 해보자. 모든 진실은 데이터 안에 숨어있다는 것을 잊지말고...(데이터도 가끔 거짓말을 하긴 하지만 말이다.)

 

 

 

[보충]

1) 이런저런 블로그를 보다보니 푸리에 변환도 컴퓨터 비전 측면에서 머신러닝 쪽에 포함된다고 하는 얘기가 있다. 그럼 예제가 엉뚱한건 아니게 되서 더 다행이다~

 

2) 한 분이 numpy 예제를 보고 "파이썬에선 함수가 2개의 리턴이 가능해요?" 하고 물어보셨다. 그러려니 하고 별 생각 없이 넘어갔던 저였지만--;, 궁금해서 함 찾아봤다. google 에서 'python function multiple return' 으로 찾으면 여러 스타일이 나오는데, 가장 간단한 샘플을 보인다.

https://stackoverflow.com/questions/354883/how-do-you-return-multiple-values-in-python

1
2
3
4
def f():
    return 12
x, y = f()
print ("x = ",x, ",y = ", y)
cs

 

c:\Python\code>python return_test.py
x =  1 ,y =  2

 

3) 아래 글도 괜찮은 듯 해서 참고 자료로 링크를 건다.

머신러닝속 수학(번역)

https://mingrammer.com/translation-the-mathematics-of-machine-learning

 

 

2017.6.25 by 자유로운설탕
cs

 

  

 

 

 

 

 

 

posted by 자유로운설탕
2017. 5. 21. 15:38 프로그래밍

  이번 시간은 지난 시간에 이어 수학 라이브러리의 일환인 그래픽(정확하게는 plotting 이겠지만..) 라이브러리를 살펴보려고 한다. 지난 시간에 언급되었던, matplotlib 과 Plotly 로 예제를 구현해 보면서, 어떻게 사용에 접근하면 좋은지에 대해 얘기해 보려고 한다.

 

 

[목차]

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. 정리 - 이런저런 이야기

 

 

 

[들어가면서]

  우선 예제를 구현해 보기전에 플로팅(plotting) 이라는게 뭔지 잠깐 생각해 보자. plot 이란 특정한 데이터 셋을 공간상에 표시해 주는 것을 이야기 한다. 굳이 2D 평면이 아닌 공간이라고 얘기해도 되는건, 미래에 홀로그램 형태의 3차원 형태의 디스플레이 같은 것이 일반화 된다면 아마 공간 상에 뿌려서 살펴 보는 것도 가능할 것 같아서이다(3D 프린터 같이 말이다). 뭐 여튼 현재 라이브러리는 2차원 화면상에 3차원 좌표까지 표시가 가능하긴 하지만, 아래와 같이 하나의 차원을 색으로 치환해, 평면상에 4차원의 데이터를 표시하는 것도 가능하다(뭐 공간적인 4차원이 아니라고 가짜라고 볼 수도 있겠지만, 어차피 plot 의 목적은 공간에 요소를 단순히 나타낸다기 보다는 각 구성 요소들의 독립, 복합적 관계를 파악하기 위한 목적이 크기 때문에(그 날의 매출 현황이라든지, 이상 이벤트 발생이라든지), 그래프 상에 사람이 차이를 식별할 수 있는 색이나, 점의 모양, 기울기 등도 하나의 인식가능한 차원의 그래프 요소라고 할 수 있을 것 같다.

http://engi-agora.tistory.com/37

 

  우리는 이렇게 좌표 형태로 그려진 그래프를 봄으로써 데이터의 추이, 이상현상, 군집, 분포, 차이 등의 특성에 대해 일련의 데이터 숫자 값들을 직접 보는 것보다 시각적으로 쉽게 파악할 수 있게 된다(물론 의미 있는 데이터를 잘 선택해 적절한 기법으로 뿌려줬을 때의 얘기긴 하지만 말이다). 어느정도 플로팅은 통계나 추이와 같은 감각들을 인간에게 시각적으로 전달해 주는 수학적 도구라고 볼 수 있을 것 같다.

 

 

  개인적으로 이 글을 쓰기 위해서 이런 저런 책과 웹페이지들을 참고해 보면서, 인상 깊었던 2개의 그래프는, 복소수의 곱을 좌표상에 표시해 준 것과, 산포도 이다. 첫번째 복소수의 곱을 좌표상에 표시한 부분은 아래의 페이지에 나온 것 같이 두 복소수를 곱했을 때의 의미가 공간 적으로는 아래와 같이 두 개의 좌표상의 각을 더한 회전으로 나타내게 된다는 것을 이야기 한다.  

[출처 : https://www2.clarku.edu/~djoyce/complex/mult.html ]

 

  두 번째 산포도는 특정한 두 요소가 서로 얼마나 상관이 있느냐 하는 것을 보여 주는 것이다. 예를 들어(통계는 잘 몰라서 예가 적절한진 모르겠지만 --;) 치킨을 100만큼 좋아하는 사람이 맥주를 얼마나 좋아하고, 치킨을 10만큼 좋아하는 사람이 맥주를 얼마나 좋아하는지를 각각 좌표안에 찍는다면, 아래의 그래프와 같이, 오른쪽으로 상승하는 좁은 타원 형태의 그래프 분포를 보인다면, 두 요소는 상관 관계를 가지고 있을 가능성이 존재한다고 합리적 의심을 가져볼 수 있다.

 

  그러면 한번 앞에 언급했던 2개의 많이 쓰이는 그래픽 라이브러리(matplotlib, plotly)를 이용해서, 복소수의 곱과, 산포도를 그려보는 예제를 만들어 보도록 해보자. 앞 시간에 살펴봤던 numpy 를 이용해 데이터를 생성하거나 담는 그릇으로 사용할 것이고, 해당 그릇을 그래프를 그리는 라이브러리에 전달하려고 한다.

 

 

 

 

 

[Matplotlib 을 이용한 복소수의 곱 과정을 그래프로 표시하기]

   뭐 저도 수학은 정말 잘 못해서, 복소수에 대해서 잘 모르기에, 이 예제를 만들기 위해서 책도 좀 읽고, 이것저것 급조를 좀 해봤다. 지나가는 수학과 분이 계시면 그냥 살포시 지나가시기를...

 

 

  위의 방정식의 해를 풀기 위해서는 x의 제곱이 -1 이 되어야 한다. 즉 아래와 같이 x가 루트 -1 이 되어야 하는데, 이 실수 관점에서 보면 혈통을 인정하기 힘든 가상의 수(루트 -1)를 i (복소수)라고 그런다. 그래서 i 의 제곱은 -1 이 된다.

 

그럼 그래프로 그려볼 간단한 곱을 하나 해보자. A 를 0.5+1i, B 를 1+0.5i 라고 하면,

(0.5+1i)*(1+0.5i) = 0.5*1 + 0.5*0.5i + 1i*1 + 1i*0.5i = 0.5 + 0.25i + 1i + 0.5i^2

= 0.5 + 0.25i + 1i + 0.5*(-1) = 1.25i 

 

  음 수식 상으로는 실수 부가 사라지고, 허수부만 '1.25i' 로 남게 된다.(고백하자면 일부러 각도가 합해지는게 잘 인지되는 그림을 그리기 위해서 복소수를 정할때, B는 x축 기준으로 30도, A는 60도 가 되게 만들어서, 두 개를 곱하면 직각인 90도가 되게 만들었다).

 

 

 

  이제 파이썬으로 그래프를 그려 보려 한다. 일단 첫번째로 막히는게, 파이썬에선 복소수를 어떻게 다루지? 하는 부분이다. 어떤 라이브러리를 써야 되는지도 모르겠고, 어떤 문법을 가졌는지도 모르겠다. 구글에다 우선 'python complex number' 라고 검색해 보자.

https://stackoverflow.com/questions/8370637/complex-numbers-usage-in-python

 

  위의 페이지를 보니, 생각보다 휠씬 간단해 보인다. 복소수를 쓰기 위해 complex 란 함수를 이용해도 되지만, 그냥 1 + 2j (파이썬은 i가 아니라 j임, 위키를 보면 전류를 나타내주는 기호 i와 구분하기 위해서 공학 쪽에서 j라고 쓴다고 하는 설이...) 라고 써주면 파이썬이 알아서 인식해 주나보다. 그럼 샘플을 참고하여 해당 방식으로 두 개의 복소수를 곱해서 결과를 화면에 나타내는 코드를 만들어 보자.

1
2
3
4
5
6
#-*- coding: utf-8 -*-
 
= 0.5+1j
= 1+0.5j
= a*b
print(c)
cs

 

  위의 파일을 c:\python\code 폴더에 utf-8 인코딩으로, complextnum.py 라고 저장한다(역시 이 부분을 잘 모르겠으면 2교시에서 복습을!). 이후 아래와 같이 실행해 본다.

c:\Python\code>python complextnum.py
1.25j

 

 

  아까 손으로 열심히 계산한, '1.25j' 결과가 나온다. 그럼 이제 그래프를 그려보기 위해서 일단 matplotlib 을 설치한다.

c:\Python\code>pip install matplotlib
Collecting matplotlib
  Downloading matplotlib-2.0.2-cp35-cp35m-win_amd64.whl (8.9MB)
..
Successfully installed cycler-0.10.0 matplotlib-2.0.2 pyparsing-2.2.0

 

 

  이후 구글에서 'complex number python plot' 라고 검색해서 아래의 스택오버플로우 페이지를 찾았다.

https://stackoverflow.com/questions/17445720/how-to-plot-complex-numbers-argand-diagram-using-matplotlib

 

  제일 밑에 보면, 복소수를 좀 더 잘 나타내는거 같은 극좌표 형식의 그래프도 있는 것 같지만, 좀 더 상황을 간단히 하기위해서(현재 그래프를 그려보는 시간이지, 복소수를 공부하는 시간은 아니므로...), 일반 평면 좌표계를 사용하려 한다.

 

  개인적으로 그리기 전에 원하던 그래프의 모양은 아래와 같았다.

  • 화면에 점만 찍는게 아니라 원점으로 부터 점까지 선이 연결되길 바랬다(그게 좀더 점들의 각도를 잘 보여줄 것 같아서)
  • 그래프의 비율이 실제 좌표와 동일하게 1:1 이길 바랬다. 비율이 다르다면 각도가 왜곡 되어 보일 수 있기 때문에 말이다
  • 각 선은 서로 다른 색으로 표시됬음 했고, 가로-세로 축 선을 표시하고, 그리드도 표시되었음 했다.

 

   여기 까지 계속 읽어 오신 분들은 이제 저랑 함께 초보 수준은 살짝 벗어났다고 생각해서, 읽는 지루함을 없애기 위해서 예전 시간들 처럼 모든 시행 착오를 다 보여주진 않고, 각각의 요소를 해결한 방법들을 간단히 명시후에, 최종 코드를 제시해 보이려고 한다^^

 

 

  먼저 위의 스택 오버플로우 코드를 참고해서 그래프를 그려 보니, 가로축과 세로축의 비율이 일정치 않아 각도가 정확하게 보이지 않았다. 그래서 구글에 '
matplotlib aspect ratio'  로 검색해 아래의 페이지의 코드를 찾았다.

https://matplotlib.org/examples/pylab_examples/equal_aspect_ratio.html

1
plt.axes().set_aspect('equal''datalim')
cs

 

 

  가로, 세로 축을 보이게 하는 코드는 'matplotlib show x y axis' 로 찾아 아래와 같다.

1
2
ax.axhline(y=0, color='k')
ax.axvline(x=0, color='k')
cs

 

 

  그리고 각각의 선의 색을 다르게 표시하기 위해 구글에 'matplotlib plot change color' 라고 검색해서 아래의 페이지에서 관련 코드를 찾았다. 여기서는 그래프에 화살표와 함께 주석을 다는 코드도 있어서 해당 코드를 이용해서 해당 되는 복소수의 선들에 이름표를 달기로 했다.

https://matplotlib.org/users/pyplot_tutorial.html

1
2
3
4
5
6
7
8
# c 가 색을 나타냄
Here are the available Line2D properties.
color or c : any matplotlib color 
 
# 그래프 안에 주석 
plt.annotate('local max', xy=(21), xytext=(31.5),
            arrowprops=dict(facecolor='black', shrink=0.05),
            )
cs

 

 

  참고로 그래프 내의 title 등에 한글 표시를 원하는 분들은 아래의 블로그 같은데에 해결 방법이 있지만('matplotlib 한글 깨짐' 같은 걸로 찾아서), 적용 해보니까 폰트에 따라서 숫자의 '-' 표시가 안 나온다든지 하는 여러가지 사소하게 귀찮은 일들이 생겨서 글의 논점을 흐릴 듯 해서 그냥 전 영문으로 진행했다.

http://pinkwink.kr/956

 

 

  그래서 우여곡절 끝에 완성된 최종 코드는 아래와 같다.

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
#-*- coding: utf-8 -*-
import matplotlib.pyplot as plt
import numpy as np
 
# 수식 정의
= 0.5+1j
= 1+0.5j
= a*b
 
# numpy 에 복소수들 담기
arr = np.array([a, b, c])
 
# 여기서 i 는 색 루프를 돌리기 위해서..
= 0
for x in arr:
   # 칼라 지정용 배열
   arrColor = np.array(['yellow','brown','magenta'])
   # 지정된 색으로 해당 복소수의 실수부(real), 허수부(iamg)를 그림.
   plt.plot([0,x.real],[0,x.imag],'ro-',c=arrColor[i])
   i = i + 1
 
# 그래프 제목
plt.title('Complex Number Multiplication')
# 그리드 보이기
plt.grid(True)
# 가로세로 1:1 비율 만들기
plt.axes().set_aspect('equal''datalim')
# x,y 축 이름 짓기
plt.ylabel('Imaginary')
plt.xlabel('Real')
# 그래프가 보이는 최대 최소 범위 지정
plt.xlim((-2,2))
plt.ylim((-2,2))
# x, y 축 보이기
plt.axhline(y=0, color='k')
plt.axvline(x=0, color='k')
 
# 주석 달기       
plt.annotate('0.5+1i (a)', xy=(0.51), xytext=(0.70.8),
            arrowprops=dict(facecolor='black', shrink=0.01),
            )
 
plt.annotate('1+0.5i (b)', xy=(10.5), xytext=(1.20.3),
            arrowprops=dict(facecolor='black', shrink=0.01),
            )
            
plt.annotate('(1+0.5i)*(0.5+1i) (c=a*b)', xy=(01.25), xytext=(0.21.1),
            arrowprops=dict(facecolor='black', shrink=0.01),
            )           
 
# 그린 그래프 보여주기
plt.show()
cs

 

 

  저장 후 실행하면 아래와 같이 그래프가 보이게 된다. 두 개의 복소수가 곱해져서, 길이는 1.25 가 되고(1.25i), 각도는 x축 기준으로 30도(b), 60도(a)가 합해져 90도(c)가 된 모습을 기하학적으로 확인 할수 있다(개인적으로는 이렇게 대수학이 기하학으로 표시되어 직관적으로 이해되는 부분이 재밌는거 같다~).

c:\Python\code>python complextnum.py

 

 

 

 

 

 

[Plotly 을 이용해 산포도 그리기]

   먼저 진행 하기 앞서서 결론적인 하나의 부분만 얘기하면, plotly 는 matplotlib 처럼 완전 무료는 아니고(커뮤니티 라이센스는 있어 그래프 종류에 따라 무료로 하루에 50~250개의 그래프는 그려볼 수 있다-> 보충 부분에 링크를 걸어놓았지만, Offline 모드로 실행을 할 수 있다. 일단 온라인 모드에만 해당하는 이야기로 정정한다.), 파이썬에서 해당 사이트로 데이터를 보내서, 사이트 내의 계정에 데이터와 그래프를 저장하는 방식같다. 클라우드 그래프 라이브러리라고 보면 될듯 하다. 다만 그래프는 상용이라 그런지 조금 더 기본 디자인이 깔끔해 보이고 이런 저런 export 등 도 가능해 보인다(예제에는 ipython notebook 에 삽입 가능하다고 되어 있는데, 명령어로 실행하는 입장이라서 잘 모르겠다..). 가격별 차이의 상세한 부분은 아래 링크를 참조한다.

https://plot.ly/products/cloud/ 

 

  먼저 plotly 라이브러리를 설치한다.

c:\Python\code>pip install plotly
Collecting plotly
....
Successfully installed decorator-4.0.11 ipython-genutils-0.2.0 jsonschema-2.6.0 jupyter-core-4.3.0 nbformat-4.3.0 plotly-2.0.8 traitlets-4.3.2

 

 

  산포도를 그리는 방법을 찾기 위해 구글에서 'scattergram plotly' 라고 검색하여 아래의 샘플 페이지를 찾는다.

https://plot.ly/python/line-and-scatter/

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 plotly 
import plotly.plotly as py
import plotly.graph_objs as go
import numpy as np
 
# 랜덤 값 만들기
= 1000
random_x = np.random.randn(N)
random_y = np.random.randn(N)
 
# 트레이스 만들기
trace = go.Scatter(
    x = random_x,
    y = random_y,
    mode = 'markers'
)
 
data = [trace]
 
# 그리기
py.iplot(data, filename='basic-scatter')
cs

 

 위의 파일을 c:\python\code 폴더에 utf-8 인코딩으로, plotlytest.py 라고 저장한다. 이후 아래와 같이 실행해 본다(...)

c:\Python\code>python plotlytest.py

KeyError: 'plotly_domain'

 

  실행하면 위와 같은 에러가 발생한다. 구글에 'KeyError: 'plotly_domain''로 찾아서 확인해 보면 iplot 대신 plot 을 사용하라고 한다. 아래 해당 부분 코드만 수정해 다시 돌려본다. (편집 환경으로 ipython 을 사용하시는 분들은 에러가 안 날수도 있을 것 같다)

https://stackoverflow.com/questions/34929778/keyerror-plotly-domain-when-using-plotly-to-do-scatter-plot-in-python

1
py.plot(data, filename='basic-scatter')
cs

 

c:\Python\code>python plotlytest.py

Aw, snap! We don't have an account for ''. Want to try again? You can authenticate with your email address or username. Sign in is not case sensitive.
Don't have an account? plot.ly
Questions?
support@plot.ly

 

그런데 이번엔 계정이 없다고 얘기한다(첨에 나온 라이센스 관련 페이지가 이걸 만나서 찾아보다가 가게된 것이다. 그래서 사실 완전 무료가 아닌 이 라이브러리를 소개해야 되나마나 조금 고민하긴 했다). 다시 구글에 'Don't have an account? plot.ly' 라고 검색하여 아래의 안내 페이지를 찾는다.

https://plot.ly/python/getting-started/

 

 

결국 plotly 라이브러리를 사용하기 위해서는 아래 네 가지를 해야한다.

  • 회원 가입
  • API 키 발급(kisa api 를 사용할 때와 비슷하다)
  • 이메일 인증(API키만 발급 하고 해보니, 안되서 이메일 인증도 했다)
  • python 코드안에 계정 정보 넣기. (어떤 원리로 캐싱이 되는진 모르지만 한번 넣고 실행 후엔 해당 계정 정보는 코드에서 빼도 해당 계정으로 실행이 되긴 한다)

 

  그럼 회원 가입 페이지로 가서 회원 가입을 한다. (이메일, 계정 이름, 패스워드)

https://plot.ly/accounts/login/?next=%2Fsettings%2Fapi

 

 

  이후 로그인 한 상태에서 아래 페이지로 가서 API 키를 '재생성' 시킨다. 이후 해당 키를 적당히 메모장 등에 저장해 둔다.

https://plot.ly/settings/api

 

 

  자신의 메일로 가면 plotly 에서 보낸 인증 메일이 와 있을 것이다. 인증 링크를 클릭한다.

 

 

  이후 코드를 최종 수정하여, 아래와 같이 API 키 정보를 집어 넣는다. (whois API 때와 마찬가지로 코드내의 '본인계정', '본인API키' 항목 부분은 각자 발급한 키로 수정해 주어야 동작한다)

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
#-*- coding: utf-8 -*-
import plotly 
import plotly.plotly as py
import plotly.graph_objs as go
import numpy as np
 
# API 키 정보 넣기
plotly.tools.set_credentials_file(username='본인계정', api_key='본인API키')
 
# 랜덤 값 만들기
= 1000
random_x = np.random.randn(N)
random_y = np.random.randn(N)
 
# 트레이스 만들기
trace = go.Scatter(
    x = random_x,
    y = random_y,
    mode = 'markers'
)
 
data = [trace]
 
# 그리기
py.plot(data, filename='basic-scatter')
cs

 

  다시 저장해 실행해 본다.

c:\Python\code>python plotlytest.py

High five! You successfuly sent some data to your account on plotly. View your plot in your browser at https://plot.ly/~계정이름/0 or inside your plot.ly account where it is named 'basic-scatter'

 

 

  실행이 잘 됬다고 나오면서, 브라우저 창에 https://plot.ly/~본인아이디/0/ url이 뜨면서, 로그인 창이 나온다. 로그인 탭을 눌러 아까 발급 받은 계정으로 로그인 한다.

 

 

  로그인이 되면 아래와 같이 그리려던 그래프가 웹 화면에 보이게 된다. 상단의 'export' 메뉴를 클릭하면 그래프를 이미지 파일로 저장하거나, 데이터를 문서로 만들거나, 다른 언어로(매트랩, R 등) 코드를 내보낼 수도 있다.

 

 

 

 

[마무리 하면서]

  설명은 엄청 장황했지만, 결론은 엄청 간단하다. 그래픽 라이브러리를 쓰는 방법은 아주 단순하다. 데이터들을 잘 모아서 numpy 배열 같은데에 담는다. 이후 plot 해 주는 라이브러리에 해당 배열을 전달해 주면 된다. 그리고 적당히 원하는데로 좌표 공간을 꾸미면 된다. 하지만 이 간단함에 숨어있는 어려움이 올바른 데이터를 수집하고, 그 수집한 데이터의 품질이 충분히 현실과 견주어 의미가 있어야 하고, 해당 특징을 잘 해석해 나타내 줄 적절한 그래프 함수를 선택하는 눈을 키우는 부분이다. 이것은 아마 다음 시간에 다룰 머신러닝에서의 파이썬 측면에서도 비슷한 문제가 될 것 같다.

 

 

 

[보충]

  검색을 하다보니 plotly 를 offline 모드로 하는 예제를 보았다. 이렇게 하면 위와 같은 인증 절차도 필요 없어 지는듯 싶다. (어쩐지 온라인 모드만 되는 툴이 저렇게 인기가 많나 했다;)

http://hamait.tistory.com/800

 

  plot 공식 사이트 쪽 설명은 아래에, 특별한 기능차이가 없다면 오프라인을 쓰는게 더 편할 듯 싶다. 온라인과 오프라인이 차이가 있는지는 명확하게 설명된 내용은 없는것 같다.

https://plot.ly/python/getting-started/

https://plot.ly/python/offline/

 

 

 

 

2017.5.28 by 자유로운설탕
cs

 

 

posted by 자유로운설탕
2017. 5. 6. 23:38 프로그래밍

  이번 시간의 주제는 향후 17교시에 '머신러닝에서의 파이썬의 역활'에 대한 얘기를 풀어보기 위한 사전 작업(밑밥...)이라고 볼수 있다. 파이썬에서 쓰는 여러가지 수학 라이브러리들을 어떤 관점에서 바라보는 것이 좋을지에 대해 설명 페이지와 간략한 샘플들을 통해 살펴보려고 한다.

 

 

[목차]

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. 정리 - 이런저런 이야기

 

 

 

[들어가면서]

  머신러닝 관련 코드들을 보다 보면 numpy 나 pandas 같은 라이브러리(모듈)들이 단골로 등장 하게 된다. 대충 알기에는 해당 모듈들이 데이터들이 담긴 array 등을 처리해 주는 것으로 알고 있지만, 해당 라이브러리들이 도데체 어떤 기능들을 가지고 있길래 그렇게 자주 애용하게 되는걸까? 해당 코드들을 보면서 자주 쓰이는 기능들을 중심으로 이해하는 방법도 있겠지만, 실제 해당 라이브리러가 어떤 목적으로 만들어 졌고, 어떤 범위를 가지고 쓰이는지 전체적으로 살펴보는 것도, 그다진 나쁘진 않은 접근 방법이라고 생각하며 이야기를 시작한다.

 

  우선 파이썬에서 자주 쓰이는 수학 라이브러리들을 리스트업 하기 위해 구글에서 'python math libraries' 라고 검색한다. 아래의 페이지를 보면 2015년 8월 기준으로 파이썬 공식페이지에서 다운로드 수 기준으로 수학 라이브러리들의 순위를 매겼다.

http://www.palrad.com/top-python-math-statistics-libraries-w-12007/ 

 

  위로부터 7개 정도만 쭉 나열해 보면 NumPy, Pandas, Scipy, matplotlib, Patsy, Sympy, Plotly 이다. 이 중 matplotlib, Plotly 는 다음 시간에 살펴볼 그래픽 라이브러리라고 볼수 있고, Patsy 는 통계모델에 대한 라이브러리라는거 같아서 잘 모르니 패스하고, NumPy, Pandas, Scipy, Sympy 네 개의 라이브러리에 대해서 간단한 예제와 함께 살펴보려고 한다.

 

  실제로 'machine learning example' 또는 'tensorflow example' 등으로 구글에서 찾아서 아래의 이런 저런 페이지를 찾아 본 결과 그 이외의 특별한 라이브러리의 사용 예는 보이지 않았다.

http://machinelearningmastery.com/machine-learning-in-python-step-by-step/
https://www.toptal.com/machine-learning/machine-learning-theory-an-introductory-primer

https://github.com/aymericdamien/TensorFlow-Examples

 

 

 

 

[Numpy]

  어떻게 보면 파이썬을 이용하면서 가장 많이 보게 되는 수학 라이브러리 이다. 블로그의 예제들을 찾아보면서 쓰임을 알아 보는 것도 좋지만, 이렇게 유명한 라이브러리들은 설명 문서가 잘되어 있기 때문에, 해당 페이지를 전체적으로 훝어 보려고 한다. 그럼 구글에서 'numpy documentation' 라고 검색하면, 아래의 공식 문서 페이지가 나온다.

https://docs.scipy.org/doc/

 

  분위기를 보면 Numpy 와 Scipy 는 같은 목적에서 시작된 프로젝트 인거 같다. 'Numpy reference' 링크를 클릭한다.

https://docs.scipy.org/doc/numpy/reference/

 

  첫번째 'array objects' 설명을 보면(https://docs.scipy.org/doc/numpy/reference/arrays.html), 여러 차원의 array(수학적 의미로는 '행렬'이 적절할 듯 싶다)를 만들고, 임의의 부분만 짜르고, 특정 요소를 지정하고, 루프를 돌리고, array 차원을 재 배열 하고, 정렬하고 하는 등에 대한 설명들이 있다.

 

  두번째 'Universal functions' 설명을 보면(https://docs.scipy.org/doc/numpy/reference/ufuncs.html), 해당 array 를 수학적 연산에 의해 서로 사칙연산을 한다든지, 각각의 요소에 대해 로그 연산을 한다든지, 서로 비트 연산을 한다든지, 두개의 array 에서 최대 값들만을 뽑아 낸다든지 하는 여러가지 array 에 대한 연산들을 설명한다.

 

  세번째 'Routines' 에서는(https://docs.scipy.org/doc/numpy/reference/routines.html) 이제 상세한 부분으로 들어가 array 의 다양한 생성 방법이나, 합치거나, 자르는 등의 조작, 텍스트 및 스트링 등으로 읽거나 쓰기, 수학이나, 기본 통계함수 적용 등 상세한 사용시 필요한 세부 기능에 대해서 설명하는 듯 하다.

 

  그 이외는 다른 여러가지 기타 설명인걸 보니 결국 numpy 의 범위는 다양한 차원의 array 를 만들고, 해당 array 끼리 연산하거나, 각각의 array 요소들에 수학적 연산을 일괄 적용하거나, array 를 여러 다양한 차원의 형태로 자유롭게 변환하게 만들어 주는 라이브러리라고 봐도 무방할 듯 싶다. 그럼 간단한 샘플을 하나 만들어 보려고 한다.

 

 

  아래의 명령어로 numpy 를 설치 한다.

c:\Python\code>pip install numpy
Collecting numpy
  Downloading numpy-1.12.1-cp35-none-win_amd64.whl (7.7MB)
    100% |################################| 7.7MB 92kB/s
Installing collected packages: numpy
Successfully installed numpy-1.12.1

 

  해당 설명 페이지를 기반으로 샘플 기능을 만들어 보면 아래와 같다. 1) array 를 만들고, 2) 각 원소에 값을 더하거나, 3) array 를 재조정 하고 4) log 함수를 적용하는 등의 일을 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#-*- coding: utf-8 -*-
import numpy as np
 
# 2*2 array 를 만든다.
= np.array([[12], [34]])
 
# array 원소들에 모두 1을 더해 b array 를 만든다.
= a + 1
print (b)
 
# b array 를 재 정렬해 1*4 로 c array 로 변환한다.
= np.reshape(b, (1,4))
print (c)
 
# c array 원소들에 log 함수를 적용해 d array 를 만든다. 
= np.log(c)
print (d)
cs

 

  언제나처럼 c:\python\code 폴더에 numpytest.py 라고 저장하고, 아래와 같이 실행을 한다. (잘 모르시는 분은 2교시를 참조한다)

c:\Python\code>python numpytest.py
[[2 3]
 [4 5]]  -> 원래의 array 에 1을 더한 결과
[[2 3 4 5]]  -> 1*4 array 로 재 조정함.

[[ 0.69314718  1.09861229  1.38629436  1.60943791]]  -> 각 요소에 log 함수를 적용함.

 

 

 

 

[Scipy]

  아까 처음의 numpy documentation 페이지에서, 'scipy reference' 링크를 클릭한다.

https://docs.scipy.org/doc/scipy/reference/

 

  목차를 살펴보면 해당 라이브러리는 numpy 구조를 기반으로, 전문적인 과학분야의 수학연산들을 도와주는 라이브러리들이다. 적분(Intergration)이나, 영상이나 신호처리 영역에서 자주 쓰이는 푸리에 변환(fourier Transforms), 선형 대수(Linear Algebra), 통계학(Statistics) 등에서 필요한 수학적 연산들을 모아두었다고 보면 될거 같다. 해당 부분의 특징은 '구슬이 서말이라도 꿰어야 보배'라는 속담처럼 해당 분야에 대한 이해를 바탕으로 특정 연산을 수행하고 싶을 때야 의미가 있을 것 같다. 지금으로선 적분 관련된 예제 하나만 살짝 보고 넘어가려고 한다.

 

 

  우선 scipy 를 설치하는데 오랜만에 에러가 발생했다.

c:\Python\code>pip install scipy
Collecting scipy

...

numpy.distutils.system_info.NotFoundError: no lapack/blas resources found

 

  구글에서 'numpy.distutils.system_info.NotFoundError: no lapack/blas resources found' 라고 찾아서 스택오버플로우 페이지로 간다.

http://stackoverflow.com/questions/28190534/windows-scipy-install-no-lapack-blas-resources-found

 

  빌드 툴을 다운받거나 통합 패키지인 아나콘다를 다운 받으라는 등 여러가지 가이드가 있지만, 그 중에 wheel 파일이 있다고 그걸 가져다 설치해 보라는 내용이 눈에 띈다(예전 시간에 설명했지만, wheel 파일은 빌드된 바이너리가 zip 으로 압축된 형태의 파일이다.) 

 

  명시된 uci 대학교 홈페이지로 가서(http://www.lfd.uci.edu/~gohlke/pythonlibs/#scipy), 64비트이고, python 3.5 를 위해 빌드된 wheel 파일(scipy‑0.19.0‑cp35‑cp35m‑win_amd64.whl)을 다운받으려 하다보니, 아래와 같은 문구가 눈에 띈다. (사실은 해당 문구를 못보고 scipy 설치만 먼저하고 샘플 코드를 실행하니 에러가 나서 구글을 찾아서 다시 돌아 왔다--;). 기존 numpy 만을 설치하면 동작이 안되고 mkl 이라는 패키지와 통합된 numpy 를 설치해야 하나 보다

Install numpy+mkl before installing scipy.

 

  해당 링크로 가면 python 3.5 버전의 64비트 wheel 파일(numpy‑1.12.1+mkl‑cp35‑cp35m‑win_amd64.whl)이 보인다. 위의 scipy wheel 파일과 함께 두개의 파일을 다운하여 c:\python\code 폴더에 저장한다. 그리고 아래와 같이 명령어로 두개를 순서대로 설치한다. 

 

c:\Python\code>pip install numpy-1.12.1+mkl-cp35-cp35m-win_amd64.whl
Processing c:\python\code\numpy-1.12.1+mkl-cp35-cp35m-win_amd64.whl
Installing collected packages: numpy
Successfully installed numpy-1.12.1+mkl

 

c:\Python\code>pip install scipy-0.19.0-cp35-cp35m-win_amd64.whl
Processing c:\python\code\scipy-0.19.0-cp35-cp35m-win_amd64.whl
Successfully installed scipy-0.19.0

 

 

  위와 같은 y = x^2 인 함수에 대해서 0~1 구간을 적분하는 소스는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
#-*- coding: utf-8 -*-
from scipy.integrate import quad
 
# y=x^2 함수를 정의
def myfn(x):
    return x**2
 
# 0~1 사이에 해당 y 함수를 적분 한다.
ans, err = quad(myfn, 01)
print (ans)
cs

 

c:\python\code 폴더에 scipytest.py 라고 저장하고, 아래와 같이 실행을 하면 적분 결과를 볼수 있다.

c:\Python\code>python scipytest.py
0.5

 

 

 

 

[Sympy]

구글에서 'sympy documentation' 이라고 검색한다.

http://docs.sympy.org/latest/index.html

 

  해당 메뉴얼을 보면 보통 대수학이나 기하학에 대한 기능들이 많은데, symbolic(기호) 연산이 가능하다는 특징이 있다고 한다. 이것은 혹시 'mathematica' 라는 툴을 써본 경험이 있는 분은 알 것 같은데(전 개인적으로 좋아하는 툴이다), 마치 수학 공식을 사람이 푸는 것처럼 기호 형태로서 풀이하는 것을 얘기한다. 온라인에서 경험해 보자면, https://www.wolframalpha.com/ 페이지로 가서, 검색 란에 'y=x^2-x-6' 를 넣고 검색을 하면, 해당 함수의 그래프나, 근, 인수분해 결과 및 과정 등을 보여준다.

 

  해당 부분은 수식 계산에도 쓸수 있지만, 수학 공부 할때 병행해 이리저리 사용해도, 뭐 나쁘진 않아 보인다. 먼저 아래와 같이 설치해 본다.

c:\Python\code>pip install sympy
Collecting sympy
....
Successfully installed mpmath-0.19 sympy-1.0

 

 

  위의 wolframalpha 페이지에 넣었던 'y=x^2-x-6' 를 인수분해 하는 코드는 아래와 같다. n승 기호가 '^' 가 아닌 '**' 로 좀 다르다(이런 부분은 만드는 사람 맘대로니...)

1
2
3
4
5
6
7
8
9
#-*- coding: utf-8 -*-
from sympy import *
 
# x 를 심볼로 지정
= symbols('x')
 
# x^2-x-6 의 인수분해를 한다.
ans = factor(x**2 - x - 6)
print(ans)
cs

 

c:\python\code 폴더에 sympytest.py 라고 저장하고, 아래와 같이 실행을 하면 인수분해한 결과가 표시 된다.

c:\Python\code>python sympytest.py
(x - 3)*(x + 2)

 

 

 

 

[pandas]

마지막으로 팬더스 차례다. 마찬가지로 구글에 'pandas documentation' 라고 검색한다.

http://pandas.pydata.org/pandas-docs/stable/

 

  뭐 여기서도 여러가지 설명들이 있지만, 기본적인 기능을 보면 csv, json 등의 데이터를 url 등으로 부터 가져와서 dataframe 이라는 numpy 의 arrary 와 비슷한 저장 공간에 넣거나, 데이터의 기준이 되는 시간 데이터 등을 자동으로 생성해 주거나, 마치 DB 안의 데이터와 같이 join 이나, 정렬, 그룹핑 등이 가능하다. 실제 panda는 numpy 를 베이스로 만들어 졌다고 한다.

http://stackoverflow.com/questions/11077023/what-are-the-differences-between-pandas-and-numpyscipy-in-python 

 

  근데 SQL 문법을 아는 분은 마지막에 있는 'Comparison with SQL' 항목을 살펴보게 되면 pandas 가 무슨일을 할수 있는지를 대략적이겠지만 좀 더 쉽게 이해할 수 있게 되는 것 같다. (R을 아는 분은 R 하고의 비교 링크도 있다) 결국 개인적인 생각으로는 pandas 는 데이터 분석 작업을 위해 메모리상에 구현되 있는 엑셀과 비슷하다고 개념을 잡으면 어떨까 싶다. 엑셀의 여러 내장 함수를 조합해서 다중 배열이나, SQL 의 여러 기능들과 비슷한 작업을 할 수 있듯이, pandas 도 메모리 상에 데이터들을 정렬해 뿌려 놓고 엑셀을 조작하듯이, 해당 데이터 개개에 여러가지 numpy 와 같은 방식으로 연산을 가하거나, DB 에 저장한 데이터 같이 여러가지 조건에 의해서 분류하거나, 조합하거나, 필터링 하는것을 쉽게 해주는 라이브러리라고 생각하면 될듯 싶다.

 

 

  그럼 샘플 실행을 위해 pandas를 설치한다.

c:\Python\code>pip install pandas
Collecting pandas
...
Successfully installed pandas-0.20.1 python-dateutil-2.6.0 pytz-2017.2

 

  pandas 용 dataframe 을 하나 만들고, 그 중에서 SQL 의 where 조건을 적용한 것과 비슷하게 'A' 열이 'fruit' 인 데이터만 추출하는 예제의 코드는 아래와 같다.  

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 pandas as pd
import numpy as np
 
# pandas 용 dataframe 을 만든다(메모리 버전 엑셀이라고 생각하자)
df = pd.DataFrame({'A''fruit drink cookie fruit'.split(),
                   'B''orange Coke chocopie mango'.split(),
                   'C': np.arange(4)})
 
# 만든 df 를 출력
print(df)
print('------------------------------')
 
# 뿌리면 아래와 같이 생김.
#      A         B      C
# 0  fruit    orange    0
# 1  drink    juice     1
# 2  cookie   chocopie  2
# 3  fruit     mango    3
 
# df 중에 A 열이 fruit 인 데이터만 추출
print(df.loc[df['A'== 'fruit'])
cs

 

c:\python\code 폴더에 pandatest.py 라고 저장하고, 아래와 같이 실행을 한다.

c:\Python\code>python pandatest.py
        A         B        C
0   fruit      orange    0
1   drink      Coke     1
2  cookie  chocopie   2
3   fruit     mango     3
------------------------------
       A       B      C
fruit  orange   0
fruit   mango  3

 

 

 

 

[마무리 하면서]

  위의 4개의 수학 모듈들을 살펴 보면서(사실 pandas 는 수학 모듈이라기 보단 범용 데이터 처리를 위한 공통 프레임 이라고 보는게 맞을 것 같지만...) 머신러닝이나 기타 분야에서 왜 numpy 나 pandas 같은 모듈들을 애용하는지에 대해서 공감을 가지게 됬음 하는 바램을 가진다. 참 이렇게 보면 파이썬은 여러 모듈들이 엃히고 설키면서 서로를 보조하며 시너지를 내어 더 인기가 많은 듯도 싶다. 이 시간을 진행함으로서 나중에 머신러닝에 대해서 얘기할때는 해당 코드에서 보이는 numpy 나 pandas 코드가 밥위의 콩처럼 분리되어 보여, 머신러닝 모듈 자체에만 집중 하게 되어, 좀 더 설명이 간략하게 될 거라고 기대를 하고 있다. 다음 시간에는 비슷한 목적으로 가공된 데이터를 좌표상에 표시해 주는 그래픽 라이브러리에 대해서 살펴보려고 한다.

 

 

 

 

2017.5.7 by 자유로운설탕
cs
posted by 자유로운설탕
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. 4. 7. 23:30 프로그래밍

  이번 시간은 Windows GUI 자동화를 살펴보는 시간이다. 만들어 보려는 프로그램은 1) 메모장을 열어서, 2) 작은 python 소스를 입력 후, 3) 콤보 박스에서 인코딩과, 파일 형식을 선택하고 특정 폴더에 저장을 하는 프로그램이다. 이후 이전 시간에 잠시 언급했던 상용 GUI 자동화 툴인 unified functional testing 으로 구현된 코드와 비교해 보며, 오픈소스 모듈과 상용 모듈의 차이점에 대해서 얘기하려 한다.

 

 

[목차]

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. 정리 - 이런저런 이야기

 

 

 

[들어가면서]

  굳이 파이썬 자동화 공부를 하면서 상용 자동화 툴을 언급하는 이유는, 첫째는 파이썬 언어 또는 연관된 selenium,  beaulifulsoup 같은 모듈들을 공부하는 이유가 효율적인 프로그래밍적 사고 및 구현 방식을 찾기 위한 것이라고 생각하기 때문이다. 그런 측면에서 향후 더 효율적인 언어나 개선된 모듈들이 나왔을 때, 현재 언어의 선입견에 갇혀서 고정된 관점에서 새로운 것들을 바라보는 것보다, 현재 사용하고 있는 언어의 장단점에 대해 객관적인 생각을 가지고 있는게 맞을 것 같기 때문이다. 둘째로는 뒤에서 보면 알겠지만, 상용툴은 사람들이 제품을 구입 하도록 어필할 수 있는 부분들이 있어야 하기 때문에, 오픈소스의 경우 수동으로 구축을 해야되는 설계, 구조적 측면 이나, 초창기 버전 들에서 흔히 간과되는 사용성 및 유지보수에 관련된 기능들이 제품 안에 기본으로 포함되어 있는 경우가 많다. 하지만 역으로 그러한 사용자를 돕는 기능들이 툴을 사용하는 프로세스를 고정시켜 버려서, 사용 범위의 제한을 가져오는 독이 될 수도 있다.

 

 

  앞의 selenuim 이나 beautifulsoup 을 보면 해당 모듈이 웹 페이지를 인식 할때, 태그(element)나 속성(attribute), css selector, xpath(이전 시간의 예제에는 사용을 안했지만, selenium은 xpath 를 지원한다. beautifulsoup 은 지원 안 하는 거 같지만, 최근 크롤링에서 많이 사용이 된다고 하는 scrapy 란 모듈도 xpath 를 지원한다). xpath 에 대해선 잘은 모르지만, xml 도큐먼트의 요소들을 정의할 때 사용된다고 하며, 왠지 정규 표현식 같은 스타일이여서, 문법에 익숙해지면 꽤 효율적일 것 같다. xpath 에 대한 상세 내용은 아래 링크를 참고한다.

  https://www.w3schools.com/xml/xpath_syntax.asp

 

 

  비슷한 맥락에서 gui 자동화 모듈은 원하는 개체(윈도우, 메뉴, 버튼, 리스트 박스 등)를 선택하고 조작하기 위해서, 위의 web 자동화의 element, attiribute 와 비슷한 기준 요소가 필요하게 되며, 그런 부분이 class 라든지 text 라든지, 좌표(position) 라든지 하는 속성들이다. 밑의 visual studio 로 gui 프로그램을 작성하는 화면을 보면, 폼 안에 위치한 버튼을 선택 했을때, 오른쪽 properies 창에서 해당되는 버튼 내의 text 등 버튼을 정의하는 많은 속성(property)들을 볼 수 있다. 모든 윈도우즈 gui 프로그램은 이 속성들을 기준으로 개체를 식별하고 메시지들을 교환한다. 이 부분이 바로 오늘 진행하는 내용의 핵심이다.

 

  그럼 앞에서 web 자동화를 구현하는데는 관련된 웹 기술들을 잘 알아야 유리하다고 얘기했듯이, gui 자동화의 구현에는 windows(또는 x-windows 든지 osx 든지) 및 그 환경에서 돌아가는 gui 프로그램 들의 구조에 대해서 잘 아는 것이 유리하다(하지만 잘 알게 되는게 쉬운일은 아니다 --; 이 부분은 저도 초보이다). 

 

 

 

 

 

[GUI 코드 구현]

  그럼 본격적으로 코드 구현으로 들어가 원하는 기능을 만들기 위해 필요한 부분을 생각해 보자.

1) 어떤 자동화 모듈을 사용해야 되는지 결정해야 한다.

2) 메모장을 띄울 수 있어야 한다.

3) 메뉴장의 메뉴를 선택하거나, 글을 입력할 수 있어야 한다.

4) 저장 다이얼 로그에서, 경로를 지정하고, 인코딩 콤보 박스와, 확장자 콤보 박스에서 원하는 항목을 선택하고, 파일 이름을 넣은 후, 저장 버튼을 누르는 작업을 할 수 있어야 한다.

 

 

 

[메모장 실행과 메뉴 선택]

  우선 메모장을 조작할 수 있는 적절한 모듈을 찾기 위해서 구글에 'windows gui automation python notepad' 라고 검색한다.

  https://pywinauto.github.io/

 

  위의 'pywinauto' 라는 모듈의 홈페이지를 보면 원하는 코드가 다 있는 건 아니지만, 기본적으로 메모장을 실행하고, '도움말 > 메모장 정보' 메뉴를 선택해서 창을 띄운 후 닫고, 키를 입력하는 코드가 들어있다. 근데 소스 내용을 보니 영문 윈도우 기준 코드인거 같아서, 한글 윈도우에선 잘 돌아가려는지 확신이 안 든다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
from pywinauto.application import Application
 
# Run a target application
app = Application().start("notepad.exe")
 
# Select a menu item
app.UntitledNotepad.menu_select("Help->About Notepad")
 
# Click on a button
app.AboutNotepad.OK.click()
 
# Type a text string
app.UntitledNotepad.Edit.type_keys("pywinauto Works!", with_spaces = True)
cs

 

  뭐 일단 좀더 정보를 수집하기 위해서 홈페이지 오른쪽에 있는 'Documentation' 링크를 눌러 보자.

  https://pywinauto.readthedocs.io/en/latest/index.html

 

  비교적 도움말이 잘되어 있다, 천천히 살펴보고 싶을 경우는, 맨 위의 'What is pywinauto' 부터 'Methods available to each different control type' 까지의 5개 정도의 설명들을 살펴 보면 될것 같다. 마지막 'Methods available to each different control type' 부분은 menu 나 textbox 등의 GUI 컨트롤 들을 어떻게 다룰수 있는지 설명하는 전반적인 레퍼런스 이다.

 

 

  도움말 첫 페이지의 내용을 보다보면 눈에 띄는 내용이 하나 있는데, 여러가지 오픈소스와 무료 및 상용 자동화 툴을 소개한 리스트가 있다. 몇 가지를 살펴 봤는데, 먼저 python 에서 쓸수 있는 winguiauto,  pyautogui 라는 툴은 메뉴얼이 상세하지 않은거 같아서 제외했고, 파이썬 모듈은 아니지만 독립적으로 돌아가는 무료 어플리케이션인 Autoit 은 윈도우즈 gui 자동화에서 유명하지만, 2015년 9월이후 더 이상 업데이트가 안되고 있어, win 7과 win 10의 지원이 명시되어 있지 않다(혹시 돌리면 돌아가는 지는 잘 모르겠지만 말이다...). 또 활발하게 버전업 되고 있는 다른 무료툴인 autohotkey 는 gui 컨트롤의 자동화 보다는 키보드와 마우스 동작 중심의 macro 프로그램(상용 프로그램으로 따지면 macro express 정도의 포지션)에 좀 더 가까운것 같다. 또 상용 gui 자동화 툴인 Winrunner 는 나중에 언급할 unified functional testing 의 과거 이름이다. silktest 는 예전엔 무척 독특하게 좋다고 생각 했었지만, 그 독특함 때문에 범용적인 winrunner 에게 시장을 많이 뺐긴 후, 오픈소스 와의 통합으로 방향을 틀었었는데 지금은 어찌 되고 있는지 잘 모르겠다. 웹이나 GUI 자동화 자체에 관심있는 분들은 해당 링크의 툴들을 찬찬히 살펴보면 괜찮을 듯 싶다.

 

 

  그럼 우선 도움말에 명시된 pip 명령어를 이용해 pywinauto 모듈을 설치해보자. 아래와 같이 명령어를 입력하면 잘 설치가 된다.

c:\Python\code>pip install pywinauto
Collecting pywinauto
  Running setup.py install for pywinauto ... done
Successfully installed pywinauto-0.6.2

 

 

  그럼 위의 샘플 소스 내용(좀 많이 위가 됬다)을 긁어서, c:\python\code 폴더에 utf-8 인코딩으로 notepad1.py 라고 저장 후, 실행해 보자(실행 부분을 잘 모르겠으면 2교시에서 복습을...)

c:\Python\code>python notepad1.py
Traceback (most recent call last):
  File "notepad1.py", line 7, in <module>
    app.UntitledNotepad.menu_select("Help->About Notepad")

.......
pywinauto.findbestmatch.MatchError: Could not find 'Help' in 'dict_keys(['서식(&O)', '보기(&V)', '도움말(&H)', '편집(&E)', '파일(&F)'])'

 

  그런데 메모장이 실행되긴 하지만, 위의 에러가 난다. 에러가 난 부분을 살펴보면 'Help->About Notepad' 메뉴를 선택하면서 에러가 난 것 이다. 위에서 보면 'menu_select' 를 실행하다 에러가 났고, 메뉴를 찾으려 하는데 'Help' 란 메뉴는 없고 자신이 알고 있는 상단 메뉴들은 (['서식(&O)', '보기(&V)', '도움말(&H)', '편집(&E)', '파일(&F)'] 밖에 없다고 한다. 대충 유추해 보면 'Help' 라는 부분을 pywinauto 가 인지하고 있는 '도움말(&H)' 로 바꿈 될거 같다. 그럼 소스를 수정해 보자(장황하겠지만 우선은 step by step 으로 진행한다--;)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#-*- coding: utf-8 -*-
from pywinauto.application import Application
 
# 메모장를 띄운다.
app = Application().start("notepad.exe")
 
# '도움말' > '메모장 정보' 메뉴를 선택한다.
app.UntitledNotepad.menu_select("도움말(&H)->About Notepad")
 
# '확인' 버튼을 눌러서 다이얼로그를 닫는다.
app.AboutNotepad.OK.click()
 
# 메모장에 내용을 적는다.
app.UntitledNotepad.Edit.type_keys("pywinauto Works!", with_spaces = True)
cs

 

  저장 후 다시 실행해 본다.

c:\Python\code>python notepad1.py
Traceback (most recent call last):
  File "notepad1.py", line 8, in <module>
    app.UntitledNotepad.menu_select("도움말(&H)->About Notepad")
...

pywinauto.findbestmatch.MatchError: Could not find 'About Notepad' in 'dict_keys(['', '도움말 보기(&H)', '메모장 정보(&A)'])'

 

 

  위를 보면 앞에 난 에러와 비슷하게 'About Notepad' 부분에서 에러가 발생했다. 그럼 한번 해본거니 비슷하게 대응되는 한글 부분으로 수정한다. 이후 저장 후 다시 실행 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#-*- coding: utf-8 -*-
from pywinauto.application import Application
 
# 메모장를 띄운다.
app = Application().start("notepad.exe")
 
# '도움말' > '메모장 정보' 메뉴를 선택한다.
app.UntitledNotepad.menu_select("도움말(&H)->메모장 정보(&A)")
 
# '확인' 버튼을 눌러서 다이얼로그를 닫는다.
app.AboutNotepad.OK.click()
 
# 메모장에 내용을 적는다.
app.UntitledNotepad.Edit.type_keys("pywinauto Works!", with_spaces = True)
cs

c:\Python\code>python notepad1.py
Traceback (most recent call last):
...
pywinauto.findbestmatch.MatchError: Could not find 'OK' in 'dict_keys(['', 'Edit'])'

 

  그런데 또 에러가 난다. 이번엔 버튼을 클릭하는 부분이다(근데 슬슬 익숙해 지지 않는가?). 이번엔 힌트도 잘 표시되지 않지만, 이 경우는 실제 실행된 메모장 화면을 참고하면 된다.

 

  위의 화면을 보면 위쪽 타이틀엔 '메모장 정보' 가 아래쪽 버튼엔 '확인' 이라는 텍스트 들어가 있다. 여기 까지 오게 되면 메뉴얼을 찬찬하게 보진 않았지만, 조금은 pywinauto 가 gui 개체를 인식하는 방식을 알수 있을 것도 같다. 메뉴는 메뉴의 이름으로(단축키 기호 포함) 접근하고, 창은 타이틀로, 버튼은 버튼에 쓰여 있는 텍스트로 접근하나 보다. 최종으로 샘플 파일을 수정해 본다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#-*- coding: utf-8 -*-
from pywinauto.application import Application
 
# 메모장를 띄운다.
app = Application().start("notepad.exe")
 
# '도움말' > '메모장 정보' 메뉴를 선택한다.
app.UntitledNotepad.menu_select("도움말(&H)->메모장 정보(&A)")
 
# '확인' 버튼을 눌러서 다이얼로그를 닫는다.
app.메모장_정보.확인.click()
 
# 메모장에 내용을 적는다.
app.UntitledNotepad.Edit.type_keys("pywinauto Works!", with_spaces = True)
cs

c:\Python\code>python notepad1.py

 

  실행하면 정상적으로 메모장이 실행되서, '메모장 정보' 창이 열렸다가 닫히고, 메모장에는 pywinauto Work! 라는 글자가 입력이 된다.

 

 

 

[파일 저장하기]

  그럼 이제 utf8 방식으로 인코딩을 선택해, c:\python\code 폴더에 samplecode.py 란 이름으로  저장하는 코드를 구현해 보자. 먼저 메모장에서 저장 메뉴를 선택했을 때 뜨는 '다른 이름으로 저장' 다이얼로그를 한번 살펴 보면서 고민을 해보자.

 

  위의 그림 상에서 원하는 대로 파일을 저장하려면 4가지 부분을 구현해야 하는데, 1) c:\python\code 로 폴더를 선택해야 하며, 2) 파일이름 텍스트 박스에 'samplecode.py' 라고 텍스트를 입력해야 하며, 3) 파일형식 콤보박스에서 '모든 파일' 로 선택해 바꿔 주어야 하고, 4) 인코딩을 ANSI 에서 'UTF-8' 로 변경해 준 후 저장 버튼을 클릭하면 된다. 일단 이렇게 되면 애매한 부분이 각각의 개체들을 어떤 '특성' 으로 접근해야 하는지 알수가 없다. 버튼처럼 타이틀이 있는 것도 아니고, 가리키는 텍스트가 있는 것도 아니다(텍스트 박스 옆에 있는 '파일이름(N):' 이라는 문장은 사실 텍스트 박스와 직접 관계는 없는 독립된 개체이다).

 

 

  일단 아직까진 힌트가 별로 없으니 구글에서 'pywinauto save as dialog' 라고 찾아보자. 그런데 파일을 save 하는 코드는 잘 보이진 않고, 아래의 2가지 스택오버플로우 글이 눈에 띈다. 

 

  http://stackoverflow.com/questions/9482019/how-do-i-select-a-folder-in-the-saveas-dialog-using-pywinauto 

  첫번째 글은 save 하며 폴더를 지정하고 싶은데, 어떻게 지정이 가능하냐는 문의다. 근데 답변 글은 대안을 제시하며, 파일 이름 텍스트 박스에 c:\python\code\samplecode.py 라고 풀 경로로 적으라는 것이다. 폴더를 선택 할수 있는 기능을 실제로 구현할수 있을지도 모르지만(이건 전적으로 pywinauto 를 만든 사람이 해당 컨트롤을 다룰 수 있게 기능을 구현해 넣었냐에 달려있다), 동일한 결과를 가지므로 폴더 선택 코드 부분은 이런 식으로 해결하자. 

 

 

  http://stackoverflow.com/questions/37027644/open-file-from-windows-file-dialog-with-python-automatically/37214623

  2번째 글은 파일을 save 하는 예제는 아니고 open 을 하는 코드이다. 근데 어차피 save 나 open 창이 비슷하기도 하고, 창을 열어 값을 입력하고, (콤보 박스 값을 선택하는 코드)만 넣음 비슷할 듯 하니 이 코드를 참고하자.

1
2
3
4
5
6
7
8
9
from pywinauto import application
 
app = application.Application().start_('notepad.exe')
 
app.Notepad.MenuSelect('File->Open')
 
# app.[window title].[control name]...
app.Open.Edit.SetText('filename.txt')
app.Open.Open.Click()
cs

 

 

  그럼 그 다음은 콤보 박스를 어떻게 선택할 것이냐는 문제가 된다. 구글에서 다시 'pywinauto combobox select' 라고 검색하여 아래의 글을 참고한다.

  https://pywinauto.github.io/docs/code/pywinauto.controls.win32_controls.html

 

  해당 내용들을 pywinauto 에서 다룰수 있는 모든 컨트롤 들을 설명한 페이지인데, 중간 쯤에 보면 콤보 박스 관련 설명이 있다.

class pywinauto.controls.win32_controls.ComboBoxWrapper(hwnd)

   Bases: pywinauto.controls.HwndWrapper.HwndWrapper

....

Select(item)
Select the ComboBox item

item can be either a 0 based index of the item to select or it can be the string that you want to select

 

  콤보박스 컨트롤 뒤에 .select 로 호출해 숫자(순서)나, 이름을 넣음 된단다.

 

 

  근데 여기까지 오니 마지막 문제에 다다르게 된다. 위의 '다른 이름으로 저장' 다이얼로그 화면을 보면, 콤보 박스가 여러 개 있다. 각각의 콤보 박스의 이름을 어떻게 알아 낼 수 있을까? 상용 자동화 툴같이 레코딩 기능(레코딩 버튼을 누르고 사용자가 원하는 행동을 하면 해당 동작을 (완벽하진 않지만) 자동화 코드로 만들어 주는 기능)이라도 지원해 주면, 결과로 저장되는 코드에서 인식되는 개체 이름들을 파악하면 되지만, pywinauto 는 그러한 레코딩 기능도 없는것 같다.

 

  다시 pywinauto 메뉴얼 페이지를 하나씩 훝어보고, 구글을 검색하고 하다가 아래 페이지 들을 찾게 되었다.  
  https://pywinauto.readthedocs.io/en/latest/getting_started.html#attribute-resolution-magic

  http://stackoverflow.com/questions/5039642/how-to-access-the-control-identifiers-in-pywinauto

 

  해결책은 print_control_identifiers() 함수를 사용하게 되면, 각 창 안에 있는 모든 컨트롤 들의 속성들이 쭈루룩 나타난다는 거다. 그걸 보고 원하는 개체를 찾아서 이용해 코딩하면 된다고 한다(이건 윈도우 개발툴인 spy++의 텍스트 버전같다. --; spy++는 아래 글을 참고로...).

  http://happyguy81.tistory.com/51

 

  왠지 좀 노가다 일 것 같은 같은 불길한 예감이 들긴 했지만 뭐 시키는 데로 했다. --;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#-*- coding: utf-8 -*-
from pywinauto.application import Application
 
# 메모장를 띄운다.
app = Application().start("notepad.exe")
 
# 메모장에 code를 적는다.
app.UntitledNotepad.Edit.type_keys("print ('test')", with_spaces = True)
 
# '파일' > '저장' 메뉴 실행
app.UntitledNotepad.menu_select("파일(&F)->저장(&S)")
 
# '다른 이름으로 저장' 창의 속성을 리스트업 한다.
app.다른_이름으로_저장.print_control_identifiers()
cs

 

  위의 내용을 c:\python\code 폴더에 utf-8 인코딩으로 notepad2.py 라고 저장해서 실행해 보자. 헐 그런데 위의 마지막 코드에 의해 '다른 이름으로 저장' 창에 있는 컨트롤 특성들이 쭈르륵 나오는데 거의 100페이지쯤은 된다.(정말 저 함수를 무슨 생각으로 저렇게 나이브하게 만들었는지 싶다. 찾는 사람 입장도 생각해야지...)

 

c:\Python\code>python notepad2.py
Control Identifiers:

Dialog - '다른 이름으로 저장'    (L770, T191, R1709, B800)
['다른 이름으로 저장', 'Dialog', '다른 이름으로 저장Dialog']
child_window(title="다른 이름으로 저장", class_name="#32770")
   |
   | DUIViewWndClassName - ''    (L781, T287, R1698, B697)
   | ['DUIViewWndClassName', '다른 이름으로 저장DUIViewWndClassName']
   | child_window(class_name="DUIViewWndClassName")
   |    |
   |    | DirectUIHWND - ''    (L781, T287, R1698, B697)
   |    | ['DirectUIHWND1', '다른 이름으로 저장DirectUIHWND1', 'DirectUIHWND0', '다른 이름으로 저장DirectUIHWND0', '다른 이름으로 저장DirectUIHWND', 'DirectUIHWND']
   |    | child_window(class_name="DirectUIHWND")

.... 이런 식으로 100페이지쯤 됨

 

 

  내용을 위의 다이얼로그 화면과 비교해 몇번의 시행 착오를 거쳐서, 아래의 항목들을 찾아내서 한땀한땀(정말로 이런 기분으로) 코드를 작성했다.

 

   |    |    |    | Edit - '*.txt'    (L972, T562, R1661, B589)
   |    |    |    | ['다른 이름으로 저장Edit1', 'Edit', '다른 이름으로 저장Edit', 'Edit1', 'Edit0', '다른 이름으로 저장Edit0']

 

ComboBox - '텍스트 문서(*.txt)'    (L969, T595, R1690, B628)
   |    |    |    | ['ComboBox2', '다른 이름으로 저장ComboBox2']
   |    |    |    | child_window(title="텍스트 문서(*.txt)", class_name="ComboBox")

 

   | ComboBox - 'ANSI'    (L1147, T738, R1372, B771)
   | ['ComboBox3', '인코딩(&E):ComboBox']

 

   |    |    |    | Button - ''    (L781, T287, R781, B287)
   |    |    |    | ['Button0', 'Button', '다른 이름으로 저장Button', 'Button1']
   |    |    |    | child_window(class_name="Button")

 

 

-> 불편한 걸 직접 느껴봤음 이제부턴 맨 밑의 '보충 내용'에 있는 AutoHotKey 의 spy++를 써서 이름을 알아내보자!

 

 

[최종 코드]

  결과적으로 아래와 같이 최종 코드를 만들어 내게 됬다.(하지만 솔직히 이런 방식으로 개발하게 되면, 메모장 말고 다른 프로그램을 대상으로 하게 되면 잘 될련지 자신은 없다.)

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
#-*- coding: utf-8 -*-
from pywinauto.application import Application
 
# 메모장를 띄운다.
app = Application().start("notepad.exe")
 
# 메모장에 code를 적는다.
app.UntitledNotepad.Edit.type_keys("print ('test')", with_spaces = True)
 
# '파일' > '저장' 메뉴 실행
app.UntitledNotepad.menu_select("파일(&F)->저장(&S)")
 
# '다른 이름으로 저장' 창의 속성을 리스트업 한다.
# app.다른_이름으로_저장.print_control_identifiers()
 
# 파일 full 경로 입력
app.다른_이름으로_저장.Edit1.SetEditText("c:\python\code\samplecode.py")
 
# '파일이름' 콤보박스에서 파일 종류 선택
app.다른_이름으로_저장.ComboBox2.Select("모든 파일")
 
# '파일형식' 콤보박스에서 인코딩 선택
app.다른_이름으로_저장.ComboBox3.Select("UTF-8")
 
# 바로 저장 버튼을 누르면 미처 콤보 박스가 안 바뀌어 에러가 나서 1초 시간 줌
import time
time.sleep(1.0)
 
# 저장 버튼 누름
app.다른_이름으로_저장.Button1.click()
 
 
cs

 

  최종 완성된 소스를 저장하고, 실행해 보자. 아래와 같이 해당 폴더에 정상적으로 문서가 저장이 된다.

c:\Python\code>python notepad2.py

 

 

 

 

[최종 코드의 유지보수 문제점]

  위의 코드의 유지보수 문제를 하나 생각해 보자(약간 이건 테스팅 관점이다). 저 코드는 메모장 프로그램이 대상이라 사실 변경될 일이 없지만, 만약 메모장이 아니라 자주 변경되는 프로그램에 대한 코드라 가정하면 '다른 이름으로 저장' 창의 타이틀이 바뀌면 어떻게 될까? 저 코드 내의 모든 '다른_이름으로_저장' 문자열을 모두 바뀐 이름으로 치환해 주어야 한다. 그런데 만약 해당 다이얼로그를 언급하는 파이썬 파일이 수십, 수백개라면 바꾸는 작업은 카오스가 될 것이다(파일이 많아지다보면, 이름들이 일부 겹치기도 하기(저장, 다른이름으로저장, 저장하기 등등) 때문에 일괄로 바꾸게 되면 경험상 분명 예상치 못한 오류가 날수 있다). 그래서 원래 저런 변화될수 있는 값들은 따로 빼놓아 관리하면 좋은데(마치 프로그램에서 하드코딩 하지 않고, 상수 등으로 빼는 것과 비슷하다고 보면 될 듯 하다), pywinauto 의 경우 아쉽게도 아래와 같이 유지보수에 조금이라도 도움을 주는 코딩 방식를 지원하지 않아 에러가 난다.

1
2
3
4
5
6
# 관리를 위해 변수로 뺌
saveAs = '다른_이름으로_저장'
 
# 파일 full 경로 입력
#app.다른_이름으로_저장.Edit1.SetEditText("c:\python\code\samplecode.py")
app.saveAs.Edit1.SetEditText("c:\python\code\samplecode.py")
cs

 

pywinauto.findbestmatch.MatchError: Could not find 'saveAS' in 'dict_keys(['Dialog', 'Notepad', '제목 없음 - 메모장', ' 다른 이름으로 저장', '제목 없음 - 메모장Notepad', '다른 이름으로 저장Dialog'])' <- saveAS 에 대응되는 컨트롤을 못 찾아 이런 에러가 난다.

 

  ※ 아마 내부적으로 이미 '다른_이름으로_저장' 이 컨트롤 이름으로 강제로 취급되어 관리되는 듯 하다(뭐 이 부분은 제가 pywinauto 를 제대로 이해 못해 그럴 수도 있으니 방법이 있을 지도 모른다고 꼬리를 남기는게 날 것 같다).

 

 

 

 

[한 걸음 더 - 상용툴(unified function testing)과의 비교]

  처음에 얘기했듯이, 위의 pywinauto 의 부족한 부분들을 이해하고, 혹시나 더 좋은 모듈이 나온다면 선택 할 수 있게 하기 위해서, 상용툴의 기능과 함 비교해 보겠다. 다만 현재 글의 목적에 맞게 테스팅 관점은 배제하고, 유지보수와 코드 구현의 관점에서만 살펴보려 한다.

 

  해당툴은 회사가 2번인가 바뀌면서 이름까지 바뀌었지만 지금도 왠지 자리를 못잡은 느낌도 나긴 한다(왜냐하면 현재 최신 버전이 윈도우7 밖에 지원 안한다). 그래도 예전의 무거운 느낌의 사용감은 요즘의 SSD 의 힘 때문인지 부드럽게 동작한다. 

 

  지금은 UI 자동화 업무는 명시적으로 안하기 때문에 해당 툴이 없으므로(예전 기준으로 몇 백만원 이상 하기 때문에 이런저런 잡무에 쓰면 좋겠다고 생각은 하지만...) trial 버전을 찾아봤다. 다행히 다운로드 방식의 트라이얼을 제공하고, 트라이얼 기간도 60일로 늘어난듯 해서, 옛 향수를 떠올리며 다운하여 설치를 했다. 뭐 파이썬 강좌이기 때문에 구현 과정을 생략하고 결과만 얘기한다.

 

 

  해당 툴의 레코딩 기능을 이용해서 pywinauto 코드를 비슷하게 구현한 코드가 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 윈도우 시작메뉴에서 notepad 실행함
Window("시작 메뉴").WinObject("항목 보기").WinList("항목 보기").Select "notepad"
 
# 메모장에 test 라고 입력 함
Window("메모장").WinEditor("Edit").Type "print ('test')"
 
# 메뉴에서 파일 > 저장 선택
Window("메모장").WinMenu("Menu").Select "파일(F);저장(S)    Ctrl+S"
 
# 다이얼 로그의 디렉토리 창에서 c:\python\code 로 이동 
Window("메모장").Dialog("다른 이름으로 저장").WinObject("항목 보기").WinList("항목 보기").Activate "python"
Window("메모장").Dialog("다른 이름으로 저장").WinObject("항목 보기").WinList("항목 보기").Activate "code"
 
# 파일 형식과 인코딩 선택
Window("메모장").Dialog("다른 이름으로 저장").WinComboBox("파일 형식:").Select "모든 파일"
Window("메모장").Dialog("다른 이름으로 저장").WinComboBox("인코딩(E):").Select "UTF-8"
 
# 이름 저장
Window("메모장").Dialog("다른 이름으로 저장").WinEdit("파일 이름:").Set "testscript.py"
 
# 저장 버튼 
Window("메모장").Dialog("다른 이름으로 저장").WinButton("저장(S)").Click
cs

 

  위의 코드를 찬찬히 보게되면, notepad 실행도 실제 시작메뉴의 메뉴 리스트를 '선택해' 실행하고, 디렉토리 선택도 실제 디렉토리 리스트를 '선택'해서 이동 했다. 나머지 부분은 거의 비슷해 보인다. 하지만 'object repository' 라는 인식된 컨트롤을 관리해 주는 메뉴로 가면 하나의 중요한 차이가 있다. 아래의 그림을 보면 코드에서 쓰는 이름과 속성들이 트리 형태로 구조적으로 정의되어 있다. 그럼 1) 코드상에 쓰이는 이름을 수정할 수도 있고(아마 관련된 코드에 자동 반영되는 걸로 안다). 2), 4)  속성이 여러개 조합될 수 있고, 추가 삭제도 할수 있다. 그리고 전체적인 윈도우와 그 안의 컨트롤 들에 대한 트리구조를 왼편에 보여준다. 아래 기능은 pywinauto의 print_control_identifiers() 함수의 결과를 시각화해서 관리하는 모드라고 볼 수있다. 이 기능은 실제 써보면 꽤 직관적이고, 메이저 GUI 상용툴은 거의 이런 식으로 컨트롤들의 실제 속성들을 개념적으로 코드와 분리해 관리한다.

 

 

  또 하나는 print_control_identifiers() 의 GUI 버전으로 아까 소개한 spy++ 와 비슷한 object spy 라는 기능이다. print_control_identifiers() 를 매번 코드에 넣어 디버깅 하듯 특성을 확인하는 것보다 스파이형태로 직접 윈도우 창을 클릭해 확인하는게 확실히 효율적이 아닐까 싶다.

 

 

  마지막으로 특정 이미지 영역을 커스텀 컨트롤로 지정해서 인식가능한 기능이 최근 추가됬다고 한다(이 기능은 정말 있었음 하던 기능이다). 이 기능으로 인해서 비표준적인 컨트롤 영역에 대한 인식을 좌표 방식과 이미지 방식 사이에서 취사선택 할 수 있는 선택점이 생긴 것 같다.

https://www.itcentralstation.com/product_reviews/hpe-uft-qtp-review-33718-by-don-ingerson

 

  뭐 업체에서 광고하는 여러가지 다른 요소들도 있겠지만, 제가 느끼기엔 위의 3가지 정도가 현재의 오픈소스 gui 자동화 툴과 상용툴의 가장 큰 차이인 것 같다. 반대로 다양한 언어를 지원하는 것은 보통 상용툴은 한가지 언어(이 툴의 경우 vbscript)만 주로 지원하고, 보통 자바나 .net 정도만 플러그인 같은 형식으로 추가적으로 지원 하기 때문에, 오픈소스 쪽이 휠씬 유리한 것 같다. 혹시 트라이얼 버전을 사용해보고 싶으면 아래의 링크에서 'free trial' 버튼을 클릭해 정보를 넣고, 메일 인증을 받은 후, 로그인해 다운 받으면 된다.

https://saas.hpe.com/en-us/software/uft 

 

 

 

 

[마치면서]

  개인적으로 pywinauto 같은 오픈 소스 gui 자동화 툴은 selenium 의 완성도에 비해서는 아직은 부족한 듯한 느낌이 있다. 하지만 위의 한계들을 인지하고, 구글에 있는 여러 레퍼런스 글들을 참조하여, 다른 좋은 파이썬 모듈들과 결합하여 사용한다면 해당 장점이 단점을 상쇄 할듯 싶다. 개인적으로 상용툴 만큼의 편리성을 지니도록 발전되었음 하는 바램을 가지면서, 다음 시간에는 자동화의 마지막 시간으로 작업 자동화 부분을 진행하려고 한다.

 

 

 

 

[보충내용]

<지나가는나그네님 문의 답변>

1) 브라우저에서 뜬 업로드 팝업을 어떻게 다루느냐에 대해서 autoit, pywinauto, win32 함수를 이용해서 3가지 해결책을 보여줌

https://sqa.stackexchange.com/questions/12851/how-can-i-work-with-file-uploads-during-a-webdriver-test

 

 

2) 위의 글에 있는 pywinauto 예제가 connect 를 안해 에러나서 찾음

https://github.com/pywinauto/SWAPY/issues/45

 

 

3) visual studio 설치시 제공하는 spy++ 를 이용해서 pywinauto 코드를 만드는 설명 - 이 글은 pywinauto 의 아쉬운 부분을 spy++로 보강할 수 있는 꼭 한번 읽어볼만한 글 인거 같음. 요즘은 visual studio 도 개인 개발자에겐 무료인듯 하니 설치해 같이 사용해 보시길... 설치시 옵션에서 공통 툴을 선택해야 깔리다고 함) --> 그리고 그것보다 본문에서 잠시 언급했던 autohotkey 설치할때 같이 깔리는 Active Window Info 툴이 제일 좋다고 쓰여 있다. (밑에 '다른 이름으로 저장' 창에 대한 정보를 보여주는 해당 툴의 그림을 추가했다-> print_control_identifiers 쓰지 말고, autohotkey 에서 깔리는 이 프로그램을 쓰자!!

http://stackoverflow.com/questions/42213490/pywinauto-how-to-select-this-dialog-which-spying-tool-to-use-what-information

 

 

  위의 세 개를 조합하여 만든 코드. 네이버 메일에서 팝업 창이 띄워져 있다는 가정하에서, 아래 코드를 실행 시키면 팝업 창의 파일 이름에 test.txt 라고 쓰여지고 열기 버튼 클릭.(물론 해당 폴더에 test.txt 파일이 없다고 에러 메시지는 날거임. IE 에서 확인)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#-*- coding: utf-8 -*-
from pywinauto.application import Application
import pywinauto
 
# 열려진 다이얼로그 창에 컨넥트 함
app = pywinauto.application.Application()
app.Connect(title="업로드할 파일 선택"
 
# 다이얼로그 창 정의
mainWindow = app['업로드할 파일 선택'# main windows' title
 
# 파일 이름 입력하는 창에가서 'test.txt' 라고 입력
ctrl=mainWindow['Edit'
mainWindow.SetFocus()
ctrl.ClickInput()
ctrl.TypeKeys("test.txt")
 
# 열기 버튼 클릭
mainWindow.Button1.click()
cs

 

 

<비주얼스튜디오_자동화님 문의 답변>

4) 일단 메뉴 아이콘을 인식하여 클릭하는 방법을 찾긴 어려울거 같고, 비주얼 스튜디오 프로그램 특성상 화면의 변경이 없이 매번 일정할 것이기 때문에, 화면 좌표를 이용한 매크로 방식으로 접근하기로 결정.

 

  a) 전체 윈도우 창을 기준으로 클릭 하긴 뭔가 예상못한 일이 생길 수도 있을 듯 해서, 일단 비주얼 스튜디오 윈도우를 인지하고, 그 창 내부의 상대 좌표에서 클릭하기 위해서 'pywinauto click within window' 로 찾아서 아래 페이지를 찾음.

https://stackoverflow.com/questions/28665941/python-click-by-coordinate-inside-a-window

 

  b) Open 아이콘을 클릭하기 위해서, 위에 언급했던, Active Window Info 로 아이콘 위치를 파악.

 

  c)  위의 코드를 적당히 맞춰 변경해서, Open 다이얼로그가 뜨는 것까지 확인 했음. 창이 넘 작으면 위의 메뉴가 겹쳐져서 좌표가 틀려질 수 있으니 주의 할것. 이번에 알았는데 타이틀로 창 이름 지정할시, '공백'이나 '-' 등은 생략해도 인지한다('Start Page - Microsoft Visual Studio' ->StartPageMicrosoftVisualStudio,  '-' 같은 경우는 생략을 안하면 에러가 난다. 아마 공백이나 특수문자는 모두 빼버려도 무방한듯 싶다). --;

1
2
3
4
5
6
7
8
9
10
from pywinauto.application import Application
import pywinauto
 
# 창 타이틀로 프로그램을 연결
app = pywinauto.Application().connect(title='Start Page - Microsoft Visual Studio')
# 메인 윈도우 창을 찾고, (200, 100) 상대 좌표를 클릭함
app.StartPageMicrosoftVisualStudio.ClickInput(coords=(200100))
 
# 최대 창으로 하고 그냥 윈도우 좌표로 클릭하고 싶을 경우는 아래의 코드로
# pywinauto.mouse.click(button='left', coords=(200, 100))
cs

 

 

 

[추가]

파이썬 3.7에서는 pywinauto 설치시 에러가 날텐데, 링크의 설명대로 wheels 를 업데이트 해주면 됩니다^^

https://stackoverflow.com/questions/14296531/what-does-error-option-single-version-externally-managed-not-recognized-ind

 

 

2017.4.8 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. 11. 21:30 프로그래밍

  이번 시간은 txt 파일에 저장되어 있는 IP 목록을 읽어 각각 루프를 돌리면서 API 를 호출을 한 후, 반환된 json 형태의 데이터에서 원하는 항목을 얻어서, 엑셀에 결과를 저장하는 예제를 시연하려고 한다. 

 

 

[목차]

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 호출 등의 예제들이 많이 나오는데, 해당 방식의 호출에 대해서는 생각보다 좀더 조심스럽게 생각해야 한다는 것이다. 용도에 맞지않게 너무 과도한 호출을 하게 되면, 대상 사이트에서 악성적인 시도로 판단하여 호출 IP 를 블록 할 수도 있고(다만 국내 IP이며, 동적 IP이기 때문에 블록 시키는 기간은 일시적일 가능성이 높다), 또 실습하는 PC가 회사 내 PC 같은 경우 NAT 을 통해 하나의 대표 IP로 나가는 경우가 대부분이기 때문에, 최악의 경우 회사 전체 사람들의 PC에서 해당 사이트로의 연결이 거부되어 버릴 수도 있다. 물론 공식적으로 제공되는 API 의 경우는, 어느 정도 익명 사용자의 잦은 호출을 가정해서 설계 후 제공이 되긴 하지만, 그래도 목적에 필요한 만큼만 호출 하는게 학습 목적에도 맞기 때문에 유의해서 호출 하시기 바란다(혹 지금 실습하는 사이트에서 연락이 올까봐 조금 걱정된다 --;)

 

 

 

 

[API 간단 설명]

  시작하기 전에 API에 대해 확실히 모르는 분을 위해서 간단하게 개념을 설명하려 한다. API(Application Programming Interface)의 예 중 우리가 가장 컴퓨터 안에서 흔하게 볼수 있는 것은 Windows API 이다(물론 이런 OS API와 구분해서 개념지을 수도 있긴 하지만 크게 보면 비슷한듯 하다). 윈도우즈 내의 여러 어플리케이션은 이러한 기본 API 들을 사용해서 윈도우 화면 에 글자를 표시하거나, 윈도우 창과 메뉴를 표시하거나, 효과음 사운드를 내거나, 애니메이션 등을 보여준다(물론 전문화된 다른 API가 또 있을 수 있다). 어찌 보면 파이썬 언어 자체도, 이러한 API 를 잘 사용하기 위해서 상위 수준에서 만들어진 언어라고 봐도 된다. 해당 부분은 리눅스 시스템도 윈도우즈와 마찬가지고 말이다. 비슷한 측면에서 인터넷이 발달한 이후에 이러한 API 들이 컴퓨터 내부를 벗어나서 네트워크(특히 WEB)를 통해서 지원하는 방식이 늘어나게 되었다. 또한 요즘은 SOA(Service Oriented Architecture - 서비스 지향 아키텍쳐), DevOps, MVC, Ajax 같은 설계들이 주류를 형성하게 되면서(개인적으로 봤을때, 해당 항목들의 공통점은 UI와 분리되어 데이터 전달만으로 소통하는 작은 기능, 즉 API 들로의 작은 쪼갬 같다), API 형태의 서비스들이 대세가 된것 같다. (물론 마이크로서비스 같이 좀더 넓은 특성으로 확장하여 지칭하는 용어는 다를 수도 있다)

https://ko.wikipedia.org/wiki/%EC%84%9C%EB%B9%84%EC%8A%A4_%EC%A7%80%ED%96%A5_%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98

 

  우리가 보통 파이썬 등의 언어에서 호출하는 구글, 네이버, 다음 등의 외부 API 는 기존의 웹 환경에서 머리 꼬리를 떼어넨 버전이라고 보면 된다. 그럼 머리 꼬리를 떼면 무엇이 남을까? 웹에서 화려한 화면을 다 떼고 나면 데이터만 남는다.(실제 웹은 브라우저의 꾸밈 역활과 그걸 즐기는 사람들이 없다면 단조로운 태그와 해더들의 세상일지도 모른다) 그럼 데이터를 주고 받을 때도, 사실 기존 get, post 방식으로 보내도 되긴 하는데, 해당 방식은 좀 더 구조적인 데이터를 보내거나 받을 때 다루기 무척 까다롭기 때문에, 첨에는 xml 같은 데이터 형식으로 많이 보내다가 요즘은 실용적인 json 형식 으로 많이 바뀌게 되었다(xml 이 정장이라면 json 은 캐주얼에 가까운 세미정장 같다). 그래서 지금 외부로 노출된 대부분의 API 들은 대부분 xml 이나 json 으로 데이터를 주고 받는 다고 봐도 무방한 듯 싶다.  

 

 

 

 

[WHOIS 사이트 문제] 

  그럼 오늘의 예제를 진행하게 된 배경에 대해 설명한다. IP 주소의 실제 정보(100%정확하진 않지만)를 찾기위해서는 보통 WhoIS라는 웹사이트에서 조회하게 된다.  구글에서 'whois' 라고 검색하면 KISA(한국 인터넷 진흥원)에서 제공하는 WhoIS 페이지가 나온다.  

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

 

 

  위의 페이지에서 밑에 표시된 샘플 정보인 1) '202'로 시작되는 IP 주소를 클릭 하면, 2) 검색 창에 해당 주소가 들어가게 되고, 이후 3) 'Search' 버튼을 클릭 하면 해당 IP 주소에 대해서 저장되어 있는 정보를 보여주게 된다. 보통 사이트 로그에 수상한 호출을 하는 IP가 들어 왔을때, 어떤 나라, 장소에서 들어왔고, 어떤 서비스로 들어왔는지를 아래에 표시된 여러 정보를 참조해서 판단하게 된다.

 

  그런데 문제는 해당 조회 방식을 사용한다면, 조회에 필요한 IP 가 수십개, 수백개로 늘어나는 경우 어떻게 해야 될까? 해당 숫자만큼 매번 페이지를 호출해 확인해야 할 테고, 그럼으로서 생기는 스트레스나 소모되는 시간도 무시할 수 없을 만큼 될 것이다. 물론 웹이나, 프로그램 중 멀티 IP 를 조회해 주는 프로그램도 있지만, 대부분의 프로그램은 국외의 WhoIS 서버(나라, 지역마다 해당 역활을 하는 서버들이 있는 듯 하다)를 호출하기 때문에, 영어로 표시된 정보이기도 하고, 국내 정보도 한국내 WhoIS 만큼 정교하게 나타나지 못할 수 있다. 

 

  그래서 첨에는 많은 웹 크롤링 예제에 있는 것처럼, 해당 페이지를 호출해서 결과 내용을 파싱해서 관련 내용을 뽑아 저장하려고 했었었다(해당 예제와 수반되는 미묘한 문제들은 다음 시간에 다룬다^^). 근데 파싱을 위한 코드들을 찾거나, 상단 페이지의 html 을 분석하는 데에 대해 은근 귀찮음이 밀려오는 상황에서, 마침 해당 페이지의 오른쪽 하단에 자리잡은 OpenAPI 기능 제공이 눈에 띄게 되었다. OpenAPI 라는 것은 비교적 모든 일반 사람들에게 사용을 허가하고 있는 범용 API 이다. 앞의 생선 그림 부분에서 얘기했듯이 API 를 쓰게 되면 일반적으로 정형화된 xml 이나 json 으로 데이터를 받을 수 있으므로, 웹페이지 상에서 html 을 파싱하는 것보다 좀 더 향후 페이지의 변경에 대해서 예외성이 없다.

 

  더 나아가 사이트 들은 보통 페이지 수정 작업 시 크롤링 하는 프로그램을 배려하려 하지 않지만(크롤링은 엄격이 따짐, 유명한 검색엔진들 이외에는 해당 사이트에서 좋아하진 않은 행위기 때문에 그럴 의무도 없고 말이다) , API 의 경우 해당 API 를 이용하여 인터넷 저 너머에서 동작하는 다른 고객의 어플리케이션들이 있을수 있기 때문에, 기존에 해당 API 를 제공한 책임을 생각해서라도 하위 호환성을 고려해서 수정하거나, 기능의 변경이 꼭 필요할땐 차라리 기존 API 를 두고 확장된 대체 API 를 만들 가능성이 많다. 그래서 한번 API 를 호출하는 방식으로 기능을 만들게 되면 해당 사이트가 사라지거나 대대적인 개보수 작업이 있기 전에는 유지보수를 위해 변경할 필요성이 거의 없어지는 장점이 있게 된다.

 

 

 

[API 사용자 등록]

  그럼 첫 페이지에서 아래의 API 링크를 클릭한다.

 

  아무리 모두에게 공개된 OpenAPI 라도 최소한의 호출 체크와 무분별한 사용을 제어하기 위해서 인증키를 발급 하는 경우가 많다. 아래를 보면 이메일을 등록하면, 해당되는 인증 키를 보내준다고 그런다. 그럼 자신이 쓰는 이메일과 사용목적을 적당히 넣고(기관이라 절대 그런일은 없겠지만 마음 한구석에 의심이 남는다면 잘 안쓰는 이메일로 넣는다), '발급신청' 버튼을 누른다. 

 

  잠시 후 자신의 이메일로 가서 확인을 하면, 키가 발급되어 오고 아래의 인증 페이지 링크를 클릭해서 해당 키를 인증 받으라고 그런다. 발급된 키는 앞으로 API 를 사용할때 호출하는 인자에 넣어서 권한을 증명할 키이니, 해당 키를 복사해서 어딘가 잘 저장해 두고, 아래의 인증페이지 URL 을 클릭 해 키 등록 페이지로 이동한다.

 

  이후 발급된 키를 넣고 '등록' 버튼을 누르면, 정상적으로 등록이 되었다고 나온다. 그럼 앞으로 특별히 상도에 어긋나는 행위를 하지 않는한, 해당 WhoIS API 를 당당하게 호출해 사용할 수 있는 권한을 획득하게 된 것이다.      

 

 

 

 

[API 예제 호출 해보기]

  그럼 아래의 Open API 사용 설명 페이지에서 API 를 이용해 IP 를 호출해서 주소를 가져오는 예제를 확인한다. 좀 오타가 있어 헷깔리긴 한데(IP를 도메인 주소라고 적었다), "인터넷주소 검색요청 URL" 의 2번째 예시가 우리가 원하는 기능을 수행하는 API 예제이다.

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

http://whois.kisa.or.kr/openapi/whois.jsp?query=도메인이름&key=발급받은KEY값&answer=[xml,json]

 

 

  해당 값을 편집해, 앞서의 샘플 IP 주소를 호출하는 예제를 만들어 본다. IP 주소는 웹에서 샘플로 사용했던 KISA 주소고, 인증키는 여러분이 발급받은 키로 넣고, answer 형식은 json 으로 한다. beautifulsoup 등을 이용할 수 있는 xml 방식이 편한 사람들은 xml 을 하셔도 되지만, 앞으로 보면 알겠지만, json 도 그 만큼 간단하니 따라와서 확인해 보길 바란다.

http://whois.kisa.or.kr/openapi/whois.jsp?query=202.30.50.51&key=발급받은키&answer=json

 

 

  그럼 해당 쿼리를 복사해서('발급받은키' 부분은 꼭 자기 키로 교체해 넣어야 한다.), IE 주소창에 붙여 넣는다. 그럼 아래와 같이 {, :, " 기호로 구조적으로(실제 나중에 보겠지만 트리 모양이다) 구분된 json 결과가 나온다. 그럼 해당 출력 결과의 문법 기호들을 잘 분석해서 원하는 자료가 있는 위치를 찾거나, 아니면 크롬, 파이어폭스 같은데에 잘 정리해 주는 플러그인이 있을 것 같긴 하다. 하지만 이 시점에서 웹 개발이나, 페이지 구성 분석, 보안 테스팅 등에 두루두루 잘 쓰일 수 있는 하나의 무료 툴을 이용하여 json 결과를 분석하는 방법을 소개하려 한다.(앞으로도 진행 하면서 종종 해당 툴을 쓰게 될 듯 하다)

 

 

 

 

[피들러 설치 - 무료지만 유료 뺨치는 툴] 

  소개하고 싶은 툴은 Fiddler 라는 툴인데(이미 사용해 보신분은 설치는 가볍게 건너뛰어 주시고, 설정 부분만 혹시 몰랐던 부분이 있나 확인하면 된다) 아래와 같이 설치를 한다. 구글에서 'fiddler' 라고 검색하여 아래의 링크로 이동한다.

http://www.telerik.com/fiddler 

 

 

  아래의 무료 다운로드 버튼을 누른다. (회사에서도 무료 사용이 가능하다)

 

 

  이후 이메일 주소를 넣고(아까의 API 키 발급의 경우처럼 메일로 인증 받거나 하는건 아니니 잘 안 쓰는 이메일 주소를 넣음 된다), 아래 그림과 같이 위쪽의 정보 메일 수신 옵션을 uncheck 하고, 아래의 라이센스 동의를 check한다. 이후 'Download' 버튼을 눌러 적절한 폴더에 설치 파일을 저장한다. (뭐 저장안하고 바로 실행해도 된다)

 

 

  설치 파일을 실행해 아래의 라이센스를 'I Agree' 하고, 그 후는 그냥 디폴트 옵션으로 쭉 설치 한다(과정이 간단해서 뒤쪽 스크린샷은 뺌)

 

 

  이후 시작메뉴에서 아래의 Fiddler4를 실행 한다. 이후 아래에 있는 'App Container' 설정이라는 창(아마 10에서만 에지 브라우저 땜에 나오는 듯 하다)이 나오는데, '취소' 를 눌러서 Warning 을 끄자.

 

 

 

  이후 처음 실행하면 브라우저에서 전송하고 가져오는 모든 리소스를 다 표시하기 때문에 몇 가지 사용 편의를 위해 설정할 부분이 있다. 일단 상단의 'Rules' 메뉴로 가서 'Hide Image Request'(보통 웹페이지는 내부에 작은 이미지 들이 많기 때문에 이미지가 모두 리스트에 나타나게 되면, 실제 살펴보길 원하는 웹 프로그램 파일(예:asp, php)을 찾아볼때 방해가 된다. 물론 이미지에 대한 분석이 필요한 경우는 다시 옵션을 바꿔주면 된다)와 'Remove All Encodings'(가져온 페이지 내용에 대해 자동으로 인코딩을 풀어서 피들러에서 바로 평문으로 볼수 있게 해준다) 두 개를 체크 한다. 

 

 

 

  그 다음은 SSL(HTTPS) 복호화 설정이다. 원칙적으로 HTTPS 는 브라우저와 서버 간에 키 교환을 통해 암호화된 데이터가 전송되어서 피들러 같이 중간에서 가로채 보는 툴이 내용을 알아 볼 수 없게 되는데, 컴퓨터 안에 피들러에서 사용할수 있는 테스트용 인증서를 설치하여, 브라우저는 피들러와 암복호화를 해서 통신하고, 다시 피들러가 브라우저 대신 서버와 암복호화를 해서 통신해서 중간에서 내용을 살펴 볼수 있게 해주는 트릭 기법이다. 이 설정이 잘 안 되 있으면, 구글 같이 호출 전체가 HTTPS 로 이루어진 사이트나, 여러 사이트의 로그인 페이지, 회원 정보 페이지 등 정보의 보호를 위해서(법적인 사항이기도 하다) HTTPS 로 호출되는 페이지들은 내용을 볼수 없게 된다(피들러만 쓰는 인증서긴 하지만 혹시나 걱정되는 사람들은 마지막에 설치된 인증서 제거 방법을 명시했으니, 사용 용도가 끝난 후 제거해도 무방하다). 사실 현재 예제에서는 SSL 파싱을 안해도 상관없으니, SSL 설정을 건너뛰셔도 상관없지만, SSL 복호화 기능을 빼고 피들러를 쓰면, http 사이트 들만 볼수 있는 반쪽 짜리 피들러가 되어버려서 설명하는 김에 같이 설명하는 중이다.

 

  그럼 위쪽 메뉴에서 'Tools > Telerik Fiddler Options...' 메뉴를 클릭하여 아래의 옵션 창을 띄운다. 'HTTPS 탭' 을 클릭하고, Capture HTTPS CONNTECTs(https 통신을 캡쳐한다), Decrypt HTTPS traffic(HTTPS 트래픽을 복호화 한다) 를 체크한다.

 

  체크하는 순간에 하단의 Alert 창이 뜬다. HTTPS 트래픽을 가로채기 위해서, 피들러가 루트 인증서를 윈도우에 설치하려 한다는 안내이다. 'YES' 를 클릭 한다.

 

  꼭 설치 하겠냐고, 보안 경고 창이 뜨면, '예'를 누른다. 

 

  다시 또 묻는다 (이게 윈도우즈 10만 이렇게 많이 묻는 듯도 싶다)

 

  최종 확인을 한다.

 

 

  이후 google 페이지에 접속해 본다. 이전에 https 복호화 설정 안했을 때는 안 보이는 구글 페이지의 호출 내용들이 다 보이게 된다.

 

 

 

[부록]

  그럼 아까 얘기한 인증서 제거하는 방법이다. 아까 옵션 창의 'HTTPS' 탭에서 오른 쪽에 있는 'Action' 버튼을 누르고, 'Open Windows Certificate Manager' 를 클릭 한다.

 

  인증서 관리 화면이 뜨면, 왼쪽에서 '신뢰할 수 있는 루트 인증 기관 > 인증서' 를 클릭 한 후, 오른 쪽에 있는 인증서 중, 아까 설치한 'DO_NOT_TRUST_FiddlerRoot' 인증서들을 삭제 한다 (선택하고 마우스 오른쪽 버튼 누르면 '삭제' 메뉴가 나온다). 그럼 설치했던 인증서들은 제거되고, 대신 피들러에서 이제부터는 https 트래픽은 해석 못하게 되버린다. https 복호화 옵션도 같이 끄면 될듯 하다.

 

 

 

 

[피들러의 동작 원리]

  그럼 실제 WhoIS 사이트 보기전에 피들러의 동작 원리에 대해서 얘기하려 한다. 그냥 시키는 데로 사용하는 것 보다는 개념을 이해하며 사용해야 기억에도 잘 남고 스스로의 바라보는 관점도 생기게 되니 말이다.

 

  아래 그림과 같이 웹 브라우저는 웹 서버와 통신을 할때 네트워크 카드를 통해서 직접 통신을 주고 받는다. 근데 Fiddler 같은 웹 프록시들이 설정되게 되면, 네트워크 카드로 패킷을 바로 보내지 않고, 보낼 패킷 들을 피들러에게 전달해 주고, 이후 피들러가 그 패킷을 네트워크 카드로 보내게 된다. 그렇게 함으로서 피들러는 중간에서 내용을 볼수도(사실 우리가 보는 피들러 화면이 바로 그 중간에 잡은 내용을 해석한 것이다) 있고,  (보안 테스트에서 사용하는 기능이지만...) 심지어 중간에서 패킷의 내용을 조작 할 수도 있다. IE 의 개발자 도구와 비슷한 부분 같다고 생각하는 사람도 있겠지만, 개발자 도구는 브라우저 내에서 작동하는 도구라 브라우저에 의존적이고, 피들러 같은 종류의 툴은 브라우저를 벗어나 버린 패킷을 가로채 보여주는 거라서 네트워크에 의존적이다. 할 수 있는 일도 조금 범위가 다르다. 다음 웹 크롤링 시간에 의도한 건 아니지만 그러한 차이 점 중 하나를 보여주는, 개발자 도구로 못보는 화면 데이터를 피들러로 보는 시연이 들어갈 예정이다. 

 

 

  피들러가 브라우저의 전송 내용들을 중개한다는 부분을 간접적으로 확인 해 보자. 아까 보았던 피들러 옵션에서 'Connections' 탭을 클릭 하면, 'Fiiddler linstens on port : 8888' (피들러가 8888 포트에서 신호를 기다리고 있다) 라는 옵션이 있다.

 

 

  피들러를 실행한 상태에서, IE나 크롬 등의 '인터넷 옵션' 의 '연결' 탭을 클릭해 보면, 아래의 '프록시 서버' 라는 옵션이 체크되어 있다(피들러를 끄면 언체크 되니 확인해 보길 바란다). 그 옆의 '고급' 버튼을 누르면, 프록시 설정 창이 뜨는 데 '127.0.0.1'(현재 내 로컬 PC의 IP 주소 - localhost 와 같은 개념이다)주소의 8888포트를 프록시로 사용한다는 옵션이 설정 되 있다. 그 127.0.0.1(내 컴퓨터) 주소에서 8888 포트로 브라우저가 뭔가 보내주기를 기다리는 프로그램이 바로 피들러이다. 반대로 웹서버에서 응답을 받을 때도 피들러가 먼저 응답을 받은 후 브라우저에게 전달해 주게된다. 

 

 

   마지막 팁으로 피들러를 사용 하다보면 옵션에서 이미지는 무시했음에도 불구하고 왼쪽 창에 계속 통신들이 많이 잡혀서 정신 없게 되는데(요즘은 ajax 통신이 많아서 페이지를 열어놓음 계속 뭔가가 잡힐 수도 있다), 아래의 Edit > Remove > All Sessions 메뉴를 이용해 기존 내용을 전체 삭제 하거나, 호출된 리스트를 Ctrl+A 로 모두 선택해서, DEL 키를 누르거나, 그냥 아래에 나온데로 Ctrl +X 를 하거나 하는 3가지 방식으로 모두 클리어 할 수 있다.

 

 

 

 

[피들러로 WhoIS 데이터 호출 보기] 

  그럼 조금 멀리 돌아왔지만(하지만 이제까지 한 얘기도 이전 시간들에 얘기한 프로그램의 외적 요소라는 것은 잊지말자), WhoIS API 의 결과를 피들러로 보면 어떤 부분이 유리한지 살펴 보겠다. 피들러를 켠 상태에서, IE 나 크롬 등에서, 아까 그 샘플 URL(발급 받은 키는 꼭 본인 키로 바꿔 넣어야 한다) 를 주소창에서 실행 한다.(혹시 피들러에서 결과가 잘 안 잡히면 브라우저에서 'F5' 키를 눌러 페이지 재로딩을 해본다. 가끔 이유는 잘 모르겠는데 안 잡힐 때가 있다--;). 아래의 호출된 화면이 나오면, 왼쪽 리스트 창에서 WhoIS API 호출한 항목을 클릭해 선택한 후, 오른쪽에서 Inspectors 탭을 클릭 한다. 

http://whois.kisa.or.kr/openapi/whois.jsp?query=202.30.50.51&key=발급받은키&answer=json

 

 

 

  그리고 오른쪽 아래에 있는 Json 탭을 클릭 한다(피들러의 오른쪽 창은 Request, Response의 두개의 창으로 나눠져 있는데 그 중 아래 쪽 Response 창이다). 그럼 아까 IE 에서는 좀 보기 힘들게 표시되던, Json 결과 데이터가 아래와 같이 트리 형식으로 휠씬 직관적으로 보이게 된다('-' 기호를 클릭함 접히기도 한다~). 저런 모양으로 표시되면 파이썬에서 파싱할 Json 데이터를 분석 할 때 좋고, 그 중 어떤 값이 사용이 필요한지도 쉽게 파악된다. 밑의 json 트리를 기반으로 예제에서 사용할 데이터를 나라이름(CountryCode), 주소(addr), 주소범위(range), 서비스ISP(servName), 입력한 IP 주소(query)  총 5개로 정했다. 

 

 

 

 

[파이썬 코드 만들기]

  그럼 요리를 시작할 환경은 다 준비가 된 것 같고, txt 파일에 IP 들을 한줄에 하나씩 여러개 넣어 두면, 파이썬이 해당 파일을 가져다가 IP 각각을 WhoIS API 를 호출해서, 결과인 json 데이터 안에서 앞에서 결정한 5개 항목을 가져온 후, 엑셀에 정리해 저장하려고 한다. 그림으로 간단히 표시하면 아래와 같다.

 

 

  그럼 다른 시간과 마찬가지로 현재 우리가 모르는게 무엇인지 생각해 본다

1) 텍스트 파일을 읽어와서, 각 IP 들을 루프 돌리는 방법을 모른다.

2) WhoIS API 를 파이썬에서 호출 하는 방법을 모른다.(Request 를 어떻게 해야하나?)

3) Json 응답을 받아와서 원하는 값을 추출하는 방법을 모른다.

(엑셀 저장 부분은 7교시 부록에 함수로 이미 만들어 논 것이 있으니, 가져와 적당히 변형해 쓰면 될듯 하다)

 

 

  먼저 텍스트 파일을 읽어와서 루프를 돌리는 부분을 찾아보자. 텍스트 파일을 읽어와서 각 IP를 리스트에 담아서 예전에 익힌 for 문을 이용해 돌리면 될것 같다, 구글에서 'python file to list' 로 검색한다. 맨 처음 나온 스택오버플로우 페이지를 참조해서, 파일 내용을 리스트로 저장 후, 루프를 돌리는 코드를 만들면 아래와 같다.

http://stackoverflow.com/questions/3925614/how-do-you-read-a-file-into-a-list-in-python

1
2
3
4
5
= open('ip.txt''r')
iplist = f.read().splitlines()
 
for ip in iplist:
  requestWhois(ip)
cs

 

 

 

  그럼 웹 페이지를 인자를 포함해 호출할 수 있는 방법을 찾아야 한다. 일단 한 개의 호출만 정상적으로 하는 코드를 만들어 검증해 보자. 이후 그 코드를 함수로 만들어 호출함 될 것 이다. 구글에서 'python request' 라고 찾아서 아래의 페이지나 한글 페이지 들을 살펴보면, requests 라는 모듈이 쓰이는 것 같다.

http://docs.python-requests.org/en/master/

 

  해당 모듈을 설치하기 위해서 위의 링크를 보다보니 pip 로 인스톨 하는 부분이 있다. 

http://docs.python-requests.org/en/master/user/install/#pip-install-requests

 

  아래와 같이 명령어를 입력하면 특별한 문제 없이 설치가 정상으로 된다~

C:\Python\code>pip install requests
Collecting requests
  Using cached requests-2.13.0-py2.py3-none-any.whl
Installing collected packages: requests
Successfully installed requests-2.13.0

 

 

  호출하는 부분은 대충 해결난거 같으니, json 결과를 받는 방법을 찾아보자.(결과를 받으려면 호출을 해야할테니 코드가 함께 있을 거 같아 두 마리 토끼를 잡을 수도 있을 것 같다) 몇 번 시행 착오를 거쳐서, 'python response json' 이라고 검색을 한후 맨위에서 아래의 페이지를 얻게 되었다.

http://stackoverflow.com/questions/16877422/parsing-json-responses

 

  해당 request 코드와 json 코드를 조합하여, 하나의 IP에 대해서 WhoIS API 를 호출하게 한 코드는 아래와 같다(물론 저렇게 인자를 URL 에 같이 넣지 않고, 따로 분리해 넣는 방법도 있으니, 해당 부분은 위의 Requests 문서 설명 부분을 참고하자. 꼭 POST 형식으로 날려야 할 경우는 반드시 인자의 분리가 필요할 듯 하다). c:\python\code 폴더에 python whois_iptest.py 라고 저장 한다(저장 시 소스 내의 '발급받은키' 부분은 자신의 키로 수정 해야 한다!).

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
# -*- coding: utf-8 -*-
import json
import requests  
 
# 요청 세션을 하나 만든다.
= requests.session()
 
# API 를 호출한다.
con = s.get('http://whois.kisa.or.kr/openapi/whois.jsp?query=202.30.50.51&key=발급받은키&answer=json')
 
# 호출 받은 API 결과를 json 형태로 받는다.
json_data = json.loads(con.text)
 
# 결과를 저장할 빈 리스트 생성
WhoIsData = []
 
# 피들러에서 파악했던 값들을 하나씩 추출해 리스트에 담는다.
WhoIsData.append(json_data['whois']['query'])
WhoIsData.append(json_data['whois']['countryCode'])
WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['addr'])
WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['range'])
WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['servName'])
 
# 리스트 출력
print(WhoIsData)
cs

 

  아래와 같이 실행 해보면 정상적으로 값을 가져온다(역시 저장 및 실행 방법을 잘 모르면 2교시에 자세히 설명했으니 보고 오자).

c:\Python\code>python whois_iptest.py
['202.30.50.51', 'KR', '서울특별시 송파구 중대로', '202.30.50.0 - 202.30.51.255', 'KRNIC-NET']

 

 

  그럼 필요한 조각 코드들이 다 만들어 졌으니, 적절히 조합하면 아래와 같이 최종 코드가 나온다. 엑셀 저장 부분은 거의 예전 코드 그대로 이고, 전역 변수 하나 사용하는 거만 차이난다. (전역 변수는 큰 프로그램에서 버그를 양산할 수 있다고 많은 경우 사용하지 말라고 하는 얘기도 많은데, 뭐 필요하니 언어에서 지원하겠지 하고 저 같은 경우는 편해서 종종 사용한다. 여기서는 엑셀의 내용을 적을 라인을 지정하느라 썼는데, 만약 엑셀 라이브러리에서 마지막 라인 정보를 지원해 준다면 안 써도 무방할듯 하다. 전역변수 관련 논의는 아래 링크를 참고한다.)

http://www.gpgstudy.com/forum/viewtopic.php?t=2123

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
77
# -*- coding: utf-8 -*-
import json
import requests
from openpyxl import Workbook
from openpyxl import load_workbook
#from openpyxl.compat import range
 
wb = Workbook()
 
# 엑셀의 액티브 워크시트 선택
ws = wb.active
ws.title = "whois ip info"
column_num = 2
 
 
 
# 엑셀에 컬럼 저장
def saveColName(cName):
    column_char = 'a'
    for name in cName:
        ws[column_char + '1'= name
        column_char = chr(ord(column_char) + 1)
        
 
# 엑셀에 조회결과 저장
def saveContent(cName):  
  column_char = 'a' 
  global column_num 
  for name in cName:
    ws[column_char + str(column_num)] = name
    column_char = chr(ord(column_char) + 1)
 
# 세션 생성
= requests.session()
 
def requestWhois(searchIP):
  # 엑셀 2번째 줄부터 IP정보를 연달아 저장하기 위해서 전역 변수 선언
  global column_num 
  
  # API 호출 하기(발급받은키는 자신의 키로 수정해야 한다)
  con = s.get('http://whois.kisa.or.kr/openapi/whois.jsp?query=' + searchIP + '&key=발급받은키&answer=json')
 
  # 호출된 결과를 json 형태로 저장
  json_data = json.loads(con.text)
  # 결과를 담을 배열 초기화
  WhoIsData = []
 
  # 각 결과 값을 배열에 저장
  WhoIsData.append(json_data['whois']['query'])
  WhoIsData.append(json_data['whois']['countryCode'])
  WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['addr'])
  WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['range'])
  WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['servName'])
 
  # 결과 화면에 뿌리기
  print(WhoIsData)
 
  # 엑셀에 결과 저장
  saveContent(WhoIsData)
  column_num = column_num + 1
  
 
# 엑셀 1째 줄에 데이터 제목 저장  
excelTitle = ['IP''countryCode''addr''IP_range''servName']
saveColName(excelTitle)
 
# 파일 열어 리스트에 담기
= open('ip.txt''r')
iplist = f.read().splitlines()
 
# 리스트에 담기 IP 정보 얻어 오기
for ip in iplist:
  requestWhois(ip)
 
# 엑셀 저장하기
wb.save("ipinfo.xlsx")
  
cs

 

 

 

 

[에러를 만나다]

  근데 위의 코드를 막상 실제에 사용하다 보니, 에러가 생기는 경우가 존재 한다. 두 가지 겨우인데 외국 IP 나 국내 IP 에서 json 데이터 형태를 다르게 주는 경우가 있어서 였다. 해당 경우를 재현해 보기 위해 외국 아이피를 하나 얻기 위해서 cmd 창에서 중국 사이트인 baidu 사이트에 ping 을 치게 되면 '103.235.46.39' 가 나온다.

c:\Python\code>ping www.baidu.com

Ping www.a.shifen.com [103.235.46.39] 32바이트 데이터 사용:

 

  아까 KISA 샘플 아이피 '202.30.50.51' 와 위의 바이두 IP 두 개를 아래와 같이 두 줄로 ip.txt 에 넣어 c:\python\code 폴더에 저장한다.

202.30.50.51

103.235.46.39

 

  그리고 위의 최종 완성된 소스를 긁어서(소스 내의 '발급받은키' 부분은 꼭 자신의 키로 수정 해야 한다) c:\python\code 폴더에 whoisapi_1st.py 로 저장하고 아래와 같이 실행해 본다.

c:\Python\code>python whoisapi_1st.py
['202.30.50.51', 'KR', '서울특별시 송파구 중대로', '202.30.50.0 - 202.30.51.255', 'KRNIC-NET']
Traceback (most recent call last):
  File "whoisapi_1st.py", line 58, in <module>
  File "whoisapi_1st.py", line 41, in requestWhois
KeyError: 'korean'

 

  이상한 에러가 하나 떨어진다. Korean 이란 키가 없단다(json 에서는 인자를 Key 라고 그러나 보다. 하나 배웠다). 분명히 처음 피들러 결과상에는 있었는데 말이다. 앞의 첫 샘플 IP는 print 결과가 잘 뿌려진거 보니 두번째 해외 IP가 문제인가 보다. 프로그램 상에서 디버깅 해도 좋긴 할테지만, 피들러를 사용해 보면 좀더 간단하게 원인을 발견할 수 있다. 

 

 

  피들러로 아래의 문제 IP 를 넣은 쿼리를 날려서 잡아 본다.(키는 본인 키로 바꾸어야 한다.)

http://whois.kisa.or.kr/openapi/whois.jsp?query=103.235.46.39&key=발급받은키 &answer=json

 

   피들러로 잡아보면 아래와 같이 외국 IP 의 경우 아예 나라(CountryCode) 와 IP(query) 밖에 데이터를 안 보내주고 있다(추가로 나중에 발견한 사실이지만, 국내의 특정 IP 도 ['PI']['netinfo']['addr'] 가 아닌, ['ISP']['netinfo']['addr'] 같이 앞에 있는 json 키가 달라진다)  

 

 

  그럼 급한 나머지 피해갈 코드를 생각하다보니, 현재 상황상 국가와 IP는 무조건 나오니 그대로 두고, 주소 등 뒤의 세 가지 값들이 없는 경우는(마침 없을때는 모두 한꺼번에 없으니) 에러를 내지 않고 그냥 넘어가 버리게 함 어떨까 싶다. 그래서 구글에 'python error pass' 라고 검색한다. 첫번째 스택오버플로우 페이지에서 이런 방식은 다른 모든 종류의 에러도 하나로 퉁 쳐서 패스시키기 때문에 나쁘다곤 하는데, 이해는 되지만 시간이 급하니 에러 코드는 구분 안 하고 일단 사용해 본다.

http://stackoverflow.com/questions/21553327/why-is-except-pass-a-bad-programming-practice

1
2
3
4
5
6
  try:
    WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['addr'])
    WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['range'])
    WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['servName'])
  except:
    pass
cs

 

 

  아래와 같이 try, except, pass 를 사용한 땜빵 코드로 수정한 소스이다.

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
77
78
79
80
81
82
83
84
85
86
87
# -*- coding: utf-8 -*-
import json
import requests
from openpyxl import Workbook
from openpyxl import load_workbook
#from openpyxl.compat import range
 
wb = Workbook()
 
# 엑셀의 액티브 워크시트 선택
ws = wb.active
ws.title = "whois ip info"
column_num = 2
 
 
 
# 엑셀에 컬럼 저장
def saveColName(cName):
    column_char = 'a'
    for name in cName:
        ws[column_char + '1'= name
        column_char = chr(ord(column_char) + 1)
        
 
# 엑셀에 조회결과 저장
def saveContent(cName):  
  column_char = 'a' 
  global column_num 
  for name in cName:
    ws[column_char + str(column_num)] = name
    column_char = chr(ord(column_char) + 1)
 
# 세션 생성
= requests.session()
 
def requestWhois(searchIP):
  # 엑셀 2번째 줄부터 IP정보를 연달아 저장하기 위해서 전역 변수 선언
  global column_num 
  
  # API 호출 하기(발급받은키는 자신의 키로 수정해야 한다)
  con = s.get('http://whois.kisa.or.kr/openapi/whois.jsp?query=' + searchIP + '&key=발급받은키&answer=json')
 
  # 호출된 결과를 json 형태로 저장
  json_data = json.loads(con.text)
  # 결과를 담을 배열 초기화
  WhoIsData = []
 
  # 각 결과 값을 배열에 저장
  WhoIsData.append(json_data['whois']['query'])
  WhoIsData.append(json_data['whois']['countryCode'])
 
  try:
    WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['addr'])
    WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['range'])
    WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['servName'])
  except:
    pass
    
  try:
    WhoIsData.append(json_data['whois']['korean']['ISP']['netinfo']['addr'])
    WhoIsData.append(json_data['whois']['korean']['ISP']['netinfo']['range'])
    WhoIsData.append(json_data['whois']['korean']['ISP']['netinfo']['servName'])
  except:
    pass
 
  # 결과 화면에 뿌리기
  print(WhoIsData)
 
  # 엑셀에 결과 저장
  saveContent(WhoIsData)
  column_num = column_num + 1
  
 
# 엑셀 1째 줄에 데이터 제목 저장  
excelTitle = ['IP''countryCode''addr''IP_range''servName']
saveColName(excelTitle)
 
# 파일 열어 리스트에 담기
= open('ip.txt''r')
iplist = f.read().splitlines()
 
# 리스트에 담기 IP 정보 얻어 오기
for ip in iplist:
  requestWhois(ip)
 
# 엑셀 저장하기
wb.save("ipinfo.xlsx")
cs

 

  에러 처리가 된 소스를(소스 내의 '발급받은키' 부분은 자신의 키로 수정 해야 된다.) c:\python\code 폴더에 whoisapi_2nd.py 로 저장하고 아래와 같이 실행해 본다. 잘 동작이 된다.

c:\Python\code>python whoisapi_2nd.py
['202.30.50.51', 'KR', '서울특별시 송파구 중대로', '202.30.50.0 - 202.30.51.255', 'KRNIC-NET']
['103.235.46.39', 'HK']

 

 

 

 

[최종 - 땜빵 코드 없애기]

  이렇게 마무리하게 되면, 왠지 불완전한 땜빵 코드를 안내하고 마치는거 같은 무거운 마음이 들어서(사실 위의 request 세션 같은 경우도 마지막에 세션을 닫아야 할거 같은 생각은 들지만 그런건 무시하고), 정도를 걷기위해 json 결과의 키 값이 없을 때 예외처리하는 방법을 구글에서 찾아본다. 여러번의 검색어 시행착오를 거쳐서 'python json key exist' 라고 검색해 아래의 스택오버플로우 페이지를 찾는다. 기대한 바와 다르게 하위에 특정 값이 없음을 바로 체크 할 순 없고, 계단식으로 1단계씩 차례로 체크해야 하는거 같다.  (예를 들면 json_data['whois']['korean']['PI']['netinfo']['addr'] 라는 json 데이터가 있다면  'addr' in json_data['whois'] 처럼 트리 단계를 점프해서 찾을 순 없다. 좀 답답해 보이지만, 디렉토리 처럼 'whois' 가 있는지 체크하고, 그 담에 'korean' 을 체크하고, 그담에 ... 하고 해야하는 식이다.
http://stackoverflow.com/questions/24898797/check-if-key-exists-and-iterate-the-json-array-using-python

 

  해당 에러 처리 코드를 구성하면 아래와 같고,

1
2
3
4
5
6
7
8
9
10
  # json 인자가 있는지 체크하는 식으로 에러처리 바꿈
  if 'korean' in json_data['whois']:
    if 'PI' in json_data['whois']['korean']:
      WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['addr'])
      WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['range'])
      WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['servName'])
    elif 'ISP' in json_data['whois']['korean']:
      WhoIsData.append(json_data['whois']['korean']['ISP']['netinfo']['addr'])
      WhoIsData.append(json_data['whois']['korean']['ISP']['netinfo']['range'])
      WhoIsData.append(json_data['whois']['korean']['ISP']['netinfo']['servName'])
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
77
78
79
80
81
82
83
84
85
# -*- coding: utf-8 -*-
import json
import requests
from openpyxl import Workbook
from openpyxl import load_workbook
#from openpyxl.compat import range
 
wb = Workbook()
 
# 엑셀의 액티브 워크시트 선택
ws = wb.active
ws.title = "whois ip info"
column_num = 2
 
 
 
# 엑셀에 컬럼 저장
def saveColName(cName):
    column_char = 'a'
    for name in cName:
        ws[column_char + '1'= name
        column_char = chr(ord(column_char) + 1)
        
 
# 엑셀에 조회결과 저장
def saveContent(cName):  
  column_char = 'a' 
  global column_num 
  for name in cName:
    ws[column_char + str(column_num)] = name
    column_char = chr(ord(column_char) + 1)
 
# 세션 생성
= requests.session()
 
def requestWhois(searchIP):
  # 엑셀 2번째 줄부터 IP정보를 연달아 저장하기 위해서 전역 변수 선언
  global column_num 
  
  # API 호출 하기(발급받은키는 자신의 키로 수정해야 한다) 
  con = s.get('http://whois.kisa.or.kr/openapi/whois.jsp?query=' + searchIP + '&key=발급받은키&answer=json')
 
  # 호출된 결과를 json 형태로 저장
  json_data = json.loads(con.text)
  # 결과를 담을 배열 초기화
  WhoIsData = []
 
  # 각 결과 값을 배열에 저장
  WhoIsData.append(json_data['whois']['query'])
  WhoIsData.append(json_data['whois']['countryCode'])
 
  # json 인자가 있는지 체크하는 식으로 에러처리 바꿈
  if 'korean' in json_data['whois']:
    if 'PI' in json_data['whois']['korean']:
      WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['addr'])
      WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['range'])
      WhoIsData.append(json_data['whois']['korean']['PI']['netinfo']['servName'])
    elif 'ISP' in json_data['whois']['korean']:
      WhoIsData.append(json_data['whois']['korean']['ISP']['netinfo']['addr'])
      WhoIsData.append(json_data['whois']['korean']['ISP']['netinfo']['range'])
      WhoIsData.append(json_data['whois']['korean']['ISP']['netinfo']['servName'])
  
 
  # 결과 화면에 뿌리기
  print(WhoIsData)
 
  # 엑셀에 결과 저장
  saveContent(WhoIsData)
  column_num = column_num + 1
  
 
# 엑셀 1째 줄에 데이터 제목 저장  
excelTitle = ['IP''countryCode''addr''IP_range''servName']
saveColName(excelTitle)
 
# 파일 열어 리스트에 담기
= open('ip.txt''r')
iplist = f.read().splitlines()
 
# 리스트에 담기 IP 정보 얻어 오기
for ip in iplist:
  requestWhois(ip)
 
# 엑셀 저장하기
wb.save("ipinfo.xlsx")
cs

 

 

  최종 소스를(소스 내의 '발급받은키' 부분은 자신의 키로 수정 해야 한다) c:\python\code 폴더에 whoisapi_3rd.py 로 저장하고 아래와 같이 실행해 본다. 잘 동작이 된다. c:\Python\code>python whoisapi_3rd.py
['202.30.50.51', 'KR', '서울특별시 송파구 중대로', '202.30.50.0 - 202.30.51.255', 'KRNIC-NET']
['103.235.46.39', 'HK']

 

  해당 폴더에 생성된 ipinfo.xlsx 파일도 확인해보면 잘 저장되어 있다.

 

 

  이렇게 피들러땜에 좀 설명이 길었던(하지만 꼭 한번 소개 하고 싶었던) 10교시를 마치고, 다음 시간에는 웹 크롤링에 대해서 몇가지 생각해 볼 주제를 Beautiful Soap 과 정규 표현식, 이번 시간 예제와 연결해서 진행해볼 예정이다. 크롤링 부터는 슬슬 자동화의 영역에 발을 들이는 기분이다. 피들러는 구글을 찾아보면 자료가 많이 있으니(다만 사이트마다 올리는 사람의 관심 분야에 따라 설명 포커스는 조금 틀린거 같다) 참고해서, 꼭 이리저리 사용해 보길 바란다. 피들러 홈페이지의 사용 설명을 읽음 더욱 좋다. 개인적으로는 웹세계의 '스위스 아미 나이프' 라고 평가한다.^^ 계속 잘 진행 된다면 언젠가 여기에 피들러 관련한 이런저런 포스팅도 하고 싶긴 하다.

 

 

[보충 #2]

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

from openpyxl.compat import range

 

 

2017.3.12 by 자유로운설탕 
cs

 

posted by 자유로운설탕
2017. 3. 5. 18:57 프로그래밍

  9교시는 GUI(Graphic User Interface)에 대한 얘기를 풀어보고, 예전 시간에 사용했던 암호화 모듈을 사용해서 윈도우 화면에 입력한 값을 복호화 해서 보여주는 샘플을 만들어 보려한다. 

 

 

[목차]

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. 정리 - 이런저런 이야기

 

 

 

[들어가면서]

  GUI 프로그램을 공부하는 것은 초보자 입장으로는 쉬운일은 아니다. 왜냐하면 사실 조금 간략화 되어 있을 뿐이지 Visual C, JAVA 등으로 만드는 윈도우 GUI 프로그래밍 기법과 거의 동일한 선상의 레벨이기 때문이다. 게다가 그 쪽 언어에서는 보통 Visual Studio 나 Eclipse 같은 사용성이 아주 좋은 IDE 를 통해 개발하기 때문에, GUI 디자인이나, GUI 객체들에 이벤트 연결 하는 부분은 거의 신경 쓰지 않고, 실제 실행되는 이벤트 코드 쪽만 집중해 개발할 수 있지만, 아무래도 아직 파이썬 쪽은 모듈별(pyqt, wxpython) 디자이너 툴이 있다고는 하지만 IDE 와 통합된 형태도 아니며, 그나마 디자인만 잡아주는 수준인듯 하다. 게다가 왠지 수동으로 디자인을 잡는건 미련한 거 같아서 wxpython 을 지원 한다고 하는 wxGlade, wxformBuilder 라는 두 개의 툴을 설치해 검토해 봤는데, 둘 다 다른 언어도 같이 지원하는 일반적인 툴이라서 그런지 윈도우 환경의 python 3.5 에서는 아직 제대로 실행이 안 되는거 같다.(이건 제가 잘 몰라서 그럴 수도 있으니 단정은 못 짓겠다). 또 실제 실행은 안됬지만 생성된 파이썬 코드도 왠지 가독성 측면에서 별로 깔끔해 보이지가 않는다. pyqt 모듈 쪽에서는 디자이너 까진 잘 지원되는 거 같으니 그 쪽 모듈을 사용해 보는 것도 고려해 볼만 하다. pyqt 디자이너에 대해서는 아래 잘 설명되 있는 듯 하다.

http://pythonstudy.xyz/python/article/108-PyQt-QtDesigner-%EB%A9%94%EC%9D%B8%EC%9C%88%EB%8F%84%EC%9A%B0

 

  그리고 이 GUI 모듈들의 사용도, 이전 시간에 다룬 SQL, 정규식 같이 파이썬 언어의 기능 밖의 부분이라고 생각해도 무방 할듯 하다. 만약 다른 언어로 GUI 툴을 개발해 봤거나, 꼭 윈도우 어플리케이션이 아니더라도, 웹 어플리케이션에서 자바스크립트로 DOM 개체와 이벤트로 상호 작용을 하는 부분을 만들어 봤다면, GUI 모듈을 사용할 기초 소양 준비는 다 되있다고 보면 된다. 만약 해당 부분의 경험이 거의 없다면, 이클립스나 비주얼스튜디오 같은 좋은 IDE 로 몇 개의 작은 GUI 어플리케이션을 작성해서, GUI 요소들과 사용자 코드가 상호 작용하는 부분에 대해 감을 익힌 후, 맨 뒤에 소개한 레퍼런스를 참조하여 python GUI 모듈의 디테일한 개발 내용을 보는게 개인적으로 조금 더 나아 보인다. (오해할까봐.. 저도 잘 못하는 부분이긴 하다...). 어떻게 보면 파이썬에서 수동으로 디자인할 때도 비쥬얼스튜디오 같은 IDE로 모양만 잡은 후 관련 좌표등을 참고해 수동으로 옮기는 것도 하나의 방법일 듯 싶다.

 

 

 

[GUI 연결 고리 만들어 보기]

  그럼 2교시때 GUI 샘플을 실행하기 위해서 이미 wxpython 모듈을 아래와 같이 설치 했었다. 혹시 모듈 선택 이유 및 설치 과정이 가물가물하면 2교시 내용을 보면 된다.

 

  스택오퍼플로어에 가이드 된데로 cmd 창에서 아래 명령어를 실행시킨다.

pip install -U --pre -f https://wxpython.org/Phoenix/snapshot-builds/ wxPython_Phoenix

 

 

 

  만들려고 하는 기능은 아래와 같다. 윈도우 창에는 입력 박스 한개와 결과를 출력하는 라벨이 한 개, 버튼이 하나 있다(좀 단촐해서 민망하다 --;). 멀뚱히 비워 놓긴 그래서, 여러가지 디폴트 문장을 표시하게 했다.

 

  텍스트 박스에 암호화 하길 원하는 문장을 넣은 후, 암호화 버튼을 누르면, 아래와 같이 암호화 된 값이 하단 텍스트 라벨에 표시된다.

 

 

  언제나 처럼 위 부분을 만드려면 모르는 게 무언지 생각해 보자. (우선 윈도우즈 창 만드는 법은 안다. 샘플 파일을 이미 실행해 봤으니까...)

1) 버튼을 만들수 있어야 한다.

2) 텍스트 박스를 만들 수 있어야 한다. 안에다 디폴드 값도 넣어 주어야 한다.

4) 결과를 뿌릴 텍스트 라벨 영역을 만들 수 있어야 한다. 이건 샘플파일에 이미 StaticText 란 코드로 출력해 봤다. 이것도 디폴트 값은 넣어주어야 한다.

5) '암호화'버튼이 클릭 됬을때, 특정한 함수 A를 실행 해야 한다. 그 A함수 안에서는 텍스트 박스에 입력한 'my word' 항목을 가져와서, 미리 만들어 놓은 암호화 함수 B를 호출해서 암호화 한 값을 리턴 받은 후, 텍스트 라벨 영역에 출력해 주어야 한다.

 

 

  그럼 첫번째로 버튼을 찾아본다. wxPython 메뉴얼을 쭉 훝어봐도 되겠지만, 구글을 찾는게 편할 듯 하다. 'wxpython show input box'. 맨 위의 스택 오버플로우 결과를 클릭한다.

http://stackoverflow.com/questions/18532827/using-wxpython-to-get-input-from-user

 

  오 여기에 버튼 뿐만아니라 텍스트 박스를 만드는 코드도 같이 있다. 디폴드 값 넣는 부분도 존재한다. 자세히 보면 버튼이 클릭 됬을 때 특정한 함수를 호출 하는 부분도 있다. 1타 3피로 거의 다 줏어 온 듯 싶다. (해당 코드 앞에 주석을 달아본다)

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
#-*- coding: utf-8 -*-
import wx
class Frame(wx.Frame):
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, title=title, size=(-1-1))
        self.panel = wx.Panel(self)
        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
        # 버튼 만드는 코드
        self.btn = wx.Button(self.panel, -1"Name-a-matic")
        # 버튼 클릭 이벤트에 함수(메쏘드) 연결하는 코드
        self.Bind(wx.EVT_BUTTON, self.GetName, self.btn)
        # 텍스트 박스 만들고, 디폴트 값 넣는 코드
        self.txt = wx.TextCtrl(self.panel, -1, size=(140,-1))
        self.txt.SetValue('name goes here')
 
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.btn)
        sizer.Add(self.txt)
 
        self.panel.SetSizer(sizer)
        self.Show()
        
        # 버튼이 눌렸을 때 실행 되는 함수,  사용자의 이름을 얻어 텍스트 박스에 넣어줌
    def GetName(self, e):
 
        dlg = wx.TextEntryDialog(self.panel, 'Whats yo name?:',"name-o-rama",""
                style=wx.OK)
        dlg.ShowModal()
        self.txt.SetValue(dlg.GetValue())
        dlg.Destroy()
 
    def OnCloseWindow(self, e):
        self.Destroy()
 
app = wx.App()
frame = Frame(None, 'My Nameomatic')
app.MainLoop()
cs

 

 

 

 

  그럼 저 뼈대를 거의 그대로 쓰고, 1) 버튼을 만드는 코드는 아래와 같다.

1
        self.btn = wx.Button(self.panel, -1'암호화')
cs

 

  2) 텍스트 박스를 만들고, 디폴트 값을 넣는 코드는 아래와 같다.

1
2
        self.txt = wx.TextCtrl(self.panel, -1, size=(140,-1))
        self.txt.SetValue('input your value')
cs

 

  3) 텍스트 라벨을 만들고, 디폴트 값을 넣는 코드는 아래와 같다.

1
2
        self.some_text = wx.StaticText(self.panel, size=(140,150), pos=(10,60))
        self.some_text.SetLabel('result is...')
cs

 

  4) 버튼을 누르면 이벤트 함수를 연결하는 코드는 아래와 같다.

1
        self.Bind(wx.EVT_BUTTON, self.GetEncryption, self.btn)
cs

 

  5) 위의 4번에 있는 GetEncryption 함수는 텍스트 박스의 값을 가져다, 암호화 함수에 던저주고, 리턴된 값을 다시 라벨에 넣어주여야 한다. 해당 기능의 코드는 아래와 같다.

  ※ 첨에는 버튼 이벤트에 GetEncryption 함수를 호출할 때 암호화 할 값을 전달해 줘야하나 하고 한참 구글을 헤멨는데, 안에서 텍스트 필드에 접근하면 되는 거라서 그냥 기존 코드를 그대로 이용했다. --;

1
2
3
    def GetEncryption(self, e): 
        self.enc = AESCipher(key).encrypt(self.txt.GetValue())
        self.some_text.SetLabel(self.enc)
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
77
78
79
80
81
82
83
84
#-*- coding: utf-8 -*-
# 암호화 코드 출처: http://blog.dokenzy.com/
import wx
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:])]
key = 'abcdefghijklmnopqrstuvwxyz123456'
 
def iv():
    return chr(0* 16
 
 
# 윈도우즈 정의 클래스
class Frame(wx.Frame):
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, title=title, size=(-1-1))
        self.panel = wx.Panel(self)
        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
 
        # 버튼 생성
        self.btn = wx.Button(self.panel, -1'암호화')
 
        # 텍스트 박스 생성
        self.txt = wx.TextCtrl(self.panel, -1, size=(140,-1))
        self.txt.SetValue('input your value')
        
        # 텍스트 라벨 생성
        self.some_text = wx.StaticText(self.panel, size=(140,150), pos=(10,60))
        self.some_text.SetLabel('result is...')
 
        # 버튼 클릭 시 이벤트 연결
        self.Bind(wx.EVT_BUTTON, self.GetEncryption, self.btn)
 
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.btn)
        sizer.Add(self.txt)
 
        self.panel.SetSizer(sizer)
        self.Show()
 
    # 버튼 클릭 시 실행 되어, 암호화 하는 함수
    def GetEncryption(self, e): 
        # 텍스트 박스(self.txt)로 부터 값을 얻어와 암호화 함수로 넘겨준다.
        self.enc = AESCipher(key).encrypt(self.txt.GetValue())
        # 받은 값을 텍스트 라벨에 출력한다.
        self.some_text.SetLabel(self.enc)
 
 
    def OnCloseWindow(self, e):
        self.Destroy()
 
 
# 암호화 관련 클래스
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')
 
 
 
# 메인코드 #
# 윈도우를 띄우고 제목을 넣는다.
app = wx.App()
frame = Frame(None, 'WxEncryption')
app.MainLoop()
cs

 

 

  그럼 최종 코드를 c:\python\code 폴더에 wxEncyrption.py 이름으로 저장한다(역시 모르면 2교시로 가서 복습을...). 아래와 같이 실행하면 원하는 기능을 하는 윈도우 창이 뜨게 된다. 

 

c:\Python\code>python wxEncyrption.py

 

 

 

 

[마무리 하면서]

  자 그럼 앞으로 더 복잡하거나 새로운 GUI 컨트롤 들을 사용해서 만드려면 어떻게 해야할까?(파일 입력 박스, 라디오 버튼, 텍스트 박스 등등) 우리가 다운받아 사용한 wxPython Phoenix 홈페이지의 문서를 보면 wxPython 에서 현재 지원하고 있는 기능이 망라되어 있는 페이지가 있다. 여기 있는 요소들을 참조해 필요한 경우가 있다면 구글을 찾아서 실제 사용 예들을 참조해 보면 될 것 같다. 예를 들어 파일 다이얼로그인 filepickerctrl 을 사용하고 싶다면, 구글에서 'wxpython use filepickerctrl' 이라고 찾아보면 아마도 절절한 스택오버플로우 페이지들이 반겨줄 것이다.

https://wxpython.org/Phoenix/docs/html/wx.1moduleindex.html

 

 

 

 그럼 이것으로 9교시를 마치려고 한다.

 

 

[부록] 콘치즈파파님 문의 내용 답변

 

  위의 예시와 거의 비슷하고, GetQRCode 메소드 내에서 UI 화면에서 입력한 inputValue 값을 기존 만드신 코드와 연결해 가져와 저장하는 코드를 완료하심 될거 같아요.  해당 액션이 잘 끝나면 "저장완료" 라고 화면에 나올거고요.

 

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
import wx
 
 
# 윈도우즈 정의 클래스 입니다.
class Frame(wx.Frame):
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, title=title, size=(500200))
        self.panel = wx.Panel(self)
        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
 
        # 버튼을 생성 합니다.
        self.btn = wx.Button(self.panel, -1'QR 코드 저장')
 
        # 텍스트 박스를 생성합니다.
        self.txt = wx.TextCtrl(self.panel, -1, size=(140,-1))
        self.txt.SetValue('input your url')
        
        # 텍스트 라벨을 생성합니다.
        self.some_text = wx.StaticText(self.panel, size=(140,150), pos=(10,60))
        self.some_text.SetLabel('result is...')
 
        # 버튼 클릭 시 이벤트를 연결 합니다.
        self.Bind(wx.EVT_BUTTON, self.GetQRCode, self.btn)
 
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.btn)
        sizer.Add(self.txt)
 
        self.panel.SetSizer(sizer)
        self.Show()
 
    # 버튼 클릭 시 실행 되어, 암호화 하는 함수 입니다.
    def GetQRCode(self, e): 
        inputValue = self.txt.GetValue()
        
        # 이 inputValue 값을 QR 코드 쪽에 연결하면 됨.
        
        # 받은 값을 텍스트 라벨에 출력합니다.
        self.some_text.SetLabel("저장완료")
 
 
    def OnCloseWindow(self, e):
        self.Destroy()
 
# 메인코드 입니다.
# 윈도우를 띄우고 제목을 넣습니다.
app = wx.App()
frame = Frame(None, 'getQRCode')
app.MainLoop()
 
cs

 

[QR 코드랑 머지한 버전 - 이름을 겹치지 않게 시간을 이용해 만들어 넣음]

  예를 들어 www.google.co.kr 이라고 입력하면 관련된 QR 코드를 만듬.

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
import wx
import qrcode
import datetime
 
# 윈도우즈 정의 클래스 입니다.
class Frame(wx.Frame):
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, title=title, size=(500200))
        self.panel = wx.Panel(self)
        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
 
        # 버튼을 생성 합니다.
        self.btn = wx.Button(self.panel, -1'QR 코드 저장')
 
        # 텍스트 박스를 생성합니다.
        self.txt = wx.TextCtrl(self.panel, -1, size=(140,-1))
        self.txt.SetValue('input your url')
        
        # 텍스트 라벨을 생성합니다.
        self.some_text = wx.StaticText(self.panel, size=(140,150), pos=(10,60))
        self.some_text.SetLabel('result is...')
 
        # 버튼 클릭 시 이벤트를 연결 합니다.
        self.Bind(wx.EVT_BUTTON, self.GetQRCode, self.btn)
 
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.btn)
        sizer.Add(self.txt)
 
        self.panel.SetSizer(sizer)
        self.Show()
 
    # 버튼 클릭 시 실행 되어, 암호화 하는 함수 입니다.
    def GetQRCode(self, e): 
        inputValue = self.txt.GetValue()
        
        # 이 inputValue 값을 QR 코드 쪽에 연결하면 됨.
        qr = qrcode.QRCode(version=1, error_correction=qrcode.constants.ERROR_CORRECT_L,box_size=10,border=4)
        qrAddress = "http://" + inputValue + "qrCodeId=CAD20181220041519"
        qr.add_data(qrAddress)
        qr.make(fit=True)
 
        img = qr.make_image()
 
        fileName = datetime.datetime.now().strftime("%Y%m%d_%H%M%S"+ ".png" 
        img.save(fileName)
        qr.clear()
        
        
        # 받은 값을 텍스트 라벨에 출력합니다.
        self.some_text.SetLabel("저장완료")
 
 
    def OnCloseWindow(self, e):
        self.Destroy()
 
# 메인코드 입니다.
# 윈도우를 띄우고 제목을 넣습니다.
app = wx.App()
frame = Frame(None, 'getQRCode')
app.MainLoop()
 
cs

 

 

[입력 창을 2개 만들어서, 이름을 받음]

  예를 들어 www.google.co.kr 과 google 이라고 입력 하면 google.png 로 생성해줌.

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
import wx
import qrcode
 
# 윈도우즈 정의 클래스 입니다.
class Frame(wx.Frame):
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, title=title, size=(500200))
        self.panel = wx.Panel(self)
        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
 
        # 버튼을 생성 합니다.
        self.btn = wx.Button(self.panel, -1'QR 코드 저장')
 
        # 텍스트 박스를 생성합니다.
        self.txt = wx.TextCtrl(self.panel, -1, size=(140,-1))
        self.txt.SetValue('input your url')
        
        # 텍스트 박스를 생성합니다.
        self.txt2 = wx.TextCtrl(self.panel, -1, size=(140,-1), pos=(0,50))
        self.txt2.SetValue('input your file name')
        
        # 텍스트 라벨을 생성합니다.
        self.some_text = wx.StaticText(self.panel, size=(140,150), pos=(10,80))
        self.some_text.SetLabel('result is...')
 
        # 버튼 클릭 시 이벤트를 연결 합니다.
        self.Bind(wx.EVT_BUTTON, self.GetQRCode, self.btn)
 
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.btn)
        sizer.Add(self.txt)
 
        self.panel.SetSizer(sizer)
        self.Show()
 
    # 버튼 클릭 시 실행 되어, 암호화 하는 함수 입니다.
    def GetQRCode(self, e): 
        inputValue = self.txt.GetValue()
        
        # 이 inputValue 값을 QR 코드 쪽에 연결하면 됨.
        qr = qrcode.QRCode(version=1, error_correction=qrcode.constants.ERROR_CORRECT_L,box_size=10,border=4)
        qrAddress = "http://" + inputValue + "qrCodeId=CAD20181220041519"
        qr.add_data(qrAddress)
        qr.make(fit=True)
 
        img = qr.make_image()
 
        fileName =  self.txt2.GetValue() + ".png"
        img.save(fileName)
        qr.clear()
        
        
        # 받은 값을 텍스트 라벨에 출력합니다.
        self.some_text.SetLabel("저장완료")
 
 
    def OnCloseWindow(self, e):
        self.Destroy()
 
# 메인코드 입니다.
# 윈도우를 띄우고 제목을 넣습니다.
app = wx.App()
frame = Frame(None, 'getQRCode')
app.MainLoop()
 
cs

 

 

 

1
2017.3.5 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