본문 바로가기
업무자동화/파이썬-아래아한글 자동화 응용

[교육업무자동화4/7] 청구서자동화4(청구서 마무리하기)

by Martinii의 회사원코딩 2021. 1. 22.

지난 포스팅은...

 

[교육업무자동화3/10] 청구서자동화3(파이썬으로 엑셀 열어서 값 얻기)

지난 포스팅은... [교육업무자동화2/10] 청구서자동화2(필드에 텍스트 자동입력) 지난 포스팅은... [교육업무자동화1/10] 청구서자동화1(누름틀 다루기) (전략) 우리 ㅇㅇㅇㅇㅇㅇ협회 교육운영팀은

www.martinii.fun

 


 

직전 포스팅에서는 100개의 text_list를 엑셀에서 불러와서 차례대로 한/글 문서에 넣는 과정을 알아보았다.

(GIF) 지난 포스팅에서는 여기까지 진행해보았다.

이번 포스팅에서는 두 가지를 설명할 예정이다.

1. HWP문서를 PDF로 저장하는 코드

2. 쪽복사, 쪽붙여넣기 기능(한/글2018부터 추가된 것으로 추정)을 이용해 100쪽짜리 청구서모음.hwp를 만드는 코드

그 전에, 튜토리얼 따라하기용으로 청구서 HWP파일과 데이터소스인 엑셀파일을 다시 한 번 첨부한다.

청구서+누름틀.hwp
0.03MB
교육생리스트#마티니한잔.xlsx
0.01MB

 


 

1. hwp문서를 pdf로 저장하는 코드

    1.1 코드 쉽게 알아내기

의외로 스크립트매크로 녹화의 효용성은 정말 크다. API문서를 뒤적거리는 것보다는 녹화가 빠르니까.

그런데, 스크립트매크로 녹화에 얹을 수 있는 더 신박한 기술이 있으니,
바로 단축키 지정이다.

단축키를 지정하고, 스크립트녹화를 해보자. 신세계다.

"PDF로 저장하기"를 위 화면처럼 Ctrl-Alt-Shift-S로 지정한 후,
스크립트 녹화(Shift-Alt-H)로 Ctrl-Alt-Shift-S 를 녹화해보면 아래와 같은 스크립트가 툭 튀어나온다.

"PDF로 저장하기"의 내부 스크립트

위 코드를 파이썬 문법으로 살짝 바꾸면 아래와 같다.

파이썬 코드로 바꾸는 방법을 간단히 설명드리면,
1. 위 스크립트의 모든 상위객체 앞에 "hwp."를 붙이기
2. with (...) 문 안에 있는 생략구문(HParameterSet.HFileOpenSave)을 중괄호 안의 모든 구문에 붙이기
3. 그리고 세미콜론 지우기 정도?ㅎ

hwp.HAction.GetDefault("FileSaveAsPdf", hwp.HParameterSet.HFileOpenSave.HSet)
hwp.HParameterSet.HFileOpenSave.FileName = "C:\\Users\\smj02\\Desktop\\청구서+누름틀.pdf"
hwp.HParameterSet.HFileOpenSave.Format = "PDF"
hwp.HParameterSet.HFileOpenSave.Attributes = 16384
hwp.HAction.Execute("FileSaveAsPdf", hwp.HParameterSet.HFileOpenSave.HSet)

실행해본 모습은 아래와 같다.

PDF로 잘 저장되는 것을 확인했다.

1.2 저장할 pdf파일의 이름 변경

100명의 이름을 저장하는데, 어떻게 파일명을 정하면 좋을까?
기왕이면 text_list에서 몇 개를 인용하면 좋겠는데..

"청구서_이름(소속).pdf"

이런 식이면 좋을 것 같다. 튜토리얼을 따라하는 독자 분들은 어떻게 해보셔도 좋다.
인덱스를 매긴다든지 하는 것도 좋고.

그러면 이전 포스팅에서 코드를 가져와서 완성해보자.

이전 포스팅까지의 코드는 아래와 같다.

import win32com.client as win32

hwp = win32.gencache.EnsureDispatch("HWPFrame.HwpObject")
hwp.Open("C:\\Users\\smj02\\Desktop\\청구서+누름틀.hwp")
# hwp.XHwpWindows.Item(0).Visible = True

field_list = hwp.GetFieldList().split("\x02")

excel = win32.gencache.EnsureDispatch("Excel.Application")
wb = excel.Workbooks.Open(
    r"C:\Users\smj02\Desktop\교육생리스트#마티니한잔.xlsx")
ws = wb.Worksheets(1)
# excel.Visible = True

for 행번호 in range(2, 102):
    text_list = ws.Range(ws.Cells(행번호, 1),
                         ws.Cells(행번호, 6)).Value[0]
    for 인덱스, 값 in enumerate(field_list):
        hwp.PutFieldText(값, text_list[인덱스])
    # Todo: PDF로 저장하는 코드

이 코드에서 #Todo 부분에다가 "PDF로 저장하는 코드"만 보태면 되는데, 함수를 하나 정의해서 간편하게 써보았다.

import win32com.client as win32

def pdf로_저장(이름, 소속):
    hwp.HAction.GetDefault("FileSaveAsPdf", hwp.HParameterSet.HFileOpenSave.HSet)
	hwp.HParameterSet.HFileOpenSave.filename = \
    f"C:\\Users\\smj02\\Desktop\\청구서모음\\청구서_{이름}({소속}).pdf"
	hwp.HParameterSet.HFileOpenSave.Format = "PDF"
	hwp.HParameterSet.HFileOpenSave.Attributes = 16384
	hwp.HAction.Execute("FileSaveAsPdf", hwp.HParameterSet.HFileOpenSave.HSet)


hwp = win32.gencache.EnsureDispatch("HWPFrame.HwpObject")
hwp.Open("C:\\Users\\smj02\\Desktop\\청구서+누름틀.hwp")
# hwp.XHwpWindows.Item(0).Visible = True

field_list = hwp.GetFieldList().split("\x02")

excel = win32.gencache.EnsureDispatch("Excel.Application")
wb = excel.Workbooks.Open(
    r"C:\Users\smj02\Desktop\교육생리스트#마티니한잔.xlsx")
ws = wb.Worksheets(1)
# excel.Visible = True

for 행번호 in range(2, 102):
    text_list = ws.Range(ws.Cells(행번호, 1),
                         ws.Cells(행번호, 6)).Value[0]
    for 인덱스, 값 in enumerate(field_list):
        hwp.PutFieldText(값, text_list[인덱스])
    pdf로_저장(hwp.GetFieldText("이름"), hwp.GetFieldText("소속"))

실행해보면 아래와 같다. (60잔 언저리에서 GIF녹화 잘랐다;)

(GIF) 차례대로 생성되는 100명분의 청구서 PDF파일

아래처럼 100장의 PDF문서가 생성되었다.

가슴이 웅장한 100장의 PDF파일들

중간에 오류가 발생했다면,
오류를 내뱉으며 파이썬 콘솔이 멈췄거나, 
엑셀 행 수는 100인데, PDF파일의 갯수가 100이 아니거나, 
100번째 PDF파일을 열었는데 100번째 사람의 내용이 아닐 것이다.
100번째 문서를 열어보니까,

100번째 PDF파일 내용(이름에 마티니백잔이 올바르게 들어가 있음)

이렇게 빡센 업무 하나를 마쳤다.

 


 

하지만 이 방법엔 경미한 약점이 하나 있는데,

메모리 부족(?), 혹은 특정 조건(저사양은 아니다.) PC에서는 튕기는 경우가 발생한다. 예를 들면 아래처럼,

잘 되는 것 같다가... 음? 갑자기 멈춤?
역시 오류가 난다.. 18번째 파일을 만드는 중에;;;;;;

위 테스트는 특정 PC 윈도우10 홈에디션, 한/글2020(UpToDate), 파이썬 3.9를 사용한 조건인데 10~20번째 파일 생성시점에 자꾸 오류가 발생한다. 속도 문제인가 싶어, 병목이 걸릴 만한 부분에 time.sleep을 충분히 넣어봐도, 파일명에 행번호만 넣었는데도 동일지점에서 오류에 걸린다... 이건 무슨... 버그 같은 건가? 혹시 독자분들 중에 이런 오류가 발생하는 분이 있다면, 아래 소개드릴 메서드를 대신 사용하자.

 


 

2. 다른 방법(100페이지 한/글파일 or 100개의 한/글파일 생성)

그냥 이런 방법도 있다는 걸 보여드리고 싶었다.

위의 방법은 한 장의 hwp를 가지고 내용을 조금만 수정하여 PDF를 찍어내는 방식이었다면,
이제 보여드릴 방법은 100장의 HWP문서, 혹은 100개의 hwp문서를 생성해버리는 것이다.

어차피 자동화라는 도구가 있다면, "번거로움"은 큰 문제가 되지 않는다.

2.1 백페이지 hwp문서 생성

그러면 동일명의 누름틀이 100개씩이 될 거고, n번째의 누름틀에 값을 넣기 위해, 이전 포스팅에서 보여드린

hwp.PutFieldText("필드명{{n}}", "n번째_행의_값")

메서드를 사용하면 될 것이다.

그런데,

언제 이런 방식을 선호하게 될까?

1. PDF출력이 아니라 실물A4용지로 100장을 출력할 때?
2. 데이터나 문서를 파이썬으로만 접근하는 것이 아니라, 남들과 공유하거나, 한/글 프로그램으로 자주 제어해야 할 때.

일회성 자동화작업이 아니라, 직접 접근하고 수정하는 작업이 필요할 때는
이런 식으로 전체 페이지를 덤프해놓는 방식도 필요할 수 있다.

그러면 위의 코드를 한 번 수정해보자.

기존 코드는 아래와 같다.

import win32com.client as win32

def pdf로_저장(이름, 소속):
    hwp.HAction.GetDefault("FileSaveAsPdf", hwp.HParameterSet.HFileOpenSave.HSet)
	hwp.HParameterSet.HFileOpenSave.filename = \
    f"C:\\Users\\smj02\\Desktop\\청구서모음\\청구서_{이름}({소속}).pdf"
	hwp.HParameterSet.HFileOpenSave.Format = "PDF"
	hwp.HParameterSet.HFileOpenSave.Attributes = 16384
	hwp.HAction.Execute("FileSaveAsPdf", hwp.HParameterSet.HFileOpenSave.HSet)


hwp = win32.gencache.EnsureDispatch("HWPFrame.HwpObject")
hwp.Open("C:\\Users\\smj02\\Desktop\\청구서+누름틀.hwp")
field_list = hwp.GetFieldList().split("\x02")

excel = win32.gencache.EnsureDispatch("Excel.Application")
wb = excel.Workbooks.Open(
    r"C:\Users\smj02\Desktop\교육생리스트#마티니한잔.xlsx")
ws = wb.Worksheets(1)

for 행번호 in range(2, 102):
    text_list = ws.Range(ws.Cells(행번호, 1),
                         ws.Cells(행번호, 6)).Value[0]
    for 인덱스, 값 in enumerate(field_list):
        hwp.PutFieldText(값, text_list[인덱스])
    pdf로_저장(hwp.GetFieldText("이름"), hwp.GetFieldText("소속"))


hwp.XHwpWindows.Item(0).Visible = True
excel.Visible = True

여기에다가

1. 한/글 1쪽을 99개 복붙해서 100페이지 문서로 만드는 코드
2. PutFieldText의 필드명을 인덱스번호로 바꾸기

두 가지를 추가하려고 한다. (참고로 PDF로 저장하는 코드는 지웠다.)
바뀐 전체 코드는 아래와 같다.

import win32com.client as win32


hwp = win32.gencache.EnsureDispatch("HWPFrame.HwpObject")
hwp.RegisterModule("FilePathCheckDLL", "SecurityModule")  # 추가
hwp.Open("C:\\Users\\smj02\\Desktop\\청구서+누름틀.hwp")
field_list = hwp.GetFieldList().split("\x02")

hwp.Run("CopyPage")       # 추가
for i in range(99):       # 추가
    hwp.Run("PastePage")  # 추가
    print(i+2)            # 추가

excel = win32.gencache.EnsureDispatch("Excel.Application")
wb = excel.Workbooks.Open(
    r"C:\Users\smj02\Desktop\교육생리스트#마티니한잔.xlsx")
ws = wb.Worksheets(1)

for 행번호 in range(2, 102):
    text_list = ws.Range(ws.Cells(행번호, 1),
                         ws.Cells(행번호, 6)).Value[0]
    for 인덱스, 필드명 in enumerate(field_list):
        hwp.PutFieldText(필드명+f"{{{{{행번호-2}}}}}", text_list[인덱스])  # 수정
        if 필드명 == "이름":
            hwp.MoveToField(필드명+f"{{{{{행번호-2}}}}}")

hwp.XHwpWindows.Item(0).Visible = True
excel.Visible = True

참고1.
우선 특이한 점이 하나 보일텐데 아래 4~5번째 라인을 보면,
"{{{{{행번호-2}}}}}" 하는 코드가 보인다. 이건 파이썬의 f스트링 문법과 한/글의 필드명 문법에서
동일하게 중괄호를 사용하기 때문인데
결론만 말씀드리면, 문자열의 .format 문법을 사용하거나,
굳이 파이썬의 f스트링을 써야겠다면, 중괄호를 다섯 쌍을 쓰면 된다.

참고2.
1페이지를 복사하여 99번 붙여넣는 코드는 액션아이디 중 CopyPage와 PastePage를 사용하였는데,
이는 한/글 2018부터 새로 생긴 메서드라고 한다. (확실치 않음ㅜ)
용지여백이나 속성 모두 복사되므로 유용하게 쓸 수 있다.

참고3.
24~25라인은 현재 진행상태를 실시간으로 확인하기 위해 한/글 창을 진행중인 페이지로 옮기려고 입력한 코드이다.
문서자동화와는 별 상관없는 코드지만, 스크롤바도 움직이지 않고(아래 움짤 참고), 화면이 멈춰있는 상황에서
작업분량이 많은 경우 진행상황을 체크할 수 있는 유용한 방법이기도 하다.

 


시연화면

for i in range(99): hwp.Run("PastePage") 실행결과. 몇 초 걸리지 않는다.
(GIF) 순식간에 100페이지가 완성되었다.

 


 

2.2 백 개의 hwp문서 생성

이 부분은 애초에 개별 PDF를 출력할 때와 아주 비슷한 경우이다.
코드진행 순서도 기존의 PDF출력 코드만 "다른 이름으로 저장"으로 바꾼 걸 제외하면 완전히 동일하므로,
설명할 부분이 많지는 않을 것으로 생각된다.

코드를 보여드리면,

import win32com.client as win32


hwp = win32.gencache.EnsureDispatch("HWPFrame.HwpObject")
hwp.RegisterModule("FilePathCheckDLL", "SecurityModule")  # 추가. 보안모듈 설치 필요.
hwp.Open("C:\\Users\\smj02\\Desktop\\청구서+누름틀.hwp")
hwp.XHwpWindows.Item(0).Visible = True

field_list = hwp.GetFieldList().split("\x02")

excel = win32.gencache.EnsureDispatch("Excel.Application")
wb = excel.Workbooks.Open(
    r"C:\Users\smj02\Desktop\교육생리스트#마티니한잔.xlsx")
ws = wb.Worksheets(1)
# excel.Visible = True

for 행번호 in range(2, 102):
    text_list = ws.Range(ws.Cells(행번호, 1),
                         ws.Cells(행번호, 6)).Value[0]
    for 인덱스, 값 in enumerate(field_list):
        hwp.PutFieldText(값, text_list[인덱스])
    
    이름, 소속 = hwp.GetFieldText("이름"), hwp.GetFieldText("소속")  # 추가
    hwp.SaveAs(rf"C:\Users\smj02\Desktop\청구서모음\청구서_{이름}({소속}).hwp")  # 수정
    
excel.Quit()  # 추가
hwp.Quit()  # 추가

실행결과는 아래와 같다.

(GIF) 100개의 HWP 파일이 생성되었다.

여기까지 누름틀을 활용한 문서 자동화에 더할 수 없이 상세하게 설명을 마쳤다.

본 예제를 "청구서"에 국한하지 말고 독자들의 루틴한 보고업무 등에 적용해본다면,
분명 괄목할 만한 효율성을 발휘할 수 있을 것이라고 확신한다.

다음 포스팅은,
이 프로그램을 동료들과 공유하기 위한 두 가지 과정인,
GUI(그래픽유저인터페이스) 탑재 및 실행파일로 배포하는 방법에 대해 알아보고자 한다.

참고로 5번라인의 FilePathCheckDLL 관련해서는 아래 포스팅을 참고하면 된다.

 

[파이썬-한/글] 보안모듈 설치방법(귀찮은 보안팝업 제거)

안녕하세요? 유튜브에서도 초반에 설명드렸던 주제인데 블로그에도 한 번 옮겨적어봅니다. 아래아한글을 파이썬이나 자바스크립트 등 외부프로그램으로 제어하면서 파일을 열고 기록하거나,

www.martinii.fun

그럼 이번 포스팅은 여기서 마친다.
긴 글 읽어주셔서 감사드린다.

행복한 하루 되시길

 


 

다음 포스팅은...

 

[교육업무자동화5/10] 청구서자동화5(개선할 부분 찾아보기) #중급

지난 포스팅은... [교육업무자동화4/10] 청구서자동화4(청구서 마무리하기) 지난 포스팅은... [교육업무자동화3/10] 청구서자동화3(파이썬으로 엑셀 열어서 값 얻기) 지난 포스팅은... [교육업무자동

www.martinii.fun

 

댓글0