0
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?

Googleサイトの問い合わせフォームに「会社名検索→法人番号の自動入力」を付ける

0
Last updated at Posted at 2026-10-10

1. 何を作ったか

Googleサイトで作った会社のホームページに、次の動きをする問い合わせ窓口を付けました。

  1. 問い合わせる人が、会社名を数文字入力する
  2. 国の公開データベース(gBizINFO)から候補が表示される
  3. 候補を選ぶと、会社名・法人番号・所在地が入った状態でGoogleフォームが開く
  4. 残りの項目(氏名、連絡先、相談内容)を入力して送信する
    image.png

受け取る側は、最初の問い合わせの時点で法人番号が手元に届きます。会社名の表記ゆれ(「(株)」「株式会社」、旧社名など)に悩まされず、顧客管理の仕組みに登録するときも法人番号をそのままキーにできます。

筆者は中小企業診断士であり、コードを書いた経験がありません。この記事のコードはすべて生成AIに書かせたもので、筆者が行ったのは「何をどこまでやらせるか」の指示と、できあがったものの確認です。何を確認したかは7章にまとめました。

全体の流れは次のとおりです。

2. 方針:中小企業の情報管理の範囲でつくる

支援先の中小企業でも同じものを作れるように、次の方針を置きました。

  • 新しい契約・サーバー・管理対象を増やさない。 使うのは、会社ですでに使っているGoogleアカウントの中にあるもの(Googleサイト、Google Apps Script、Googleフォーム)だけです。
  • 外に出す情報は「検索された会社名」だけにする。 Googleの外に出る通信は、会社名をgBizINFOに問い合わせる1本だけです。氏名・メールアドレス・問い合わせ本文は、この仕組みの中を通しません。それらはGoogleフォームに直接入力してもらいます。
  • 止まっても問い合わせは受け取れるようにする。 検索が使えないときも、会社を選ばずにフォームへ進める経路を残します。

情報システムの専任者がいない会社では、「管理するものが1つ増える」こと自体が負担になります。そのため、便利さよりも、増やさないことを優先しました。

3. 用意するもの

用意するもの 補足
Googleアカウント Googleサイト、Apps Script、フォームを使います
gBizINFOのAPIトークン gBizINFO のサイトから利用申請をして受け取ります
Googleサイトで作ったページ 検索窓を置く場所です

gBizINFOは経済産業省が運営する、法人の公開情報のデータベースです。利用にあたっては、利用規約とAPIのポリシーを先に読んでおくことをおすすめします(8章にリンクを置きました)。

4. フォーム側の準備

4.1 項目を作る

Googleフォームに、次の項目を作ります。

  • ご相談の種類(プルダウン)
  • 会社名(記述式)
  • 法人番号(記述式)
  • 所在地(記述式)
  • お名前、メールアドレス、ご相談内容 など

上の4つが、検索窓から自動で入る項目です。

4.2 事前入力のURLから項目の番号を調べる

  1. フォームの編集画面で、右上のメニュー(︙)から「事前入力したURLを取得」を選ぶ
  2. 自動で入れたい項目に、目印になる文字(例:AAA、BBB)を入力する
  3. 「リンクを取得」を押して、URLをコピーする

コピーしたURLは次の形をしています。

https://docs.google.com/forms/d/e/【フォームのID】/viewform?usp=pp_url&entry.1111111111=AAA&entry.2222222222=BBB

entry. で始まる番号が、各項目の番号です。どの番号がどの項目かを控えておきます。

5. GASで中継を作る

5.1 なぜ中継が必要か

gBizINFOのAPIは、呼び出すときにトークン(合言葉のようなもの)が必要です。これをホームページ側のコードに書くと、ページを見た人なら誰でも読めてしまいます。

そこで、トークンを知っているのはGASだけにします。ホームページからはGASに会社名を渡し、GASがトークンを付けてgBizINFOに問い合わせ、結果だけを返します。

5.2 トークンを保存する

  1. Apps Script で新しいプロジェクトを作る
  2. 左の歯車(プロジェクトの設定)を開く
  3. 「スクリプト プロパティ」に、プロパティ名 GBIZ_TOKEN、値に受け取ったトークンを入れて保存する

トークンはコードの中には書きません。生成AIとのやり取りにも貼りません。

5.3 コード(Code.gs)

Code.gs
/**
 * 会社名検索の中継スクリプト(gBizINFO REST API)
 *
 * 使い方:
 *  1. スクリプト プロパティに GBIZ_TOKEN という名前でAPIトークンを保存する
 *  2. testSearch を実行して、ログに会社の候補が出ることを確認する
 *  3. ウェブアプリとしてデプロイする(実行ユーザー:自分 / アクセス:全員)
 *
 * トークンはこのコードの中に書かないでください。
 */

// 新しいAPI(v2)を先に試し、使えなければ従来のAPI(v1)に切り替えます
var API_URLS = [
  'https://api.info.gbiz.go.jp/hojin/v2/hojin',
  'https://info.gbiz.go.jp/hojin/v1/hojin'
];

var MIN_LENGTH = 2;      // この文字数未満では検索しない
var MAX_RESULTS = 10;    // 返す候補の最大件数
var CACHE_SECONDS = 21600; // 同じ検索語の結果を6時間保持

/** サイトの検索窓から呼ばれる入口。?q=会社名 で検索する */
function doGet(e) {
  var q = '';
  if (e && e.parameter && e.parameter.q) {
    q = String(e.parameter.q).trim();
  }
  var result = (q.length >= MIN_LENGTH) ? searchCompanies_(q) : { items: [] };
  return ContentService
    .createTextOutput(JSON.stringify(result))
    .setMimeType(ContentService.MimeType.JSON);
}

/** 会社名で検索し、名称・法人番号・所在地の一覧を返す */
function searchCompanies_(q) {
  var cache = CacheService.getScriptCache();
  var cacheKey = 'q_' + Utilities.base64EncodeWebSafe(
    Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, q, Utilities.Charset.UTF_8));

  var cached = cache.get(cacheKey);
  if (cached) {
    return JSON.parse(cached);
  }

  var token = PropertiesService.getScriptProperties().getProperty('GBIZ_TOKEN');
  if (!token) {
    return { items: [], error: 'GBIZ_TOKEN が設定されていません' };
  }

  var lastError = '';
  for (var i = 0; i < API_URLS.length; i++) {
    var url = API_URLS[i] + '?name=' + encodeURIComponent(q) + '&limit=' + MAX_RESULTS;
    try {
      var res = UrlFetchApp.fetch(url, {
        method: 'get',
        headers: { 'X-hojinInfo-api-token': token, 'Accept': 'application/json' },
        muteHttpExceptions: true
      });
      var code = res.getResponseCode();
      if (code !== 200) {
        lastError = API_URLS[i] + ' → HTTP ' + code;
        continue;
      }
      var body = JSON.parse(res.getContentText());
      var list = body['hojin-infos'] || body['hojin_infos'] || body['hojinInfos'] || [];
      var items = [];
      for (var j = 0; j < list.length && items.length < MAX_RESULTS; j++) {
        var h = list[j];
        items.push({
          name: h.name || '',
          number: h.corporate_number || h.corporateNumber || '',
          address: h.location || ''
        });
      }
      var result = { items: items };
      cache.put(cacheKey, JSON.stringify(result), CACHE_SECONDS);
      return result;
    } catch (err) {
      lastError = API_URLS[i] + ' → ' + err;
    }
  }
  return { items: [], error: lastError };
}

/** 動作確認用。エディタで選んで「実行」し、実行ログを確認する */
function testSearch() {
  var result = searchCompanies_('トヨタ自動車');
  Logger.log(JSON.stringify(result, null, 2));
}

このコードがしていることは、次の4点です。

  • 2文字未満では検索しない
  • 返すのは名称・法人番号・所在地の3つだけで、最大10件
  • 同じ検索語の結果は6時間覚えておき、gBizINFOへの問い合わせ回数を減らす
  • 受け取った会社名はどこにも保存しない(上記の一時的な記憶を除く)

5.4 動作確認とデプロイ

  1. エディタ上部の関数選択で testSearch を選び、「実行」を押す(初回は権限の確認が出ます)
  2. 実行ログに会社の候補が出ることを確認する
  3. 右上の「デプロイ」→「新しいデプロイ」→種類「ウェブアプリ」を選ぶ
  4. 「次のユーザーとして実行:自分」「アクセスできるユーザー:全員」にしてデプロイする
  5. 表示されたウェブアプリのURL(https://script.google.com/macros/s/…/exec)を控える

コードを直したときは、「デプロイを管理」から新しいバージョンとして更新します。更新しないと、古いコードのまま動き続けます。

6. Googleサイトに検索窓を埋め込む

6.1 埋め込み方

  1. Googleサイトの編集画面で「挿入」→「埋め込む」→「埋め込みコード」を選ぶ
  2. 下のコードを貼り、YOUR_… の部分を自分のものに書き換える
  3. 枠の大きさを調整して公開する

書き換える箇所は3つです。

箇所 入れるもの
GAS_URL 5.4で控えたウェブアプリのURL
FORM_URL フォームのURL(…/viewform まで)
ENTRY 4.2で調べた項目の番号

6.2 コード

<div id="cs">
  <label for="cs-type">ご相談の種類</label>
  <select id="cs-type">
    <option value="">選択してください(任意)</option>
    <option>相談の種類A</option>
    <option>相談の種類B</option>
    <option>その他</option>
  </select>
  <label for="cs-q">会社名を入力してください</label>
  <input id="cs-q" type="text" autocomplete="off" placeholder="例:トヨタ自動車">
  <div id="cs-status"></div>
  <ul id="cs-list"></ul>
  <div id="cs-picked"></div>
  <a id="cs-go" target="_blank" rel="noopener">この内容で問い合わせる</a>
  <a id="cs-skip" target="_blank" rel="noopener">会社を選ばずに問い合わせる</a>
  <p style="margin:16px 0 0;font-size:11px;line-height:1.5;color:#666;text-align:center">会社名の候補:「Gビズインフォ」(経済産業省)を加工して作成<br>https://info.gbiz.go.jp/</p>
</div>

<style>
  #cs { font-family: Roboto, "Noto Sans JP", sans-serif; font-size: 15px; color: #222; max-width: 560px; margin: 0 auto; padding: 8px; }
  #cs label { display: block; font-weight: bold; margin-bottom: 6px; }
  #cs-type { width: 100%; box-sizing: border-box; padding: 10px 12px; font-size: 16px; border: 1px solid #888; border-radius: 4px; background: #fff; margin-bottom: 14px; }
  #cs-q { width: 100%; box-sizing: border-box; padding: 10px 12px; font-size: 16px; border: 1px solid #888; border-radius: 4px; }
  #cs-status { min-height: 20px; font-size: 13px; color: #555; margin: 4px 0; }
  #cs-list { list-style: none; margin: 0; padding: 0; max-height: 240px; overflow-y: auto; border: 1px solid #ccc; border-radius: 4px; display: none; }
  #cs-list li { padding: 8px 12px; cursor: pointer; border-bottom: 1px solid #eee; }
  #cs-list li:last-child { border-bottom: none; }
  #cs-list li:hover, #cs-list li:focus { background: #eef1f7; outline: none; }
  #cs-list small { display: block; color: #555; font-size: 12px; }
  #cs-picked { display: none; margin: 10px 0; padding: 10px 12px; background: #f3f5f9; border-radius: 4px; line-height: 1.6; }
  #cs-go { display: none; text-align: center; padding: 12px; background: #2b3350; color: #fff; text-decoration: none; border-radius: 4px; font-weight: bold; }
  #cs-skip { display: block; text-align: center; margin-top: 10px; font-size: 13px; color: #2b3350; }
</style>

<script>
(function () {
  var GAS_URL = 'YOUR_GAS_WEBAPP_URL';

  var FORM_URL = 'https://docs.google.com/forms/d/e/YOUR_FORM_ID/viewform';
  var ENTRY = { subject: 'entry.YOUR_ID_1', name: 'entry.YOUR_ID_2', number: 'entry.YOUR_ID_3', address: 'entry.YOUR_ID_4' };

  var q = document.getElementById('cs-q');
  var list = document.getElementById('cs-list');
  var status = document.getElementById('cs-status');
  var picked = document.getElementById('cs-picked');
  var go = document.getElementById('cs-go');
  var timer = null;
  var latest = 0;

  var type = document.getElementById('cs-type');
  var skip = document.getElementById('cs-skip');
  var current = null;

  function buildUrl(item) {
    var url = FORM_URL + '?usp=pp_url';
    if (type.value) { url += '&' + ENTRY.subject + '=' + encodeURIComponent(type.value); }
    if (item) {
      url += '&' + ENTRY.name + '=' + encodeURIComponent(item.name)
        + '&' + ENTRY.number + '=' + encodeURIComponent(item.number)
        + '&' + ENTRY.address + '=' + encodeURIComponent(item.address);
    }
    return url;
  }

  function refreshLinks() { skip.href = buildUrl(null); if (current) { go.href = buildUrl(current); } }
  type.addEventListener('change', refreshLinks);
  refreshLinks();

  function clearList() { list.innerHTML = ''; list.style.display = 'none'; }

  function choose(item) {
    clearList();
    status.textContent = '';
    q.value = item.name;
    picked.textContent = '';
    [item.name, '法人番号:' + item.number, item.address].forEach(function (t, i) {
      if (i) picked.appendChild(document.createElement('br'));
      picked.appendChild(document.createTextNode(t));
    });
    picked.style.display = 'block';
    current = item;
    go.href = buildUrl(item);
    go.style.display = 'block';
  }

  function show(items) {
    clearList();
    if (!items.length) { status.textContent = '該当する会社が見つかりませんでした。正式名称の一部で試してください。'; return; }
    status.textContent = items.length + '件の候補(クリックで選択)';
    items.forEach(function (item) {
      var li = document.createElement('li');
      li.tabIndex = 0;
      li.appendChild(document.createTextNode(item.name));
      var s = document.createElement('small');
      s.textContent = item.address;
      li.appendChild(s);
      li.onclick = function () { choose(item); };
      li.onkeydown = function (e) { if (e.key === 'Enter') choose(item); };
      list.appendChild(li);
    });
    list.style.display = 'block';
  }

  function search(word) {
    var mine = ++latest;
    clearList();
    status.textContent = '検索中…';
    var ctrl = new AbortController();
    var limit = setTimeout(function () { ctrl.abort(); }, 15000);
    fetch(GAS_URL + '?q=' + encodeURIComponent(word), { signal: ctrl.signal })
      .then(function (r) { return r.json(); })
      .then(function (data) { clearTimeout(limit); if (mine === latest) show(data.items || []); })
      .catch(function () { clearTimeout(limit); if (mine === latest) { clearList(); status.textContent = '検索に時間がかかっています。会社名を少し変えて試すか、下のリンクからお問い合わせください。'; } });
  }

  var composing = false;

  function onChange() {
    clearTimeout(timer);
    picked.style.display = 'none';
    go.style.display = 'none';
    current = null;
    if (composing) { return; }
    var word = q.value.trim();
    if (word.length < 2) { latest++; clearList(); status.textContent = ''; return; }
    status.textContent = '検索中…';
    timer = setTimeout(function () { search(word); }, 500);
  }

  q.addEventListener('compositionstart', function () { composing = true; clearTimeout(timer); latest++; clearList(); status.textContent = '変換を確定すると検索します'; });
  q.addEventListener('compositionend', function () { composing = false; onChange(); });
  q.addEventListener('input', onChange);
  q.addEventListener('keydown', function (e) { if (e.key === 'Enter' && !e.isComposing) { composing = false; onChange(); } });
})();
</script>

6.3 このコードがしていること

  • 入力が止まってから0.5秒待って検索する(1文字ごとに問い合わせない)
  • 日本語の変換中は検索せず、確定してから検索する
  • 候補を選ぶと、会社名・法人番号・所在地を入れたフォームのURLを組み立てる
  • 「会社を選ばずに問い合わせる」リンクを常に表示する
  • 15秒待っても結果が返らないときは、その旨を表示してフォームへ案内する
  • 出典(gBizINFOを加工して作成した旨)を検索窓の下に表示する

7. 生成AIに書かせて、人が確認したこと

コードは生成AIが書きましたが、次の点は筆者が指示し、できあがったものを自分で確かめました。コードが読めなくても確認できる方法で行っています。

確認したこと 理由 確かめ方
トークンがコードにも、ホームページにも書かれていない 書かれていると、誰でも自分の名義でAPIを使えてしまう コードとページのソースを「トークンの先頭数文字」で検索し、見つからないことを確認
氏名・メールアドレス・問い合わせ本文が、検索窓とGASを通らない 自前で個人情報を受け取らないため 検索窓にあるのは「相談の種類」と「会社名」の2項目だけであることを確認
フォームのURLに入るのは、相談の種類と会社の公開情報だけ URLは履歴などに残りやすいため 候補を選んで開いたフォームのURLを目で見て確認
検索が止まっても、問い合わせは受け取れる APIは予告なく制限されることがあり、GASにも1日の上限がある GASのURLをわざと間違えた状態で、「会社を選ばずに問い合わせる」からフォームが開くことを確認
出典を表示している gBizINFOの利用規約で求められているため 公開ページで表示を確認

なお、GASのウェブアプリは「全員」がアクセスできる設定のため、URLを知っている第三者が大量に呼び出すことはできます。その場合に止まるのは会社名検索の機能で、問い合わせの受付は止まりません。筆者はこの範囲の損失は受け入れられると判断し、追加の制限は設けていません。ここは作る会社ごとに判断が分かれるところだと考えています。

生成AIに任せられるのは「書くこと」までで、どの情報を持たせるか、止まったときにどうなるかを決めるのは人の仕事だと感じました。

8. 出典・参考

本記事の検索窓で表示する会社名の候補は、「Gビズインフォ」(経済産業省)を加工して作成したものです。

0
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
0
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?