pythonsoaptutorialweb-services

Python で SOAP API を呼び出す方法: zeep、requests、suds の比較ガイド

SOAPless Team12 min read

Python で初めて SOAP API に接続する開発者が直面する最初の疑問は「どのライブラリを使うべきか?」です。Python のエコシステムには複数の選択肢があり、使いやすさ、WSDL サポート、メンテナンス状況でそれぞれ異なるトレードオフがあります。

このガイドでは zeep (現代的な選択)、requests + 手組み XML (最大限のコントロール)、suds-community (レガシー向け) の 3 つのアプローチを、完全なコード例、エラーハンドリングパターン、シナリオ別の推奨とともに解説します。

クイック比較表

機能zeeprequests + XMLsuds-community
WSDL パース自動手動自動
型安全性あり (WSDL から)なしあり (WSDL から)
Python 3 対応完全完全コミュニティ fork
メンテナンスアクティブN/A (標準ライブラリ)低頻度
WS-Securityプラグインサポート手動限定的
学習コスト低い高い低い
複雑な型自動処理手動で XML 構築自動処理
推奨シナリオほとんどのプロジェクト単発の呼び出しレガシーコードベース

アプローチ 1: zeep (推奨)

zeep は Python で最も広く使われている SOAP ライブラリです。WSDL をパースし、すべての型に対応する Python オブジェクトを生成し、XML のシリアライズ・デシリアライズを自動で処理します。

インストール

pip install zeep

基本的な使い方

from zeep import Client

# WSDL URL からクライアントを初期化
client = Client("https://weather.example.com/soap?wsdl")

# オペレーションを呼び出し — zeep が XML エンベロープを構築
result = client.service.GetCurrentWeather(
    city="Tokyo",
    countryCode="JP"
)

# 結果は Python オブジェクト (XML ではない)
print(f"気温: {result.temperature}°{result.unit}")
print(f"天候: {result.condition}")
print(f"湿度: {result.humidity}%")

zeep は WSDL を読み込み、GetCurrentWeather オペレーションを発見し、正しい名前空間で適切な SOAP エンベロープを構築し、HTTP リクエストを送信し、XML レスポンスをパースして Python オブジェクトを返します。実質 3 行のコードでこのすべてが完了します。

複雑な型の取り扱い

SOAP オペレーションがネストされた構造や繰り返し要素を返す場合、zeep はそれらを Python オブジェクトにマッピングします:

client = Client("https://weather.example.com/soap?wsdl")

# GetForecast は ForecastDay オブジェクトのリストを返す
result = client.service.GetForecast(city="Tokyo", days=5)

for day in result.forecasts:
    print(f"{day.date}: {day.low}°-{day.high}° ({day.condition})")

複雑な入力型を作成するには、zeep の型ファクトリを使います:

# 複雑な型のインスタンスを作成
factory = client.type_factory("ns0")

address = factory.Address(
    street="丸の内1-1",
    city="東京",
    postalCode="100-0005",
    country="JP"
)

result = client.service.CreateOrder(
    customerId="CUST-001",
    shippingAddress=address,
    items=[
        factory.OrderItem(productId="PROD-A", quantity=2),
        factory.OrderItem(productId="PROD-B", quantity=1),
    ]
)

WS-Security 認証

from zeep import Client
from zeep.wsse.username import UsernameToken

# ユーザー名/パスワード認証
client = Client(
    "https://secure.example.com/soap?wsdl",
    wsse=UsernameToken("api_user", "api_password")
)

result = client.service.GetAccountBalance(accountId="ACC-12345")

タイムスタンプ付きトークンの場合:

from zeep.wsse.username import UsernameToken
from datetime import timedelta

client = Client(
    "https://secure.example.com/soap?wsdl",
    wsse=UsernameToken(
        "api_user",
        "api_password",
        use_digest=True,
        timestamp_token=True,
        timestamp_freshness=timedelta(minutes=5)
    )
)

エラーハンドリング

from zeep import Client
from zeep.exceptions import Fault, TransportError, ValidationError
from requests.exceptions import ConnectionError, Timeout

client = Client("https://weather.example.com/soap?wsdl")

try:
    result = client.service.GetCurrentWeather(city="Tokyo")

except Fault as e:
    # SOAP Fault — サーバーが構造化エラーを返した
    print(f"SOAP Fault: {e.message}")
    print(f"Fault code: {e.code}")
    if e.detail is not None:
        print(f"Detail: {e.detail}")

except TransportError as e:
    # HTTP レベルのエラー (4xx, 5xx)
    print(f"Transport error: {e.status_code} - {e.message}")

except ValidationError as e:
    # WSDL スキーマに対する入力バリデーション失敗
    print(f"Validation error: {e}")

except ConnectionError:
    print("SOAP サービスに接続できませんでした")

except Timeout:
    print("リクエストがタイムアウトしました")

トランスポート設定

from zeep import Client
from zeep.transports import Transport
from requests import Session

session = Session()
session.timeout = 30  # 秒
session.verify = "/path/to/ca-bundle.crt"  # カスタム CA
session.headers.update({"User-Agent": "MyApp/1.0"})

# HTTP プロキシ
session.proxies = {
    "https": "http://proxy.example.com:8080"
}

transport = Transport(session=session)
client = Client(
    "https://weather.example.com/soap?wsdl",
    transport=transport
)

アプローチ 2: requests + 手組み XML

SOAP 専用ライブラリをインストールせずに単一の SOAP オペレーションを呼び出したい場合、または WSDL が利用できないか壊れている場合に、Python の requests ライブラリで XML エンベロープを手動構築できます。

基本的な使い方

import requests
import xml.etree.ElementTree as ET

url = "https://weather.example.com/soap"

soap_envelope = """<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope
  xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:tns="http://example.com/weatherservice">
  <soap:Body>
    <tns:GetCurrentWeather>
      <tns:city>Tokyo</tns:city>
      <tns:countryCode>JP</tns:countryCode>
    </tns:GetCurrentWeather>
  </soap:Body>
</soap:Envelope>"""

headers = {
    "Content-Type": "text/xml; charset=utf-8",
    "SOAPAction": "http://example.com/weatherservice/GetCurrentWeather",
}

response = requests.post(url, data=soap_envelope, headers=headers)
response.raise_for_status()

# XML レスポンスをパース
root = ET.fromstring(response.content)

# XPath クエリ用の名前空間定義
ns = {
    "soap": "http://schemas.xmlsoap.org/soap/envelope/",
    "tns": "http://example.com/weatherservice",
}

body = root.find(".//soap:Body", ns)
temp = body.find(".//tns:temperature", ns)
condition = body.find(".//tns:condition", ns)

print(f"気温: {temp.text}")
print(f"天候: {condition.text}")

パラメータ化されたリクエスト

XML インジェクションの脆弱性を避けるため、f-string や format() でユーザー入力を XML に直接挿入してはいけません。xml.etree.ElementTree でプログラム的にエンベロープを構築します:

import requests
import xml.etree.ElementTree as ET

def build_get_weather_envelope(city: str, country_code: str = None) -> str:
    SOAP_NS = "http://schemas.xmlsoap.org/soap/envelope/"
    TNS = "http://example.com/weatherservice"

    ET.register_namespace("soap", SOAP_NS)
    ET.register_namespace("tns", TNS)

    envelope = ET.Element(f"{{{SOAP_NS}}}Envelope")
    body = ET.SubElement(envelope, f"{{{SOAP_NS}}}Body")
    operation = ET.SubElement(body, f"{{{TNS}}}GetCurrentWeather")

    city_el = ET.SubElement(operation, f"{{{TNS}}}city")
    city_el.text = city

    if country_code:
        cc_el = ET.SubElement(operation, f"{{{TNS}}}countryCode")
        cc_el.text = country_code

    return ET.tostring(envelope, encoding="unicode", xml_declaration=True)


envelope = build_get_weather_envelope("Tokyo", "JP")

response = requests.post(
    "https://weather.example.com/soap",
    data=envelope.encode("utf-8"),
    headers={
        "Content-Type": "text/xml; charset=utf-8",
        "SOAPAction": "http://example.com/weatherservice/GetCurrentWeather",
    },
    timeout=30
)

SOAP Fault の検出

def check_soap_fault(response_content: bytes) -> None:
    """レスポンスに SOAP Fault が含まれている場合に例外を発生させる"""
    root = ET.fromstring(response_content)
    ns = {"soap": "http://schemas.xmlsoap.org/soap/envelope/"}

    fault = root.find(".//soap:Fault", ns)
    if fault is not None:
        code = fault.findtext("faultcode", default="Unknown")
        message = fault.findtext("faultstring", default="Unknown error")
        detail = fault.find("detail")
        detail_text = (
            ET.tostring(detail, encoding="unicode")
            if detail is not None else None
        )
        raise Exception(
            f"SOAP Fault [{code}]: {message}"
            + (f"\nDetail: {detail_text}" if detail_text else "")
        )

# 使用例
response = requests.post(url, data=envelope, headers=headers, timeout=30)
check_soap_fault(response.content)

アプローチ 3: suds-community

suds-community はオリジナルの suds ライブラリのコミュニティメンテナンス版 fork です。zeep と似た使い方ができますが、メンテナンス頻度は低めです。

インストール

pip install suds-community

基本的な使い方

from suds.client import Client

client = Client("https://weather.example.com/soap?wsdl")

# 利用可能なオペレーションを一覧表示
for service in client.wsdl.services:
    for port in service.ports:
        for method in port.methods.values():
            print(f"  {method.name}")

# オペレーションを呼び出し
result = client.service.GetCurrentWeather(city="Tokyo", countryCode="JP")

print(f"気温: {result.temperature}")
print(f"天候: {result.condition}")

suds を使うべき場面

suds-community が有用なのは主に以下のケースです:

  • 既に suds を使っているコードベースのメンテナンス
  • Python 2 からの移行コードとの互換性が必要
  • zeep が処理できない WSDL の癖がある場合 (まれですが、存在します)

新規プロジェクトでは、アクティブなメンテナンス、優れた Python 3 サポート、充実した WS-Security 機能を持つ zeep が推奨されます。

パフォーマンス比較

天気予報サービスへの典型的な SOAP 呼び出しでの参考値:

フェーズzeeprequests + XMLsuds
WSDL パース (初回)~200-500msN/A~300-800ms
リクエスト構築~1-5ms~0.1-0.5ms~2-8ms
ネットワーク往復同等同等同等
レスポンスパース~2-10ms~1-5ms~3-15ms

requests によるアプローチが最も高速なのは、WSDL パースを完全にスキップするためです。ただし、zeep と suds はパース済み WSDL をキャッシュするため、初回のパースコストは後続の呼び出しで分散されます。

zeep では、アプリケーション再起動をまたいで WSDL パースをキャッシュできます:

from zeep import Client
from zeep.cache import SqliteCache
from zeep.transports import Transport

transport = Transport(
    cache=SqliteCache(path="/tmp/zeep_cache.db", timeout=86400)
)
client = Client(
    "https://weather.example.com/soap?wsdl",
    transport=transport
)

ベストプラクティス

1. WSDL をキャッシュする

WSDL ファイルはめったに変更されません。一度パースしてクライアントオブジェクトを再利用しましょう:

# 良い例 — クライアントを一度だけ作成
client = Client("https://service.example.com/soap?wsdl")

def get_weather(city: str):
    return client.service.GetCurrentWeather(city=city)

# 悪い例 — 毎回 WSDL をパース
def get_weather(city: str):
    client = Client("https://service.example.com/soap?wsdl")
    return client.service.GetCurrentWeather(city=city)

2. タイムアウトを設定する

SOAP サービスは遅いことがあります。常に明示的なタイムアウトを設定してください:

from zeep.transports import Transport
from requests import Session

session = Session()
session.timeout = 30

transport = Transport(session=session)
client = Client(wsdl_url, transport=transport)

3. デバッグ時に生の XML をログ出力する

問題が発生した際、実際の SOAP XML を確認できると非常に役立ちます:

import logging

logging.getLogger("zeep.transports").setLevel(logging.DEBUG)

4. SOAP Fault を個別にハンドリングする

汎用的な例外をキャッチしないでください。SOAP Fault は構造化されたエラー情報を持っています:

from zeep.exceptions import Fault

try:
    result = client.service.ProcessPayment(amount=100)
except Fault as e:
    if "InsufficientFunds" in str(e.detail):
        handle_insufficient_funds()
    elif e.code == "soap:Server":
        retry_later()
    else:
        raise

判断フローチャート

適切なアプローチを選ぶためのフローチャートです:

複数の SOAP オペレーションを呼び出す必要がある?
├── はい → WSDL は利用可能で有効?
│          ├── はい → zeep を使う
│          └── いいえ → requests + XML (ドキュメントやサンプルを参照)
└── いいえ → 単発のスクリプト?
             ├── はい → requests + XML (依存が少ない)
             └── いいえ → zeep を使う (長期的なメンテナンス性が高い)

SOAPless による解決

3 つのアプローチすべてで、Python コードが SOAP 固有の処理 — WSDL パース、XML エンベロープ構築、名前空間管理、SOAP Fault ハンドリング — を担う必要があります。SOAP サービスの数が増えるにつれて、この複雑さは倍増します。

SOAPless は Python コードから SOAP レイヤーを完全に排除します。zeep を使ったり XML を手組みしたりする代わりに、標準的な REST エンドポイントを呼び出すだけです:

import requests

response = requests.post(
    "https://api.soapless.com/v1/your-service/GetCurrentWeather",
    headers={
        "X-API-Key": "your_api_key",
        "Content-Type": "application/json",
    },
    json={"city": "Tokyo", "countryCode": "JP"},
    timeout=30
)

data = response.json()
print(f"気温: {data['temperature']}°{data['unit']}")

zeep も不要、XML も不要、名前空間も不要、SOAP Fault のパースも不要です。SOAPless がサーバー側で WSDL パース、XML 変換、SOAP 通信を処理し、認証情報は AES-256-GCM で暗号化管理されます。自動生成される OpenAPI 3.0 仕様は Python 固有の SOAP ツールに限らず、あらゆる HTTP クライアントライブラリで利用できます。