利用環境で選んでください。スプレッドシート → 4.1 IMPORTDATA / 4.2 GAS / 4.3 Looker Studio、プログラム → 4.4 pandas / 4.5 記事制作ツール / 4.6 AI アプリにコードを書かせる。AI アプリで会話して使う場合はこのページではなく 6. AIアプリ連携 です。
ご契約区分が「KWTOOL 契約連動」「体験版」の場合、市場の詳細データは kwtool の注目市場に登録した市場だけ取得できます(登録していない市場は HTTP 403 outside_my_markets)。以下の例の市場名(旅行 など)は、注目市場に登録済みの市場名に読み替えてください(→ データ範囲)。
一番かんたんな方法です。セルに数式を貼るだけで、上位サイト表が読み込まれ、約1時間ごとに自動更新されます。
市場名 と kwk_あなたのキー を書き換えます=IMPORTDATA("https://api.kwtool-ai.com/v1/export/csv?resource=market.report&name=市場名&table=sites&key=kwk_あなたのキー")
| table= の値 | 出力される表 |
|---|---|
| sites | 上位サイト(順位・ドメイン・推計流入数・シェア%) |
| queries | 市場の全クエリと検索ボリューム |
| queryRanks | サイト × クエリ順位の縦持ち(ピボット向き) |
IMPORTDATA は約 1 時間ごとに再取得し、その都度データ取得コールとして使用量に数えられます。データは週次更新なので、数式は必要最小限にしてください(履歴が要る場合は 4.2 の追記版)。自社ドメインが上位 20 位に入らない場合は URL に &sites=100 を足します。
外部由来の文字列(サイトのタイトル・検索語など)が = + - @ で始まる場合、数式として実行されないよう先頭に ' を付けて出力しています。スプレッドシートや Excel では ' は表示されず、そのまま文字列として扱われます(数値列は数値のままです)。
キーをシートに出さず、複数市場をまとめて取得し、トリガーで週 1 回の自動更新もできる方法です。
markets の市場名は自分のものに書き換え)KW_API_KEY・値 kwk_あなたのキー を保存しますwatchMarkets を選んで「実行」(初回は権限の承認ダイアログが出ます)watchMarkets を時間主導型(例: 週 1 回、月曜の朝。データは週次更新なので毎日は不要です)に設定しますfunction watchMarkets() {
const KEY = PropertiesService.getScriptProperties().getProperty("KW_API_KEY");
const BASE = "https://api.kwtool-ai.com";
const markets = ["旅行"]; // 自分の市場名に書き換える
const rows = [["市場名","調査週","順位","ドメイン","推計流入数","シェア%"]];
const errors = [["市場名","HTTP ステータス","detail"]];
for (const name of markets) {
const r = UrlFetchApp.fetch(
BASE + "/v1/query?resource=market.report&name=" + encodeURIComponent(name),
{ headers: { "X-API-Key": KEY }, muteHttpExceptions: true });
const code = r.getResponseCode();
if (code !== 200) { // 404(市場名違い)・403(権限・注目市場外)・429(上限) 等は理由を残して次の市場へ
let detail = r.getContentText();
try {
const body = JSON.parse(detail);
detail = typeof body.detail === "string" ? body.detail : JSON.stringify(body.detail);
if (body.reason) detail += " [" + body.reason + "]";
} catch (e) {}
console.warn(name + ": HTTP " + code + " " + detail);
errors.push([name, code, detail]);
continue;
}
const d = JSON.parse(r.getContentText());
d.sites.forEach(s => rows.push([d.market.name, d.weekEnd, s.rank, s.domain, s.estClicks, s.shareRate]));
}
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("市場ウォッチ") || ss.insertSheet("市場ウォッチ");
sheet.clearContents();
sheet.getRange(1, 1, rows.length, rows[0].length).setValues(rows);
const errSheet = ss.getSheetByName("取得エラー") || ss.insertSheet("取得エラー");
errSheet.clearContents();
errSheet.getRange(1, 1, errors.length, errors[0].length).setValues(errors);
}
上のコードは「市場ウォッチ」タブを毎回作り直すので、履歴は残りません。推移を追いたい場合は、sheet.clearContents() 以降を次の関数に置き換えて「履歴」タブに追記します(取得日と調査週の列が付きます)。
function appendHistory(rows) { // rows は watchMarkets の rows(見出し行を除く)
const ss = SpreadsheetApp.getActiveSpreadsheet();
const hist = ss.getSheetByName("履歴") || ss.insertSheet("履歴");
if (hist.getLastRow() === 0) hist.appendRow(["取得日","市場名","調査週","順位","ドメイン","推計流入数","シェア%"]);
const last = hist.getLastRow() > 1 ? hist.getRange(hist.getLastRow(), 3).getValue() : "";
if (rows.length && rows[0][1] === last) return; // 同じ調査週は 2 回追記しない
const today = new Date();
rows.forEach(r => hist.appendRow([today, ...r]));
}
// watchMarkets の末尾: appendHistory(rows.slice(1));
クエリ別の順位が要る場合(順位急変の監視など)は、d.sites[].queryRanks を 1 行 = サイト × クエリに展開して同じように追記します。注目市場の一覧(myMarkets.groupMarkets)は IMPORTDATA では取れないので、この方法で取ります(IMPORTDATA で取れるのは市場レポートと全クエリだけです)。
取得できなかった市場は、「取得エラー」タブに HTTP ステータスと detail が記録されます(Apps Script の実行ログにも出ます)。原因と対処は「5.4 トラブルシューティング」で detail の文言から確認してください。
他のデータ(全クエリ・セグメント・重要形態素など)も resource= を変えるだけで同じパターンで取れます。resource の一覧は「3. API リファレンス」を参照してください(GAS 用の汎用ヘルパー kwQuery() も掲載しています)。
Looker Studio は Google シートをデータソースにできるため、CSV → シート → Looker Studio が最短経路です。
大量データ・多市場の本格運用についてのご要望は、サポートチームまでご相談ください。
キーを環境変数に置けば、ノートブックや分析スクリプトからそのまま使えます。
# 事前に: export KW_API_KEY=kwk_...
import os
from urllib.parse import urlencode
import httpx
import pandas as pd
BASE = "https://api.kwtool-ai.com"
KEY = os.environ["KW_API_KEY"]
# パターンA: CSV を DataFrame に直読み(キーはヘッダーで渡す)
# 日本語の市場名は URL エンコードが必要なので、パラメータは urlencode で組み立てる
params = urlencode({"resource": "market.report", "name": "旅行", "table": "sites"})
sites = pd.read_csv(f"{BASE}/v1/export/csv?{params}",
storage_options={"X-API-Key": KEY}) # URL 読み込みでは storage_options がヘッダーになる
# パターンB: JSON で細かく制御(URL は /v1/query 1本、resource で切り替え)
r = httpx.get(f"{BASE}/v1/query",
params={"resource": "market.report", "name": "旅行"},
headers={"X-API-Key": KEY})
r.raise_for_status()
report = r.json()
queries = pd.DataFrame(report["queries"]) # 全クエリ×ボリューム
top = queries.nlargest(10, "volume") # あとは普通の pandas 分析
# 文字列列の先頭に付く ' (表計算ソフト向けの数式注入対策 → 4.1 の注意) は必要に応じて外す
for col in ("title", "query", "market"):
if col in sites.columns:
sites[col] = sites[col].astype("string").str.removeprefix("'")
?key= を付ける書き方はキーがログや履歴に残るため、ヘッダーで渡してくださいstorage_options でヘッダーを渡せない古い pandas の場合は、パターンB と同じく httpx で CSV を取得し、pd.read_csv(io.StringIO(r.text)) で読み込んでください「注目市場の取り込み → 対策クエリのコンテンツ分析 → 重要形態素と競合情報の読み込み」を自社ソフトに組み込む完全版です。骨格は API リファレンスの標準フローと同じで、実運用で必要になるエラーハンドリング(既存分析の scId 照合・回数上限)を足しています。SDK は不要、必要なのは API キー1本です。
# 事前に: export KW_API_KEY=kwk_... / export KW_BASE_URL=https://api.kwtool-ai.com
import os
import time
import httpx
BASE = os.environ["KW_BASE_URL"]
H = {"X-API-Key": os.environ["KW_API_KEY"]}
def q(resource: str, **params):
for _ in range(3):
r = httpx.get(f"{BASE}/v1/query", params={"resource": resource, **params},
headers=H, timeout=60)
wait = int(r.headers.get("Retry-After", "0"))
if r.status_code == 429 and 0 < wait <= 60:
time.sleep(wait) # 短い待ち(照会間隔・分単位のレート)は待って再試行
continue
break
r.raise_for_status() # 日次上限・ログイン制限などの長い待ちや月間上限は例外として扱う
return r.json()
# ① 注目市場を取り込む(searchConditionId は分析結果とセットで保存する)
markets = q("myMarkets.groupMarkets", groupId=-1)["markets"]
market = next(m for m in markets if m["name"] == "旅行")
sc_id = market["searchConditionId"]
# ② 市場のクエリから対策キーワードを選ぶ
queries = q("market.queries", name=market["name"])["queries"]
query = queries[0]["query"]
# ③ 分析の有無を確認し、無ければ開始して完了を待つ
# 既存分析は過去世代の scId に紐づいていることがあるため、まず分析済み一覧で照合する
known = next((f for f in q("contentAnalysis.files")["files"]
if f["searchWord"] == query), None)
if known:
sc_id = known["searchConditionId"] # 既存分析はそのファイルの scId で照会する
st = q("contentAnalysis.status", marketId=sc_id, query=query)
if st["status"] == "not_analyzed":
res = q("contentAnalysis.request", marketId=sc_id, query=query) # 分析開始
if not res["accepted"]: # over_limit = 上流の1日の回数上限
raise RuntimeError(f"analysis not accepted: {res['result']}")
while st["status"] != "researched": # 通常10〜20分、空いていれば1分程度
time.sleep(30) # 同じ分析の status 照会は 10 秒以上あける
st = q("contentAnalysis.status", marketId=sc_id, query=query)
file_id = st["fileId"]
# ④ 重要形態素(業界特有度キーワード)を読み込む
kw = q("contentAnalysis.keywords", fileId=file_id, minSites=6, limit=100)
for k in kw["keywords"][:10]:
print(k["term"], k["industryScore"], f'{k["siteCount"]}/{kw["pageCount"]}サイト')
# ⑤ 競合情報を読み込む
detail = q("contentAnalysis.detail", fileId=file_id)
for page in detail["pages"]: # 検索上位10ページのページ単位スコア
print(page["url"], page["contentsScore"], page["portalScore"], page["uniqueScore"])
report = q("market.report", name=market["name"])
for site in report["sites"][:10]: # 市場レベルの競合サイト(順位・推定流入)
print(site["rank"], site["domain"], site["estClicks"])
const BASE = process.env.KW_BASE_URL;
const H = { "X-API-Key": process.env.KW_API_KEY };
const sleep = (ms) => new Promise((ok) => setTimeout(ok, ms));
const q = async (resource, params = {}) => {
const url = new URL(BASE + "/v1/query");
url.searchParams.set("resource", resource);
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
for (let attempt = 0; ; attempt++) {
const r = await fetch(url, { headers: H });
const wait = Number(r.headers.get("Retry-After") || 0);
if (r.status === 429 && wait > 0 && wait <= 60 && attempt < 2) {
await sleep(wait * 1000); // 短い待ち(照会間隔・分単位のレート)は待って再試行
continue;
}
if (!r.ok) throw new Error(`${r.status} ${await r.text()}`);
return r.json();
}
};
const { markets } = await q("myMarkets.groupMarkets", { groupId: -1 });
const market = markets.find((m) => m.name === "旅行");
let scId = market.searchConditionId;
// ② 対策クエリを選ぶ(Python 版と同じく market.queries から。実運用では選定ロジックに置き換える)
const { queries } = await q("market.queries", { name: market.name });
const query = queries[0].query;
// 既存分析は過去世代の scId に紐づいていることがあるため、まず分析済み一覧で照合する
const { files } = await q("contentAnalysis.files");
const known = files.find((f) => f.searchWord === query);
if (known) scId = known.searchConditionId; // 既存分析はそのファイルの scId で照会する
let st = await q("contentAnalysis.status", { marketId: scId, query });
if (st.status === "not_analyzed") {
const res = await q("contentAnalysis.request", { marketId: scId, query }); // 分析開始
if (!res.accepted) throw new Error(`analysis not accepted: ${res.result}`);
}
while (st.status !== "researched") {
await sleep(30000); // 同じ分析の status 照会は 10 秒以上あける
st = await q("contentAnalysis.status", { marketId: scId, query });
}
const kw = await q("contentAnalysis.keywords", { fileId: st.fileId, minSites: 6, limit: 100 });
const detail = await q("contentAnalysis.detail", { fileId: st.fileId }); // 競合ページ情報 (pages[])
| 項目 | 内容 |
|---|---|
| scId の世代 | 分析データはリクエスト時の scId に紐づく。市場の scId は調査世代で変わるため、request に使った scId を分析結果とセットで保存し、status 照会は同じ scId で行う |
| キューの設計 | 上流の result: "over_limit" と 1 日上限の HTTP 429 は翌日以降の再試行キューへ。monthly quota exceeded(月間上限)は再試行せず停止して通知。分あたりの 429 は Retry-After 秒待って再試行(回数上限付き) |
| 上限・所要時間・表示期限 | → API リファレンス 3.2 の注意表と 3.6 制約・レート制限(分析は通常 10〜20 分、同じ分析の状態照会は 10 秒以上あける、結果の表示期限は validUntil) |
この API は、AI アプリ(コーディングエージェント)に読ませるための仕様書を 2 種類配信しています。URL を指示文に入れて渡すだけで、AI が仕様を読み取って組み込みコードを書きます。
| 読ませる URL | 使い分け |
|---|---|
https://api.kwtool-ai.com/llms.txt | 仕様の要約1枚(プレーンテキスト)。まずはこちら。認証・全リソース・標準フロー・実装上の注意が短くまとまっています |
https://api.kwtool-ai.com/manual/api | 本マニュアルの詳細リファレンス。パラメータ表・レスポンス例まで必要な込み入った実装のとき |
https://api.kwtool-ai.com/llms-chat.txt | コードを書かずに AI アプリで会話して使うときの手引き(データの読み方・作法)。指示欄に貼る。MCP コネクタで接続した場合は同じ趣旨が自動で AI に伝わる(→ 6. AIアプリ連携) |
https://api.kwtool-ai.com/llms.txt を読んで、この API を使って
「担当市場の上位サイト一覧を表示するページ」を管理画面に追加して。
API キーは環境変数 KW_API_KEY から読むこと。キーをブラウザ側に出さないこと。
https://api.kwtool-ai.com/manual/api を読んで、
「注目市場ごとに対策キーワードのコンテンツ分析を回し、業界特有度キーワード
(重要形態素)上位20語を DB に保存するバッチ」を Python で書いて。
分析は非同期なのでポーリングし、429 は Retry-After に従って再試行、monthly quota exceeded は
停止して通知、over_limit はリトライキューに積むこと。
KW_API_KEY)から読ませる。コード・リポジトリに直書きさせない/v1/query?resource=calendar.latestWeek の疎通確認から動かすとデバッグが楽です問い合わせ: サポートチームまで。 ← ダッシュボードへ戻る