제가 옛날에 토플 공부를 한 적이 있습니다. 이 때 글쓰기의 엄청난 꿀팁을 배웠는데 바로 '훅(hook)'인데요, 맨 처음부터 사람들의 관심을 사로잡아야 그 다음이 진행이 되든 말든 한다는 것이었습니다.
비록 9탄에 와서야 생각이 났지만,, 그래도 연재를 진행하며 고민을 많이 해서 좀 더 나은 결과를 얻지 않았나..(?)하는 마음을 위안으로 삼으며 후기 느낌으로 정리해 보도록 하겠습니다.
한글 공문 매크로 프로그램
좀 구미가 당기시나요? 간단한(?) 설명과 예시 파일은 압축해서 글 아래에 첨부하도록 하겠습니다...만 꼭 저의 제작기를 읽어주셨으면 하는 마음이 들기도 합니다.ㅋㅋㅋㅋ
해당 연재는 누구나 쉽게(?) 접할 수 있는 파이썬을 기반으로, 챗지피티를 적절히 활용하여 단순한 반복 작업을 대신 해주는 프로그램을 제작하는 과정을 담고 있습니다.
코딩을 할 때 챗지피티를 어떡하면 잘 활용해서 호다닥 프로그램을 만들 수 있는지!
최대한 응용하기 쉽게, 자세한 원리나 이론을 설명하기보다 기능적인 관점에서 접근하도록 했으니 차근차근 따라하지 말고(!) 가능한 훑어보기만 하면서 어떤 식으로 진행되나~ 정도만 파악해도 따라할 수 있도록 써보려 노력했습니다. 잘 되었는지는 모르겠지만,,,, 무엇보다 제가 그런 부분을 잘 모르기 때문이죠.(당당)ㅋㅋㅋ
첨부파일에는
1. 실행파일 2. 설명서 ppt 3. 예시 엑셀/한글 양식
이렇게 구성돼 있습니다.
그림 1. 첨부 파일 구성
output 폴더는 출력 파일이 저장되는 폴더로, 실행파일을 실행하면 자동으로 생기는 거라 뺐습니다.
꼭 인터넷을 뒤지다 보면 '내가 필요한' 작업에 바로 적용할 수 있는, 소위 '날로 먹을 수 있는' 프로그램은 바로 구하기가 참 어렵더라구요...ㅋㅋㅋㅋ 뭔가 애매하게 안 맞다거나..ㅋㅋㅋㅋ 그런 분들은 바꾸고 싶은 부분에 따라 해당되는 소제목을 찾아가면 금방 해결하실 수 있습니다!(광고)ㅋㅋㅋㅋㅋ
크게 수정이 필요할 부분은 이렇게만 있을 것 같네요. 항상 강조하지만 들어가서 대충 훑어보고 필요한 부분만 적용하시면 아주 효율적으로 적용이 가능할 것 같습니다.ㅋㅋㅋㅋ
기반이 되는 코드는 전부 이전 게시글에 있는 코드 조합이라 보시면 되고, 현재 파일이 들어있는 위치를 인식하지 못하는 오류가 떠서
그림 2. 실행 파일 위치 가져오기
우리 친구가 친절히 수정해줬답니다. 아니 왜 exe로 실행할 때랑 코드로 실행할 때랑 달라지는거야...
import os
import sys
if getattr(sys, 'frozen', False): # .exe로 실행된 경우
current_dir = os.path.dirname(sys.executable)
else: # .py로 실행된 경우
current_dir = os.path.dirname(os.path.abspath(__file__))
그림 3. 사용 방법 정리
첨부된 ppt에는 각각의 파일을 어떻게 준비해야 하는지, 어떻게 사용할 수 있는지 나름 구체적으로 정리했습니다. 제 게시글 중에서 유일하게 얘만 차근차근 읽어 보시면 금방 따라할 수 있을 것 같습니다.
우선 한글과 엑셀 양식 준비하는 방법이 있습니다.
그림 4. 한글 양식
한글 양식에서는 '누름틀'이라는 것만 설정하시면 되는데, 얘는 대체 텍스트를 쉽게 넣어주는 기능이라고 하네요. 저는 이번에 처음 알았습니다.ㅋㅋㅋㅋ
그림 5. 엑셀 양식
엑셀은 어떤 종류의 데이터가 어떤 순서로 정리돼야 하는지 정리를 했습니다.
그림 6. 한글 환경설정
양식과 별개로 한글 프로그램에서 어떤 설정을 해야 하는지, 파일 저장은 어떻게 해야 하는지 제가 문제를 겪은 부분을 정리했는데, 스트레스 받으면서 겨우 해결했는데 막상 해결하니 너무 단순한 것 같기도 하네요. 뭔가 억울한 느낌?ㅋㅋㅋㅋㅋㅋㅋ 뭐 우리 주변의 문제들도 마찬가지로 다 해결하고 보면 별 것 아니니까요~~
그림 7. 실행 환경설정
추가로 폴더 구성을 어떻게 해야 하는지, 윈도우 콘솔을 써본 적 없는 분이 겪을 수 있을 법한 문제와 해결 방안을 같이 첨부했습니다. 콘솔 사용에 익숙까진 아니더라도 한 번 쯤은 써보신 분께는 별 문제가 아니겠지만 아닌 사람들도 있으니까요~~~~...사실 저도 꽤 익숙하다고 생각했는데 왜 이런 별 거 아닌 부분이 맨날 헷갈리는지..ㅋㅋㅋㅋㅋ
만약 중간에 중단한 경우 파일/폴더 삭제가 안되면
코드 내부에서 한글 중단 작업이 실행되기 때문에 프로그램을 한번 더 실행해 보시고, 그래도 안되면
그림 8. 작업 관리자 실행
(컨트롤+쉬프트+esc) 혹은 (컨트롤+알트+delete -> 작업관리자)로 작업 관리자를 들어가서 앱 혹은 백그라운드 프로세스에서 한글을 찾아서 작업끝내기를 해주시면 됩니다.
첨부 파일은 용량 문제로 세 개로 나눴는데, 같은 폴더에 다운받으시고 vol1 파일 켜서 압축 풀기 하시면 됩니다!
지난 코드에서 xl_path, hwp_path는 우리 프로그램과 같은 위치에 있을 테니 프로그램이 위치한 현재 폴더를 가져오는 함수가 있으면 좋겠죠?
파이썬에서는 한 줄이면 됩니다!!!..정확히는 두 줄....ㅋㅋㅋㅋ
import os
current_dir = os.path.dirname(__file__)
# print(current_dir)
요렇게 하면 현재 위치가 저장된 current_dir이라는 변수가 생성됩니다!
직접 확인하고 싶으면 print함수를 쓰면 된다고 말씀드렸죠?!
다음으로,
현재 폴더에서 엑셀 파일과 한글 양식을 찾아보도록 하겠습니다.
우리 폴더에는 '프로그램, 한글, 엑셀' 총 세 개만 들어있습니다. 그렇다면 확장자가 xlsx면 엑셀 파일, hwp면 한글 파일이 되겠죠?
1. 현재 폴더 찾기 2. 현재 폴더에 들어있는 것들 목록 구하기 3. 한글 파일, 엑셀 파일 갖고오기
순서로 진행하면 될 것 같습니다.
2번에서 파일 목록을 쭈루룩 뒤져서 3번에서 각각 변수에 저장하면 될 것 같죠?
file_list = os.listdir()
listdir이라는 함수를 사용하면 하위 폴더나 파일 목록을 모두 갖고올 수 있습니다.
이제 이 목록에서 확장자에 따라 기존에 있던 hwp_file과 xl_file 변수에 저장해보도록 하겠습니다.
for file in file_list:
if '.xlsx' in file:
xl_file = file
elif '.hwp' in file:
hwp_file = file
전체 file_list에 들어있는 file에 대해 각각의 조건이 맞으면 xl_file, hwp_file이라는 변수에 해당되는 값을 집어 넣는 조건문이 추가된 구조입니다.
같은 확장자가 여러 개면 마지막 값으로 덮어씌기가 되겠죠? 그래서 해당되는 값이 하나씩만 있어야 하는 게 중요하답니다. 그렇게 구한 위치와 파일명을 기존에 만든 함수에 각각 넣어주면 데이터 읽어오기까지 끝낼 수 있답니다.
4번 저장하기는,
파일이 중구난방으로 저장되면 정신 사납겠죠? 일단 현재 폴더에서 'output'이라는 하위 폴더에 출력 파일을 저장하도록 해봅시다. 그러려면 일단 폴더가 있어야 겠죠?
if 'output' not in file_list:
os.mkdir(current_dir+r'\output')
# os.mkdir(current_dir+'\\output')
혹시나 같은 이름의 폴더가 있으면 문제가 생길 수 있으므로, output이라는 폴더가 없을 경우에만 새로 만들어주는 코드를 추가했습니다. current_dir 바로 뒤에 output이라는 위치를 붙이면 폴더가 구분이 되지 않고 이름이 이어 붙기 때문에 \ 문자로 구분을 꼭 해줘야 한답니다.
여기서 '\' 문자의 특성 때문에 여러가지 방법이 가능하답니다. 자세한 건 파이썬 '문자열'을 공부하시면 된답니다.
그런데 사실 '/' 문자를 써도 돼요. 저는 얘를 선호해요ㅋㅋㅋ
이제 마지막으로 pdf 파일 이름만 설정하면 끝입니다!
pdf_file 변수는 hwp_file 이름과 똑같은데 확장자만 다르죠??
여러 가지 방법이 있겠지만, 단순하게 생각하면 문자열에서 문자만 바꾸는 식으로 하겠습니다.
pdf_file = hwp_file.replace('.hwp','.pdf')
한 줄이면 끝난답니다!
종합해서 함수로 만들면 깔끔하겠죠?
def getFiles():
import os
current_dir = os.path.dirname(__file__)
file_list = os.listdir(current_dir)
for file in file_list:
if '.xlsx' in file:
xl_file = file
elif '.hwp' in file:
hwp_file = file
pdf_file = file.replace('.hwp','.pdf')
if 'output' not in file_list:
os.mkdir(current_dir +'/output')
pdf_path = current_dir +'/output'
return current_dir, xl_file, hwp_file, pdf_file, pdf_path
folder,xl,hwp,pdf,pdf_folder = getFiles()
생각해보니 current_dir을 구했으니 굳이 xl_path와 hwp_path를 만들지 말고 그대로 사용하면 되겠더라구요. 그래서 걔네는 패쓰!!
이렇게 원래 폴더로 들어가면 output이라는 폴더가 만들어진 것을 알 수 있습니다.
그렇게 그대로 넣기만 한다면.....
그림 1. 이럴 수가
이런 문제가 생기네요. 얘가 앞서 언급한 폴더 구분이 안된다는 예시입니다.ㅋㅋㅋㅋㅋㅋㅋ
코드를 잘 살펴보고 폴더 위치 값이 항상 '/'로 구분되게 해 놓으면 문제가 해결 될 거에요(아마도)ㅋㅋㅋㅋㅋ
그렇게 최종 코드는
def getFiles():
import os
current_dir = os.path.dirname(__file__)
file_list = os.listdir(current_dir)
for file in file_list:
if '.xlsx' in file:
xl_file = file
elif '.hwp' in file:
hwp_file = file
pdf_file = file.replace('.hwp','.pdf')
if 'output' not in file_list:
os.mkdir(current_dir +'/output')
pdf_path = current_dir +'/output/'
return current_dir+'/', xl_file, hwp_file, pdf_file, pdf_path
def getCompanyNames(xl_path,xl_file):
from openpyxl import load_workbook
file_path = xl_path + xl_file
wb = load_workbook(file_path)
ws = wb.active
total_rows = sum(1 for row in ws["B"] if row.value is not None)
company_list = []
for i in range(total_rows-1):
company_list += [ws['B%s'%(i+2)].value]
return company_list
def getPDFfromBodyChange(hwp_path,hwp_file,pdf_path,company_name):
import win32com.client as win32
hwp = win32.Dispatch("HWPFrame.HwpObject")
hwp.XHwpWindows.Item(0).Visible = True
file_path = hwp_path+hwp_file
pdf_path1 = pdf_path+'%s.pdf'%(company_name)
hwp.Open(file_path,'HWP','forceopen:true')
hwp.PutFieldText("field",r' 까꿍')
hwp.SetMessageBoxMode(0x1000)
hwp.SaveAs(pdf_path+'%s.hwp'%(company_name),'HWP','')
action = hwp.CreateAction("Print")
option = action.CreateSet()
action.GetDefault(option)
option.SetItem("Device", 3)
option.SetItem("FileName", pdf_path1)
action.Execute(option)
hwp.Quit()
return
current_folder,xl,hwp,pdf,pdf_folder = getFiles()
company_list = getCompanyNames(current_folder,xl)
for company in company_list:
getPDFfromBodyChange(current_folder,hwp,pdf_folder,company)
드디어 끝이 다가오는 것 같습니다!! 사실 어떻게 결과물이 나올 지 저도 잘 모르겠슴니다... 진행하면서 이거 넣으면 좋겠다 저거 넣으면 좋겠다 하면서 추가하는 느낌이랄까...ㅋㅋㅋㅋㅋ
오늘은 그냥 지금까지 했던 작업을 통합하는 과정인데, 그냥 담백하게 적으면 본문이 너무 짧을 것 같아 사족을 좀 많이 붙여서 중간중간 굵은 글씨가 별로 없습니다. 별로 중요하지 않은 내용이란 뜻이죠.ㅋㅋㅋ 참고하시면 되겠습니다.ㅋㅋㅋ
하나의 파일에 통합 코드 작성하기
어떻게 표현을 해야할 지 모르겠네요.. 포스팅은 넘나 어려운 것..
이번 시간엔 지금까지 배운 코드를 하나로 통합하는 걸 해보도록 하겠습니다.
보통 코드를 짤 때는 바로 쓰는(?) 코드를 돌리는 실행 파일과 그 코드에 필요한 함수(?) 같은 것들이 포함된 파일을 따로 만들더라구요. 하지만 그렇게 하기엔 우리의 코드는 매우 단순하기 때문에(!) 하나의 파일에 통합해보도록 하겠습니다.
해당 연재의 2편에서 설명했듯, 우리의 프로젝트는
1. 엑셀 파일을 열어 회사 목록을 가져온다. → 3. 회사명 갖고오기
2. 한글 파일을 열어 앞서 구한 회사명을 입력한다. → 2. 내용 바꾸기
3. 수정된 한글 파일을 pdf로 저장한다. → 4. pdf로 추출하기
4. pdf 파일을 메일로 첨부해서 보낸다. → ..?
의 단계를 가지고 있습니다.
메일로 첨부해서 보내기는 약간 미래의 작업으로...ㅋㅋㅋㅋㅋㅋㅋ
보통 코드는 필요한 작업을 그때그때 본문에 추가하는 것이 아니라, 자주 쓰는, 혹은 쓰게 될 작업은 함수라는 것으로 만들어서 미리 가지고 있습니다. 그러면 같은 작업을 반복해서 작성할 필요 없이 필요할 때 불러서 쓸 수 있게 되는 것이죵.
위의 단계 목록에서 각 단계가 모두 함수로 표현된다고 생각하시면 됩니다.
파이썬에서 함수는
def함수이름(입력값):
어쩌구 저쩌구
return출력값
의 구조로 되어 있습니다. 입력값이나 출력값을 따로 안 쓰겠다 싶으면 빈 칸으로 둬도 괜찮습니다.
함수에 대해 자세히 공부하는 건 또 다음으로 미뤄두고(!) 바로 실전 응용으로 들어가 보겠습니다.
def getCompanyNames(xl_path,xl_file):
from openpyxl import load_workbook
file_path = xl_path + xl_file
wb = load_workbook(file_path)
ws = wb.active
total_rows = sum(1 for row in ws["B"] if row.value is not None)
company_list = []
for i in range(total_rows-1):
company_list += [ws['B%s'%(i+2)].value]
return company_list
앞에서 했던 코드가 그대로 들어있는 뭔가가 생겼죠?
입력 값에는 왠지~~ 따로 설정하고 싶은 위치와 파일에 대한 변수를 넣는 걸로 했습니다.
위의 코드의 경우, getCompanyNames라는 함수를 불러올 때 path,file라는 변수를 두 개 입력하면 내부의 작업을 통해 저장된 company_list 값을 가지는 변수로 바뀐다고 생각하시면 됩니다.
근데 지금은 path와 file라는 입력 변수가 본문에서 안 쓰였죠? 하지만 우리가 getCompanyNames라는 함수는 저 둘의 변수를 써야한다고 약속했기 때문에 무슨 값이라도 넣어주긴 해야 합니다. 만약 이렇게 본문에서 쓰이지 않는다면 굳이 입력 값에 넣을 필요가 없이 빈 칸으로 두고 나중에 불러올 때 빈 괄호로 놔두시면 됩니다.
xl_path와 xl_file은 각각 엑셀 파일이 들어있는 위치와 엑셀 파일 이름으로 하고 싶네요. 사실 abc,def 이렇게 해도 되긴 합니다만...변수가 많아지면 어렵겠죠?
사실 코딩은 변수 이름 정하는 게 제일 어렵습니다.(저한테는)ㅋㅋㅋ
이제 1단계가 끝났습니다.
마찬가지 방식으로 2단계에 대해서는 왠~~~지 3단계랑 합치고 싶지 않나요?ㅋㅋㅋㅋ 뭔가 내용 바꾸고 추출하기가 한 단계로 이뤄져야 할 것 같은 느낌이랄까?ㅋㅋ
겨우겨우 Action ID 가 Print이고, Item ID에서 PIT_UT1 Type의 3번이 pdf 파일로 저장인 것을 알았습니다. 찾았죠? 이제 다 끝났다고 보시면 됩니다. 오평파(이썬).
그림 5. 그렇구나.
뭔가 사용할 수 있을 줄 알았는데, 너무 어려웡.. 그렇다면!!
그림 6. 주워들은 거 조합해서 물어보기
답은 있어보이게 물어보기 신공입니다. 휴 역시 해결.
이렇게 챗지피티를 쓸 때도 필요한 요소를 대략적으로라도 찾아서 물어보면 해결의 실마리가 보인답니다...였습니다만...
그림 7. 이 새..친구는 머야...
하..............................에러 문구는 안 뜨고 저장도 잘 되는데 pdf 출력이 안 됩니다..........스트레스 받네요...
그림 8. 아니 드라이버 설치라니 한글 pdf 추출에 드라이버를 설치한 적이 없는데..
우리 친구가 의미 없는 대답만 반복하는 와중에, 답을 겨우 찾았습니다.
그림 9. 기본 설정은 분명 Microsoft PDF로 돼 있는데...
ㅋㅋㅋㅋㅋㅋㅋㅋ하.. 기본 설정은 분명 돼 있는데,,
그림 10. PDF 저장이 기본 옵션이네?!
제가 코드를 볼 때 분명 'Microsoft PDF 어쩌구'가 들어간 코드가 있고, SetItem("Device","3")으로 돼 있는 코드가 있었거든요. 그래서 왠~~~~~~~~지 그림 4에 있던 3번이 기본 설정에 있는 "PDF 저장" 얘인가 싶어서 눌렀더니
그림 11. 떄앰~
얘가 인쇄 버튼이 비활성화 되길래 더블클릭했더니 이렇게 뜨더라구요.. 이런 건 처음 봤다....
그림 12. 신기한 설정창..그림 13. 해결따리~~
그렇게 따라했더니 해결!!! 휴.. 매우 기분이 좋아졌습니다.
그렇게 최종 코드는
import win32com.client as win32
hwp = win32.Dispatch("HWPFrame.HwpObject")
hwp.XHwpWindows.Item(0).Visible = True # 한글 창 보이기
file_path = "D:/한글 양식 자동화/양식.hwp"
pdf_path = "D:/한글 양식 자동화/tmp/양식.pdf"
hwp.Open(file_path, "","")
# 🔹 ③ 한글의 메시지 박스 모드 변경 (저장 시 창 뜨지 않도록)
hwp.SetMessageBoxMode(0x1000)
# 🔹 ② 인쇄 설정 값 가져오기
action = hwp.CreateAction("Print")
option = action.CreateSet()
# 🔹 ② PDF 저장을 위한 인쇄 설정
# hwp.HAction.Run("Print") # 인쇄 창 직접 실행
action.GetDefault(option) # 기본 인쇄 설정 불러오기
# 🔹 ③ 'Device'를 PDF로 변경 (3번 설정)
option.SetItem("Device", 3)
# 🔹 ④ PDF 파일명 설정
option.SetItem("FileName", pdf_path)
# 🔹 ⑤ 설정 적용 후 인쇄 실행
action.Execute(option)
# 한글 종료
hwp.Quit()
이렇게 나왔답니다^^
중간에 뜬금 없는 '메시지 박스 모드'가 생겼는데, 얘는 있든 없든 차이가 없던데, 뭔가 우리의 똑똑이 친구가 가르쳐 주기로는 같은 이름 파일이 있어도 자동으로 덮어쓰게 해주는 설정이라네요. 제 기준에서는 없어도 잘 덮어지긴 하던데 혹시나 싶어 놔뒀답니다.
드디어 끝이 보이는 것 같네요. 이제 지금까지 했던 코드를 조합해서 하나의 코드로 합체시키고, 하나의 프로그램으로 만든다면 좀 더 까리한(!) 프로젝트가 될 것 같습니다.ㅋㅋㅋ
처음부터 차근차근 읽지 말고 쭉 훑어보고 필요한 부분만 찬찬히(?) 읽어 보시길 추천드립니다.
2편에 이어 3편은 엑셀로 갖고 있는 회사명을 코드로 갖고 오는 과정입니다.
회사 목록 엑셀 파일을 파이썬 코드로 갖고 오기
지난 번 말씀 드렸듯, 코딩은 언제나 알고리즘이 우선입니다. 항상 '어떻게 해야 멍청한 컴퓨터가 내가 하는 작업을 잘 알아들을까?'라는 고민을 먼저 한 후에 코딩을 시작해야 효율이 좋아지기 마련이죠.
이번 프로젝트는 한글 문서 양식에서 회사명을 바꿔가며 서로 다른 이름의 pdf 파일로 저장하고, 메일로 보내는 작업까지 진행할 예정입니다.
일단 시작하기에 앞서 아주아주 기초적인 사용법은 익혔기 때문에, 약간의 마음의 여유를 갖고 알고리즘을 고민할 시기가 왔습니다.
우리는 한글 문서 양식과 엑셀 회사명 목록을 갖고 있고, 알고리즘을 생각하면
1. 엑셀 파일을 열어 회사 목록을 가져온다.
2. 한글 파일을 열어 앞서 구한 회사명을 입력한다.
3. 수정된 한글 파일을 pdf로 저장한다.
4. pdf 파일을 메일로 첨부해서 보낸다.
이런 순서가 될 것 같죠?
우리는 1편과 2편에서 위 목록의 2번 한글 파일을 다루는 방법을 익혔습니다. 왜냐하면 1번은 쉽기 때문이죠.(진지)
ㅋㅋㅋ1번이 쉬운 이유는 엑셀은 무려 세계적인 프로그램(!)이기 떄문이죠... 아래한글은 우리나라만 쓰는데.... 그러면서 범용성도 꾸진......쿨럭
아무튼 다시 본론으로 돌아가서, 회사명이 들어 있는 엑셀 파일을 먼저 살펴봅시다.
그림 1. 회사명.xlsx
엑셀은 다들 아시다시피 파일 안에 여러 시트가 들어있는 구조로 되어 있습니다. 파이썬은 화면을 바로 클릭할 수 없기 때문에 시트 정보를 이용해 내용을 처리해야 한답니다.
그 다음은 언제나처럼 우리의 구세주(제발)가 되어주던 챗지피티!
그림 2. 엑셀 지피티그림 3. 엑셀 지피티 2
openpyxl 또는 pandas를 쓰라네요. 우리는 둘 다 모르니 뭔가 있어 보이는 openpyxl을 쓰도록 하겠습니다.
from openpyxl import load_workbook
# 🔹 ① 엑셀 파일 열기
file_path = r"D:/한글 양식 자동화/회사이름.xlsx"
wb = load_workbook(file_path) # 엑셀 파일 로드
ws = wb.active # 기본 활성화된 시트 선택
# 🔹 ② 특정 셀 값 읽기 (A1 셀)
print(ws["A1"].value) # 첫 번째 셀 값 출력
# # 🔹 ③ 특정 셀 값 변경 후 저장
# ws["A1"] = "새로운 값"
# wb.save(r"C:\경로\새로운_파일.xlsx")
응용을 위해 한 줄 단위로 사용해 보겠습니다. 일단 값을 읽고, 다른 값으로 바꿔서 저장하는 코드를 준 것 같은데, 우리는 여러 개 값을 읽어오는 게 목표기 때문에 일단 저장하는 부분은 주석 처리 후 하나만 읽어 보겠습니다.
그림 4. 짜잔
ws["A1"].value를 하면 A1 값이 보이네요. 먼저 데이터가 총 몇 행인지 알아봅시다.
그림 6. 얘로 간다
사실 총 세 가지를 주던데, '회사명' 열에 있는 데이터만 확인하고 가져오면 되겠죠? 고로 두 번째 방식으로 가겠습니다.
from openpyxl import load_workbook
# 🔹 ① 엑셀 파일 열기
file_path = r"D:/한글 양식 자동화/회사이름.xlsx"
wb = load_workbook(file_path) # 엑셀 파일 로드
ws = wb.active # 기본 활성화된 시트 선택
# 🔹 ② 특정 셀 값 읽기 (A1 셀)
print(ws["A1"].value) # 첫 번째 셀 값 출력
# len(ws)
total_rows = sum(1 for row in ws["B"] if row.value is not None)
print(f"A열의 데이터 개수: {total_rows}줄")
# # 🔹 ③ 특정 셀 값 변경 후 저장
# ws["A1"] = "새로운 값"
# wb.save(r"C:\경로\새로운_파일.xlsx")
저장을 하는 건 결국 마지막 단계일 테니 중간에 끼워 넣겠습니다.
그림 7. 오 된다
우린 데이터가 총 4개 여야 하는데, 5줄이라 나오죠? 첫 줄도 포함하는가 보네...하면서 야매로 -1을 해줍시다.
from openpyxl import load_workbook
# 🔹 ① 엑셀 파일 열기
file_path = r"D:/한글 양식 자동화/회사이름.xlsx"
wb = load_workbook(file_path) # 엑셀 파일 로드
ws = wb.active # 기본 활성화된 시트 선택
# 🔹 ② 특정 셀 값 읽기 (A1 셀)
print(ws["A1"].value) # 첫 번째 셀 값 출력
# len(ws)
total_rows = sum(1 for row in ws["B"] if row.value is not None)
print(f"A열의 데이터 개수: {total_rows-1}줄")
# # 🔹 ③ 특정 셀 값 변경 후 저장
# ws["A1"] = "새로운 값"
# wb.save(r"C:\경로\새로운_파일.xlsx")
뭔가 바꼇습니다. 대충 total_rows에 1을 빼면 되니 그 자리에 일단 넣어봤더니 되는군요. 굳
그림 8. 야매로 갯수 맞추기
다음으로 넘어가기 전에 python의 기초적인 반복문을 알아보겠습니다. 파이썬에서 반복문은 for라는 놈을 쓴다고 생각하시면 됩니다. for 구문은
for (변수 이름) in (변수 범위): (반복할 내용)
의 구조로 이뤄져 있습니다.
변수 이름은 보통 몇 번째인지를 index라고 해서 i 라 하고, 밑의 양식을 따른답니다.
company_list = []
for i in range(total_rows-1):
company_list += [ws['B%s'%(i+2)].value]
# company_list += [ws['B'+str(i+2)].value]
요기서 뜬금 없이 등장한 %는 파이썬에서 글자에 임의의 변수를 넣는 방식이랍니다. 바로 밑의 주석 처리 된 부분과 같은 의미에요. 요기는 파이썬 문법이기 때문에 이번 글에서는 넘어가도록 하겠습니다..만 간단하게 설명하자면,
1. 빈 리스트 만들기 2. 총 데이터 행 수만큼 반복하기 3. 앞서 만든 리스트에 B(숫자) 위치의 데이터 추가하기
순서로 이뤄진 코드랍니다. 자세한 사용법(?)이 궁금하신 분들은 파이썬 기본 문법을 익히시면 된답니다. 저는 위키독스를 스승님으로 모시고 있...ㅋㅋㅋ
from openpyxl import load_workbook
# 🔹 ① 엑셀 파일 열기
file_path = r"D:/한글 양식 자동화/회사이름.xlsx"
wb = load_workbook(file_path) # 엑셀 파일 로드
ws = wb.active # 기본 활성화된 시트 선택
# 🔹 ② 특정 셀 값 읽기 (A1 셀)
# print(ws["A1"].value) # 첫 번째 셀 값 출력
# len(ws)
total_rows = sum(1 for row in ws["B"] if row.value is not None)
# print(f"A열의 데이터 개수: {total_rows-1}줄")
company_list = []
for i in range(total_rows-1):
company_list += [ws['B%s'%(i+2)].value]
# company_list += [ws['B'+str(i+2)].value]
# # 🔹 ③ 특정 셀 값 변경 후 저장
# ws["A1"] = "새로운 값"
# wb.save(r"C:\경로\새로운_파일.xlsx")
다 우리의 똑똑한 친구 챗지피티와 구글신 덕분인데 염치가 없나 봅니다만 이왕 쓰는 거 끝까지 가보도록 하겠습니다..
import win32com.client as win32
# 한컴 한글 실행
hwp = win32.Dispatch("HWPFrame.HwpObject")
hwp.XHwpWindows.Item(0).Visible = True # 한글 창 보이기
# 기존 문서 열기
file_path = r"파:/일/경/로/파일명.hwp"
hwp.Open(file_path,'HWP','forceopen:true')
겨우 이거 하느라 시간이 한참 걸렸네요.
제가 코딩을 잘하는 편은 아니지만, 코딩에서 코드를 짜는 것보다 중요한 건 어떤 작업을 할 지 고민하는 것입니다. 알고리즘을 짠다고 하죠.
우리의 목적은 코딩을 공부한다기보다 프로젝트를 만드는 것이니까요. 우리는 똑똑한 사람들이 짜 놓은 코드를 베끼기만..(?) 하는 것입니다!(당당)
그런 의미에서, 이번 포스팅의 목표는
한글 문서 양식의 본문 수정
입니다.
사실 지난 번 우리의 친구 챗지피티한테 표 내용을 바꾸는 법을 물어봤죠.
그림 1. 챗지피티 재탕1그림 2. 챗지피티 재탕2
지난 1탄에서 우리는 기존 문서 여는 방법은 익혔기 때문에, 추가 수정이 필요했습니다.
그림 3. 수정된 코드.
그런데, 표 수정은 커녕 커서가 이동하지도 않더라구요..
그림 4. 에러 코드.
도와줘 챗지피티!
그림 5. 똑똑한 건지 멍청한 건지..
수정된 코드를 써도 다시 문제가 생겼습니다. 지난 번의 악몽... 질문이 도돌이표가 돼 버리니 따로 해결해 봅시다.
이럴 땐 차근차근 문제가 어디서 생겼는 지 확인할 필요가 있습니다.
import win32com.client as win32
# 한컴 한글 실행
hwp = win32.Dispatch("HWPFrame.HwpObject")
hwp.XHwpWindows.Item(0).Visible = True # 한글 창 보이기
# 기존 문서 열기
file_path = r"D:/한글 양식 자동화/양식.hwp"
hwp.Open(file_path,'HWP','forceopen:true')
일단 요까지는 잘 굴러가는 걸 확인했습니다.
그렇다면 표 안의 내용을 변경하기 앞서, 원하는 지시(커서를 이동하고 표를 선택하는 것)가 제대로 이행되는지 살펴봐야 합니다.
# 커서를 문서의 처음으로 이동 (표 찾기 전에 안전한 위치로 이동)
hwp.HAction.Run("MoveTopLevelBegin")
# 첫 번째 표 선택
hwp.HAction.Run("TableCellBlock") # 표 내부 선택
hwp.HAction.Run("TableSelect") # 표 전체 선택
# 커서를 첫 번째 셀로 이동
hwp.HAction.Run("TableCellBlock") # 다시 표 내부 선택
hwp.HAction.Run("TableLeftCell") # 왼쪽 상단 셀 선택
# 텍스트 입력
# hwp.InsertText(r"hihihi")
# # 새로운 파일 이름으로 저장
# new_file_path = r"C:\경로\새로운_파일명.hwp"
# hwp.SaveAs(new_file_path, "HWP")
# # 한컴 한글 종료
# hwp.Quit()
파이썬에서 #이 붙는 건 해당 줄을 없는 셈 치겠다(주석 처리)는 의미입니다.
시킨 대로 커서가 잘 이동하는지 보기 위해 뒷 부분을 전부 날렸습니다.
그림 6. 아니 커서가 왜 그대로야.
이럴 수가.. 커서가 이동하지 않습니다. 이렇다면 우리는 이렇게 어려운 방식을 버려야 합니다. 왜냐하면 우리는 HAction이라는 명령어를 어떻게 쓸 수 있는지, 문서를 어디서 찾아야 하는지 같은 고급 기술을 익히기엔 시간이 부족합니다. 빨리 놀러 나가야 합니다.
그림 7. 구관이 명관.
구글로 돌아왔습니다. 더 개꿀 빠는 방법을 가르쳐 달란 말이야!
라는 열망에 찾았습니다.
호다닥 프로젝트를 달성하고 싶은 분들이 아닌 진득하게 자세한 사용법을 익히고 싶은 분은 요기로 가시면 됩니다.
뭔가를 시작하는 일은 막연히 '~~~를 해야지!'라는 마음으로 첫 발이 떼지지 않네요. 구체적으로 어떤 걸 만들고 싶다는 주제가 있어야 시작할 수 있는 느낌이랄까..?
그런 의미에서! 노가다..! 업무를 도와주는!!! 한글 파일 양식에 맞춰 파일명,내용을 바꿔가며 pdf로 저장하는 자동화 프로젝트를 첫 주제로 잡아보았습니다.
제가 프로젝트를 진행하며 겪은 이런저런 시행착오를 같이 포함한 글이기 때문에, 차근차근 따라하기보다 후루룩 읽어보고 필요한 부분만 따라하시는 게 효율이 좋을 듯 합니다!
읽기 편하게 꾸며보고 싶은데, 능력이 부족한 관계로...
1. 최대한 사진만 보고 따라할 수 있도록 구성했습니다.
2. 다른 프로젝트 진행할 때도 응용할 수 있도록 (문제+해결방안)으로 구성했습니다.
3. 본문의 코드는 마지막에 첨부하도록 하겠습니다.
파이썬 라이브러리 설치 (feat. 똑똑한 우리의 친구 챗지피티)
오.. 따옴표가 들어가니 좀 있어 보이네요.
파이썬에는 여러 기능을 활용할 수 있는 라이브러리라는 것이 존재합니다. 파이썬이라는 언어를 통해 특정 내용을 담고 있는 책..이랄까요??ㅋㅋㅋ
아무튼, 첫 번째 목표는 과연 파이썬에 한글 문서를 자동화해주는 라이브러리가 있는지 확인하는 것입니다!
사실 이제는 챗지피티라는 똑똑한 친구가 있어서 매우 편하게 진행할 수 있다죠..ㅋㅋㅋㅋ
그림1. 똑똑한 친구.
참 쉽죠?
원하는 작업을 요청하면 어떻게 하는 지도 가르쳐 준답니다!
그림2. 똑똑한 친구2.그림3. 똑똑한 친구3.
하지만 세상 일이 이렇게 쉽게 풀리기만 한다면 따분한 인생을 보낼 수밖에 없겠죠?
그림4. 똑똑한 친구 취소.
이 자식이 뭔지도 모르는 에러를 던지네요..
그림5. 기회를 한번 더 주겠다.
오..에러를 뭔가 있어 보이는 얘기를 하며 해결 해준다고 하네요.
그림6. 오..차이가 없어 보이지만 뭔가 바꼈다.
눈으로는 차이를 모르겠지만 일단 진행해 보도록....
그림 7. 복붙 아님 또 에러임.
했지만 통수네요.
하지만 괜찮습니다. 파이썬은 잘 몰라도 한글을 아니까요.
우선 코드로 돌아가야 합니다.
코드에 문제가 생겼기 때문이죠..(?)
그림 8. 반만 똑똑한 친구의 흔적.
에러 문구와 코드를 같이 보면, 그림 7에서 <어쩌구 HWPFrame.HwpObject>, 저쩌구 in Open 라는 문구가 있죠?
그림 8의 코드를 보면 HWPFrame.HwpObect는 hwp에 저장되어 있고, Open은 15줄에 있네요. 이러쿵저러쿵 조합하면 hwp.Open(file_path)에 문제가 생긴 걸 유추할 수 있습니다. 저도 찍었어요(진짜로)
여기서 우리는 훌륭한 교훈을 얻을 수 있습니다.
에러가 나면 에러 문구랑 코드랑 비교해서 어디가 문제가 생겼는지 찾아보기
모든 문제 해결의 근본은 눈치 챙기기 입니다.
휴, 일단 hwp.Open(file_path)에 문제가 있는 건 알았네요. 근데 해결책은 의외로 매우 쉬울 지도 몰라요
그림 9. 얘는 복붙입니다.
일단 매개변수의 개수가 잘못되었다고 했으니, 우리는 지금 hwp.Open에 file_path라는 변수 하나만 들어가 있는 것을 알 수 있습니다.
그런데 앞에 숫자는..뭔지 모르지만 일단 들어있으니까 빼고, 두번째 문구는..마찬가지로 들어있으니까 빼고..None이 두 개 있네요. 뭔가 변수가 두 개 부족한가 봅니다. 값은 따로 안내 문구가 없으니 자리만 채우면 될 거 같은데, 빈 칸으로 한번...
그림 10. 눈치로 야매 해결
그림 11. 일단 해결
와 진짜 해결 됐습니다!! 저 창은 뭔지 모르지만 일단 '접근 허용'을 누르고 나중에 해결하도록 하죠. 갈 길이 멀다...ㅋㅋㅋㅋ
..죄송합니다.ㅋㅋㅋㅋ 근데 가~~끔 이렇게 때려 맞춰질 때도 있기 때문에, 한번 읽어보고 간단한 해결방향은 도전해볼만 합니다.ㅋㅋㅋㅋㅋ
그림 12. 야매 말고 정공법1.
이렇게 우리 친구가 정신을 못 차릴 땐 정공법이 답이겠죠? 일단 원인이었던 hwp.open()을 검색해보겠습니다. 많이 뜨긴 한데, 원하는 내용은 없더라구요.
이건 사실 hwp라는 변수를 우리 친구가 임의로 정했기 때문인데, 이런 경우에는 원래 라이브러리를 검색하시면 편할 수 있습니다.
그림 13. 정공법2.
검색에서 중요한 건, 본문에서 원하는 문구를 와랄라 훑어보고 동일한 코드가 있는지 확인하는 것입니다.
각 사이트에서 본문에 코드만 훑어보고 hwp.Open()이 있는지만 찾아보시면 됩니다.
그림 14. 구글신은 답을 알고 있다.
역시 구글링은 답을 다 알고 있습니다.
file_path는 파일 위치를, 'HWP'는 확장자인데, 어차피 한글 문서를 다루니 고정으로 보시면 됩니다. 마지막 forceopen:true는 저도 잘 모르겠지만..뭔가 강제로 여는 건가 보네요..데헷
import win32com.client as win32
# 한컴 한글 실행
hwp = win32.Dispatch("HWPFrame.HwpObject")
hwp.XHwpWindows.Item(0).Visible = True # 한글 창 보이기
# 기존 문서 열기
file_path = r"파:/일/경/로/파일명.hwp"
hwp.Open(file_path,'HWP','forceopen:true')
파일 경로 앞의 r은 일단 그냥 붙이는 거라 생각하셔도 됩니다.(글자 인식 관련된 거 라는데, 잘 몰라용..)
파일 경로는 파일이 있는 위치를 입력하시면 되는데, 탐색기에서 위치 있는 부분을 클릭하시면 보이는 위치 뒤에 파일명까지 입력하시면 됩니다.
그림 15. 파일 위치는 주소칸을 클릭하시면 두두등장
'원'표시는(W) 슬래시(/)로 바꾸시면 되고, 뒤에 한글문서 파일명을 넣으시면 됩니다.
위의 경우에는 file_path = r'D:/한글 양식 자동화/한글문서.hwp'가 되겠죠.