カテゴリー: 業務自動化
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で読み込みます。
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] : タイトルのみ(グラフや表を直接載せるときにいちばん便利です)
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 - 概要・グラフ・表のスライドを作る
概要スライド(箇条書き)
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グラフ!)
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 : 凡例がグラフ領域に食い込んで狭くなるのを防ぎます。これを入れないと、グラフが変に潰れてしまいます。
表スライド
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-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です。
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業務自動化シリーズ
連載 · 全14回中14回目
Python業務自動化
- #1엑셀 자동화 입문 (openpyxl)! (韓国語)
- #2웹 크롤링 입문 (requests + BeautifulSoup)! (韓国語)
- #3파일 정리 자동화 (이름 일괄 변경)! (韓国語)
- #4디스코드 웹훅(Webhook)으로 알림 보내기 (requests)! (韓国語)
- #5로컬 LLM(LM Studio)으로 고객 문의 자동 분류·요약하기! (무료 AI) (韓国語)
- #6예약 실행 자동화! 매일 정해진 시간에 스크립트 돌리기 (schedule·APScheduler) (韓国語)
- #7동적 페이지·로그인 자동화! requests로 안 되는 사이트 긁기 (Selenium) (韓国語)
- #8PDF 자동 처리! 여러 파일 병합·분할부터 비밀번호까지 (pypdf) 📄 (韓国語)
- #9마우스·키보드 GUI 자동화! 클릭·타이핑을 파이썬이 대신하게 하기 (PyAutoGUI) (韓国語)
- #10워드(Word) 문서 자동 생성! 엑셀 명단으로 계약서·안내문 100장 뽑기 (python-docx) 📄 (韓国語)
- #11개인화 대량 메일 자동 발송! 명단별 첨부파일·발송 로그·재시도까지 (smtplib 실전편) 📬 (韓国語)
- #12구글 시트 연동하기! 팀원과 같이 보는 자동 시트 (gspread) 📗 (韓国語)
- #13파이썬 자동화 로그·재시도·디스코드 실패 알림 만들기 - 조용히 멈추는 스크립트 잡기 (韓国語)
- #14PowerPointレポートを自動作成!Excelの数字を変えるだけで発表資料が完成(python-pptx)📊現在の記事
