도메인에 매핑되어 있는 IP들을 cmd 명령어 nslookup을 통해 하나하나 확인 후 기록해달라는 요청을 받았다.

photo1

처음 엑셀 파일을 열어보고 당황했지만, 갯수나 세어보고 얼마 안되면 해치워버리자는 마음으로 갯수를 세보았다.
그랬더니 100개가 넘어가네..

photo2

물론 시간을 들인다면 해낼 수 있겠지만, 4가지 특이점이 왔었다.

  1. 도저히 그럴 엄두가 안난다.
  2. 100회가 넘어가는 반복 동작이면 분명 실수가 발생할 것이다.
  3. 발생한 실수를 찾아낼 자신이 없다.
  4. 단순 동작이 반복된다.

이 때 4번이 나한테 희망을 주고 있었는데, 얼마전부터 계속 진행하던 [Python 재무제표 크롤링 #1] 덕분에 쌓였던 ‘파이썬으로는 무엇이든 할 수 있다!’ 라는 기대감이 4번과 증폭되어서, ‘파이썬으로 자동화를 시켜보자!’ 라는 생각에 이르렀다.

설계 준비

당장 나에게 필요한 정보가 두 가지였다.

  • 파이썬을 이용해서 cmd 프로그램에 명령어를 입력할 수 있는가?
  • 파이썬을 이용해서 cmd 프로그램의 출력 내용을 가져올 수 있는가?

알아본 바로는 os 모듈을 이용하면 cmd 프로그램에 입/출력이 가능하다고 한다.
기쁜 마음으로 구현을 시작하려는 찰나, 한 가지 더 필요한 정보가 떠올랐다.

  • nslookup은 대화형 명령어인데, 비대화형으로 사용할 수 있는가?

생각치도 못한 문제였다. 대화형 명령어?
대화형 명령어의 경우 명령어가 시작되면 cmd 입출력 전권을 명령어가 가지기 때문에 입/출력을 통제하는 코드 작성이 복잡해진다. 그렇게 되고 싶진 않은데…
다행스럽게도 이 곳에서 방법을 찾을 수 있었다.
배운 방법을 토대로 코리아(korea.com) 도메인 실험을 진행해보자.
(뭐하는 사이트인지는 모른다. 그냥 도메인 실험에 가장 적합한 형태였다.)

// 대화형
C:\Users\admin> nslookup
기본 서버:  its.domain.net
Address: 123.123.12.3

> korea.com
서버:   its.domain.net
Address: 123.123.12.3

권한 없는 응답:
이름:   korea.com
Addresses:  119.205.213.227

>

// 비대화형
C:\Users\admin> nslookup korea.com
서버:   its.domain.net
Address: 123.123.12.3

권한 없는 응답:
이름:   korea.com
Addresses:  119.205.213.227

C:\Users\admin>

비대화형으로 입력하는 방법은 간단했다. 명령어 뒤에 검색할 도메인 명을 입력하면 끝!
그럼 대화형으로 도메인명을 입력한 것과 완전히 동일하게 결과를 내어준다. 실험도 성공적으로 마쳤으니 코드 작성을 시작하면 된다.

구현

domain_list = []
domain_list.append("domain0.com")
domain_list.append("domain1.co.kr")    
domain_list.append("domain2.co.in")       
domain_list.append("domain3.co.in")         
domain_list.append("domain4.in")               
domain_list.append("domain5.co.in")              
domain_list.append("domain6.in")
domain_list.append("domain7.co.kr")          
domain_list.append("domain8.com")
domain_list.append("domain9.co.kr")

도메인은 위와 같이 domain_list 리스트에 저장해둔다. 엑셀 파일에서 도메인을 읽어와 리스트에 추가하는 동작까지 자동화 시킬 수 있다면 더 좋겠지만! 파일공유시스템의 보안 덕분에 해당 동작 구현이 불가능했다. 엑셀 세로방향 그대로 복사 붙여넣기 후, 다중커서모드로 domain_list.append(“, 다중커서모드에서 End키로 맨 뒤로 넘어가서 ”) 해주는 걸로 금방 리스트를 채울 수 있다.

import os      
import subprocess 
import pandas as pd

cmd 프로그램을 컨트롤하기 위한 os 모듈과, 파이썬 프로그램 내에서 새로운 프로세스를 스폰하고 여기에 입출력 파이프를 연결하며 리턴코드를 획득할 수 있도록 하는 subprocess 모듈을 import 한다. pandas의 경우 결과를 .csv로 저장하여 손쉽게 복사할 수 있도록 하기 위해서 import 했다.
아, 곧장 원본 엑셀 파일에 결과를 저장할 수 있으면 편하겠지만, 역시 파일공유시스템에 막혀 직접 건드릴 수는 없었다. 때문에 복사 붙여넣기가 가능하도록 깔끔한 형태의 csv 파일을 만들 예정이라 pandas 모듈이 필요했다.

f = open('test.txt','w')                        
for domain in domain_list :                    
    command = "nslookup " + domain            
    
    sysMsg = subprocess.getstatusoutput(command)    
    f.write(sysMsg[1])                         
f.close()                    

cmd-nslookup 결과를 우선 txt파일로 저장하기 위해서 write모드로 open해준다. 굳이 test.txt 파일을 안남기고 list타입 변수만으로 해결해도 괜찮지만, test.txt 파일을 열어서 올바르게 동작되는지 파악하기 위해서 test.txt 파일을 남기고 그 파일을 다시 읽어서 동작하는 방식을 취하도록 했다.
for문을 통해 모든 domain에서 반복할 것을 준비하고, command 변수에 비대화형 nslookup 명령을 매핑한다. 그 후 subprocessgetstatusoutput 함수를 통해 command를 입력, sysMsg 변수에 결과를 저장한다. 이 때 결과는 첫 번째 원소 위치에 상태값, 두 번째 원소 위치에 결과값이 저장되므로 write(sysMsg[1])을 통해 결과 값만 저장하도록 한다.
그 후 파일 닫기!

r = open('test.txt', 'r')                       
t = pd.DataFrame(columns=['domain', 'Address']) 
t_count = 0                                    
read_line = "A"                               
while read_line :                              
    READ_LIST = []                             
    read_line = r.readline()                   

저장한 test.txt 파일을 이번에는 read모드로 열어둔다. 그리고 컬럼이 domainAddress로 정해진 빈 DataFrame을 만든다. t_count int타입 변수는 DataFrame의 행(row)를 세기 위한 변수고, read_line = “A”의 경우 바로 아래의 while문 조건에 걸리게 하기 위해 의미없이 집어넣은 값이다. 파이썬에는 do while문이 존재하지 않고, 이를 위해서는 다른 방식을 채택해야하는데, 굳이 복잡한 로직을 구상하기보다는 그냥 간단히 조건을 미리 결정해주는 것으로 해결했다.
while 반복문이 시작되면 READ_LIST 라는 리스트를 선언한다. 각각 test.txt 파일로부터 domain과 Address를 읽어와 0번, 1번 자리를 채우게 될 변수다. read_line 변수에 readline()함수를 이용하여 한 줄씩 읽기 시작한다.

    if '이름' in read_line :                    
        read_line = read_line.split()           
        read_underline = r.readline()    
        read_underline = read_underline.split()
        READ_LIST.append(read_line[1]) 
        READ_LIST.append(read_underline[1])

        t.loc[t_count] = READ_LIST            
        t_count += 1                 

while 반복문 내부에서 마주치는 첫 번째 분기문이다. 읽어온 문장에 이름 이라는 단어가 포착될 경우 탐색에 성공한 것이므로 동작을 시작한다.

이름:   korea.com
Address: 119.205.213.227

문장을 공백 기준으로 잘라 리스트로 저장한다. 그리고 곧 장 한 줄을 더 읽어들여 공백 기준으로 잘라 또 다른 리스트로 저장한다. 그럼 read_lineread_underline에는 아래와 같은 형태가 된다.

read_line = [“이름:”, “korea.com”]
read_underline = [“Address:”, “119.205.213.227”]

여기서 각각 두 번째 원소를 READ_LIST에 넣게 되면?

READ_LIST = [“korea.com”, “119.205.213.227”]

DataFrame의 한 행을 채울 준비가 완료된다. loc매크로를 이용해서 채워넣자. 이 때 위에서 선언했던 t_count 변수가 쓰인다. 쓰인 후에는 1을 증가시켜주자.

    if '***' in read_line :     
        read_line = read_line.split() 
        read_line[2] = read_line[2].replace("을(를)", "") 
        READ_LIST.append(read_line[2])         
        READ_LIST.append("N/A")
        t.loc[t_count] = READ_LIST             
        t_count += 1                         

while 반복문 내부에서 마주치는 두 번째 분기문이다. 읽어온 문장에 *** 이라는 기호가 포착될 경우 탐색에 실패한 것이므로 동작을 시작한다.

*** its.domain.net이(가) hyeon9mak.com을(를) 찾을 수 없습니다. Non-existent domain

이번에도 문장을 공백 기준으로 잘라 리스트로 저장한다. 한 줄 더 읽을 필요는 없다. 두 번째 줄이 없으니까. 잘라낸 단어들 중, 세 번째 단어를 찾아낸다. 공백을 기준으로 잘랐을 때 세 번째 단어가 도메인 명이다.
도메인명 뒤에 을(를)이 붙어있으므로, 이를 replace 함수를 통해 제거한다. 그후 READ_LIST 변수에 아래와 같은 형태로 집어넣는다.

READ_LIST = [“hyeon9mak.com”, “N/A”]

이번에도 완성된 행을 loc매크로를 이용해서 채워넣고, t_count 값을 증가시켜주자.

r.close()                                
t.to_csv('C:\\Users\\admin\\Desktop\\test1.csv', encoding='cp949', index=False, mode='w')

다 읽었으니 read모드로 열었던 파일은 닫아준다.
DataFrame의 to_csv함수를 이용해서 csv파일로 저장한다. 이때 인코딩은 한글 도메인이 섞여있는 경우도 있었으므로 cp949로 지정했다. index는 필요없으므로 false, 저장은 write모드로 덮어씌워지도록 했다.

결과

photo3 최종적으로 이미지와 같이 두 개의 Column으로 구분 된 정보를 깔끔하게 얻을 수 있었다. 결과를 얻어낸 기쁨도 잠시 혹여나 잘못된 정보를 가져온게 아닐까 불안감에 휩싸여 20개 정도의 도메인을 일일히 테스트하면서 자동화시킨 결과와 대조해봤다. 결과는 이상 없음! 그제서야 마음놓고 기뻐할 수 있었다.

고찰 및 후기

크게 고찰할 거리는 없었다. 내가 대단해서 대단한 자동화를 시킨것도 아니고, 제약사항도 워낙 명백했으며, 파이썬 모듈 자체가 워낙 잘 만들어져 있어서 존재하는 기능들을 끌어다 짜집기 한게 전부였다.

예전에 기사로 봤었던 전설의 공익이 떠오른다. 몇 년 걸릴 일을 일주일로 단축시켜버렸다던 그 분. 우연치 않게 그 분이 운영하시는 SNS(였나 블로그였나)를 읽어볼 기회가 있었는데, 그 때 그분이 하셨던 말씀이 코드를 작성하는 내내 떠올랐다.

과연 이걸 해결할 수 있을까 싶지만, ‘파이썬으로는 무엇이든 가능하다’ 라는 믿음만 있다면 못 할 것이 없으니, 시도해보자!

정말 무엇이든 가능하다는 믿음만 있으면 못 할 것이 없는 재밌는 언어같다.
그래도 난 C, C++이 더 좋다. 파이썬은 모듈찾는 시간이 고민하는 시간보다 더 긴 것 같아..

아래 최종 코드를 첨부한다.

최종코드

import os
import subprocess
import pandas as pd

domain_list = []
domain_list.append("domain0.com")
domain_list.append("domain1.co.kr")               # 실제 이용해보실 때는 도메인 주소를 일렬로 복사 붙여넣기 하신 후
domain_list.append("domain2.co.in")            # Ctrl+Alt+(방향키 위 또는 아래) 로 다중 커서를 적용 하시고
domain_list.append("domain3.co.in")              # domain_list.append(" 까지 입력,
domain_list.append("domain4.in")                 # 다중 커서 상태로 End 키를 눌러 모든 행의 맨 끝으로 이동 
domain_list.append("domain5.co.in")               # ") 를 작성하시면 왼쪽과 같은 형태를 금방 채울 수 있습니다.
domain_list.append("domain6.in")
domain_list.append("domain7.co.kr")          
domain_list.append("domain8.com")
domain_list.append("domain9.co.kr")

f = open('test.txt','w')                        # cmd 결과를 저장 할 txt파일 생성 및 쓰기 형식으로 열기
for domain in domain_list :                     # 전체 도메인 반복
    command = "nslookup " + domain              # "nslookup something.co.kr"
    
    sysMsg = subprocess.getstatusoutput(command)    # cmd에 명령어 입력 후, 결과 가져오기
    f.write(sysMsg[1])                          # 가져온 결과물 파일에 적기
f.close()                                       # txt파일 닫기

r = open('test.txt', 'r')                       # 닫았던 txt파일 읽기 형식으로 열기
t = pd.DataFrame(columns=['domain', 'Address']) # column이 domain / Address 인 Dataframe 생성
t_count = 0                                     # Dataframe 행 카운터
read_line = "A"                                 # while 반복문 조건을 위해 선언한 문자열
while read_line :                               # readline 결과가 NULL이라면 (EOF) 탈출
    READ_LIST = []                              # 빈 list 선언
    read_line = r.readline()                    # 줄 단위 읽어오기

    if '이름' in read_line :                    # 읽어온 줄에 '이름' 이 포함되어 있다면 (IP가 확인된다면)
        read_line = read_line.split()           # 공백 기준 나누기
        read_underline = r.readline()           # 바로 아래줄 추가로 읽어오기
        read_underline = read_underline.split() # 추가로 읽어온 아래줄 공백 기준 나누기
        READ_LIST.append(read_line[1])          # 두 줄 모두 비어있던 list에 추가
        READ_LIST.append(read_underline[1])
        
        t.loc[t_count] = READ_LIST              # list를 DataFrame에 추가
        t_count += 1                            # Dataframe 행 카운터++
    
    if '***' in read_line :                     # 읽어온 줄에 '***' 이 포함되어 있다면 (IP가 확인되지 않는다면)
        read_line = read_line.split()           # 공백 기준 나누기
        read_line[2] = read_line[2].replace("을(를)", "") # 을(를) 지우기
        READ_LIST.append(read_line[2])          # list에 domain명 추가하고, IP 컬럼에는 N/A
        READ_LIST.append("N/A")
        t.loc[t_count] = READ_LIST              # list를 Dataframe에 추가
        t_count += 1                            # Dataframe 행 카운터++

r.close()                                       # 다 읽은 파일 닫기
t.to_csv('C:\\Users\\admin\\Desktop\\test1.csv', encoding='cp949', index=False, mode='w')   # Dataframe 엑셀로 저장

끝!

태그:

업데이트:

댓글남기기