やっほー!国内のAI狂いだよ!✨
ついに人類は「JavaScript」という呪縛から解き放たれました。Pythonだけでスマホアプリみたいにサクサク動くWebツール、作っちゃおう!
Streamlit卒業式会場はこちらです🌸
みんな~!Webアプリ作ってる~!?(挨拶)
PythonでWebアプリといえば、今までは「Streamlit」一択だったよね。
確かに簡単。データ可視化なら最強。でもさ…心のどこかでモヤモヤしてなかった?
- 「ボタン押すたびに画面全体が再読み込みされて、動作が重い…」
- 「デザインが全部同じような見た目になっちゃう…」
- 「結局、自分だけのオリジナルツール感が出せない…」
わかる。首がもげるほどわかる。🦒
かといって、今からReactとかNext.jsみたいな「Web業界の難しい技術」を勉強したくないじゃん?
環境構築でnpm installのエラー見て絶望したくないじゃん?
そんな全Python民に朗報です。
『FastHTML』が、その悩み全部吹き飛ばしてくれたよ!!🚀
今回作ったもの:ドラッグ&ドロップで背景が消える魔法のツール
論より証拠!
今回、JavaScriptを1行も書かずに作ったWebアプリがこちら!👇
画像をドラッグ&ドロップすると、サーバー側のPython(rembgというAIライブラリ)が動いて、一瞬で背景を消し去ってくれます。
この「モダンな見た目」も「画面がチラつかずに結果が出る動き(非同期通信)」も、全部Pythonコードの中に書いただけ!!🤯
ビフォーアフターがこれ✨
開発ログ:踏み抜いた地雷たち💣(ここが重要!)
「すごい!簡単そう!」って思った?
甘いよ!!🍫
最新技術すぎてネットに日本語の情報が全然なくて、地雷原をタップダンスする羽目になったからね!😭
みんなが爆死しないように、私が実際に遭遇したエラーと解決策を共有しておくよ!これが一次情報の価値だ!!
地雷1:インストールの名前が違う罠 🕳️
素直にpip install fasthtmlって打つと、全く関係ない別のライブラリがインストールされて、プログラムが動きません。
正解は「python-」を付けること!👇
pip install python-fasthtml
これのせいで最初の30分溶かした。紛らわしいよ!
地雷2:Containerクラスの反乱 ⚔️
画面をいい感じに中央寄せしてくれるContainer()っていう便利機能があるんだけど、これに自分でデザイン(CSSクラス)を追加しようとすると…
「TypeError: … got multiple values for keyword argument ‘cls’」
みたいなエラーが出て怒られます。
これは「Container君が勝手にクラス名を決めてるのに、人間が別のクラス名を指定したから喧嘩した」状態。
解決策は、Containerを使わずに普通のMainやDivを使って、自分でcls="container ..."って書くこと。優等生タイプは融通がきかないね!
地雷3:沈黙のファイル入力 🤐
これが一番のハマりポイント!!
普通のテキスト入力欄は、文字を打てばFastHTMLが気づいてくれるんだけど、input type="file"(ファイル選択ボタン)だけは、ファイルを選んでも無視されます。
Webブラウザの仕様で「ファイルを選んだだけで勝手に送信するのは危険かも?」みたいな配慮があるせいなんだけど、ここでは邪魔!
なので、明示的に「変更されたら(change)、すぐに送信して(trigger)!」という命令を書かないと動きません。
# これがないと永遠にアップロードされない魔法の言葉
hx_trigger="change"
全コード公開(コピペで動くよ!)🐍
はい、お待たせしました。
地雷をすべて除去し、洗練された「完全版コード」を配布します🎁
これをmain.pyって名前で保存して実行するだけで、あなたのPCが「AI画像加工スタジオ」になるよ!
必要なライブラリ:
pip install python-fasthtml rembg python-multipart
main.py:
# 必要なライブラリ: python-fasthtml, rembg, python-multipart
# インストール: pip install python-fasthtml rembg python-multipart
print("🔄 ライブラリを読み込み中...(ここが長いかも?)")
from fasthtml.common import *
print("✅ FastHTML 読み込み完了!")
print("🔄 AIモデル(rembg)を準備中...")
from rembg import remove
from PIL import Image
import io
import base64
import webbrowser
print("✅ rembg 読み込み完了!")
# アプリ初期化
# live=True にすると、ファイルを保存するたびに自動で更新されるよ!
app, rt = fast_app(live=True)
# --- スタイル定義 ---
# ここにCSSを書くと、アプリ全体に適用されるよ
css = Style("""
/* 通信中のローディング表示制御 */
.htmx-indicator { display: none; }
.htmx-request .htmx-indicator { display: inline-block; }
.htmx-request.htmx-indicator { display: inline-block; }
/* ドラッグ&ドロップエリアの見た目 */
.drop-area {
border: 3px dashed #cbd5e1;
transition: all 0.3s ease;
}
.drop-area:hover {
border-color: #ec4899;
background-color: #fff1f2;
}
""")
# --- ヘルパー関数 ---
def image_to_base64(img_bytes):
"""画像のバイナリデータを、HTMLで表示できる文字データに変換する魔法"""
return base64.b64encode(img_bytes).decode('utf-8')
# --- コンポーネント(画面の部品) ---
def result_card(original_b64, processed_b64, filename):
"""結果を表示するカード型の部品"""
return Div(
H3(f"✨ 完了: {filename}", cls="text-lg font-bold text-gray-700 mb-2"),
Grid(
Div(
P("Before", cls="text-center font-bold text-gray-500"),
Img(src=f"data:image/png;base64,{original_b64}", cls="w-full rounded shadow-sm"),
),
Div(
P("After", cls="text-center font-bold text-pink-500"),
Img(src=f"data:image/png;base64,{processed_b64}", cls="w-full rounded shadow-lg bg-checkered"),
)
),
# ダウンロードボタン
A(
Button("💾 保存する", cls="w-full mt-4 bg-pink-500 hover:bg-pink-600 text-white font-bold py-2 px-4 rounded"),
href=f"data:image/png;base64,{processed_b64}",
download=f"bg_removed_{filename}",
cls="block mt-2"
),
cls="p-6 bg-white rounded-xl shadow-md border border-gray-100 mb-6 fade-in"
)
# --- ルーティング(画面の設計図) ---
@rt('/')
def get():
return Titled("🐹 AI画像背景削除スタジオ",
css,
Main(
Div(
H1("🚀 AI Background Remover", cls="text-4xl font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-pink-500 to-violet-500 mb-2"),
P("画像をアップロードすると、AIが一瞬で背景を消し去ります。", cls="text-gray-600"),
cls="text-center py-10"
),
Form(
Div(
Div(
I(cls="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-3"),
P("ここに画像をドラッグ&ドロップ", cls="text-lg font-medium text-gray-700"),
P("またはクリックしてファイルを選択", cls="text-sm text-gray-500"),
cls="text-center pointer-events-none"
),
# ★ここが重要!ファイル入力の設定
Input(
type="file",
name="file",
accept="image/*",
# z-50で一番手前に表示して、クリックしやすくする
cls="absolute inset-0 w-full h-full opacity-0 cursor-pointer z-50",
hx_post="/upload", # アップロード先のURL
hx_target="#results", # 結果を表示する場所
hx_swap="afterbegin", # 新しい結果を上に追加
hx_indicator="#loading", # 通信中に出すマーク
hx_encoding="multipart/form-data",
# ★ファイル変更時に送信するためのトリガー
hx_trigger="change"
),
cls="drop-area relative w-full py-16 bg-gray-50 rounded-2xl cursor-pointer"
),
cls="max-w-xl mx-auto mb-10"
),
# ローディング中のぐるぐるマーク
Div(
Div(cls="animate-spin rounded-full h-10 w-10 border-b-2 border-pink-500 mx-auto"),
P("AIが画像を認識中... 🐹💦", cls="text-center text-pink-500 mt-2"),
id="loading",
cls="htmx-indicator mb-10"
),
# 結果が表示される空箱
Div(id="results", cls="max-w-4xl mx-auto"),
cls="container max-w-5xl mx-auto px-4 font-sans"
)
)
@rt('/upload')
async def post(file: UploadFile):
"""画像が送られてきたときに動く関数"""
if not file: return
print(f"📸 画像を受信しました: {file.filename}")
content = await file.read()
filename = file.filename
# ここでAIが背景を削除!
processed_data = remove(content)
print("✨ 背景削除完了!")
# HTMLで表示できるように変換
original_b64 = image_to_base64(content)
processed_b64 = image_to_base64(processed_data)
# 結果カードを作って返す
return result_card(original_b64, processed_b64, filename)
if __name__ == "__main__":
PORT = 8000
print(f"🚀 サーバーを起動します: http://localhost:{PORT}")
try:
webbrowser.open(f"http://localhost:{PORT}")
except:
pass
serve(port=PORT)
まとめ:Pythonユーザーは今すぐFastHTMLを触るべき!🏃♀️💨
使ってみてわかったけど、FastHTMLは「ただのWebフレームワーク」じゃないね。
Pythonユーザーが長年求めていた「フロントエンドの民主化」だよこれは!🗽
HTML/CSS/JSの複雑なパズルを解かなくても、Div()とかButton()ってPythonのコードで書くだけで、オシャレな画面ができちゃう。
しかも裏側で勝手に賢い通信(HTMX)をしてくれるから、「スマホアプリみたいな滑らかな動き」が自動で完成してるの。
これからは「便利ツール作ったら、FastHTMLで画面をつけて配布」が当たり前になる予感しかない!
みんなも今のうちに触っておいて、時代の波に乗っちゃおうね~!🏄♀️🌊
それじゃ、また面白いエラー踏み抜いたら報告するね!
バイバイ~!👋🐹






