カテゴリー: 業務自動化

Python業務自動化#14PowerPointレポートを自動作成!Excelの数字を変えるだけで発表資料が完成(python-pptx)📊

この記事は韓国語の原文から翻訳したものです。

目次13項目

こんにちは!Mojangcamです 😎

前回は、ExcelのデータをGoogleスプレッドシートへ自動でアップロードする方法(gspread)を紹介しました。データを集めて、保存して、共有するところまでは、もうPythonが全部やってくれます。

でも、仕事はそこで終わりません。結局、誰かがその数字をPowerPointに移さないといけないんです。 毎月同じフォーマットで、数字だけが変わるレポートを。私も以前は、月末のたびに表を貼り付けてグラフを描き直すのに2時間ずつ溶かしていました。

今回はその作業をPythonに代わりに作らせてみましょう。Excelファイルを1つ差し替えるだけで、タイトルスライド → 概要 → グラフ → 詳細表まで、4枚構成のレポートが自動で出来上がります。ライブラリは python-pptx ひとつだけでOKです!

📌 準備するもの

  • Python 3.8以上(python-pptx 1.0.2のRequires-Python条件が >=3.8 です)
  • python-pptx 1.0.2 — PPTXファイルを作成・編集するライブラリ
  • openpyxl 3.1.x — 元データとなるExcelを読み込むときに使います
  • PowerPointはインストールされていなくても大丈夫です! python-pptxはファイル形式を直接扱うので、Officeがなくても.pptxが作れます。(Hancom Office・Googleスライド・LibreOfficeでも開けます)
ターミナル
pip install python-pptx openpyxl

インストールの確認はこうします。

ターミナル
python -c "import pptx; print(pptx.__version__)"
# 1.0.2

💡 パッケージ名は python-pptx ですが、importするときは pptx です。pip install pptx と打つとまったく別のパッケージが入ってしまうので注意してください!

📌 ステップ1 - 元のExcelを用意して読み込む

まずはレポートの材料になるExcelを1つ作ります。sales.xlsx の最初のシートに、これだけ入っていればOKです。

チーム 目標 実績
営業1チーム 5000 5400
営業2チーム 4500 3900
オンラインチーム 3000 3600

これをPythonで読み込みます。

Python
import openpyxl

def load_rows(xlsx_path):
    wb = openpyxl.load_workbook(xlsx_path, data_only=True)
    ws = wb.active
    rows = []
    for name, target, actual in ws.iter_rows(min_row=2, values_only=True):
        if name is None:
            continue
        rows.append({"name": str(name), "target": int(target), "actual": int(actual)})
    return rows

ひとつずつ見ていきましょう 💡

  • data_only=True : セルに数式が入っていても計算結果の値を取得します。これを外すと =B2*1.1 のような数式の文字列がそのまま入ってきます。
  • ws.iter_rows(min_row=2) : 1行目は見出しなので、2行目から読み込みます。
  • values_only=True : セルオブジェクトではなく値だけをタプルで返すので、そのままアンパックして使えます。
  • if name is None: continue : Excelの下のほうにある空行が紛れ込むのを取り除きます。これをしないと int(None) でエラーになります!

📌 ステップ2 - スライドレイアウトを理解してタイトルスライドを作る

python-pptxでいちばん混乱しやすいのがレイアウト番号です。デフォルトのテンプレートには11個のレイアウトが入っていますが、実務で使うのは事実上3つだけです。

  • slide_layouts[0] : タイトルスライド(タイトル + サブタイトル)
  • slide_layouts[1] : タイトルとコンテンツ(箇条書きの本文)
  • slide_layouts[5] : タイトルのみ(グラフや表を直接載せるときにいちばん便利です)
Python
from pptx import Presentation
from pptx.util import Inches

prs = Presentation()
prs.slide_width = Inches(10)      # 4:3 の比率
prs.slide_height = Inches(7.5)

slide = prs.slides.add_slide(prs.slide_layouts[0])
slide.shapes.title.text = "2026年9月 実績報告"
slide.placeholders[1].text = "自動生成 · Mojangcam"

ひとつずつ見ていきましょう 💡

  • Presentation() : 引数なしで呼ぶと、デフォルトのテンプレートから始まります。会社のテンプレートがあるなら Presentation("회사양식.pptx") のように渡せば、そのデザインをそのまま使えます。これは実務で本当に役立つテクニックです!
  • slide_width / slide_height : デフォルトは4:3(10×7.5インチ)です。16:9にしたいなら Inches(13.333) × Inches(7.5) に変えてください。
  • shapes.title : レイアウトに含まれているタイトルの枠(プレースホルダー)を取得します。
  • placeholders[1] : 0番がタイトル、1番がサブタイトル(または本文)です。番号はレイアウトごとに違うので、迷ったら for ph in slide.placeholders: print(ph.placeholder_format.idx, ph.name) で出力してみればわかります。

📌 ステップ3 - 概要・グラフ・表のスライドを作る

概要スライド(箇条書き)

Python
from pptx.dml.color import RGBColor

def add_summary_slide(prs, rows):
    slide = prs.slides.add_slide(prs.slide_layouts[1])
    slide.shapes.title.text = "概要"
    body = slide.placeholders[1].text_frame

    total_t = sum(r["target"] for r in rows)
    total_a = sum(r["actual"] for r in rows)
    body.text = f"全体 目標 {total_t:,}万ウォン / 実績 {total_a:,}万ウォン"

    for r in rows:
        rate = r["actual"] / r["target"] * 100
        p = body.add_paragraph()
        p.text = f'{r["name"]} : 達成率 {rate:.1f}%'
        p.level = 1
        if rate < 100:
            p.runs[0].font.color.rgb = RGBColor(0xC0, 0x00, 0x00)
  • text_frame.text = ... : 最初の段落を埋めるコードです。2行目以降は add_paragraph() を使う必要があります。
  • p.level = 1 : インデントの段階です。0が最上位で、数字が大きくなるほど内側に入ります。
  • p.runs[0].font.color.rgb : 目標未達のチームだけ赤字で強調しました。p.text で値を入れるとラン(run)がちょうど1つできるので、runs[0] で取得すればOKです。

グラフスライド(本当に編集できるPowerPointグラフ!)

Python
from pptx.chart.data import CategoryChartData
from pptx.enum.chart import XL_CHART_TYPE, XL_LEGEND_POSITION

def add_chart_slide(prs, rows):
    slide = prs.slides.add_slide(prs.slide_layouts[5])
    slide.shapes.title.text = "チーム別 目標と実績"

    data = CategoryChartData()
    data.categories = [r["name"] for r in rows]
    data.add_series("目標", [r["target"] for r in rows])
    data.add_series("実績", [r["actual"] for r in rows])

    graphic_frame = slide.shapes.add_chart(
        XL_CHART_TYPE.COLUMN_CLUSTERED,
        Inches(0.8), Inches(1.8), Inches(8.4), Inches(4.5),
        data,
    )
    chart = graphic_frame.chart
    chart.has_legend = True
    chart.legend.position = XL_LEGEND_POSITION.BOTTOM
    chart.legend.include_in_layout = False

ここがpython-pptx最大の強みです。画像ではなく、PowerPointネイティブのグラフが入ります。発表の直前に上司から「この数字ちょっと直して」と言われても、PowerPoint上ですぐに編集できます! 🙌

  • XL_CHART_TYPE.COLUMN_CLUSTERED : 集合縦棒グラフです。折れ線は LINE、円グラフは PIE、横棒は BAR_CLUSTERED です。
  • add_chart(種類, left, top, width, height, data) : 位置とサイズはすべて Inches() か Cm() で指定します。スライドの左上が(0, 0)の基準です。
  • legend.include_in_layout = False : 凡例がグラフ領域に食い込んで狭くなるのを防ぎます。これを入れないと、グラフが変に潰れてしまいます。

表スライド

Python
from pptx.util import Pt

def add_table_slide(prs, rows):
    slide = prs.slides.add_slide(prs.slide_layouts[5])
    slide.shapes.title.text = "詳細表"

    table = slide.shapes.add_table(
        len(rows) + 1, 4, Inches(0.8), Inches(1.8), Inches(8.4), Inches(0.8)
    ).table

    for col, head in enumerate(["チーム", "目標", "実績", "達成率"]):
        table.cell(0, col).text = head

    for i, r in enumerate(rows, start=1):
        rate = r["actual"] / r["target"] * 100
        table.cell(i, 0).text = r["name"]
        table.cell(i, 1).text = f'{r["target"]:,}'
        table.cell(i, 2).text = f'{r["actual"]:,}'
        table.cell(i, 3).text = f"{rate:.1f}%"
  • add_table(行数, 列数, left, top, width, height) : 見出しの1行を足す必要があるので len(rows) + 1 です。
  • 最後に .table を必ず付けてください! add_table() が返すのは図形(GraphicFrame)で、セルを操作するにはその中の .table が必要です。これを付け忘れて AttributeError に出くわす人、本当に多いです 😅
  • height は最小の高さなので、行が増えればPowerPointが自動で広げてくれます。

📌 全体のコード(コピペOK)

Python
# python-pptx 1.0.2 / openpyxl 3.1.x / Python 3.8+
from pathlib import Path

import openpyxl
from pptx import Presentation
from pptx.chart.data import CategoryChartData
from pptx.dml.color import RGBColor
from pptx.enum.chart import XL_CHART_TYPE, XL_LEGEND_POSITION
from pptx.util import Inches, Pt

XLSX_PATH = Path("sales.xlsx")
OUT_PATH = Path("output/2026_09_report.pptx")
POINT_COLOR = RGBColor(0x2E, 0x5B, 0xFF)

def load_rows(xlsx_path):
    wb = openpyxl.load_workbook(xlsx_path, data_only=True)
    ws = wb.active
    rows = []
    for name, target, actual in ws.iter_rows(min_row=2, values_only=True):
        if name is None:
            continue
        rows.append({"name": str(name), "target": int(target), "actual": int(actual)})
    return rows

def add_title_slide(prs, title, subtitle):
    slide = prs.slides.add_slide(prs.slide_layouts[0])
    slide.shapes.title.text = title
    slide.placeholders[1].text = subtitle
    return slide

def add_summary_slide(prs, rows):
    slide = prs.slides.add_slide(prs.slide_layouts[1])
    slide.shapes.title.text = "概要"
    body = slide.placeholders[1].text_frame
    total_t = sum(r["target"] for r in rows)
    total_a = sum(r["actual"] for r in rows)
    body.text = f"全体 目標 {total_t:,}万ウォン / 実績 {total_a:,}万ウォン"
    for r in rows:
        rate = r["actual"] / r["target"] * 100
        p = body.add_paragraph()
        p.text = f'{r["name"]} : 達成率 {rate:.1f}%'
        p.level = 1
        if rate < 100:
            p.runs[0].font.color.rgb = RGBColor(0xC0, 0x00, 0x00)
    return slide

def add_chart_slide(prs, rows):
    slide = prs.slides.add_slide(prs.slide_layouts[5])
    slide.shapes.title.text = "チーム別 目標と実績"

    data = CategoryChartData()
    data.categories = [r["name"] for r in rows]
    data.add_series("目標", [r["target"] for r in rows])
    data.add_series("実績", [r["actual"] for r in rows])

    graphic_frame = slide.shapes.add_chart(
        XL_CHART_TYPE.COLUMN_CLUSTERED,
        Inches(0.8), Inches(1.8), Inches(8.4), Inches(4.5),
        data,
    )
    chart = graphic_frame.chart
    chart.has_legend = True
    chart.legend.position = XL_LEGEND_POSITION.BOTTOM
    chart.legend.include_in_layout = False
    chart.plots[0].series[1].format.fill.solid()
    chart.plots[0].series[1].format.fill.fore_color.rgb = POINT_COLOR
    return slide

def add_table_slide(prs, rows):
    slide = prs.slides.add_slide(prs.slide_layouts[5])
    slide.shapes.title.text = "詳細表"

    table = slide.shapes.add_table(
        len(rows) + 1, 4, Inches(0.8), Inches(1.8), Inches(8.4), Inches(0.8)
    ).table
    for col, head in enumerate(["チーム", "目標", "実績", "達成率"]):
        table.cell(0, col).text = head
    for i, r in enumerate(rows, start=1):
        rate = r["actual"] / r["target"] * 100
        table.cell(i, 0).text = r["name"]
        table.cell(i, 1).text = f'{r["target"]:,}'
        table.cell(i, 2).text = f'{r["actual"]:,}'
        table.cell(i, 3).text = f"{rate:.1f}%"
    for row in table.rows:
        for cell in row.cells:
            for p in cell.text_frame.paragraphs:
                for run in p.runs:
                    run.font.size = Pt(14)
    return slide

def main():
    rows = load_rows(XLSX_PATH)
    prs = Presentation()
    prs.slide_width = Inches(10)      # 16:9 は Inches(13.333)
    prs.slide_height = Inches(7.5)

    add_title_slide(prs, "2026年9月 実績報告", "自動生成 · Mojangcam")
    add_summary_slide(prs, rows)
    add_chart_slide(prs, rows)
    add_table_slide(prs, rows)

    OUT_PATH.parent.mkdir(parents=True, exist_ok=True)
    prs.save(OUT_PATH)
    print(f"완료: {OUT_PATH} (슬라이드 {len(prs.slides)}장)")

if __name__ == "__main__":
    main()

📌 実行結果

ターミナル
python make_report.py
ターミナル
완료: output/2026_09_report.pptx (슬라이드 4장)

output フォルダにできたファイルを開くと、タイトル → 概要 → グラフ → 表の順で4枚が作られています。グラフをクリックして「データの編集」(데이터 편집)を押すと、Excelのウィンドウが開きます。本物のPowerPointグラフである証拠ですね 😎

さて、来月はどうするか? sales.xlsx の数字だけ変えてもう一度実行すれば終わりです。2時間かかっていた作業が2秒になります。

📌 おまけ - 会社のテンプレートをそのまま使う 💡

デザインまで揃えたいなら、会社のテンプレートをそのまま開いて使えばOKです。

Python
prs = Presentation("회사양식.pptx")

# このテンプレートにどんなレイアウトがあるか、まず確認!
for i, layout in enumerate(prs.slide_layouts):
    print(i, layout.name)

テンプレートごとにレイアウトの順番が違うので、番号を出力してみて、使いたいレイアウトのインデックスを選ぶのが安全です。既存のスライドを残したまま後ろに追加していくこともできますし、空のテンプレートファイル(マスターだけのpptx)を用意しておいて使う方法もおすすめです。

📌 よくあるエラー

1. ModuleNotFoundError: No module named 'pptx'

  • 原因:pip install pptx で間違ってインストールしたか、別の仮想環境にインストールされている
  • 解決:pip install python-pptx で入れ直してください。インストール先が怪しいときは、python -m pip install python-pptx のように実行するPythonと同じインタープリターでインストールすれば確実です。

2. AttributeError: 'GraphicFrame' object has no attribute 'cell'

  • 原因:add_table() の後ろに .table を付けていない
  • 解決:shapes.add_table(...).table まで付けてください。

3. KeyError: 'no placeholder on this slide with idx == 1'

  • 原因:そのレイアウトには1番のプレースホルダーがありません。特に slide_layouts[5](タイトルのみ)には本文の枠がありません
  • 解決:for ph in slide.placeholders: print(ph.placeholder_format.idx, ph.name) で実際の番号を確認するか、本文が必要なら slide_layouts[1] を使ってください。

4. PermissionError: [Errno 13] Permission denied: '...pptx'

  • 原因:作成しようとしているPPTファイルをPowerPointで開いたまま実行した
  • 解決:ファイルを閉じてから再実行してください。実務では、最初から 2026_09_report_20260920.pptx のようにファイル名に日付を付けて上書きを避けるのがおすすめです。

5. 韓国語の文字が四角(□□□)で表示される

  • 原因:コードの問題ではなく、発表用のPCにそのフォントが入っていないケースです
  • 解決:run.font.name = "맑은 고딕"(Malgun Gothic)のように、どのPCにも入っている標準フォントを指定してください。珍しいフォントはファイルと一緒には持ち運ばれません。

📌 まとめ

今回はpython-pptxでExcelデータ → 4枚構成のPowerPointレポートを自動生成してみました。まとめるとこうなります 💬

  • Presentation() で始めて、slide_layouts[0/1/5] の3つさえ知っていればほとんど解決
  • グラフは CategoryChartData + add_chart() → 画像ではない本物のPowerPointグラフ
  • 表は add_table(...).table で、最後の .table を忘れずに
  • 会社のテンプレートは Presentation("양식.pptx") でそのまま再利用

ここにこれまでの回を組み合わせれば完成形になります。クローリングやスプレッドシートでデータを集めて → 今回学んだことでPowerPointを作って → メールの自動送信で上司に送るところまで、人の手がまったく触れないパイプラインになります!

次回は作ったレポートをPDFに変換して配布する方法を扱い、その次はこれまで学んだことを全部組み合わせた🏆 最終プロジェクト - 毎朝勝手に届く業務レポートシステムでシリーズを締めくくります。お楽しみに 🙌

気になる点があればコメントで教えてください 💬 次回またお会いしましょう!

📚 Python業務自動化シリーズ

  • #10 - Word文書の自動作成(python-docx) → 見に行く
  • #11 - パーソナライズした大量メールの自動送信(smtplib) → 見に行く
  • #12 - Googleスプレッドシート連携(gspread) → 見に行く
  • #13 - ログ・リトライ・失敗通知の仕組みを作る(logging・tenacity)
  • #14 - PowerPointレポートの自動作成(python-pptx) ← 今読んでいる記事!
  • #15 - PDF変換・配布(予定)

連載 · 全14回中14回目

Python業務自動化

  1. #1엑셀 자동화 입문 (openpyxl)! (韓国語)
  2. #2웹 크롤링 입문 (requests + BeautifulSoup)! (韓国語)
  3. #3파일 정리 자동화 (이름 일괄 변경)! (韓国語)
  4. #4디스코드 웹훅(Webhook)으로 알림 보내기 (requests)! (韓国語)
  5. #5로컬 LLM(LM Studio)으로 고객 문의 자동 분류·요약하기! (무료 AI) (韓国語)
  6. #6예약 실행 자동화! 매일 정해진 시간에 스크립트 돌리기 (schedule·APScheduler) (韓国語)
  7. #7동적 페이지·로그인 자동화! requests로 안 되는 사이트 긁기 (Selenium) (韓国語)
  8. #8PDF 자동 처리! 여러 파일 병합·분할부터 비밀번호까지 (pypdf) 📄 (韓国語)
  9. #9마우스·키보드 GUI 자동화! 클릭·타이핑을 파이썬이 대신하게 하기 (PyAutoGUI) (韓国語)
  10. #10워드(Word) 문서 자동 생성! 엑셀 명단으로 계약서·안내문 100장 뽑기 (python-docx) 📄 (韓国語)
  11. #11개인화 대량 메일 자동 발송! 명단별 첨부파일·발송 로그·재시도까지 (smtplib 실전편) 📬 (韓国語)
  12. #12구글 시트 연동하기! 팀원과 같이 보는 자동 시트 (gspread) 📗 (韓国語)
  13. #13파이썬 자동화 로그·재시도·디스코드 실패 알림 만들기 - 조용히 멈추는 스크립트 잡기 (韓国語)
  14. #14PowerPointレポートを自動作成!Excelの数字を変えるだけで発表資料が完成(python-pptx)📊現在の記事
全14回を表示閉じる

著者

Mojangcam

自分でコードを書き、サーバーを動かし、詰まったエラーを解決しながら学んだことを記録する開発・IT実践ブログ