brunch

You can make anything
by writing

- C.S.Lewis -

by 반병현 Nov 01. 2018

크롤러를 이용해 우체국 등기우편을 자동으로 정리해 보자

코딩하는공익(2)

  필자는 노동청에서 사회복무요원으로 복무 중이다. 주로 청에서 밖으로 나가는 우편물을 취급하고, 외부에서 청으로 보낸 우편물을 처리하는 업무를 수행 중이다.


  노동청에는 많은 사람들이 방문한다. 억울하게 임금을 체불당한 사람이나 일자리를 알아보려는 사람들이 민원인의 대부분을 이루지만 실업급여를 수령하고자 하는 사람들이나 서민금융업무를 위해 방문하는 사람도 많다. 심지어 나랏돈을 부정한 방법으로 수급받은 혐의나 임금체불 등으로 소환조사를 받는 용의자들도 방문한다. 생각보다 업무범위도 넓고 정말 다양한 사람들이 찾아오는 곳이다.


  그러다 보니 별의별 분쟁이 다 벌어진다. 민원인과 공무원이 대면해서 하는 절차는 그나마 해결이 용이하게 되는 것 같다만 우편을 이용해 절차가 진행되는 경우, 머리가 아픈 일이 발생한다.

   

  노동청에서 보낸 우편물을 민원인이 수신한 적이 없다고 잡아떼는 경우도 있다. 그렇다고 이러한 주장을 매번 배척할 수도 없는 것이, 우체국에서 조회해 보니 정말로 민원인이 수신하지 못하고 반송된 경우도 있다. 분쟁이 생길 때마다 등기 조회서비스를 이용하는 방안도 있겠지만, 우체국은 최근 1년간의 등기우편만 발송 기록 조회서비스를 제공한다.


  "최근 1년간 노동청에서 발송된 모든 등기우편의 발송내역을 조회하여 종이에 인쇄해 보관하자."


  누군가 이런 명료하면서도 아름답지 못 한 솔루션을 생각해 내 버렸고, 이 일은 필자에게 돌아왔다.


최근 1년간 노동청에서 발송된 우편물은 3900건이 넘는다.

  최근 1년 치 우편을 모두 정리하라니, 정말 생각만 해도 정신이 아득해진다. 등기우편을 조회하여 인쇄하려면 아래와 같은 3단계의 절차를 거쳐야 한다.


        (1) 우체국 홈페이지에 접속한다.

        (2) 등기번호 13자리를 입력한다.

        (3) 검색 결과가 나오면 이를 인쇄한다.


  근무시간 내내 이 일만 붙들고 너무 오랜 시간이 걸릴 것 같았다. 이건 사람이 직접 수행하기에 적합한 일이 아니야! 그래서 이 일을 대신 처리해 줄 크롤러를 만들기로 결심하였다.


  필자는 이전에 크롤러를 만들어 본 경험이 없었다. 하지만 당황할 필요는 전혀 없다. 대학원과 스타트업 생활을 하며 깨달은 지혜가 있기 때문이다. 그것은 바로 "'파이썬과 함께라면 못 만들 것은 없다.'는 마음가짐만 있다면 정말로 못 만들 것은 없다"라는 마음가짐이다.


  그리고 구글신은 모든 것을 알고 계실 것이다. 망설임 없이 구글에 "Python crawler library"라는 키워드를 검색했고, Selenium이라는 라이브러리를 찬양하는 스택오버플로우 답변을 발견하게 되었다. 관심이 생겨 조금 더 검색해 보니 정말 괜찮아 보였다.


  셀레니움의 기초적인 사용방법은 아래 글을 통하여 공부했다. 생각보다 훨씬 사용하기 편해 보였다. 도구는 얼추 갖추어졌으니 작업만 시작하면 될 것 같다.

  https://beomi.github.io/2017/02/27/HowToMakeWebCrawler-With-Selenium/

  


  누구나 코딩을 할 때에 습관이나 취향이 있을 것이다. 필자는 학부시절 Bioinforrmatics 연구실에서 개별 연구를 진행하면서 생긴 습관이 하나 있다. 내가 배포할 기능을 전부 모듈화 하는 것이다. 이쪽 필드에서는 잘 만든 라이브러리가 논문이 되기도 한다. 그러다 보니 핵심 기능은 코드 한 줄로 불러와서 사용할 수 있도록 구현하고 메인 함수는 수도 코드처럼 '이 작업을 수행하는 데 사용할 알고리즘' 자체만 전달이 잘 되도록 구현하는 습관이 생긴 것이다.


평소 하던 스타일로 프로그램을 한번 설계해 보았다


  이러한 스타일의 장점으로는 열심히 짠 코드를 제삼자가 다른 목적으로 변형하여 사용하기가 편하다는 점이다. 단점으로는 필자와 동일한 목적의 작업을 수행하고 싶은 데 코드에 수정할 부분이나 추가하고 싶은 기능이 있을 경우, 많은 양의 코드를 읽어야 구조 전체가 눈에 들어온다는 점이 있다.  


  우체국에서 등기우편을 조회하려면 홈페이지 우측 검색창에 등기번호 13자리를 입력해야 한다. 고민하며 구글을 정처 없이 방황하던 끝에 아래 주소 뒤에 등기번호를 하이픈 없이 입력하면 자동으로 조회 페이지로 이동된다는 사실을 깨달았다.


http://service.epost.go.kr/trace.RetrieveRegiPrclDeliv.postal?sid1=


  등기번호는 엑셀 파일에 모두 정리해 두었다. 이 엑셀 파일을 열어서 등기번호를 불러오고, 위 URL을 통하여 배송 조회를 하는 크롤러를 만들면 된다.


  post_crawler.py

  crawler라는 이름의 클래스를 만들고, 대부분의 기능을 이 클래스의 메서드로 구현했다.


  1. Initiation 과정에서 셀레니움으로 크롬 드라이버를 불러와 창을 연다.

크롬드라이버 경로와 창 크기를 입력해준다.

    인쇄가 목적이라면 윈도우 사이즈가 A4용지 비율과 비슷하도록 세팅해주는 것이 좋다.


  2. 스크린샷을 저장하는 save_screenshot 함수를 만든다.

쿼리와 경로를 입력받아 저장하는 간단한 함수다

  3. 외부에서 크롤러를 킬 할 수 있게 다듬어 준다.


  

  main.py

  1. crawler를 불러온다.

  2. 엑셀 파일을 읽어와 등기번호를 메모리에 올린다.

  3. 스크린샷이 저장될 디렉터리를 확인하여 이미 작업이 끝난 등기번호는 메모리에서 제거한다.

  4. 아래 작업을 반복한다.

    (1) 등기번호를 우체국에서 조회한다.

    (2) 페이지가 로딩되면 스크린샷을 뜬다.

    (3) 스크린샷을 저장한다.


크롤러가 돌아가는 과정


  간단하게 구현해서 한 번 돌려봤다. 잘 돌아간다.

  

  오전에 코드를 돌려 두고 점심을 먹고 오니 작업이 끝나 있었다. 3900장의 종이를 프린터로 인쇄하는 것은 또 다른 문제였다. 인쇄에만 3시간가량 시간이 들었다. 용지를 다 쓰면 새로운 A4용지 박스를 까서 충전을 해야 했기 때문에 자리를 뜰 수도 없었다. 뭐 어찌 됐든 이 작업은 퇴근시간을 한 시간 가량 남기고 하루 만에 끝낼 수 있었다.


  하지만 생각지도 못 한 문제가 또 있었다. 우체국에서는 수취인의 신변보호를 위하여 이름 일부를 마스킹한다. 예를 들어 '반병현' 이라는 사람이 '상상텃밭' 이라는 회사에 등기우편을 보내면 아래와 같이 표기되는 것이다.

    

    발신인 반*현    수신인 상*텃밭


  이렇게 실명 일부가 가려질 경우 민원인이 본인이 아니라 다른 사람이라고 주장할 여지가 있다. 따라서 마스킹을 모두 해제하여 기록을 다시 정리할 필요가 있었다. 그런데 이 과정이 정말 골치가 아프다.


  조회 페이지에서 마스킹 해제 조회 버튼을 누르고


  팝업창에서 발신인과 수신인의 이름을 정확히 입력해야 한다. 한 가지 다행이라면 이름 전체를 입력할 필요는 없고 *표시로 가려진 두 번째 글자 하나만 입력하면 된다.


  와 이건 진짜 손으로 해야 되는 것 아닌가? 일단 양이 너무 많아서 못 하겠으며, 우편은 매일 쌓이기 때문에 이 일은 끝이 없다고 우겨 봤지만 택도 없더라. 공익의 근무시간은 하루 8시간이니까 8시간 내내 하면 반년 안에 끝날 일이라는 말씀을 반박할 수 없었다.


  그래서 불쌍한 공익은 삐뚤어지기로 결심하고 post_crawler.py에 매크로를 추가로 구현해 보기로 했다. 파이썬과 함께라면 못 만들 것은 없다는 마음가짐만 있다면 못 만들 것은 없지 않은가. 필요한 기능은 아래와 같다.


    1. 마스킹을 풀어 주는 함수가 있다고 가정하고, 마스킹이 풀리면 스크린샷을 저장하는 메서드

    2. 클릭, 타이핑, 팝업창 인식 함수가 모두 있다고 가정하고, 마스킹을 풀어 주는 메서드

    3. 클릭 기능을 구현하는 함수

    4. 타이핑 기능을 구현하는 함수

    5. 화면의 특정 픽셀의 RGB 값을 구해주는 함수

    6. 팝업창 로딩 여부를 감지하여 대기하는 함수

    7. 팝업창 로딩 여부를 감지하여 대기를 중지하는 함수

    8. 페이지에 에러가 발생할 경우 이 쿼리를 죽이는 함수


  크롤러도 이번이 첫 구현이었고, 윈도우 GUI에서 입출력을 주는 기능도 구현해 본 적이 없었지만 대학원 시절 아무것도 모르는 상태에서 과제 때문에 딥러닝 논문을 코드로 구현해 가야 했던 나날들에 비하면 쉬운 일이다. 우선 위에서부터 차례대로 구현해 갔다.


  다행히 파이썬에는 윈도우와 소통하기 위한 win32api, win32con, win32gui라는 라이브러리가 있다고 구글신께서 알려주셨다. 아아.. 구글신이시어..


마스킹을 풀어주는 함수가 있다고 가정하고, 마스킹이 풀리면 스크린샷을 저장하는 메서드.

  key1은 발신인 명의의 마스킹 키가 된다. 예를 들어 반병현이 반*현으로 암호화되었다면, '병'이라는 문자에 해당한다. key2는 수신인 명의의 마스킹 키다. 개인정보 인증 창에서 key1과 key2를 입력하면 마스킹이 풀리게 되어 수신인과 발신인의 실명을 조회할 수 있다. 마스킹 해제 기능이 호출된 뒤에는 팝업창의 로딩이 끝날 때까지 기다리는 redbox_based_awake() 함수를 호출하고 스크린샷을 저장한다.

  만약 마스킹 해제가 실패할 경우 실패했음을 알리고 작업을 종료한다.


마스킹을 풀어 주는 함수

  마스킹 해제 버튼이 화면상에 위치하는 좌표를 button_location이라는 튜플로 지정했다. key1_location과 key2_location은 각각 발신인과 수신인의 마스킹 키를 입력하는 입력 칸의 좌표이며 popup_ok_location은 팝업창의 '확인'버튼의 좌표다.

  흐름은 굉장히 간단하다.


    (1) 마스킹 해제 버튼을 클릭한다.

    (2) 화면이 로딩될 때까지 기다린다.

    (3) 로딩이 완료되면 발신인 마스킹 키 입력창을 클릭하고

    (4) 해당 자리에 발신인의 마스킹 키를 입력한다.

    (5) 수신인 키 입력 창도 클릭하고

    (6) 수신인 마스킹 키를 입력한다.

    (7) 노동청 공익용 컴퓨터 성능상의 문제인지 뭔지 입력 후 잠시 멈추는 경우가 있어 0.1초를 대기해 준 다음

    (8) 팝업창의 확인 버튼을 눌러준다.

    (9) 에러가 뜨면 '마스킹 해제 실패' 메시지를 리턴한다.


클릭 구현

    클릭은 간단하게 구현했다. 좌표를 튜플로 입력받고, 그 좌표에 "마우스 왼쪽 버튼 누름", "마우스 왼쪽 버튼 뗌" 이벤트를 각각 발생시킨다.


타이핑 구현

  타이핑은 고민을 좀 했다. 영문자 같으면 쉽게 되겠지만 내가 쓰는 셸은 한글을 넣은 코드는 돌아가지도 않았고 한글 자모를 분해해준다는 라이브러리는 설치가 아예 안 됐다.

  고민 끝에 아래와 같은 방식으로 처리했다.


    (1) 입력받은 문자열을 클립보드에 넣는다.

    (2) Ctrl 키와 V키를 누르는 이벤트를 발생시킨다.

    (3) Ctrl 키와 V키를 떼는 이벤트를 발생시킨다.


  한글뿐만 아니라 한문이나 특수문자도 잘~ 처리된다.


  그 외의 기능은 get_color 함수를 구현하고 이를 활용해서 대부분 처리했다.

윈도우상의 x, y좌표의 픽셀의 '색'을 탐지하고 16진수로 변환하여 리턴하는 함수

  특정 좌표의 색이 빨간색인지, 아닌지, 특정 구역에 회색 박스가 생겼는지 등등을 간단한 조건문 처리하여 나머지 기능을 구현했다.


  아슬아슬하게 퇴근시간 5분 전에 코드를 완성할 수 있었고, 그냥 돌려두고 퇴근했다. 다음 날 출근하여 보니 당연히 모든 작업이 끝나 있었다.


  같은 층에 근무하는 다른 공무원들이 너무 신기해해서 뿌듯했다.


  어휴. 하마터면 하루 8시간씩 반년 동안 열심히 일할 뻔했다.

  

(이 글에서 사용된 소스코드는 깃허브에 모두 공개되어 있습니다.)

  https://github.com/needleworm/post_crawler




매거진의 이전글 업무 자동화 스크립트 짜주다가  국정원에 적발당한 썰

매거진 선택

키워드 선택 0 / 3 0
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari