1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

決算説明会の音声を「検索可能なナレッジ」に変える ― Zoom Scribe API × Summarizer API で音声データ活用を自動化する

1
Last updated at Posted at 2026-07-02

はじめに

普段は個人でシステムトレードのPythonプログラム(自動売買・検証パイプライン)を開発していて、「検証が嘘をつくとき」というシリーズで、バックテストと実運用の乖離やオーバーフィッティングの問題について記事を書いています。

その過程でずっと感じていた課題があります。

決算説明会や企業のIRセミナーなど、音声・動画データは大量にあるのに、テキスト化されておらず検索も分析もできていない。

決算説明会の音声はIR資料と違って「経営陣の言い回しのニュアンス」「質疑応答でのトーンの変化」など、数字だけでは拾えない情報が詰まっています。ですが、これを毎回手動で聞き直して文字起こしするのは非現実的です。

今回、Zoomが公開した Zoom AI ServicesScribe API(文字起こし)と Summarizer API(要約)を使って、この課題を解決する簡単な仕組みをPythonで作ってみました。本記事はその実践記録です。

この記事でやること

  1. 決算説明会・IRセミナーなどの音声ファイルを Scribe API でバッチ文字起こし
  2. 得られたテキストを Summarizer API で要点抽出
  3. 結果を Markdown ノートとして構造化し、そのまま自分の調査ノート(Obsidian / Notion等)に貯められる形にする

将来的には、この仕組みで溜めたテキストデータを自分の裁量トレード用のリサーチ用ナレッジベースとして活用し、AutoTrader.pyの特徴量候補(ニュース・IRセンチメント系)の元データにすることも視野に入れています。

Zoom AI Servicesとは

Zoomが2026年にリリースした開発者向けPaaS製品群で、Zoomの高品質なAIモデルをシンプルなREST APIとして呼び出せます。今回使うのは以下の2つです。

  • Scribe API: 高精度な音声文字起こし
  • Summarizer API: 長文テキストの要約

新規登録すると20ドル分の無料クレジットが付与され、Batchでの文字起こしなら約133時間分、翻訳なら260万文字以上が試せる計算になります。個人開発で試すには十分すぎる量です。

APIキーの取得方法は以下の記事にまとまっているので、まだの方はこちらを先にどうぞ。

全体構成

音声ファイル(決算説明会など)
   │
   ▼
[Scribe API] バッチ文字起こし
   │
   ▼
生テキスト(発言録)
   │
   ▼
[Summarizer API] 要約
   │
   ▼
構造化Markdownノート(検索・引用可能な形で保存)

処理はシンプルに「文字起こし → 要約 → 保存」の3ステップです。Batch APIなので、大量の音声ファイルを溜めておいて夜間にまとめて処理する、という運用とも相性が良いです。

実装

1. 認証まわり

Zoom AI ServicesはOAuthのアクセストークンを使ってAPIを呼び出します。トークン取得部分を共通化しておきます。

import requests
import base64
import time

ACCOUNT_ID = "YOUR_ACCOUNT_ID"
CLIENT_ID = "YOUR_CLIENT_ID"
CLIENT_SECRET = "YOUR_CLIENT_SECRET"

def get_access_token() -> str:
    credentials = base64.b64encode(
        f"{CLIENT_ID}:{CLIENT_SECRET}".encode()
    ).decode()

    resp = requests.post(
        "https://zoom.us/oauth/token",
        headers={"Authorization": f"Basic {credentials}"},
        params={
            "grant_type": "account_credentials",
            "account_id": ACCOUNT_ID,
        },
    )
    resp.raise_for_status()
    return resp.json()["access_token"]

2. Scribe APIでバッチ文字起こし

音声ファイルをアップロードし、非同期でジョブを処理させます。ジョブ完了までポーリングする形です。

def submit_transcription_job(access_token: str, audio_path: str) -> str:
    with open(audio_path, "rb") as f:
        files = {"file": f}
        resp = requests.post(
            "https://api.zoom.us/v2/ai_services/scribe/batch",
            headers={"Authorization": f"Bearer {access_token}"},
            files=files,
            data={"language": "ja-JP"},
        )
    resp.raise_for_status()
    return resp.json()["job_id"]


def wait_for_transcript(access_token: str, job_id: str, interval: int = 10) -> str:
    url = f"https://api.zoom.us/v2/ai_services/scribe/batch/{job_id}"
    headers = {"Authorization": f"Bearer {access_token}"}

    while True:
        resp = requests.get(url, headers=headers)
        resp.raise_for_status()
        data = resp.json()

        if data["status"] == "completed":
            return data["transcript_text"]
        if data["status"] == "failed":
            raise RuntimeError(f"transcription failed: {data}")

        time.sleep(interval)

決算説明会は1時間前後のものが多いですが、Batch APIなので「投げておいて後で結果を回収する」という運用にできるのがありがたいポイントです。

3. Summarizer APIで要約

生の発言録は情報量が多すぎるため、要点だけを抽出します。プロンプト指定で「決算のポイント」「質疑応答の論点」に絞った要約が作れます。

def summarize(access_token: str, text: str) -> str:
    resp = requests.post(
        "https://api.zoom.us/v2/ai_services/summarizer",
        headers={"Authorization": f"Bearer {access_token}"},
        json={
            "text": text,
            "instructions": (
                "決算説明会の発言録です。"
                "業績のポイント、経営陣のコメントの要旨、"
                "質疑応答での注目論点を箇条書きで要約してください。"
            ),
        },
    )
    resp.raise_for_status()
    return resp.json()["summary"]

4. Markdownノートとして保存

最後に、文字起こし全文と要約をセットでMarkdownに出力します。日付・銘柄コードをファイル名に含めておくと、後から検索しやすくなります。

from datetime import date
from pathlib import Path

def save_note(ticker: str, summary: str, full_text: str, out_dir: str = "notes"):
    Path(out_dir).mkdir(exist_ok=True)
    filename = f"{out_dir}/{date.today()}_{ticker}_earnings_call.md"

    content = f"""# {ticker} 決算説明会メモ({date.today()})

## 要約

{summary}

## 発言録全文

{full_text}
"""
    Path(filename).write_text(content, encoding="utf-8")
    print(f"saved: {filename}")


def run_pipeline(ticker: str, audio_path: str):
    token = get_access_token()
    job_id = submit_transcription_job(token, audio_path)
    full_text = wait_for_transcript(token, job_id)
    summary = summarize(token, full_text)
    save_note(ticker, summary, full_text)


if __name__ == "__main__":
    run_pipeline(ticker="6920.T", audio_path="earnings_call_6920.mp3")

これで音声ファイル1本につき、要約付きのMarkdownノートが1つ自動生成されます。生成したノートはそのままObsidianやNotionに放り込めば検索対象になりますし、複数回分を溜めていけば「その銘柄の経営陣が過去何を語ってきたか」を横断的に見返せるナレッジベースになります。

実際に触ってみた所感

  • Batch方式なので、リアルタイム性が求められないユースケース(決算説明会・社内会議のアーカイブ化など)には運用コストが低くて向いている
  • 認証・ジョブ投げっぱなし・ポーリングという構成がシンプルで、既存のPythonバッチ処理(自分の場合は夜間の学習・検証パイプライン)に組み込みやすい
  • 要約のプロンプトを変えるだけで「決算ポイント抽出」「質疑応答論点抽出」など、用途別の切り出しが簡単にできる

数字だけを追う決算分析に、経営陣の発言のニュアンスという定性情報を安価に足し込めるのは、地味に強いユースケースだと感じました。

今後やってみたいこと

  • Translator APIと組み合わせて、海外決算説明会(英語)も同じパイプラインで日本語ノート化する
  • 要約結果をベクトル化して、過去の発言との類似度検索(「前回と同じような弱気なコメントをしていないか」など)ができるようにする
  • 自分のトレード検証パイプラインの特徴量候補として、要約テキストのセンチメントスコアを組み込む

まとめ

Zoom AI Servicesの Scribe API + Summarizer API を使うことで、「溜まっているだけだった音声データ」を数十行のPythonコードで検索可能なテキスト資産に変換できました。個人開発でも20ドルの無料クレジットで十分試せる規模感なので、社内に眠っている通話録音やIR系の音声データがある方は、まず「Hello World」レベルから試してみることをおすすめします。

参考リンク

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?