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

calendar

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

Notice

2017. 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 자유로운설탕