ComfyUI APIをPythonで操作する手順|画像生成の自動化

※本記事にはプロモーション(広告・アフィリエイトリンク)を含みます。
ComfyUI には起動直後から使える HTTP API と WebSocket API が組み込まれている。Python からワークフロー JSON を送り、完了通知を受け取って画像を保存するまでの手順をまとめた。Stable Diffusion WebUI の --api オプションに慣れた人でも、ComfyUI の API 体系は別物なのでエクスポート方法から確認してほしい。
ComfyUI API の仕組みと SD WebUI との違い
まず構造上の違いを押さえておく。
| 項目 | SD WebUI (A1111) | ComfyUI |
|---|---|---|
| API の有効化 | --api フラグが必要 |
デフォルトで有効 |
| リクエスト形式 | パラメータを個別に指定 | ワークフロー JSON 全体を送信 |
| 完了確認 | ポーリングまたはコールバック | WebSocket |
| デフォルトポート | 7860 | 8188 |
ComfyUI の API は、GUI で組んだワークフロー全体を JSON として送信する設計になっている。「どのノードにどの値を渡すか」をすべて JSON に含めるため、ノード構成を変えるだけで txt2img も img2img も ControlNet も同じエンドポイントで扱える。
主要なエンドポイントは次の 3 つ。
| エンドポイント | メソッド | 用途 |
|---|---|---|
/prompt |
POST | ワークフローをキューに積む |
/history/{prompt_id} |
GET | 生成結果(ファイル名など)を取得 |
/view |
GET | 出力画像をダウンロード |
WebSocket は ws://host:8188/ws?clientId=クライアントID で接続し、実行状態をリアルタイムに受信できる。
ワークフローを API 形式でエクスポートする
ComfyUI の GUI で組んだワークフローをそのまま API に送ることはできない。「API 形式」の JSON を取り出す手順:
- ComfyUI(デフォルト
http://localhost:8188)をブラウザで開く - 右上の歯車アイコン → 「Enable Dev mode Options」 をオンにする
- ページをリロードする(設定が即反映されないことがある)
- ワークフローを組み終えたら、ツールバーの 「Save (API Format)」 をクリック
workflow_api.jsonがダウンロードされる
「Save (API Format)」が見つからなければ Dev mode が有効になっていない可能性が高い。
エクスポートした JSON の構造は次のとおり。キーはノード ID(文字列の数字)で、inputs に各パラメータが入る。
{
"3": {
"class_type": "KSampler",
"inputs": {
"seed": 42,
"steps": 20,
"cfg": 7,
"sampler_name": "euler",
"scheduler": "normal",
"denoise": 1,
"model": ["4", 0],
"positive": ["6", 0],
"negative": ["7", 0],
"latent_image": ["5", 0]
}
},
"6": {
"class_type": "CLIPTextEncode",
"inputs": {
"text": "a cat",
"clip": ["4", 1]
}
}
}
Python からパラメータを変えるときは、このノード ID とキー名を直接書き換える。同じ class_type が複数ある場合(CLIPTextEncode など)は inputs.text の初期値や接続先のノード ID で判別する。
Python でプロンプトをキューに積む
標準ライブラリの urllib・json・uuid と、WebSocket 用の websocket-client(pip install websocket-client)で動く。
import json
import urllib.request
import uuid
SERVER = '127.0.0.1:8188' # ComfyUI のアドレス
def queue_prompt(workflow: dict, client_id: str) -> str:
payload = json.dumps({'prompt': workflow, 'client_id': client_id}).encode()
req = urllib.request.Request(
f'http://{SERVER}/prompt',
data=payload,
headers={'Content-Type': 'application/json'},
)
with urllib.request.urlopen(req) as res:
data = json.loads(res.read())
return data['prompt_id']
ワークフロー JSON を読み込み、変えたい値を書き換えてからキューに積む:
with open('workflow_api.json') as f:
workflow = json.load(f)
# ノード ID は自分のワークフローに合わせて変更する
workflow['6']['inputs']['text'] = 'a cat sitting on a wooden chair, photorealistic'
workflow['7']['inputs']['text'] = 'blurry, low quality, watermark'
workflow['3']['inputs']['seed'] = 123456789
client_id = str(uuid.uuid4())
prompt_id = queue_prompt(workflow, client_id)
print(f'キュー登録完了: {prompt_id}')
ノード ID はワークフローによって変わる。送信前に次のコマンドで class_type を確認しておくと効率的。
grep -n 'class_type' workflow_api.json
WebSocket で完了を待ち、画像を取得する
プロンプトをキューに積んだ後、WebSocket で完了を待つ。node が null になったタイミングが全ノードの実行完了を表す。
import websocket
def wait_and_get_filenames(prompt_id: str, client_id: str) -> list:
ws = websocket.WebSocket()
ws.settimeout(300) # 秒。長い生成ジョブでは延ばす
ws.connect(f'ws://{SERVER}/ws?clientId={client_id}')
try:
while True:
msg = json.loads(ws.recv())
if msg['type'] == 'executing':
data = msg['data']
if data['node'] is None and data['prompt_id'] == prompt_id:
break # 全ノード完了
finally:
ws.close()
# 履歴からファイル名を取得
with urllib.request.urlopen(
f'http://{SERVER}/history/{prompt_id}'
) as res:
history = json.loads(res.read())
filenames = []
for node_output in history[prompt_id]['outputs'].values():
for img in node_output.get('images', []):
filenames.append(img['filename'])
return filenames
ファイル名が取得できたら /view エンドポイントでダウンロードする:
import urllib.parse
from pathlib import Path
def download_image(filename: str, save_dir: str = '.') -> Path:
params = urllib.parse.urlencode({'filename': filename, 'type': 'output'})
url = f'http://{SERVER}/view?{params}'
save_path = Path(save_dir) / filename
urllib.request.urlretrieve(url, save_path)
return save_path
# 実行例
filenames = wait_and_get_filenames(prompt_id, client_id)
for fn in filenames:
path = download_image(fn, save_dir='./outputs')
print(f'保存: {path}')
FLUX や LoRA を組み込んだワークフローでも同じコードがそのまま使える。ワークフロー側の構成については ComfyUIでFLUX LoRAを適用する手順とトラブル対処 を参考にしてほしい。
つまずきやすいポイントと対処
Connection refused になる
ComfyUI はデフォルトで 127.0.0.1 のみリッスンしている。別マシンから叩くと繋がらない。--listen 0.0.0.0 で起動するか、SSH トンネルを経由する方が安全。
ssh -L 8188:localhost:8188 user@server-ip
その後は SERVER = '127.0.0.1:8188' のまま Python からアクセスできる。
WebSocket が途中で切れる
ws.settimeout(300) の値が足りない場合は延ばす。ControlNet を多段に重ねた構成や高解像度生成では、タイムアウトが発生しやすい場合があります。完了確認を /queue エンドポイントのポーリングに切り替える方法もある(環境によって異なります)。
ノード ID が分からない
GUI でノードをクリックすると右上隅にノード ID が表示される。JSON の class_type と照らし合わせると確実。同種ノードが複数ある場合は、接続先(["ノードID", 出力番号] の配列表記)をたどって判別する。
node_errors が返ってくる
queue_prompt のレスポンスに node_errors キーがあり、中身が空でない場合はワークフローの検証エラー。モデルファイルが見つからない・LoRA のパスが間違っているケースが代表的です。
まとめ
ComfyUI API と Python を繋ぐ手順をまとめると次のようになる。
| ステップ | 内容 |
|---|---|
| 1. エクスポート | Dev mode ON → 「Save (API Format)」 |
| 2. ノード確認 | class_type でノード ID を特定 |
| 3. キューに積む | /prompt に POST(client_id 必須) |
| 4. 完了待ち | WebSocket で node: null を検知 |
| 5. 画像取得 | /history でファイル名 → /view でダウンロード |
SD WebUI API と比べると初期設定にひと手間あるが、ワークフロー全体を JSON で送る設計のおかげで、ノード構成を変えてもコードを書き直さずに済む。まず手元の ComfyUI でシンプルな KSampler ワークフローを API 形式でエクスポートし、上のコードをそのまま実行してみてほしい。
よくある質問
ComfyUI APIにPythonで接続するとConnection refusedになるのはなぜですか?
ComfyUIはデフォルトで127.0.0.1のみリッスンしているためです。別マシンから接続する場合は起動時に`--listen 0.0.0.0`を付けるか、SSHトンネル(`ssh -L 8188:localhost:8188 user@host`)を経由してください。
ComfyUI APIで使うワークフローJSONのノードIDはどこで確認しますか?
ComfyUIのGUIでノードをクリックすると右上隅にIDが表示されます。エクスポートしたworkflow_api.jsonで`grep -n 'class_type' workflow_api.json`を実行すると種類と番号を一覧できます。
ComfyUI APIの画像生成完了をPythonでどう検知しますか?
WebSocketで受信するメッセージのうち、typeがexecutingかつdataのnodeキーがnullになったタイミングが全ノード実行完了のサインです。その後に`/history/{prompt_id}`でファイル名を取得します。
ComfyUI APIをPythonから使うのに必要なライブラリは何ですか?
標準ライブラリ(urllib、json、uuid)と`pip install websocket-client`だけで動きます。requestsライブラリは不要で、外部依存を最小限に抑えたい場合に向いた構成です。