CompanyオブジェクトのSearch APIのカタカナ検索

CompanyオブジェクトのSearchAPI(/crm/v3/objects/companies/search)において、カタカナ1文字で検索(CONTAINS_TOKEN)すると、データが取得できません。

漢字や平仮名あるいはカタカナ2文字以上だとデータを取得できます。

原因がわかるかたがいらっしゃいましたら、ご教授願います。

カタカナ1文字

curl --request POST \
 --url https://api.hubapi.com/crm/v3/objects/companies/search \
 --header 'authorization: Bearer YOUR_ACCESS_TOKEN' \
 --header 'content-type: application/json' \
 --data '{
 "filterGroups": [
 {
 "filters": [
 {
 "value": "ア",
 "propertyName": "name",
 "operator": "CONTAINS_TOKEN"
 }
 ]
 }
 ],
 "sorts": [
 "name"
 ],
 "properties": [
 "name"
 ],
 "limit": 1,
 "after": 0
}'

結果

HTTP 200

{
 "total": 0,
 "results": []
}

カタカナ2文字

curl --request POST \
 --url https://api.hubapi.com/crm/v3/objects/companies/search \
 --header 'authorization: Bearer YOUR_ACCESS_TOKEN' \
 --header 'content-type: application/json' \
 --data '{
 "filterGroups": [
 {
 "filters": [
 {
 "value": "アイ",
 "propertyName": "name",
 "operator": "CONTAINS_TOKEN"
 }
 ]
 }
 ],
 "sorts": [
 "name"
 ],
 "properties": [
 "name"
 ],
 "limit": 1,
 "after": 0
}'

結果

HTTP 200

{
 "total": 2,
 "results": [
 {
 "id": "**********",
 "properties": {
 "createdate": "2016-07-04T00:02:53.437Z",
 "hs_lastmodifieddate": "2021-08-02T11:31:30.287Z",
 "hs_object_id": "********",
 "name": "アイ*********株式会社"
 },
 "createdAt": "2016-07-04T00:02:53.437Z",
 "updatedAt": "2021-08-02T11:31:30.287Z",
 "archived": false
 }
 ],
 "paging": {
 "next": {
 "after": "1"
 }
 }
}

@TakuyaAimi
確かにカタカナ1文字では検索ができないですね。
queryでやってもダメでした。
直接的な質問の回答にはなっていないのですが、回避策として、「会社名 =アを含む」 を条件にした動的リストを組んでその会社リストを読み込むのはいかがでしょうか?

ご回答いただきありがとうございます。

同様に皆様迷われるところですよね…。

新しいAPIのDocにどういった値なのか書いてないのが結構つらいです!!

@TakuyaAimi

CONTAINS_TOKENはワイルドカードの利用を前提としている節がありますので、恐らく次のようにワイルドカードで挟んであげると期待通りの挙動となるかと思いますが、いかがでしょうか?

{
 "value": "*ア*",
 "propertyName": "name",
 "operator": "CONTAINS_TOKEN"
}

ご参考:https://developers.hubspot.com/docs/api/crm/search#:~:text=the%20specified%20property-,CONTAINS_TOKEN,-Contains%20a%20token

(大変ご無沙汰しております…!)

@assi
確かにワイルドカードでできますね!コメントありがとうございます!
なぜカタカナだけそれを必要とするのか引っ掛かりますが😕

@KMurota

アルファベット1文字の場合でも前後にワイルドカードを入れると入れないとではレスポンスが全然異なるので、カタカナだから、アルファベットだからというよりは、根本的にオペレーターがCONTAINS_TOKENでワイルドカードを用いない場合のマッチングの処理の仕方に問題があるのではと思います。

以前似たような話で、INオペレーターを用いた際に実際の値が大文字であっても何故か小文字で検索かけないとマッチしないことがあったので、このエンドポイントはちょいちょいバグがあるのだと思います…w

https://community.hubspot.com/t5/APIs-Integrations/API-v3-Search-via-custom-property-with-IN-operator-no-result-but/td-p/673381

@assi

ご回答いただきありがとうございます。

ワイルドカード前提だったのですね。ドキュメントを読めておりませんでした。

アルファベットやひらがななどはレスポンスが返ってきていたので、正常な気がしていましたが、漏れぬけが発生しておりました。

以前似たような話で、INオペレーターを用いた際に実際の値が大文字であっても何故か小文字で検索かけないとマッチしないことがあったので、このエンドポイントはちょいちょいバグがあるのだと思います…w


これは正直つらいですね…。

p.s.

こちらこそ大変ご無沙汰しております…!!

つい最近書籍を購入し、勉強させていただいております😄

@assi

いけたと思いきや…「*」をつけると、今度は2文字以上の漢字検索があやふやになりました。。

{
 "value": "*高木*",
 "propertyName": "name",
 "operator": "CONTAINS_TOKEN"
}

高しか入っていない会社も検索されます。

{
 "id": "********",
 "properties": {
 "createdate": "2017-01-11T09:06:35.303Z",
 "hs_lastmodifieddate": "2022-09-06T20:14:51.345Z",
 "hs_object_id": "******",
 "name": "株式会社*****高***"
 },
 "createdAt": "2017-01-11T09:06:35.303Z",
 "updatedAt": "2022-09-06T20:14:51.345Z",
 "archived": false
}

@TakuyaAimi

私の方でも色々パターンを試してみまして、少し仕様が掴めて来たのでご共有いたします(挙動からの判断なので正確ではないかもしれませんがおおよそ次のような形かと思います)

まず前提として、CONTAINS_TOKENのオペレーターは、フィルタのvalueにワイルドカードを使わない場合「トークン」という単位でマッチング(検索)を行うようです。

このトークンの基本は英単語のように半角スペースで区切られているもののようで、「You and Me Inc.」の場合は「You/and/Me/Inc.」の4トークンとなり、この会社名をマッチさせるにはフィルタのvalueを「you」「and」「me」「inc」のいずれかにする必要があります。「ou」など、フィルタのvalueが会社名のトークンと完全一致しない場合はマッチしません(検索として引っかかりません)。

このトークンの仕様はカタカナにも共通するようで、以下検証した内容です。

○…レスポンスとして返ってきた

×…レスポンスとして返ってこなかった

フィルタのvalue タリーズ コーヒー ジャパン 株式会社・東京支部 タリーズコーヒージャパン株式会社 スターバックス コーヒー ジャパン
*ジャパン*
ジャパン ×
*タリーズ* ×
タリーズ × ×
*タ*
× × ×

「タリーズ コーヒー ジャパン 株式会社・東京支部」は意図的に単語の間に半角スペースを入れて「タリーズ」「コーヒー」「ジャパン」それぞれがトークンとして成立するようにしていますので、ワイルドカードなしの場合でマッチしています。

なお「タリーズ」を「タ リーズ」とタの後に半角スペースを入れると、フィルタのvalueがワイルドカード無しの「タ」でもマッチしたので、トークンは1文字でも成立するようです。

説明が前後してしまいましたが、この「トークン」という単位をすっ飛ばせると言いますか、かなり拡大して解釈させられるのがワイルドカードの利用のようです。

上記の表のほか、例えば冒頭の「You and Me Inc.」をフィルタのvalue「ou」で検索しても「ou」のトークンはないためマッチしませんが、「*ou*」とワイルドカードを利用することによりマッチさせることができます。

まずここまでの結論として、

  • アルファベットは「ou」など中途半端で単語を形成しない検索の仕方は通常しないと思いますので、ワイルドカードなしで良いのでは
  • カタカナは半角スペースで区切る習慣が日本語にはないため、現実的にワイルドカードの利用が必要

と思います。

次に漢字ですが、どうやら漢字は半角スペースで区切られていなくとも、1文字で1トークンを形成するようです。例えば「株式会社高木」は「株/式/会/社/高/木」と6トークンになります。

そのため、漢字は基本的にワイルドカード無しの利用でも問題ないように思います。

なおフィルタのvalueが2文字以上の場合は、連続を問わないAND検索となるようです。「高木」は「高と木を含む(高と木が連続している必要はない)」ですね。

ワイルドカードを利用した場合、ご返信いただいた内容のようにマッチの仕組みが複雑になっています。何となく、「“*高木*”」は「高を1文字でも含む会社がマッチし、木は無視される」というような感じがしています。以下が検証結果です。

フィルタのvalue 株式会社高木 株式会社木材 有限会社低中高 株式会社木中高
×
*高* ×
高木(高 AND 木) × ×
*高木*(高) ×
木高(木 AND 高) × ×
*木高*(木) ×

最後にひらがなですが、これも基本的に漢字と同様の仕組みのようです。若干力尽きましたが…、検証結果は以下の通りでした。

フィルタのvalue 株式会社あいうえお商事 あおいそら株式会社
*あ*
あい(あ AND い)
*あい*(あ)
あら(あ AND ら) ×
*あら*(あ)

とんでもない仕様ですね!まさかここまで複雑とは思いませんでした……。。。

PS. 拙著のご購入ありがとうございます…!不明点ございましたら、いつでもDMくださいませm(_ _)m

@assi

こんなに丁寧に調べていただきありがとうございます…!!

文字種別によって「*」の有無を判別しないといけないとなると結構ややこしいですね。カタカナや英字と漢字(ひらがな)の混合などになると震えそうです。

他にきちんと検索できるAPIなどあればいいのですが…。

@TakuyaAimi 複数の文字種別混合になると確かにつらいですね……。

とはいえ検索系APIも他にないので、後できるとすれば混合の場合は無理に1つのフィルタのvalueにまとめるのではなく、AND検索やOR検索に分解することや、APIコール自体を複数行い、その複数のレスポンスから更に自前でフィルタするプログラムを書くことかな、と思いました…。

ご参考:Search the CRM - HubSpot docs

  • filterGroupsが複数あれば、filterGroups同士のOR検索になります
  • filterGroups内のfiltersが複数あれば、filters同士のAND検索になります
  • filterGroupsとfiltersはそれぞれ3つずつまでです

@assi

ご提案いただきありがとうございます。

日本語だとSearchAPIは使いにくそうですね…。

@TakuyaAimi
自分の案に固執するわけではないですが、やはり動的リストやワークフローのフィルターですと複数条件でもきちんと絞り込まれている気がしますがどうでしょうか? リストのAPIを見ると"CONTAIN"なのでSearch APIとは違うようです。

"含む"の値 合同会社 共同通信 総合研究所
×
合同 × ×
×
合 or 同
合 AND 同 × ×

※実際に共同通信の方とコンタクトがある訳ではございません
カタカナでも結果同様な感じでした。
いずれにせよ、コンタクトでなく会社リストだとAPIでCRUDができないので、手動で作ってダウンロードして使うしかなさそうですが・・・
こちらも最初は手動ですが、ワークフローを使用してやりたいアクションをするのも可能だと思います。
OperationのPro以上でなければ、邪道かもしれませんが、メールをJSONにパースしてくれるサービスを使用すればワークフローでトリガー、フィルターされたものをコード実行環境に持っていけます。Email to JSON Converter – Parse Incoming Emails to JSON and POST Request Them! | BOTSTER.io
https://mailparser.io/)
件名や本文に特定因子を挟んでメールし、そのリストをフェッチするのも可能だと思います。例)件名:FugaFuga{object ID} にして、後で”件名にFugaFugaを含む”で絞り込む
Twilo×ワークフローでSMS送信してもWebhookは得られると思います。
いずれも動的に最初の検索ができないので、もしそれが必須なら全てをフェッチして絞り込むしかないですよね。Pythonライブラリではget_all()メソッドがありますがお使いの言語ではいかがでしょうか?

@KMurota

ご提案いただきありがとうございます。

Webで検索を実現したいので、リストを使ったやりかただと無理なのかな、と思っていました。

諦めて全フェッチしてからプログラム側で検索かけることにしたいと思います。。

@TakuyaAimi

なるほど。それはリストでは無理ですね。

ただallのメソッドは、レスポンスが遅いのでご注意ください。ライブラリの中身を詳しく見てないのですが、rate limitを気にしてか、リターンが遅いですし、該当箇所のアクセスが多くなったときはエラーになりそう。。。

allがフロント的にだめならHubDBかSQLに会社情報を連携させておくくらいですかね。HubDBの方はallよりレスポンスが速い気がします。会社名とobject idだけ格納すればサイズも減らせますし。