Categories: 'IT 제품리뷰'

파워포인트 자동 보고서 만들기 feat. 파이썬

파워포인트 자동 보고서 만들기 를 위해서 파이썬을 활용하는 방법은 없을까?

엑셀로 데이터를 가지고 있고 정해신 양식대로 엑셀 값을 붙여넣어 PPT 슬라이드를 여러장 만들어야하는 사례가 많이 있을 것 같은데요, 복사 붙여넣기로 이루어진 단순 작업, 더 효율적으로 해치울수 있는 방법이 없을까 고민하셨던 분들을 위해  python-pptx 패키지 튜토리얼을 작성해보았습니다.

 

매년 발표하는 브랜드 가치 데이터를 활용하여 각 브랜드별 PPT 페이지를 만들어보는 예제를 소개하겠습니다.

 

파워포인트 자동 보고서 만들기 feat. 파이썬

 

파워포인트 자동 보고서 만들기 feat. 파이썬 (확인)


 

파워포인트 자동 보고서 만들기 feat. 파이썬
파워포인트 자동 보고서 만들기 feat. 파이썬

 

 


1. 필요 패키지 설치

  • python-pptx (pip install python-pptx)
  • pandas (pip install pandas)

 

2. 준비물

첨부된 파일을 다운받아 같은 폴더에 저장해주세요.

준비물

* 엑셀로 정리된 데이터값과 로고 이미지는 selenium 패키지를 활용하여 Interbrand 홈페이지에서 자동으로 수집하였고, 차트 이미지는 plotly 패키지를 활용하여 자동 생성하였습니다. 향후 포스트에서 웹크롤링과 차트 그리기 등의 내용도 다루어 볼 예정입니다.

 

3. PPT 템플릿, 엑셀 파일 및 그림파일 읽어오기

#.py 파일을 준비물들과 같은 폴더에 저장했다고 가정하겠습니다.
from pptx import Presentation
prs = Presentation('Best Global Brands 2022_interbrand_template.pptx')
# ppt파일을 읽어와 prs라는 변수명으로 저장합니다.

import pandas as pd
df = pd.read_excel("Best Global Brands 2022_interbrand.xlsx")
# 엑셀파일을 pandas dataframe으로 읽어옵니다.

import os
cwd = os.getcwd()
# cwd(current working directory)를 읽어옵니다.

logos = [file for file in os.listdir(f"{cwd}\logos") if os.path.isfile(f"{cwd}\logos\{file}")]
# logos 폴더 내에 있는 모든 파일명을 logos라는 변수명의 리스트로 저장합니다.

charts = [file for file in os.listdir(f"{cwd}\charts") if os.path.isfile(f"{cwd}\charts\{file}")]
# charts 폴더 내에 있는 모든 파일명을 charts라는 변수명의 리스트로 저장합니다.

check = f"{cwd}\images\check.png"
# images 폴더 내에 있는 체크모양 이미지의 경로값을 check라는 변수명으로 저장합니다.

 

4. 필요 함수 정의하기

① 원하는 text값을 가진 객체(그림, 도형, 텍스트박스, 표 등)를 읽어오는 함수

def select_shape_by_text(slide, text):
    for x in slide.shapes:
    # 선택한 슬라이드 내 모든 shape들 중에서
        if x.has_text_frame and x.text == text:
        # text_frame이 있고 해당 shape의 text값이 입력한 text값과 같으면
            return x
            # 해당 shape을 반환합니다.
    print('요청한 Shape를 찾을 수 없습니다.')

 

brand_name = select_shape_by_text(slide, 'Brand name')
brand_name.text = 'Hyundai'
prs.save('example.pptx')

위 코드를 실행하면 템플릿 내에서 “Brand name”라는 텍스트를 가진 박스가 선택되고 해당 박스의 텍스트값 Hyundai로 바뀌어 example.pptx라는 파일로 저장되는 것을 확인할 수 있습니다. 이때 기존 템플릿과 달리 폰트가 바뀌게 되는데 폰트를 유지하면서 텍스트만 바꾸는 방법은 뒤에서 소개하겠습니다.

코드 실행 결과

 

② 원하는 text값을 가진 표를 읽어오는 함수

def select_table_by_text(slide, text):
    for x in slide.shapes:
        if x.has_table and x.table.cell(0,0).text == text:
            return x.table
    print('요청한 Shape를 찾을 수 없습니다.')

비슷한 방식으로 정의한 slide 내에서 특정 text값을 가진 표를 읽어오는 함수입니다.

좌측 상단 셀의 text값이 입력한 text값과 같을 경우 해당 표를 읽어옵니다.

 

③ 선택한 PPT 파일에서 index번째 슬라이드를 복사하여 슬라이드를 만드는 함수

import copy           
def copy_slide(prs, index):
    template = prs.slides[index]
    # prs 내 slide 중 index번째 슬라이드를 선택하여 template라는 변수명으로 저장합니다.
    try:
        blank_slide_layout = prs.slide_layouts.get_by_name('빈 화면')
    except:
        blank_slide_layout = prs.slide_layouts[0]
    # 새 슬라이드를 만들때 활용할 layout을 선택합니다.
    # '빈 화면'이라는 레이아웃을 선택하되 에러가 날 경우 layout중 첫번째를 선택합니다.
    
    copied_slide = prs.slides.add_slide(blank_slide_layout)
    # '빈 화면' 레이아웃을 적용한 새 슬라이드를 만들어 copied_slide라는 변수명으로
    # 저장합니다.
    
    for shape in template.shapes:
        elem = shape.element
        new_elem = copy.deepcopy(elem)
        copied_slide.shapes._spTree.insert_element_before(new_elem, 'p:extLst')
    # template 슬라이드에서 모든 shapes의 element를 복사하여
    # copied_slide에 붙여넣습니다.
        
    return copied_slide
    
# 참고. https://stackoverflow.com/questions/50866634/how-to-copy-a-slide-with-python-pptx

 

5. 템플릿에 원하는 텍스트 정보 및 이미지 입력하기

① 텍스트박스에 원하는 정보 입력하기

copied_slide = copy_slide(prs, 0)
# 0번째 템플릿 슬라이드를 복사하여 새로운 슬라이드를 만들고
# copied_slide라는 변수명으로 저장합니다.

brand_name = select_shape_by_text(copied_slide, 'Brand name').text_frame
# 'Brand name'이라는 text를 가진 shape을 찾아서 해당 shape의 text_frame을 읽어옵니다.
p = brand_name.paragraphs[0]
# 선택된 text_frame 내 paragraph중에서 첫번째 문단을 선택하여 읽어옵니다.
run = p.runs[0]
# 선택된 문단의 첫번째 run을 선택하여 읽어옵니다.
run.text = 'Hyundai'
# 선택된 run 의 텍스트값을 원하는 값으로 변환합니다.

위 코드에서 paragraph 오브젝트는 말그대로 텍스트 프레임 내 문단을 의미하기 때문에 바로 이해가 되실텐데요, run의 개념은 생소하실 수 있을 것 같습니다. 같은 문단 내에서 폰트 등 서식이 바뀌게되는 경우 같은 서식을 갖고있는 부분까지가 같은 run에 해당합니다. 한 문단이 모두 같은 서식으로 작성되었다면 문단내 run의 갯수는 하나이며 중간에 서식이 바뀌게되면 run도 나뉘게 됩니다.

한 paragraph 내에 3개의 run

 

② 표에 원하는 정보 입력하기

table = select_table_by_text(copied_slide, 'Rank')
# 먼저 좌측 상단 셀의 텍스트가 Rank인 표를 선택합니다.

rank = table.cell(0,1).text_frame
p = rank.paragraphs[0]
run = p.runs[0]
run.text = '35'
# (0,1) 맨윗줄 왼쪽에서 두번째 셀에 원하는 값을 입력합니다.

 

③ 이미지 입력하기

from pptx.util import Cm
# 센티미터값 입력을 위한 모듈을 import합니다.

chart = [x for x in charts if 'Hyundai' in x][0]
# charts 폴더 내에 있는 모든 파일명 중에서 Hyundai라는 이름의 파일명을 읽어옵니다.

chart = f"{cwd}\charts\{chart}"
# 해당 파일의 경로를 정의합니다.

copied_slide.shapes.add_picture(chart, Cm(16.93), Cm(2.77), width=Cm(16), height=Cm(8))
# 복사된 슬라이드에 그림을 추가합니다.
# add_picture(파일경로, 가로위치_왼쪽 위 모서리 기준, 세로위치_왼쪽 위 모서리 기준, 넓이, 높이)
# 위치값을 모르겠다면 PPT파일을 열어서 그림을 원하는 위치에 맞추고 우클릭, 크기 및 위치를 눌러
# 확인할 수 있습니다.

 

④ 읽어온 엑셀파일의 모든 열에 대해서 slide 만들기

for i, r in df.iterrows():
    # pandas dataframe의 iterrows함수를 이용해 모든 row를 iterate 합니다.
    copied_slide = copy_slide(prs, 0)
    
    brand_name = select_shape_by_text(copied_slide, 'Brand name').text_frame
    p = brand_name.paragraphs[0]
    run = p.runs[0]
    run.text =  r['브랜드명']
    
    comment = select_shape_by_text(copied_slide, 'comment').text_frame
    p = comment.paragraphs[0]
    run = p.runs[0]
    run.text =  r['코멘트']
    
    
    table = select_table_by_text(copied_slide, 'Rank')
    
    rank = table.cell(0,1).text_frame
    p = rank.paragraphs[0]
    run = p.runs[0]
    run.text =  str(r['순위'])
    
    value = table.cell(1,1).text_frame
    p = value.paragraphs[0]
    run = p.runs[0]
    run.text = f"{r['브랜드가치']:,} $m"
    # 엑셀에는 브랜드가치가 정수값으로 저장되어있기 때문에 ,로 구분한 포맷으로 바꾸어줍니다.
    
    growth = table.cell(2,1).text_frame
    p = growth.paragraphs[0]
    run = p.runs[0]
    run.text = f"{r['성장률']:.0%}"
    # 엑셀에는 성장률이 소숫점으로 저장되어있기 때문에 % 포맷으로 바꾸어줍니다.

    copied_slide.shapes.add_picture(check, Cm(18.05), Cm(4.05), width=None, height=None)
    # 체크모양 이미지를 입력해줍니다.

    chart = [x for x in charts if r['브랜드명'] in x][0]
    chart = f"{cwd}\charts\{chart}"
    copied_slide.shapes.add_picture(chart, Cm(16.93), Cm(2.77), width=Cm(16), height=Cm(8))
    # 차트 이미지를 입력해줍니다.
    
    
    logo = [x for x in logos if r['브랜드명'] in x][0]
    logo = f"{cwd}\logos\{logo}"
    inserted_logo = copied_slide.shapes.add_picture(logo, Cm(2.38), Cm(4.81))
    # 로고 이미지를 입력하여 inserted_logo라는 변수명으로 저장합니다.
    # 로고 이미지별로 크기가 제각각이기 때문에 이후 크기를 조정하기 위함입니다. 
    
    
    if inserted_logo.height>Cm(2):
        inserted_logo.width = int(inserted_logo.width * (Cm(2)/inserted_logo.height))
        inserted_logo.height = Cm(2)
        # 높이가 2cm보다 크면 2cm로 줄이고 넓이도 비율에 맞게 줄여줍니다.
        
    if inserted_logo.width>Cm(6):
        inserted_logo.height = int(inserted_logo.height * (Cm(6)/inserted_logo.width))
        inserted_logo.width = Cm(6)
        # 넓이가 6cm보다 크면 6cm로 줄이고 높이도 비율에 맞게 줄여줍니다.
        
    if inserted_logo.height < Cm(2) :
        inserted_logo.top = int(Cm(4.81) + (Cm(2)-inserted_logo.height)*0.5)
        # 크기 보정 후 높이가 2cm 보다 작아지면 이미지 위치가 위로 쏠리기 때문에
        # 아래로 조금 내려줍니다.
        
    
prs.save('Best Global Brands 2022_interbrand.pptx')
# 저장해보면 100개의 슬라이드가 생성된것을 확인할 수 있습니다.

최종 결과물

6. 전체 코드 정리

앞에서 소개한 코드를 정리하며 오늘의 포스트를 마치겠습니다. 감사합니다.

from pptx import Presentation
from pptx.util import Cm
import pandas as pd
import os
import copy

prs = Presentation('Best Global Brands 2022_interbrand_template.pptx')
slide = prs.slides[0]

df = pd.read_excel("Best Global Brands 2022_interbrand.xlsx")

cwd = os.getcwd()
logos = [file for file in os.listdir(f"{cwd}\logos") if os.path.isfile(f"{cwd}\logos\{file}")]
charts = [file for file in os.listdir(f"{cwd}\charts") if os.path.isfile(f"{cwd}\charts\{file}")]
check = f"{cwd}\images\check.png"

def select_shape_by_text(slide, text):
    for x in slide.shapes:
        if x.has_text_frame and x.text == text:
            return x
    print('요청한 Shape를 찾을 수 없습니다.')
    
def select_table_by_text(slide, text):
    for x in slide.shapes:
        if x.has_table and x.table.cell(0,0).text == text:
            return x.table
    print('요청한 Shape를 찾을 수 없습니다.')
     
def copy_slide(prs, index):
    template = prs.slides[index]
    try:
        blank_slide_layout = prs.slide_layouts.get_by_name('빈 화면')
    except:
        blank_slide_layout = prs.slide_layouts[0]
    copied_slide = prs.slides.add_slide(blank_slide_layout)
    
    for shape in template.shapes:
        elem = shape.element
        new_elem = copy.deepcopy(elem)
        copied_slide.shapes._spTree.insert_element_before(new_elem, 'p:extLst')
    return copied_slide

for i, r in df.iterrows():
    copied_slide = copy_slide(prs, 0)
    
    brand_name = select_shape_by_text(copied_slide, 'Brand name').text_frame
    p = brand_name.paragraphs[0]
    run = p.runs[0]
    run.text =  r['브랜드명']
    
    comment = select_shape_by_text(copied_slide, 'comment').text_frame
    p = comment.paragraphs[0]
    run = p.runs[0]
    run.text =  r['코멘트']
    
    table = select_table_by_text(copied_slide, 'Rank')
    
    rank = table.cell(0,1).text_frame
    p = rank.paragraphs[0]
    run = p.runs[0]
    run.text =  str(r['순위'])
    
    value = table.cell(1,1).text_frame
    p = value.paragraphs[0]
    run = p.runs[0]
    run.text = f"{r['브랜드가치']:,} $m"
    
    growth = table.cell(2,1).text_frame
    p = growth.paragraphs[0]
    run = p.runs[0]
    run.text = f"{r['성장률']:.0%}"
    
    copied_slide.shapes.add_picture(check, Cm(18.05), Cm(4.05), width=None, height=None)

    chart = [x for x in charts if r['브랜드명'] in x][0]
    chart = f"{cwd}\charts\{chart}"
    copied_slide.shapes.add_picture(chart, Cm(16.93), Cm(2.77), width=Cm(16), height=Cm(8))
    
    logo = [x for x in logos if r['브랜드명'] in x][0]
    logo = f"{cwd}\logos\{logo}"
    inserted_logo = copied_slide.shapes.add_picture(logo, Cm(2.38), Cm(4.81))
    
    if inserted_logo.height>Cm(2):
        inserted_logo.width = int(inserted_logo.width * (Cm(2)/inserted_logo.height))
        inserted_logo.height = Cm(2)
        
    if inserted_logo.width>Cm(6):
        inserted_logo.height = int(inserted_logo.height * (Cm(6)/inserted_logo.width))
        inserted_logo.width = Cm(6)
        
    if inserted_logo.height < Cm(2) :
        inserted_logo.top = int(Cm(4.81) + (Cm(2)-inserted_logo.height)*0.5)

prs.save('Best Global Brands 2022_interbrand.pptx')

 

 

urjent

Share
Published by
urjent

Recent Posts

배재고 스타벅스 가야지 망언 응원, 청룡기 광주일고전에서 발생한 지역비하 발언 쟁점

배재고 야구부 선수들이 2026년 6월 29일 광주일고와의 경기 중 '스타벅스 가야지'라는 응원 구호를 반복해 외친…

3시간 ago

이재용 구미 로봇투자 계획 확인하세요…AI 데이터센터도 함께 유치

이재용 삼성전자 회장은 2026년 6월 29일 이재명 대통령 주재로 진행된 ‘대한민국 대도약 3대 메가프로젝트 국민보고회’에서…

7시간 ago

가수 이수 이혼 진실과 결혼 생활 고백, 미운 우리 새끼 방송 비하인드 완전 정리

가수 린이 자신의 이혼 경험을 담담히 고백하며 대중의 공감을 이끌어냈습니다. 지난해 이혼 소식이 전해진 뒤…

17시간 ago

삼전닉스 50배 레버리지 상품, 바이낸스에서 출시…해외에서만 허용되는 초고위험 투기 상품

바이낸스가 삼성전자와 SK하이닉스 주가를 기반으로 최대 50배 레버리지 투자를 가능하게 한 파생상품을 출시했습니다. 국내에서는 상상조차…

1일 ago

조원희 옌스 비판, 남아공전 교체 투입 후 활발한 움직임 없었다는 분석

조원희 KBS 해설위원은 2026년 월드컵 남아공전 후반 교체 선수 옌스 카스트로프의 활발한 플레이를 확인할 수…

1일 ago

촉법소년 13세 조건부 하향 2026년 개정안 공식 발표

정부가 촉법소년(형사미성년자) 연령 기준을 만 14세에서 만 13세로 조건부 하향하는 방향으로 결론을 내렸습니다. 이는 살인,…

1일 ago