Pythonで HTML+PHPのローカルサーバを起動する

ファイルをチェックするとき、パスの関係でヘッダーやフッダー、画像など、うまく開けなかったりすることってありますよね。
そんなとき、ローカルサーバがあるとすごく便利です。URLでアクセスできるようになるので、
Excelマクロと組み合わせれば、複数ページのスクリーンショットをまとめて撮ることもできます。
ローカルサーバを立ち上げて、HTMLやPHPの動作を確認できるようにしておくと、
Webディレクターにとってはかなりメリットがあります。
自分で動きを確認できるので、デザインや仕様のズレにすぐ気づけるし、開発メンバーへの指示も的確に出せるようになります。
クライアントへの事前説明やデモも、ローカルで動かして見せられるので話が早く、公開前にしっかり確認できます。
ネットがない場所でも作業できるし、外部に公開しないからセキュリティ的にも安心。
スケジュール管理や品質チェックにも役立ちます。
XAMPPみたいなツールを使ってもOKですが、
Pythonが入っていれば、コマンド一発でサーバを立ち上げられるので、
わざわざ環境を整えなくてもすぐ確認できます
また複数のフォルダーから起動できるのでアプリと違って、どのフォルダーからでも立ち上げられるのでバージョン違いや必要なフォルダーを指定できるのがが嬉しいポイントです。
_run_server.py
という名前で保存して実行
このPythonスクリプトは、index.php
または index.html
をローカルで簡単に確認できる ローカルサーバ起動ツール です。PHPが使える場合はPHPサーバを、使えない場合はPythonの簡易HTTPサーバを自動で選んで起動してくれます。
import subprocess
import sys
import importlib.util # モジュール存在チェック用
import webbrowser
import time
import shutil
import os
import socket
import urllib.parse
from http.server import SimpleHTTPRequestHandler
from socketserver import TCPServer
import threading
import re
# ────────────────────────────
# スクリプト実行ディレクトリを基準とする
# ────────────────────────────
base_path = os.path.dirname(os.path.abspath(__file__))
# サーブするフォルダーをスクリプトのあるフォルダーに固定
os.chdir(base_path)
# ────────────────────────────
# ① 必要外部モジュールの確認と自動インストール
# ────────────────────────────
required_modules = {
"bs4": "beautifulsoup4",
"lxml": "lxml"
}
for module_name, package_name in required_modules.items():
if importlib.util.find_spec(module_name) is None:
resp = input(f"モジュール `{package_name}` が見つかりません。インストールしますか? (Y/n): ").strip().lower()
if resp in ["", "y", "yes"]:
print(f"`{package_name}` をインストール中...")
try:
subprocess.check_call([sys.executable, "-m", "pip", "install", package_name])
print(f"`{package_name}` のインストールに成功しました。")
except subprocess.CalledProcessError:
print(f"❌ `{package_name}` のインストールに失敗しました。")
sys.exit(1)
else:
print(f"❌ `{package_name}` が必要です。スクリプトを終了します。")
sys.exit(1)
# bs4 のインポート(SSI風 include 用)
from bs4 import BeautifulSoup
# ────────────────────────────
# 利用可能なポート探し (8000~8008)
# ────────────────────────────
def find_available_port(start_port=8000, max_tries=9):
for port in range(start_port, start_port + max_tries):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
try:
s.bind(("localhost", port))
return port
except OSError:
continue
return None # 全部使用中なら None
# ────────────────────────────────────
# カスタム HTTP ハンドラ (GET と POST 共通処理)
# ────────────────────────────────────
class CustomHTTPRequestHandler(SimpleHTTPRequestHandler):
def do_GET(self):
self.handle_request("GET")
def do_POST(self):
self.handle_request("POST")
def handle_request(self, method="GET"):
# クエリパラメータを除去してパス取得
parsed = urllib.parse.urlparse(self.path)
raw_path = parsed.path.lstrip("/")
# POST のルートは index.html にフォールバック
target = raw_path or ("index.html" if method == "POST" else "")
file_path = os.path.join(base_path, target)
if method == "POST":
# POST データ読み込み
length = int(self.headers.get('Content-Length', 0))
raw = self.rfile.read(length).decode('utf-8')
post_fields = urllib.parse.parse_qs(raw)
# コンソールへログ出力
print("📥 POSTされた内容:")
for k, v in post_fields.items():
print(f" - {k} = {v[0]}")
# テンプレート or 一覧生成
if os.path.isfile(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
content = self.process_includes(content)
for k, v in post_fields.items():
content = content.replace(f"{{{{ {k} }}}}", v[0])
else:
items = "".join([f"<li><strong>{k}</strong>: {v[0]}</li>" for k, v in post_fields.items()])
content = f"<html><body><h2>POSTデータを受信しました</h2><ul>{items}</ul></body></html>"
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.end_headers()
self.wfile.write(content.encode('utf-8'))
return
# GET処理
# ルートアクセスはディレクトリ一覧
if parsed.path in ["/", ""]:
self.path = "/"
return super().do_GET()
# ディレクトリアクセスは一覧表示
if os.path.isdir(file_path):
self.path = "/" + raw_path
return super().do_GET()
# ファイルアクセス
if os.path.isfile(file_path):
ext = os.path.splitext(file_path)[1].lower()
if ext in ['.html', '.htm']:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
content = self.process_includes(content)
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.end_headers()
self.wfile.write(content.encode('utf-8'))
else:
try:
with open(file_path, 'rb') as f:
data = f.read()
self.send_response(200)
self.send_header("Content-Type", self.guess_type(file_path))
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
except Exception as e:
self.send_error(500, f"バイナリ読み込み失敗: {e}")
else:
self.send_error(404, f"{raw_path or '/'} not found")
def process_includes(self, content):
# SSI風 include 処理
pattern = r'<!--\s*#include\s+file="([^"]+)"\s*-->'
def repl(m):
inc = m.group(1)
try:
with open(os.path.join(base_path, inc), 'r', encoding='utf-8') as f:
return f.read()
except Exception as e:
return f"<!-- Include error: {e} -->"
return re.sub(pattern, repl, content)
# ───────────────────────────
def start_python_server(port):
handler = CustomHTTPRequestHandler
try:
httpd = TCPServer(("", port), handler)
except OSError:
port = find_available_port(start_port=port+1)
if port is None:
return None, None
httpd = TCPServer(("", port), handler)
print(f"✅ Python HTTPサーバを起動: http://localhost:{port}")
thread = threading.Thread(target=httpd.serve_forever, daemon=True)
thread.start()
return httpd, port
def start_php_server(port):
try:
proc = subprocess.Popen(["php", "-S", f"localhost:{port}"])
except OSError:
port = find_available_port(start_port=port+1)
if port is None:
return None, None
proc = subprocess.Popen(["php", "-S", f"localhost:{port}"])
print(f"✅ PHPサーバを起動: http://localhost:{port}")
return proc, port
# PHP利用可否の通知
php_available = shutil.which("php") is not None
has_index_php = os.path.isfile(os.path.join(base_path, "index.php"))
if php_available:
print("✅ PHP が利用可能です。PHPサーバを起動します。" )
else:
print("ℹ PHP が見つかりません。Pythonサーバを使用します。")
# サーバ起動判定
initial_port = 8000
if php_available and has_index_php:
server_proc, PORT = start_php_server(initial_port)
if server_proc is None:
print("❌ 8000~8008 がすべて使用中です。終了します。")
sys.exit(1)
use_sub = True
else:
httpd, PORT = start_python_server(initial_port)
if httpd is None:
print("❌ 8000~8008 がすべて使用中です。終了します。")
sys.exit(1)
use_sub = False
# 自動ブラウザ起動
time.sleep(1)
webbrowser.open(f"http://localhost:{PORT}")
# 終了待機ループ
try:
while True:
k = input("終了:大文字Q を押してください > ")
if k.strip().upper() == "Q":
print("🛑 サーバ停止中...")
if use_sub:
server_proc.terminate()
else:
httpd.shutdown()
break
except KeyboardInterrupt:
print("\n🛑 キーボード割り込みでサーバ停止中...")
if use_sub:
server_proc.terminate()
else:
httpd.shutdown()
使い方
このスクリプトの使い方
このスクリプトを使えば、ローカル環境でHTMLやPHPファイルを簡単に表示・確認できます。Webディレクターやデザイナーでも扱いやすく、面倒な設定は不要です。
事前準備
- 表示させたい
index.html
またはindex.php
などのファイルを、スクリプト(例:run_server.py
)と同じフォルダに置きます。 - PHPファイルを動かしたい場合は、PCにPHPをインストールしておきましょう(HTMLだけなら不要です)。
起動方法
- スクリプトファイル(例:_
run_server.py
)を ダブルクリック するか、ターミナル(コマンドプロンプト)から以下を実行します:
python .\_run_server.py
- 実行すると、フォルダ内の
index.php
が存在し、PHPが使える場合は PHPのローカルサーバ が起動します。 - もしPHPが使えない場合や
index.php
がない場合は、Pythonの簡易HTTPサーバ が自動で使われます。
自動でブラウザが開く!
1秒ほど待つと、ブラウザが自動で開いて http://localhost:8000
を表示します。 これで、対象のHTMLやPHPページをローカルで確認できます。
⏹ サーバの停止方法
- サーバを終了したいときは、Pythonのコンソール画面で 「q」 を入力してください。
- もしくは、
Ctrl + C
を押して強制終了も可能です。
PHPも使いたいときは?
PHPを使いたい場合は、あらかじめPCにPHPをインストールしておけばOK!
このスクリプトは自動でPHPサーバを起動してくれるので、特別な設定は不要です。
HTMLだけならそのままでも使えるので、まずは気軽に試してみてください。
PHPのインストール方法(Windows向け)
✅ 1. PHP をダウンロードする
- 公式サイト(https://www.php.net/downloads)にアクセス
- 「Windows downloads」をクリック
- 「ZIP版(Non Thread Safe / x64 など)」(Zip [xx.xxMB]を選んでダウンロード
- ダウンロードしたZIPファイルを解凍し、
例:C:\data\php
フォルダに置いておく
✅ 2. 環境変数に PHP のパスを追加する
PHPをコマンドで使えるようにするための設定です。
- スタートメニューを開いて
「環境変数」や「システムの環境変数を編集」と入力して、出てきた項目をクリック- 「システムのプロパティ」というウィンドウが開きます
→ 右下の「環境変数(N)…」をクリック- 「環境変数」という画面が開きます
→ 下の「システム環境変数」または上の「ユーザー環境変数」の中から
「Path」 を探して選び、「編集(E)…」をクリック- 「Pathの編集」画面で
→ 「新規(N)」をクリックして、PHPを置いたフォルダのパスを入力
例:C:\data\php
- OK を押して、すべてのウィンドウを閉じる
✅ 3. コマンドプロンプトで動作確認
次のコマンドを入力して Enter:
スタートメニューから「cmd」または「コマンドプロンプト」を起動php -v
ちょっとした画面チェックや、クライアントに見せる簡易デモにもぴったり!
ネットがない環境でも使えるから、外出先や制限のある社内ネットでも安心です。
HTMLだけでも、PHPがあればさらに柔軟に対応できるので、ディレクター業務の強い味方になりますよ。
サンプルコードは、下記から取れます
2025.0623更新