フォーム検出は解決済みの問題に聞こえます。1,000社のウェブサイトに向けるまでは。失敗は珍しいものではありません。フォームがそこにあるのにパーサーが見つけられない。パーサーが9つの入力欄を見つけ、メッセージを違う欄に書き込む。保護されたフォームで止めるはずのガードが、存在しないプロパティを読み、undefined を受け取り、すべてを通してしまう。使ってよい公開の連絡経路を私たちは窓口と呼んでいます。問い合わせフォーム営業でそれを見つける作業が、実際に何を含んでいるのかを書きます。
01問い合わせフォーム営業の検出は、まずプラットフォームから
先に良い知らせを。問い合わせフォームはロングテールではありません。私たちが読み取った企業全体で、出てくるものの大半は5種類で説明できます。HubSpot、ActiveCampaign、WPForms、Typeform、そして設置されているサイト自身へ送信する素の HTML フォームです。まずプラットフォームを特定し、そのために書かれた抽出器を動かします。ページ上のすべての入力欄を拾う汎用の処理は、5つのうち少なくとも3つを間違え、しかも何も報告せずに間違えます。
- HubSpot。ページはポータルIDとフォームGUIDを持っていて、送信した内容は、今見ているページには一切届きません。送信はポータルに紐付く地域別エンドポイントへの JSON の POST で、地域はヘッダーではなくアドレスの一部です。ですから欧州のポータルを既定のホストへ送っても、誰にも受け取られません。項目の一覧は DOM ではなくフォーム定義から来ます。
- ActiveCampaign。埋め込みは script タグで、マークアップは JavaScript の文字列の中に、二重にエスケープされて入っています。山括弧は unicode エスケープで届き、引用符はすべてバックスラッシュ付きです。HTML パーサーをそのページに向けてもフォームは1つも見つかりません。その時点では実際に存在しないからです。文字列をアンエスケープし、結果をパースし、その結果から抽出します。
- WPForms。名前は位置で決まります。入力欄は wpforms[fields][3] という名前で、この3は他社の WordPress データベースの行IDですから、名前は意味を持ちません。意味はラベルのテキストと項目の型属性のほうが持っています。
- Typeform。ページにフォームはありません。JSON のブロックとして記述されたホスト型のフローがあるだけで、これは私たちが使える窓口ではない、というのが正直な読み方です。
- 素の HTML。action と method があり、たいていは隠しトークンがあって、それを発行したセッションクッキーと一緒に返す必要があります。取得と送信で同じクッキーの入れ物を共有しなければ、投稿は偽造として拒否されます。それはサイト側として正しい挙動です。
この5つはそれぞれ、描画上の癖ではなく送信の取り決めです。一方の抽出器は、他方の抽出器を少し調整したものではありません。そうでないふりをすると、ほぼすべてのページで成功を報告する検出器と、それに同意しない配信率ができあがります。
02難しくなるのは項目の対応付けです
フォームが見つかり、パースできたとします。手元には4個から11個ほどの入力欄と、置くべきメッセージが1つあります。どの欄がメッセージでしょうか。
すべての入力欄を、name、id、placeholder、対応するラベルのテキスト、aria-label、要素の型、maxlength にわたってスコア付けし、しきい値を超えた最良のものを取ります。textarea は単独で最も強いシグナルですが、それでも決定打ではありません。住所の入力に textarea を使うフォームはいくらでもありますし、メッセージ欄に maxlength が500の1行入力を使うところもあります。最も頻繁に取り違えるのは件名と本文の組です。どちらもテキストで、隣り合っていて、入れ替えると受け手には4段落の件名が届きます。
そして、触れてはいけない欄があります。ハニーポットは、人には隠され、素朴なスクリプトには見えている入力欄で、それを埋めることこそ、サイトがこちらをスクリプトだと知る手順です。次のいずれかに当てはまる入力欄をハニーポットとして印を付けます。display または visibility で隠されている、画面外に配置されている、不透明度がゼロ、tabindex が負、aria-hidden が付いている、あるいはよくある名前(url、website、hp、_gotcha、コメント欄がすでにある場合の comment)である場合です。印の付いた欄は、見つけたそのままの値で送信します。これは何かの迂回ではありません。人の前にあるブラウザーが埋めるとおりにフォームを埋めているだけです。
しきい値を下回った場合、推測はしません。窓口はなく、読み取りは理由を書いたうえで留め置かれ、静かな試行ではなく見送りとして見送り台帳に現れます。「当社をどこでお知りになりましたか」というラベルの欄に段落を投稿するのは、何も投稿しないより悪い。返信のできないフォームの内側で、見知らぬ人の注意を使わせるからです。
03最悪の形で静かだった2つのバグ
1つ目は、これまで出荷した中で最悪のものです。検出器は入れ子の結果を返し、CAPTCHA の検出結果は result.form.captcha にあります。ガードは result.captcha を読んでいました。そのプロパティは undefined で、undefined は偽値ですから、ガードはどのフォームにも CAPTCHA はないと結論しました。例外は投げられず、ログにも何も出ませんでした。保護されたフォームがその日の午後のほとんどのあいだ通り抜け、発覚したのは、保護されていると分かっているフォームに対する手動の確認が問題なしと返ってきて、誰もその結果を信じなかったからです。
修正は、プロパティのパスを直すことではなくルールです。ガードは今、先へ進むために明示的な false を要求します。それ以外はすべてジョブを留め置きます。undefined、null、オブジェクトの欠落、文字列、真値のいずれもです。検出結果の形は、楽観的に読み進めるのではなく境界で検証します。そしてフラグを1段深い場所に移した結果をガードに渡し、留め置きになることを期待するテストがあります。このテストは旧コードに対して失敗します。回帰テストの有用な定義は、それだけです。
2つ目のバグは小さく、より多くの時間を奪いました。項目の照合が後方一致を許していたので、contact[email] という名前の欄と contact[secondary_email] という名前の欄の両方が、メールアドレスの欄を探すルールを満たし、勝つのは文書順で後に来たほうでした。予備のアドレスが任意入力で検証もされていないフォームで、必須の項目を任意の項目へ対応付けてしまいました。投稿は成功し、サイトは送信完了ページを返し、メッセージは誰も読まない欄に落ちました。
今は3つのルールが順番に適用されて決着します。名前の完全一致は後方一致に勝ち、その時点で探索は終わります。必須は任意に勝ちます。判定は required 属性、aria-required、そしてプラットフォームが提供する場合はその項目定義から読みます。同点なら文書内で先に来るほうです。できあがった対応付けは監査記録に書き込まれるので、下書きを承認する人は、どの値がどの欄に落ちるのかを、承認したあとではなく承認する前に確認できます。
04どこで止まるか
CAPTCHA を解くことも迂回することも、決してありません。解答サービスも使わず、人間らしく見えるよう調整したブラウザーも使わず、保護されたものの裏にある無防備な旧エンドポイントを探すこともしません。それは同じ行為を、より上品な作法でやっているだけです。検出器が CAPTCHA を報告すると、ジョブは needs_review へ移り、人が開くまでそこに留まります。ほとんどの場合、誰も開きません。それは失敗ではなく結果です。
問い合わせフォームの CAPTCHA は、表明された意思です。運営者は、ソフトウェアに向けてそれを言うためのほぼ唯一の語彙で、向こう側に人がいてほしいと述べました。私たちの仕事は、見知らぬ相手に購入の検討を依頼することです。その会話は、相手の意思がこちらを拘束しないと示すところからは、うまく始まりません。
窓口として認めないものが他にもいくつかあります。ログインの内側にあるフォームは公開されていません。サポートのチケットフォームは相手が違います。有料顧客のために人員を置いたキューであり、そこに営業のメッセージを入れれば、誰かの午後を奪うことになります。ニュースレターの登録は連絡経路ではありません。いずれも、適合性のゲートに落ちた企業と同じ扱いで、文章の理由を付けた見送りとして記録されます。
現在の配信は、相手企業自身の公開された問い合わせフォームだけを通ります。メール送信は2026年第4四半期の予定で、未実装です。ですから窓口がないときの代替経路はなく、最初の経路が保護されているときの第2のチャネルもありません。その不在が、このルールを正直に保っています。簡単な代替があるなら、ジョブを留め置くことには何の代償もなく、代償のないルールは何も語りません。
ここに賢いところは1つもありません。プラットフォームの検出器と、スコア関数と、閉じる方向に倒れる2つのガードと、動かさなかったすべての読み取りに付いた文章の理由。これだけです。このシステムの賢い版は、より高いノック件数を報告し、そしてより悪いものになります。