Webサーバのアクセスログは、いつ・どのページに・どんな結果で来たかが詰まった宝の山です。今回はPythonのPolarsで2万件の合成ログを読み込み、時間帯・URL・HTTPステータス別に集計しました。可視化はmatplotlibで折れ線・横棒・円・ヒートマップの4枚を作り、実際にPNGとして保存できています。
コードを小さく分解するので、初心者でも追体験しやすいはずです。
Polarsの基本概念、要件定義、実装、動作確認までを順番に学べる構成です。動画は目次から確認したい場面へ移動でき、本文だけでも手順と考え方が完結します。
動画の内容をテキストで確認する
オープニング。Polarsとmatplotlibを使ってアクセスログ集計・可視化ツールを作るカリキュラムを始めます。概要紹介。
Polarsとmatplotlibの役割と使い方を学ぶ アクセスログ集計・可視化ツールの要件を整理する 完成コードと実行結果を確認する 最後に実集計と可視化ファイルで完成挙動を確かめる 具体的にやること。
乱数シードを固定して2万件の合成ログを生成する エポック秒を日時型へ変換する 日時から0から23の時間帯を取り出す ステータスから100番台単位のクラスを算出する 時間帯別にアクセス数を集計する 実装環境・必須アプリ。
OS:Windows 11 Pro Python:3.13.3シェル:PowerShell 5.1必須アプリ:コードエディター、ターミナル、エクスプローラー パッケージ:pip、polars、matplotlib、numpy Polarsとmatplotlibとは。
Polars:PolarsはRust言語で実装された高速なデータフレームライブラリmatplotlib:matplotlibはPythonで最も広く使われているグラフ描画ライブラリ Polarsとmatplotlibでアクセスログ可視化の要点。
out_dirフォルダを作成する _generate_logsで2万件のログを得るfrom_epochで日時と時間帯を付与する アクセスログ集計・可視化ツールの要件定義。
総アクセス数として20000が返るhourly_accessなど4枚のPNGが保存される 最多URLとしてルートパスが返る エラー率が0以上1未満で返る 図の枚数として4が返る INTRO: Monaco Editorでアクセスログ集計・可視化ツールを実装。コードを1行ずつ入力し、補完と自動インデントを使いながら実行結果を確認します。
LINE 001: モジュールの概要説明。このツール全体の目的を説明するドキュメント文字列の始まりです。PolarsとMatplotlibを使ってWebアクセスログを分析することが示されています。
LINE 003: 処理内容の補足説明1。合成アクセスログを生成し、時間帯・URL・HTTPステータス別に集計することが説明されています。ドキュメント文字列の続きの部分です。
LINE 004: 処理内容の補足説明2。集計結果を4種類のグラフとして可視化することが説明されています。ドキュメント文字列の締めくくりの内容です。
LINE 005: ドキュメント文字列の終了。モジュールの説明文であるドキュメント文字列を閉じています。ここまでがこのファイル全体の説明部分です。
LINE 007: osモジュールの読み込み。フォルダ作成やパス操作を行うための標準ライブラリosを読み込んでいます。後で出力先フォルダの作成に使われます。
LINE 008: datetimeクラスの読み込み。日時を扱うためのdatetimeクラスをdatetimeモジュールから読み込んでいます。ログのタイムスタンプ生成に利用されます。
LINE 010: matplotlib本体の読み込み。グラフ描画ライブラリのmatplotlibを読み込んでいます。次の行で描画モードを設定するために必要です。
LINE 012: 描画バックエンドの設定。画面表示を行わないAggバックエンドを指定しています。これによりサーバー環境でも画像ファイルとしてグラフを保存できます。
LINE 013: pyplotの読み込み。グラフを描画するためのpyplotモジュールをpltという別名で読み込んでいます。以降のグラフ作成処理で使用されます。
LINE 014: numpyの読み込み。数値計算ライブラリnumpyをnpという別名で読み込んでいます。乱数生成や配列計算に利用されます。
LINE 015: polarsの読み込み。高速なデータフレーム処理ライブラリpolarsをplという別名で読み込んでいます。ログデータの集計処理に使用されます。
RUN 1/4: ライブラリの読み込みを確認する。PolarsやnumpyとmatplotlibのインポートとAggバックエンドの指定が済んでいるかを、簡単な動作で確認します。CHECK 1/4: 途中実行に成功。
polars行数: 3 numpy乱数生成: 85 matplotlib backend: Agg RETURN 01: エディターへ戻りました。入力済みのコードを維持したまま、次の実装へ進みます。LINE 018: 時間帯確率関数の定義開始。
時間帯ごとのアクセス確率を返す関数を定義しています。この関数はログ生成時に使われます。LINE 020: 時間帯重みの配列開始。
0時から23時までの各時間帯に対応する重みの値を配列として定義し始めています。数値が大きいほどアクセスが多い時間帯を表します。LINE 021: 時間帯重みの数値一覧。
昼から夕方にかけてアクセスが増える傾向を表す具体的な重みの数値が並んでいます。深夜は小さく日中は大きい値になっています。LINE 022: 時間帯重みの続きの数値。
前の行に続く時間帯重みの数値です。夕方以降は徐々に値が小さくなっていく様子が表現されています。LINE 023: 配列のデータ型指定。
作成した重み配列の要素をfloat型で扱うよう指定しています。後で割り算をする際に小数として計算できるようにしています。LINE 024: 配列定義の終了。
np.arrayの呼び出しを閉じており、weightという変数に時間帯重み配列が代入されます。LINE 025: 確率への正規化と返却。重みの合計値で各要素を割ることで、合計が1になる確率分布に変換して返しています。
この確率が時間帯の抽選に使われます。RUN 2/4: 時間帯別の確率プロファイルを確認する。_hour_profileが返す配列が24個の要素を持ち、合計が1になる確率分布かどうかを確認します。
CHECK 2/4: 途中実行に成功。要素数: 24 確率合計: 1.0 最大の時間帯: 12 RETURN 02: エディターへ戻りました。入力済みのコードを維持したまま、次の実装へ進みます。
LINE 028: ログ生成関数の定義開始。件数nとシード値seedを引数に取り、合成アクセスログのデータフレームを作成する関数を定義しています。デフォルトで2万件のログを生成します。
LINE 030: 乱数生成器の作成。指定したシード値を使って乱数生成器を作成しています。同じシード値であれば毎回同じ乱数列が得られるため結果を再現できます。
LINE 033: URL候補一覧の定義開始。アクセスログに登場するURLの候補を一覧として定義し始めています。トップページから管理画面まで様々なパスが含まれています。
LINE 034: URL候補一覧の続き。前の行に続くURL候補のリストです。静的ファイルや画像、チェックアウトページなどが並んでいます。
LINE 035: URL出現重みの定義。各URLがどの程度の割合でアクセスされるかを表す重みの配列を定義しています。トップページが最も大きい値になっています。
LINE 036: URL重みの正規化。URL重みの配列を合計値で割ることで、合計が1になる確率分布に変換しています。この確率がURLの抽選に使われます。
LINE 038: HTTPステータス候補の定義。ログに登場するHTTPステータスコードの候補を配列として定義しています。200番台から500番台まで含まれています。
LINE 039: ステータス出現重みの定義。各ステータスコードがどの程度の割合で出現するかを表す重みの配列を定義しています。正常系の200が最も大きい値です。
LINE 040: ステータス重みの正規化。ステータス重みの配列を合計値で割ることで、合計が1になる確率分布に変換しています。この確率がステータスの抽選に使われます。
LINE 043: アクセス時刻の時間帯を抽選。_hour_profile関数が返す確率分布に従って、各アクセスの時間帯を0から23の中からランダムに選んでいます。LINE 044: 時間内の秒数を抽選。
各アクセスが発生した時間帯の中で、0秒から3599秒までの経過秒数をランダムに生成しています。時刻をより細かく表現するためです。LINE 045: 基準日時のエポック秒計算。
2026年8月4日午前0時をエポック秒に変換して基準時刻を作成しています。この値を起点にアクセス時刻を計算します。LINE 046: アクセスタイムスタンプの計算。
基準時刻に時間帯と経過秒数を足し合わせて、各アクセスの実際のタイムスタンプを算出しています。この値がログのtimestamp列になります。LINE 048: アクセスURLのインデックス抽選。
URL出現確率に従って、各アクセスがどのURLにアクセスしたかをインデックスとしてランダムに選んでいます。LINE 049: HTTPステータスの抽選。ステータス出現確率に従って、各アクセスのHTTPステータスコードをランダムに選んでいます。
LINE 050: 転送バイト数の生成。200バイトから60000バイトの範囲で、各アクセスの転送データ量をランダムに生成しています。LINE 052: データフレーム作成の開始。
ここまでに生成した各種データをまとめてpolarsのデータフレームとして構築する処理が始まっています。LINE 053: 辞書データの開始。データフレームの元になる辞書を定義し始めています。
各キーが列名に対応します。LINE 054: タイムスタンプ列の設定。先ほど計算したアクセス時刻の配列をtimestamp列として設定しています。
LINE 055: URL列の設定。抽選したインデックスを使ってURL文字列のリストを作成し、url列として設定しています。LINE 056: ステータス列の設定。
抽選したHTTPステータスコードの配列をstatus列として設定しています。LINE 057: バイト数列の設定。生成した転送バイト数の配列をbytes列として設定しています。
LINE 058: 辞書データの終了。データフレームの元になる辞書定義を閉じています。ここまでの4列が合成ログの構成要素になります。
LINE 059: データフレーム作成の終了。pl.DataFrameの呼び出しを閉じ、作成したデータフレームが関数の戻り値として返されます。RUN 3/4: 合成アクセスログの生成を確認する。
_generate_logsが2万件のログを生成し、想定した4つの列を持つDataFrameを返すかを確認します。CHECK 3/4: 途中実行に成功。
生成件数: 20000 列名: ['timestamp', 'url', 'status', 'bytes'] 先頭2行: shape: (2, 4) ┌────────────┬───────────┬────────┬───────┐ │ timestamp ┆ url ┆ status ┆ bytes │ │ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ str ┆ i64 ┆ i64 │ ╞════════════╪═══════════╪════════╪═══════╡ │ 1785866040 ┆ /products ┆ 302 ┆ 28717 │ │ 1785851066 ┆ /products ┆ 200 ┆ 13703 │ └────────────┴───────────┴────────┴───────┘ RETURN 03: エディターへ戻りました。
入力済みのコードを維持したまま、次の実装へ進みます。LINE 062: 分析関数の定義開始。出力先フォルダのパスを引数に取り、分析結果を辞書として返すanalyze関数を定義しています。
この関数がこのツールの中心的な処理です。LINE 064: 出力先フォルダの作成。指定された出力先フォルダが存在しない場合に新しく作成しています。
既に存在する場合はエラーにならず処理を続けます。LINE 067: 合成ログの生成呼び出し。先ほど定義した_generate_logs関数を呼び出して、分析対象となる合成アクセスログのデータフレームを作成しています。
LINE 070: 日時列追加の開始。データフレームに新しい列を追加するwith_columns処理が始まっています。ここではエポック秒から日時列を作成します。
LINE 071: エポック秒から日時への変換。timestamp列のエポック秒を日時型に変換し、datetimeという新しい列として追加しています。以降の時間帯抽出のもとになります。
LINE 072: 日時列追加処理の終了。with_columnsの呼び出しを閉じ、datetime列が追加されたデータフレームがdf変数に代入されます。LINE 073: 時間帯とステータス分類の追加開始。
さらに新しい列を追加するwith_columns処理が始まっています。ここでは時間帯とステータスクラスの列を作成します。LINE 074: 時間帯列の抽出。
datetime列から時刻の時の部分だけを取り出し、hourという新しい列として追加しています。時間帯別集計に使われます。LINE 075: ステータスクラス列の作成。
HTTPステータスコードを100で割って100倍することで、200番台や400番台といった大分類に丸め、status_class列として追加しています。LINE 076: 列追加処理の終了。with_columnsの呼び出しを閉じ、hour列とstatus_class列が追加されたデータフレームがdf変数に代入されます。
LINE 079: 時間帯別アクセス数の集計。hour列でグループ化し、各グループの件数を数えてcount列を作成し、時刻順に並べ替えています。時間帯別アクセス数の推移グラフに使われます。
LINE 082: URL別集計処理の開始。url列でグループ化する集計処理が始まっています。以降の行でアクセス数を集計し上位を絞り込みます。
LINE 083: URLごとのグループ化。url列を基準にデータをグループ化しています。同じURLへのアクセスをひとまとめにするための準備です。
LINE 084: URL別件数の集計。グループ化したURLごとにアクセス件数を数え、count列として追加しています。LINE 085: アクセス数の降順ソート。
集計したcount列を基準にアクセス数が多い順に並べ替えています。人気のURLを上位に表示するためです。LINE 086: 上位10件への絞り込み。
並べ替えた結果の先頭10件だけを取り出しています。これによりアクセス数トップ10のURL一覧が得られます。LINE 087: URL別集計処理の終了。
ここまでの一連の処理を閉じ、結果がby_url変数に代入されます。LINE 090: ステータス別アクセス数の集計。status列でグループ化し、各ステータスコードごとのアクセス件数を数えてcount列を作成し、ステータス順に並べ替えています。
円グラフの元データになります。LINE 093: ステータスクラス一覧の定義。200番台から500番台までの4種類のステータスクラスをリストとして定義しています。
クロス集計の列に対応します。LINE 094: ヒートマップ用行列の初期化。24時間分の行と4つのステータスクラス分の列を持つ、値がすべて0の行列を作成しています。
この後の集計結果を格納する器になります。LINE 095: 時間帯とステータスの組み合わせ集計。hour列とstatus_class列の組み合わせごとにグループ化し、それぞれの件数を数えてcount列として作成しています。
LINE 096: 集計結果の1行ずつの取り出し。クロス集計の結果を1行ずつ辞書形式で取り出すループを開始しています。この後、行列にアクセス数を書き込む処理へ続きます。
LINE 097: ステータスクラスの取り出し。クロス集計結果の1行から、ステータスクラスの値を取り出して変数scに入れています。次の判定処理でこの値を使います。
LINE 098: 対象クラスかどうかの判定。取り出したステータスクラスがclassesのリストに含まれているかを確認しています。含まれていない場合はこの後の処理をスキップします。
LINE 099: 行列への集計値の格納。対象のステータスクラスであれば、時間帯とクラスの位置を指定してmatrixにアクセス数を書き込んでいます。これによりヒートマップ用の2次元データが完成します。
LINE 102: 折れ線グラフ用の図の作成。サイズを指定して新しい図と座標軸を作成しています。この後の折れ線グラフの描画先になります。
LINE 103: 時間帯別データの折れ線描画。時間帯を横軸、アクセス数を縦軸として折れ線グラフを描画しています。データ点には丸いマーカーを付けています。
LINE 104: マーカーと色の指定。折れ線グラフの見た目として、マーカーの形と線の色を指定しています。グラフを見やすくするための設定です。
LINE 105: グラフタイトルの設定。折れ線グラフの上部に表示するタイトルを設定しています。何を表すグラフかが一目で分かるようにしています。
LINE 106: 横軸ラベルの設定。横軸が時刻(時)であることを示すラベルを設定しています。グラフの見方を分かりやすくします。
LINE 107: 縦軸ラベルの設定。縦軸がアクセス数であることを示すラベルを設定しています。数値の意味を明確にします。
LINE 108: 横軸目盛りの設定。横軸の目盛りを2時間おきに表示するよう設定しています。24時間分の目盛りが詰まりすぎないようにしています。
LINE 109: グリッド線の表示。グラフの背景に薄いグリッド線を表示しています。数値を読み取りやすくするための補助線です。
LINE 110: レイアウトの自動調整。図全体の余白やラベルの配置を自動調整しています。文字が切れたり重なったりしないようにしています。
LINE 111: 画像ファイルの保存。作成した折れ線グラフを指定フォルダにhourly_access.pngという名前で保存しています。解像度はdpi=120で保存されます。
LINE 112: 図のクローズ。保存が終わった図をメモリから解放しています。複数のグラフを作る際にメモリを無駄に使わないための後片付けです。
LINE 115: 横棒グラフ用の図の作成。サイズを指定して新しい図と座標軸を作成しています。この後のURL別ランキングの横棒グラフの描画先になります。
LINE 116: URL別アクセス数の横棒描画。URLを縦軸、アクセス数を横軸として横棒グラフを描画しています。棒の色も指定しています。
LINE 117: 縦軸の順序反転。縦軸のURLの並び順を反転させています。これによりアクセス数が多いURLが上から順に表示されます。
LINE 118: グラフタイトルの設定。横棒グラフの上部に表示するタイトルを設定しています。トップ10のランキングであることが分かるようにしています。
LINE 119: 横軸ラベルの設定。横軸がアクセス数であることを示すラベルを設定しています。棒の長さが何を表すか分かりやすくします。
LINE 120: レイアウトの自動調整。図全体の余白やラベルの配置を自動調整しています。長いURL名が切れないようにする役割もあります。
LINE 121: 画像ファイルの保存。作成した横棒グラフを指定フォルダにurl_ranking.pngという名前で保存しています。解像度はdpi=120で保存されます。
LINE 122: 図のクローズ。保存が終わった図をメモリから解放しています。次のグラフ作成に影響を与えないための後片付けです。
LINE 125: 円グラフ用の図の作成。縦横同じサイズを指定して新しい図と座標軸を作成しています。この後のステータス別割合の円グラフの描画先になります。
LINE 126: ラベル用文字列の作成。ステータスコードの数値を文字列に変換したリストを作成しています。円グラフの各扇形に表示するラベルとして使います。
LINE 127: 円グラフの描画。ステータス別のアクセス数を割合として円グラフに描画しています。各扇形にラベルを付けています。
LINE 128: 割合表示と開始角度の指定。各扇形にパーセント表示を追加し、描画開始角度を90度に設定しています。見た目を整えるための指定です。
LINE 129: グラフタイトルの設定。円グラフの上部に表示するタイトルを設定しています。HTTPステータスの割合を表すグラフであることが分かるようにしています。
LINE 130: レイアウトの自動調整。図全体の余白やラベルの配置を自動調整しています。ラベルが図からはみ出さないようにしています。
LINE 131: 画像ファイルの保存。作成した円グラフを指定フォルダにstatus_ratio.pngという名前で保存しています。解像度はdpi=120で保存されます。
LINE 132: 図のクローズ。保存が終わった図をメモリから解放しています。ヒートマップ作成の前に不要な図を片付けています。
LINE 135: ヒートマップ用の図の作成。サイズを指定して新しい図と座標軸を作成しています。この後の時間帯とステータスクラスのヒートマップの描画先になります。
LINE 136: ヒートマップの描画。先ほど作成したmatrixを色の濃淡で表示するヒートマップを描画しています。色が濃いほどアクセス数が多いことを表します。
LINE 137: グラフタイトルの設定。ヒートマップの上部に表示するタイトルを設定しています。時間帯とステータスクラスの関係を表すグラフであることが分かるようにしています。
LINE 138: 横軸ラベルの設定。横軸がステータスクラスであることを示すラベルを設定しています。列の意味を分かりやすくします。
LINE 139: 縦軸ラベルの設定。縦軸が時刻(時)であることを示すラベルを設定しています。行の意味を分かりやすくします。
LINE 140: 横軸目盛り位置の設定。横軸の目盛りをステータスクラスの数だけ設定しています。この後にラベル文字を割り当てる準備になります。
LINE 141: 横軸目盛りラベルの設定。横軸の各目盛りに200番台のような分かりやすい文字ラベルを設定しています。数値だけより意味が伝わりやすくなります。
LINE 142: 縦軸目盛りの設定。縦軸の目盛りを2時間おきに表示するよう設定しています。24行分の目盛りが詰まりすぎないようにしています。
LINE 143: カラーバーの追加。色の濃淡がどのアクセス数に対応するかを示すカラーバーを図に追加しています。ラベルとしてアクセス数と表示しています。
LINE 144: レイアウトの自動調整。図全体の余白やラベルの配置を自動調整しています。カラーバーやラベルが重ならないようにしています。
LINE 145: 画像ファイルの保存。作成したヒートマップを指定フォルダにhour_status_heatmap.pngという名前で保存しています。解像度はdpi=120で保存されます。
LINE 146: 図のクローズ。保存が終わった図をメモリから解放しています。全てのグラフ作成が完了したことを示す後片付けです。
LINE 149: ピーク時間帯の取得。アクセス数が最も多い時間帯の行を取り出して変数peakに格納しています。カウント数で降順に並べ替えた先頭行を取得しています。
LINE 150: 総アクセス数の取得。データフレーム全体の行数を取得し、総アクセス数として変数totalに格納しています。要約情報の基礎となる値です。
LINE 151: エラー件数の集計。ステータスコードが400以上の行を抽出し、その件数をerrorに格納しています。サーバーエラーやクライアントエラーの件数を表します。
LINE 152: 結果辞書の作成開始。ここまでの集計結果をまとめた辞書を戻り値として作成し始めています。この後の各項目が要約情報として格納されます。
LINE 153: 総アクセス数の格納。先ほど計算した総アクセス数を、結果辞書の総アクセス数というキーに格納しています。呼び出し元に渡す要約情報の一部です。
LINE 154: ピーク時間帯の文字列化。ピーク時の時間を「◯時」という文字列に整形し、結果辞書に格納しています。人が読みやすい形式にしています。
LINE 155: ピーク時アクセス数の格納。ピーク時間帯におけるアクセス数を結果辞書に格納しています。どれだけアクセスが集中していたかを示す値です。
LINE 156: エラー件数の格納。先ほど計算したエラー件数を結果辞書に格納しています。400以上のステータスの発生件数を示す項目です。
LINE 157: エラー率の計算と格納。エラー件数を総アクセス数で割り、小数点以下4桁に丸めてエラー率として結果辞書に格納しています。全体に対するエラーの割合を表します。
LINE 158: 最多URLの格納。URL別集計で最もアクセス数が多かったURLを取り出し、結果辞書に格納しています。トップ10の先頭行から取得しています。
LINE 159: 図の枚数の格納。作成したグラフの枚数として固定値の4を結果辞書に格納しています。今回生成した画像ファイルの総数を表します。
LINE 160: 画像ファイル一覧の開始。保存した画像ファイル名の一覧をリストとして結果辞書に格納し始めています。この後に各ファイル名が続きます。
LINE 161: 折れ線グラフのファイル名。時間帯別アクセス数の折れ線グラフの画像ファイル名をリストに含めています。呼び出し元が画像を参照する際に使われます。
LINE 162: 横棒グラフのファイル名。URL別アクセス数トップ10の横棒グラフの画像ファイル名をリストに含めています。呼び出し元が画像を参照する際に使われます。
LINE 163: 円グラフのファイル名。HTTPステータス別割合の円グラフの画像ファイル名をリストに含めています。呼び出し元が画像を参照する際に使われます。
LINE 164: ヒートマップのファイル名。時間帯とステータスクラスのヒートマップの画像ファイル名をリストに含めています。呼び出し元が画像を参照する際に使われます。
LINE 165: 画像ファイル一覧の終了。画像ファイル名のリストを閉じています。ここまでで4つの画像ファイル名がすべて格納されました。
LINE 166: 結果辞書の返却。作成した要約情報の辞書をanalyze関数の戻り値として返しています。これにより呼び出し元は分析結果全体を受け取れます。
RUN 4/4: 集計と可視化の全体を確認する。完成したanalyzeを実行し、総アクセス数と図の枚数、最多URLが要約へ含まれているかを確認します。CHECK 4/4: 途中実行に成功。
総アクセス数: 20000 図の枚数: 4 最多URL: / RETURN 04: エディターへ戻りました。入力済みのコードを維持したまま、次の実装へ進みます。実行1/4: 集計結果の要約を確認する。
完成したanalyze関数を実行し、返ってきた要約から総アクセス数と図の枚数、最多URLを確認します。確認1/4: 集計結果の要約を確認する。完成したanalyze関数を実行し、返ってきた要約から総アクセス数と図の枚数、最多URLを確認します。
RETURN 05: エディターへ戻りました。入力済みのコードを維持したまま、次の実装へ進みます。実行2/4: 生成された4枚のPNGを確認する。
analyzeを実行し、要約に記録された画像ファイルが出力先フォルダへ保存されているかを確認します。確認2/4: 生成された4枚のPNGを確認する。analyzeを実行し、要約に記録された画像ファイルが出力先フォルダへ保存されているかを確認します。
RETURN 06: エディターへ戻りました。入力済みのコードを維持したまま、次の実装へ進みます。実行3/4: エラー件数とピーク時間帯を確認する。
要約に含まれるエラー件数が正の値で、エラー率が0以上1未満に収まり、ピーク時間帯が求められているかを確認します。確認3/4: エラー件数とピーク時間帯を確認する。要約に含まれるエラー件数が正の値で、エラー率が0以上1未満に収まり、ピーク時間帯が求められているかを確認します。
RETURN 07: エディターへ戻りました。入力済みのコードを維持したまま、次の実装へ進みます。実行4/4: シード固定による再現性を確認する。
analyzeを2回実行し、総アクセス数と最多URLが毎回同じ結果になることを確認します。確認4/4: シード固定による再現性を確認する。analyzeを2回実行し、総アクセス数と最多URLが毎回同じ結果になることを確認します。
RETURN 08: エディターへ戻りました。入力済みのコードを維持したまま、次の実装へ進みます。学習内容のまとめ。
pl.DataFrameで辞書から表データを作成するsubplotsで図と描画領域を用意するout_dirフォルダを作成するmatplotlibのバックエンドをAggに固定する 小さく実行確認しながら完成状態まで段階的に組み立てる エンディング。Python研修はCodeCampでご確認ください。
Polarsとmatplotlibとは
今回使用する主要なライブラリについて、役割と使い分けを順番に確認します。
データフレームライブラリPolars
PolarsはRust言語で実装された高速なデータフレームライブラリです。CSVや配列から表形式のデータを読み込み、group_byやaggで集計をまとめて記述できます。今回はアクセスログをDataFrameにまとめ、時間帯やURLごとの件数を数える中心的な役割を担います。
列指向の設計により、2万件規模の集計も軽快に処理できる点が特徴です。
アクセスログの集計でPolarsが受け持つ主な操作は次のメソッドです
- pl.DataFrameで辞書から表データを作成する
- group_byとaggで件数を数える
- sortとheadで上位URLを絞り込む
- from_epochでエポック秒を日時へ変換する
- dt.hourで時刻から時間帯を取り出す
- iter_rowsで集計結果を1行ずつ取り出す
グラフ描画を担うmatplotlib
matplotlibはPythonで最も広く使われているグラフ描画ライブラリです。折れ線や棒、円グラフなど多彩な図を数行で作成でき、PNGやPDFなどの画像ファイルとして保存できます。本ツールでは集計結果を4種類の図に描き分け、傾向を目で見て理解できる形に整えます。
画面を持たないサーバでも動くAggバックエンドを使う点も特徴のひとつです。
matplotlibで4種類の図を描き分けるために使う主な要素は次のとおりです
- subplotsで図と描画領域を用意する
- plotで折れ線グラフを描く
- barhで横棒グラフを描く
- pieで円グラフを描く
- imshowでヒートマップを描く
- savefigでPNGファイルへ書き出す
Python・Polarsで開発する場合の環境構築
この記事のセットアップ手順と掲載コードは、Windows 11 Pro、PowerShell 5.1、Python 3.13.3、Polars 1.43.2で動作確認しています。仮想環境を有効化せず、その中のPythonを直接指定するため、以下のコマンドはPowerShellとコマンドプロンプト(cmd)の両方で使えます。
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install --upgrade pip
.\.venv\Scripts\python.exe -m pip install polars matplotlib numpy
macOS・Linuxでは仮想環境内のPythonパスが異なります。今回の動作確認環境とは異なるため、以下は環境差分を補う参考手順です。
python3 -m venv .venv
./.venv/bin/python -m pip install --upgrade pip
./.venv/bin/python -m pip install polars matplotlib numpy
さらに詳しく学べるPython研修の詳細はこちら
アクセスログ集計・可視化ツールの要件定義
目的は、Polarsで2万件の合成アクセスログを集計しmatplotlibで時間帯やURLやステータス別の傾向を4枚のグラフに可視化して確認できるようにすることです。
対象者として、Pythonの基本文法を理解しPolarsでのデータ集計とmatplotlibでの可視化を実践的に学びたい初心者を想定しています。
完成物は、Polarsで集計しmatplotlibで折れ線・横棒・円・ヒートマップの4枚のPNGを生成しアクセス傾向の要約まで確認できるログ分析ツールです。
実装へ入る前に、機能・品質・受け入れ条件を分けて確認します。
機能要件
- 乱数シードを固定して2万件の合成ログを生成する
- エポック秒を日時型へ変換する
- 日時から0から23の時間帯を取り出す
- ステータスから100番台単位のクラスを算出する
- 時間帯別にアクセス数を集計する
- URL別アクセス数の上位10件を抽出する
- HTTPステータス別にアクセス数を集計する
- 時間帯とステータスクラスをクロス集計する
- 折れ線グラフで時間帯別アクセス数を描画する
- 横棒グラフでURL別アクセス数を描画する
- 円グラフでHTTPステータスの割合を描画する
- ヒートマップで時間帯とステータスの分布を描画する
- 4枚のグラフをPNGとして保存する
- 総アクセス数やピーク時間帯を含む要約を返す
非機能要件
- matplotlibのバックエンドをAggに固定する
- 乱数シードを42に固定して結果を再現可能にする
- 出力先フォルダをexist_ok付きで作成する
- 4枚のグラフをdpi120でPNG保存する
- 描画後にplt.closeで図を解放する
- 標準ライブラリのosとdatetimeで入出力と時刻を扱う
実装方針
今回はPolarsとmatplotlibの基本動作を追いやすくするため、アクセスログ集計・可視化ツール本体を1つのPythonファイルへまとめます。
入力、判定、結果表示の役割を分け、実行結果を確認しながら機能を積み上げます。
アクセスログ集計・可視化ツールを安全に組み立てるための実装方針は次のとおりです。
- matplotlibのバックエンドをAggに固定する
- 乱数シードを42に固定して結果を再現可能にする
- 出力先フォルダをexist_ok付きで作成する
- 4枚のグラフをdpi120でPNG保存する
- 描画後にplt.closeで図を解放する
- 標準ライブラリのosとdatetimeで入出力と時刻を扱う
完成と判断する条件
- 総アクセス数として20000が返る
- hourly_accessなど4枚のPNGが保存される
- 最多URLとしてルートパスが返る
- エラー率が0以上1未満で返る
- 図の枚数として4が返る
アクセスログ集計・可視化ツールを作る際の重要ポイント
本ツールの中心は、合成アクセスログを作ってから集計し、4枚の図と要約にまとめる一連の流れです。まず乱数シードを固定して2万件のログを生成し、時間帯やURL、ステータスごとに件数を数えます。続いて折れ線・横棒・円・ヒートマップの4枚を描いてPNGに保存し、総アクセス数やピーク時間帯、エラー率などをまとめた辞書を返します。
小さな関数に分けているため、どの段階で何をしているかを追いやすい構成です。
analyze関数が呼び出しから完了までに実行する主な処理は次の順序です
- out_dirフォルダを作成する
- _generate_logsで2万件のログを得る
- from_epochで日時と時間帯を付与する
- 4種類の集計をgroup_byで作る
- 4枚のグラフをPNGへ保存する
- 総アクセス数やエラー率の要約辞書を返す
合成ログを生成する再現性の工夫
実際のアクセスログを用意しなくても学べるように、本ツールはnumpyの乱数でログを合成します。default_rngへ42というシードを渡すので、何度実行しても同じデータが得られる仕組みです。時間帯やURL、ステータスには出現しやすさの重みを設定し、現実に近い偏りを再現しています。
合成ログの各アクセスが持つ生成ルールは次のとおりです
- 昼から夕方にピークが来る時間帯の重み
- ルートやproductsが多いURLの重み
- 200番台が大半を占めるステータスの重み
- 200から60000バイトの転送量
クロス集計をヒートマップ用の行列にする処理
時間帯とステータスクラスのクロス集計は、24行4列のゼロ行列へ件数を書き込んで表現します。group_byで求めた件数をiter_rowsで1行ずつ取り出し、該当するマスへ値を入れていく流れです。この行列をimshowへ渡せば、色の濃さで件数の多い時間帯とステータスを一目で確認できます。
クロス集計から行列を組み立てる手順は次のとおりです
- 24行4列のゼロ行列を用意する
- 時間帯とステータスクラスで件数を集計する
- iter_rowsで各行の件数を取り出す
- 該当する時刻とクラスのマスへ代入する
分析結果をまとめる要約辞書
最後に、分析全体の要点を辞書へまとめて返します。総アクセス数やピーク時間帯、400以上のエラー件数とエラー率、最も多いURLなどを1か所に集約する形です。生成した画像ファイル名も併せて返すので、どの図を確認すればよいかがすぐに分かります。
要約辞書に含まれる主な項目は次のとおりです
- 総アクセス数とピーク時間帯
- ピーク時のアクセス数
- 400以上のエラー件数とエラー率
- 最多URLと図の枚数
Pythonでアクセスログ集計・可視化ツールの完成コード
コード全体は、ログ生成を担う_generate_logsと、集計と描画をまとめるanalyzeという関数で構成しています。
analyzeはまず出力先フォルダを作成し、合成ログをPolarsのDataFrameとして受け取る処理です。
続いてwith_columnsで日時と時間帯・ステータスクラスの列を足し、group_byで4種類の集計を作ります。
最後にmatplotlibで折れ線・横棒・円・ヒートマップを描き、それぞれをPNGとして保存する流れです。
このセクションの用語
- group_by
- 指定した列の同じ値ごとにデータをまとめ、件数や合計などを計算する集計操作です。
- with_columns
- 既存のDataFrameに新しい列を追加したり、作り替えたりするPolarsの操作です。
- エポック秒
- 1970年1月10日からの経過秒数で時刻を表す形式で、整数1つで日時を持てます。
- Aggバックエンド
- 画面表示を使わず画像ファイルへ描画するmatplotlibの方式で、サーバ上でも図を保存できます。
"""PolarsとmatplotlibによるWebアクセスログ分析ツール。
合成アクセスログを生成し、時間帯・URL・HTTPステータス別に集計して
4種類のグラフに可視化する。
"""
import os
from datetime import datetime
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
import polars as pl
def _hour_profile() -> np.ndarray:
# 昼から夕方にピークを持つ時間帯別アクセス確率
weight = np.array(
[2, 1, 1, 1, 1, 2, 4, 7, 12, 18, 22, 25,
26, 24, 22, 20, 21, 23, 20, 16, 12, 9, 6, 3],
dtype=float,
)
return weight / weight.sum()
def _generate_logs(n: int = 20000, seed: int = 42) -> pl.DataFrame:
# 乱数シードを固定して再現性を確保
rng = np.random.default_rng(seed)
# URLとHTTPステータスの候補と出現重み
urls = ["/", "/products", "/product/detail", "/api/search", "/cart",
"/login", "/static/app.js", "/images/logo.png", "/checkout", "/admin"]
url_p = np.array([24, 20, 15, 11, 8, 7, 6, 5, 3, 1], dtype=float)
url_p /= url_p.sum()
statuses = np.array([200, 301, 302, 404, 500, 503])
status_p = np.array([82, 5, 4, 5, 2, 2], dtype=float)
status_p /= status_p.sum()
# 各アクセスの時刻・URL・ステータス・転送量を生成
hours = rng.choice(24, size=n, p=_hour_profile())
seconds_in_hour = rng.integers(0, 3600, size=n)
base_ts = int(datetime(2026, 8, 4).timestamp())
ts = base_ts + hours * 3600 + seconds_in_hour
url_idx = rng.choice(len(urls), size=n, p=url_p)
status = rng.choice(statuses, size=n, p=status_p)
size_bytes = rng.integers(200, 60000, size=n)
return pl.DataFrame(
{
"timestamp": ts,
"url": [urls[i] for i in url_idx],
"status": status,
"bytes": size_bytes,
}
)
def analyze(out_dir: str) -> dict:
# 出力先フォルダの準備
os.makedirs(out_dir, exist_ok=True)
# データの読み込み(合成アクセスログ)
df = _generate_logs()
# 前処理(エポック秒から日時と時間帯・ステータスクラスを作成)
df = df.with_columns(
pl.from_epoch(pl.col("timestamp"), time_unit="s").alias("datetime")
)
df = df.with_columns(
pl.col("datetime").dt.hour().alias("hour"),
(pl.col("status") // 100 * 100).alias("status_class"),
)
# 集計1: 時間帯別アクセス数
by_hour = df.group_by("hour").agg(pl.len().alias("count")).sort("hour")
# 集計2: URL別アクセス数トップ10
by_url = (
df.group_by("url")
.agg(pl.len().alias("count"))
.sort("count", descending=True)
.head(10)
)
# 集計3: HTTPステータス別アクセス数
by_status = df.group_by("status").agg(pl.len().alias("count")).sort("status")
# 集計4: 時間帯×ステータスクラスのクロス集計
classes = [200, 300, 400, 500]
matrix = np.zeros((24, len(classes)))
cross = df.group_by(["hour", "status_class"]).agg(pl.len().alias("count"))
for row in cross.iter_rows(named=True):
sc = row["status_class"]
if sc in classes:
matrix[row["hour"], classes.index(sc)] = row["count"]
# 可視化1: 折れ線グラフ(時間帯別アクセス数の推移)
fig, ax = plt.subplots(figsize=(9, 4.5))
ax.plot(by_hour["hour"].to_list(), by_hour["count"].to_list(),
marker="o", color="#2563eb")
ax.set_title("時間帯別アクセス数")
ax.set_xlabel("時刻(時)")
ax.set_ylabel("アクセス数")
ax.set_xticks(range(0, 24, 2))
ax.grid(alpha=0.3)
fig.tight_layout()
fig.savefig(os.path.join(out_dir, "hourly_access.png"), dpi=120)
plt.close(fig)
# 可視化2: 横棒グラフ(URL別アクセス数トップ10)
fig, ax = plt.subplots(figsize=(9, 4.5))
ax.barh(by_url["url"].to_list(), by_url["count"].to_list(), color="#0ea5e9")
ax.invert_yaxis()
ax.set_title("URL別アクセス数トップ10")
ax.set_xlabel("アクセス数")
fig.tight_layout()
fig.savefig(os.path.join(out_dir, "url_ranking.png"), dpi=120)
plt.close(fig)
# 可視化3: 円グラフ(HTTPステータス別の割合)
fig, ax = plt.subplots(figsize=(6, 6))
labels = [str(s) for s in by_status["status"].to_list()]
ax.pie(by_status["count"].to_list(), labels=labels,
autopct="%1.1f%%", startangle=90)
ax.set_title("HTTPステータス別の割合")
fig.tight_layout()
fig.savefig(os.path.join(out_dir, "status_ratio.png"), dpi=120)
plt.close(fig)
# 可視化4: ヒートマップ(時間帯×ステータスクラス)
fig, ax = plt.subplots(figsize=(7, 6))
im = ax.imshow(matrix, aspect="auto", cmap="YlOrRd")
ax.set_title("時間帯×ステータスクラスのクロス集計")
ax.set_xlabel("ステータスクラス")
ax.set_ylabel("時刻(時)")
ax.set_xticks(range(len(classes)))
ax.set_xticklabels([f"{c}番台" for c in classes])
ax.set_yticks(range(0, 24, 2))
fig.colorbar(im, ax=ax, label="アクセス数")
fig.tight_layout()
fig.savefig(os.path.join(out_dir, "hour_status_heatmap.png"), dpi=120)
plt.close(fig)
# 要約の作成
peak = by_hour.sort("count", descending=True).row(0, named=True)
total = df.height
error = df.filter(pl.col("status") >= 400).height
return {
"総アクセス数": total,
"ピーク時間帯": f"{peak['hour']}時",
"ピーク時アクセス数": peak["count"],
"エラー件数(400以上)": error,
"エラー率": round(error / total, 4),
"最多URL": by_url.row(0, named=True)["url"],
"図の枚数": 4,
"images": [
"hourly_access.png",
"url_ranking.png",
"status_ratio.png",
"hour_status_heatmap.png",
],
}
コード全文は上の折り畳みに入れてあるので、全部を上から読む必要はありません。ここでは特に重要な部分だけを抜き出して、何をしているのか順番に見ていきます。
_hour_profileで時間帯の重み付け
weight = np.array(
[2, 1, 1, 1, 1, 2, 4, 7, 12, 18, 22, 25,
26, 24, 22, 20, 21, 23, 20, 16, 12, 9, 6, 3],
dtype=float,
)
return weight / weight.sum()昼から夕方にアクセスが集まるよう、24時間分の重みを配列で用意しました。合計で割ると確率に直すと、時間帯ごとの発生しやすさに差が付きます。
default_rngで乱数シードを固定
rng = np.random.default_rng(seed)numpyの乱数生成器にシード番号を渡して固定しています。こうすると誰が実行しても同じログが再現され、結果を見比べやすくなります。
from_epochでエポック秒を日時に変換
df = df.with_columns(
pl.from_epoch(pl.col("timestamp"), time_unit="s").alias("datetime")
)整数のエポック秒を、Polarsのdatetime型へ変換しています。time_unit="s"で秒単位だと伝えると、あとから時間帯を取り出せる形になります。
dt.hourとstatus_classで前処理
df = df.with_columns(
pl.col("datetime").dt.hour().alias("hour"),
(pl.col("status") // 100 * 100).alias("status_class"),
)日時から時間帯(0〜23)を取り出し、ステータスを100で割って100倍することで200番台や400番台のクラスにまとめています。集計の軸になる2つの列を一度に追加しました。
group_byで時間帯別に集計
by_hour = df.group_by("hour").agg(pl.len().alias("count")).sort("hour")hour列でグループ化し、pl.len()で各時間帯の件数を数えています。sort("hour")で0時から順に並べ、折れ線グラフへそのまま渡せるようにします。
URL別トップ10をheadで抽出
by_url = (
df.group_by("url")
.agg(pl.len().alias("count"))
.sort("count", descending=True)
.head(10)
)URLごとに件数を集計し、多い順に並べて上位10件だけ取り出しています。descending=Trueが降順の指定、head(10)が件数の多い10件への絞り込みです。
matplotlib.use('Agg')で画面なし描画
matplotlib.use("Agg")画面表示を使わないAggバックエンドを指定しています。ウィンドウのないサーバやCIでも、図をPNGファイルとして保存できます。
参考:
©matplotlib公式ドキュメントAgg: raster graphics -- high quality images using the Anti-Grain Geometry engine
アクセスログ集計・可視化ツールの動作確認
実際にanalyzeを動かすと、指定したフォルダに4枚のPNGが生成できました。
折れ線のhourly_accessは昼から夕方の混雑を、横棒のurl_rankingは人気ページの順位を見せてくれます。
円のstatus_ratioは応答結果の割合を、ヒートマップのhour_status_heatmapは時間帯ごとのステータス分布を表します。
総アクセス数は2万件、最も混み合うピーク時間帯は11時という結果でした。
このセクションの用語
- ヒートマップ
- 数値の大小を色の濃淡で表す図で、2つの軸の組み合わせごとの傾向を一目で見られます。
- クロス集計
- 2つの切り口を縦横に組み合わせて件数を数える集計方法で、表やヒートマップにできます。
| 項目 | 実測値 |
|---|---|
| 総アクセス数 | 20000件 |
| ピーク時間帯 | 11時 |
| 生成した図 | 4枚 |
| URL候補数 | 10種類 |
| HTTPステータスの種類 | 6種類 |




| 項目 | 実測値 |
|---|---|
| 総アクセス数 | 20000 |
| ピーク時間帯 | 11時 |
| ピーク時アクセス数 | 1695 |
| エラー件数(400以上) | 1813 |
| エラー率 | 0.0906 |
PythonのPolarsとmatplotlibのエラー対処
ここでは、初心者が実行時につまずきやすいエラーをまとめました。
多くはインストール漏れや列名の打ち間違え、描画バックエンドの指定順で起こります。
このセクションの用語
- 正規化
- 数値の合計が1になるようそれぞれを割る処理で、確率として扱えるようにします。
| エラー例 | 原因 | 対処 |
|---|---|---|
| ModuleNotFoundError: No module named 'polars' | Polarsが未インストール | pip install polarsで導入する |
| 図が保存されずバックエンド関連の警告が出る | GUIバックエンドが選ばれている | pyplotのimport前にmatplotlib.use("Agg")を呼ぶ |
| ColumnNotFoundError | 列名の打ち間違えやaliasの不一致 | 集計前に列名を確認しaliasと合わせる |
| ValueError: probabilities do not sum to 1 | rng.choiceのpの合計が1でない | sum()で割って正規化する |
| グラフの日本語が□(豆腕)になる | 日本語フォントが未設定 | フォントを指定するか英字ラベルにする |
アクセスログ集計・可視化ツールで注意したい点
group_byでの集計は手軽な反面、並び順やシードの扱いで結果の見え方が変わります。
group_byの直後は行の順序が保証されないため、sortで明示的に並べ替えると折れ線が安定しました。
matplotlib.use("Agg")はpyplotのimportより前に呼ぶ必要があり、順番を誤ると設定が効きません。
ポイントとしては、再現性と描画環境まわりの設定を先に固めておくことです。
乱数シード:固定で結果を再現
描画設定:Aggを先に呼ぶ
確率の合計:1になるよう正規化
アクセスログ分析ツールが実務で役立つ場面
作ったツールは合成ログ向けですが、列名を合わせれば実際の運用ログにも応用できます。
時間帯・URL・ステータスという切り口は、運用監視や障害調査の入口として役立ちます。
| 使える場面 | 具体的な使い方 |
|---|---|
| サイトの混雑把握 | 時間帯別の折れ線でアクセスが集中する時間を見つけ、増強や監視の重点時間を決める |
| 障害の早期発見 | ステータス別の円やヒートマップで404や500の急増を捕え、原因調査の入口にする |
| 人気ページ分析 | URL別トップ10でよく見られるページを把握し、導線改善やキャッシュの優先度を決める |
| 負荷対策の計画 | 時間帯×ステータスのクロス集計でエラーが増える時間を特定し、対策の時間配分を決める |
アクセスログ集計・可視化ツール開発のまとめ
PythonのPolarsとmatplotlibで、アクセスログを集計して4枚の図に可視化するツールを作りました。
numpyでシードを固定した合成ログを使うので、手元でも同じ結果を再現しながら学べます。
まずは合成ログで流れをつかみ、慣れたら本物のログを読み込む部分だけ差し替えると発展させやすいです。
さらに発展させるなら、次のような拡張が考えられます。
- 実ログのCSVを
pl.read_csvで読み込む部分の差し替え - 転送量
bytesの合計や平均を時間帯別に集計 - 曜日や日付をまたいだ期間での比較
参考にした一次情報
- ^ Polars User Guide. https://docs.pola.rs/, (参照26-08-05).
- ^ Polars API reference: group_by. https://docs.pola.rs/api/python/stable/reference/dataframe/api/polars.DataFrame.group_by.html, (参照26-08-05).
- ^ matplotlib pyplot API. https://matplotlib.org/stable/api/pyplot_summary.html, (参照26-08-05).
- ^ matplotlib: Creating annotated heatmaps. https://matplotlib.org/stable/gallery/images_contours_and_fields/image_annotated_heatmap.html, (参照26-08-05).
※内容は執筆時点のものです。ライブラリやサイトの仕様は変わる可能性があるため、公式ドキュメントもあわせてご確認ください。
ITやプログラミングに関するコラム
【Python】FastAPIで料金プラン見積もりシミュレーターを作ってみた
【Python】pandasとmatplotlibで在庫データのABC分析と構成比を可視化してみた
【Python】Flaskで社内FAQをカテゴリ検索できるWebアプリを作ってみた
【Python】argparseでJSON整形・構文検証・キー検索CLIを試してみた
【Python】NumPyとmatplotlibでモンテカルロ法による円周率推定と収束過程の可視化を試してみた
【Python】Playwrightでスクレイピングを試してみた
【CSS】notで複数の件を除外する方法
【Git】remote設定を変更する方法
【VBA】コメントアウトを設定する方法
x86とx64の違いを分かりやすく解説
ITやプログラミングに関するニュース
VercelがAI GatewayにSeedream 5.0 Proを追加、AI SDKのモデル指定で画像生成と編集が可能に
AWSがAmazon LocationのPlaces APIを強化、住所表記の指定と移動手段別の検索が可能に
VercelがトレースにTree・Waterfallビューを追加、ログ画面で処理の階層と所要時間を確認可能に
Googleがエージェント評価の再考を提唱、難易度を情報量で測るDiscovery Benchを解説
Google CloudがCloud Runサンドボックスを公開プレビューで提供、サービスヘルスは一般提供に
Google Cloud EMEAが英国金融の重要第三者に指定、イングランド銀行・PRA・FCAの直接監督下に
AWS DMS Schema ConversionがSQL Serverのオフライン変換に対応、ソースDBへ接続せずスキーマを変換可能に
EC2 G7インスタンスが米国東部(バージニア北部)で利用可能に、G6比でAI推論性能が最大4.6倍
SageMaker HyperPodが継続プロビジョニングでのAMIベース構成に対応、S3のスクリプト管理なしでSlurmクラスターを作成可能に
AWSがEMR on EKSでSparkトラブルシューティングエージェントに対応、失敗ジョブの原因分析を自然言語で依頼可能に
