共用方式為


教學課程:建立電話號碼的自訂分析器

在搜尋解決方案中,具有複雜模式或特殊字元的字串可能很難使用,因為 預設分析器 會去除或錯誤解譯模式有意義的部分。 這會導致使用者找不到預期資訊時,搜尋體驗不佳。 電話號碼是難以分析的字串傳統範例。 它們會以各種格式出現,並包含預設分析器忽略的特殊字元。

以電話號碼為主題,本教學使用 Search Service REST API 利用 自訂分析器解決模式化資料問題。 此方法可以直接用於電話號碼,也可以調整以適用於具有相同特性的資料欄位,例如URL、電子郵件、郵遞區號和日期。

在本教學課程中,您會:

  • 了解問題
  • 開發處理電話號碼的初始自訂分析器
  • 測試自訂分析器
  • 重複改進自訂分析器的設計,以進一步改善結果

必要條件

下載檔案

本教學課程的原始程式碼位於 Azure-Samples/azure-search-rest-samples GitHub 存放庫中的 custom-analyzer.rest 檔案中。

複製系統管理金鑰和 URL

本教學課程中的 REST 呼叫需要搜尋服務端點和系統管理 API 金鑰。 您可以從 Azure 入口網站取得這些值。

  1. 登入 Azure 入口網站 ,然後選取您的搜尋服務。

  2. 從左側窗格選擇 「概覽 」並複製端點。 格式應該是: https://my-service.search.windows.net

  3. 從左側窗格選擇 設定>金鑰 ,複製管理員金鑰以獲得服務的完整權限。 可互換的管理金鑰有兩個,可在您需要變換金鑰時提供商務持續性。 你可以在請求中使用任一鍵來新增、修改或刪除物件。

    Azure 入口網站中 URL 和 API 金鑰的螢幕擷取畫面。

建立初始索引

  1. 在 Visual Studio Code 中開啟新的文字檔。

  2. 將變數設定為搜尋端點,以及您在上一節中收集的 API 金鑰。

    @baseUrl = PUT-YOUR-SEARCH-SERVICE-URL-HERE
    @apiKey = PUT-YOUR-ADMIN-API-KEY-HERE
    
  3. 使用 .rest 檔案副檔名來儲存檔案。

  4. 貼上下列範例,以建立名為 phone-numbers-index 且具有兩個字段的小型索引: idphone_number

    ### Create a new index
    POST {{baseUrl}}/indexes?api-version=2025-09-01  HTTP/1.1
    Content-Type: application/json
    api-key: {{apiKey}}
    
    {
      "name": "phone-numbers-index",  
      "fields": [
        {
          "name": "id",
          "type": "Edm.String",
          "key": true,
          "searchable": true,
          "filterable": false,
          "facetable": false,
          "sortable": true
        },
        {
          "name": "phone_number",
          "type": "Edm.String",
          "sortable": false,
          "searchable": true,
          "filterable": false,
          "facetable": false
        }
      ]
    }
    

    因此將預設使用standard.lucene分析器,因為您尚未定義分析器。

  5. 選擇傳送要求。 您應該有 HTTP/1.1 201 Created 回應,而響應主體應該包含索引架構的 JSON 表示法。

  6. 使用包含各種電話號碼格式的文件將資料載入索引。 這是您的測試資料。

    ### Load documents
    POST {{baseUrl}}/indexes/phone-numbers-index/docs/index?api-version=2025-09-01  HTTP/1.1
    Content-Type: application/json
    api-key: {{apiKey}}
    
    {
      "value": [
        {
          "@search.action": "upload",
          "id": "1",
          "phone_number": "425-555-0100"
        },
        {
          "@search.action": "upload",
          "id": "2",
          "phone_number": "(321) 555-0199"
        },
        {
          "@search.action": "upload",
          "id": "3",
          "phone_number": "+1 425-555-0100"
        },
        {
          "@search.action": "upload",
          "id": "4",
          "phone_number": "+1 (321) 555-0199"
        },
        {
          "@search.action": "upload",
          "id": "5",
          "phone_number": "4255550100"
        },
        {
          "@search.action": "upload",
          "id": "6",
          "phone_number": "13215550199"
        },
        {
          "@search.action": "upload",
          "id": "7",
          "phone_number": "425 555 0100"
        },
        {
          "@search.action": "upload",
          "id": "8",
          "phone_number": "321.555.0199"
        }
      ]
    }
    
  7. 請嘗試類似使用者可能輸入的查詢。 例如,使用者可能會以任意數目的格式搜尋 (425) 555-0100 ,但仍預期會傳回結果。 從搜尋 (425) 555-0100開始。

    ### Search for a phone number
    POST {{baseUrl}}/indexes/phone-numbers-index/docs/search?api-version=2025-09-01  HTTP/1.1
    Content-Type: application/json
    api-key: {{apiKey}}
    
    {
      "search": "(425) 555-0100"
    }
    

    查詢會傳回四個預期結果中的三個,但也傳回兩個非預期的結果。

    {
        "value": [
            {
                "@search.score": 0.05634898,
                "phone_number": "+1 425-555-0100"
            },
            {
                "@search.score": 0.05634898,
                "phone_number": "425 555 0100"
            },
            {
                "@search.score": 0.05634898,
                "phone_number": "425-555-0100"
            },
            {
                "@search.score": 0.020766128,
                "phone_number": "(321) 555-0199"
            },
            {
                "@search.score": 0.020766128,
                "phone_number": "+1 (321) 555-0199"
            }
        ]
    }
    
  8. 請再試一次,無格式化:4255550100

    ### Search for a phone number
    POST {{baseUrl}}/indexes/phone-numbers-index/docs/search?api-version=2025-09-01  HTTP/1.1
    Content-Type: application/json
    api-key: {{apiKey}}
    
    {
      "search": "4255550100"
    }
    

    此查詢的表現更差,僅傳回四個正確相符項目的其中一個。

    {
        "value": [
            {
                "@search.score": 0.6015292,
                "phone_number": "4255550100"
            }
        ]
    }
    

如果您發現這些結果令人困惑,那麼您並不孤單。 下一節說明您取得這些結果的原因。

檢閱分析器的運作方式

若要了解這些搜尋結果,您必須瞭解分析器正在執行的動作。 您可以從該處使用 分析 API 來測試預設分析器,並提供設計更符合您需求的分析器的基礎。

分析器全文搜尋引擎的元件,負責處理查詢字串和索引檔中的文字。 視案例而定,不同的分析器會以不同的方式處理文字。 在此案例中,我們需要建置專門用於電話號碼的分析器。

分析器由三個元件組成︰

  • 字元篩選器,會從輸入文字中移除或取代個別字元。
  • Tokenizer,會將輸入文字分成標記,成為搜尋索引中的索引鍵。
  • 權杖篩選器,會操作權杖化工具所產生的權杖。

下圖顯示這三個元件如何一起運作,以標記句子。

權杖化句子的分析器流程圖表

然後,這些詞元會儲存在反向索引中,以實現快速的全文檢索。 反向索引會將在語彙分析期間擷取的所有唯一詞彙對應到其發生所在的文件,以實現全文檢索搜尋。 您可以在下圖中看到範例:

反向索引範例

所有搜尋都會往下一路搜尋儲存在反向索引中的詞彙。 當使用者發出查詢時:

  1. 系統會剖析查詢並分析查詢詞彙。
  2. 系統會掃描反向索引是否有具有相符字詞的檔。
  3. 評分演算法會將擷取的檔排名。

分析器流程次序相似度圖表

如果查詢詞彙不符合反向索引中的詞彙,則不會傳回結果。 若要深入了解查詢的運作方式,請參閱 Azure AI 搜尋中的全文搜索

附註

部分詞彙查詢是此規則的重要例外狀況。 與一般詞彙查詢不同,這些查詢(前置詞查詢、通配符查詢和 regex 查詢)會略過語彙分析程式。 在與索引中的詞彙匹配之前,部分詞彙僅會被轉換為小寫。 如果分析器未設定為支援這些類型的查詢,您通常會收到非預期的結果,因為索引中沒有相符字詞。

使用分析 API 測試分析器

Azure AI 搜尋服務提供分析 API,可讓您測試分析器以了解其如何處理文字。

使用下列要求呼叫分析 API:

### Test analyzer
POST {{baseUrl}}/indexes/phone-numbers-index/analyze?api-version=2025-09-01  HTTP/1.1
Content-Type: application/json
api-key: {{apiKey}}

{
  "text": "(425) 555-0100",
  "analyzer": "standard.lucene"
}

API 會使用您指定的分析器,傳回從文字擷取的權杖。 標準 Lucene 分析器會將電話號碼分割成三個不同的令牌。

{
    "tokens": [
        {
            "token": "425",
            "startOffset": 1,
            "endOffset": 4,
            "position": 0
        },
        {
            "token": "555",
            "startOffset": 6,
            "endOffset": 9,
            "position": 1
        },
        {
            "token": "0100",
            "startOffset": 10,
            "endOffset": 14,
            "position": 2
        }
    ]
}

相反地,格式化為沒有任何標點符號的電話號碼 4255550100 則會權杖化為單一權杖。

{
  "text": "4255550100",
  "analyzer": "standard.lucene"
}

回應:

{
    "tokens": [
        {
            "token": "4255550100",
            "startOffset": 0,
            "endOffset": 10,
            "position": 0
        }
    ]
}

請記住,系統會同時對查詢詞彙和已編制索引的文件進行分析。 回想上一個步驟中的搜尋結果,您可以開始查看傳回這些結果的原因。

在第一個查詢中,傳回非預期的電話號碼,因為其中一個權杖 555 符合您所搜尋的其中一個詞彙。 在第二個查詢中,系統只傳回一個數字,因為這個數字是唯一有權杖符合 4255550100 的記錄。

建置自訂分析器

既然您已瞭解所看到的結果,請建置自定義分析器來改善令牌化邏輯。

我們的目標是要針對電話號碼提供直覺式的搜尋,而不論查詢或索引字串的格式為何。 若要達成此結果,請指定 字元篩選Tokenizer令牌篩選

字元篩選

字元篩選會先處理文字,再將文字送入令牌化程式。 字元篩選的常見用法是篩選出 HTML 元素,並取代特殊字元。

針對電話號碼,您想要移除空格符和特殊字元,因為並非所有電話號碼格式都包含相同的特殊字元和空格。

"charFilters": [
    {
      "@odata.type": "#Microsoft.Azure.Search.MappingCharFilter",
      "name": "phone_char_mapping",
      "mappings": [
        "-=>",
        "(=>",
        ")=>",
        "+=>",
        ".=>",
        "\\u0020=>"
      ]
    }
  ]

篩選器會從輸入中移除 -()+. 和空格。

輸入 輸出
(321) 555-0199 3215550199
321.555.0199 3215550199

權杖化工具

權杖化工具會在過程中將文字分割成多個權杖,並捨棄一些字元,例如標點符號。 在許多情況下,權杖化的目標是要將句子分割成個別單字。

針對此案例,請使用關鍵字權杖化工具 keyword_v2,將電話號碼擷取為單一詞彙。 這不是解決此問題的唯一方法,如 替代方法 一節中所述。

在獲得相同文字時,關鍵字權杖化工具一律會將其輸出為單一詞彙。

輸入 輸出
The dog swims. [The dog swims.]
3215550199 [3215550199]

權杖篩選器

權杖篩選器會修改或篩選出權杖化工具所產生的權杖。 權杖篩選器的其中一個常見用法是使用小寫權杖篩選器將所有字元設為小寫。 另一個常見的用法是篩選出 停用字詞, 例如 theandis

雖然您不需要在此案例中使用任一篩選器,請使用 nGram 權杖篩選器來實現電話號碼的部分搜尋。

"tokenFilters": [
  {
    "@odata.type": "#Microsoft.Azure.Search.NGramTokenFilterV2",
    "name": "custom_ngram_filter",
    "minGram": 3,
    "maxGram": 20
  }
]

NGramTokenFilterV2

nGram_v2 權杖篩選器會根據 minGrammaxGram 參數,將權杖分割成指定大小的 n 元。

針對電話分析器,minGram 被設定為 3,因為使用者預期要搜尋的最短子字串是此。 maxGram 會設定為 20 以確保所有電話號碼 (即使有分機) 都能夠放入單一 n 元。

n 元的不幸副作用是會傳回一些誤判。 您可以在稍後的步驟中修正此問題,方法是為不包含 n-gram 令牌篩選器的搜尋建置個別分析器。

輸入 輸出
[12345] [123, 1234, 12345, 234, 2345, 345]
[3215550199] [321, 3215, 32155, 321555, 3215550, 32155501, 321555019, 3215550199, 215, 2155, 21555, 215550, ... ]

分析器

有了字元篩選、令牌化程式和令牌篩選,您就可以開始定義分析器。

"analyzers": [
  {
    "@odata.type": "#Microsoft.Azure.Search.CustomAnalyzer",
    "name": "phone_analyzer",
    "tokenizer": "keyword_v2",
    "tokenFilters": [
      "custom_ngram_filter"
    ],
    "charFilters": [
      "phone_char_mapping"
    ]
  }
]

根據分析 API,給定以下輸入,自訂分析器的輸出如下所示:

輸入 輸出
12345 [123, 1234, 12345, 234, 2345, 345]
(321) 555-0199 [321, 3215, 32155, 321555, 3215550, 32155501, 321555019, 3215550199, 215, 2155, 21555, 215550, ... ]

輸出資料行中的所有標記都存在於索引中。 如果您的查詢包含任何字詞,則會傳回電話號碼。

使用新的分析器重建

  1. 刪除目前的索引。

    ### Delete the index
    DELETE {{baseUrl}}/indexes/phone-numbers-index?api-version=2025-09-01 HTTP/1.1
    api-key: {{apiKey}}
    
  2. 使用新的分析器重新建立索引。 此索引架構會在電話號碼欄位上新增自訂分析器定義和自定義分析器指派。

    ### Create a new index
    POST {{baseUrl}}/indexes?api-version=2025-09-01  HTTP/1.1
    Content-Type: application/json
    api-key: {{apiKey}}
    
    {
        "name": "phone-numbers-index-2",  
        "fields": [
          {
              "name": "id",
              "type": "Edm.String",
              "key": true,
              "searchable": true,
              "filterable": false,
              "facetable": false,
              "sortable": true
          },
          {
              "name": "phone_number",
              "type": "Edm.String",
              "sortable": false,
              "searchable": true,
              "filterable": false,
              "facetable": false,
              "analyzer": "phone_analyzer"
          }
        ],
        "analyzers": [
            {
              "@odata.type": "#Microsoft.Azure.Search.CustomAnalyzer",
              "name": "phone_analyzer",
              "tokenizer": "keyword_v2",
              "tokenFilters": [
              "custom_ngram_filter"
            ],
            "charFilters": [
              "phone_char_mapping"
              ]
            }
        ],
        "charFilters": [
            {
              "@odata.type": "#Microsoft.Azure.Search.MappingCharFilter",
              "name": "phone_char_mapping",
              "mappings": [
                "-=>",
                "(=>",
                ")=>",
                "+=>",
                ".=>",
                "\\u0020=>"
              ]
            }
          ],
          "tokenFilters": [
            {
              "@odata.type": "#Microsoft.Azure.Search.NGramTokenFilterV2",
              "name": "custom_ngram_filter",
              "minGram": 3,
              "maxGram": 20
          }
        ]
    }
    

測試自訂分析器

重新建立索引之後,請使用下列要求來測試分析器:

### Test custom analyzer
POST {{baseUrl}}/indexes/phone-numbers-index-2/analyze?api-version=2025-09-01  HTTP/1.1
Content-Type: application/json
api-key: {{apiKey}} 

{
  "text": "+1 (321) 555-0199",
  "analyzer": "phone_analyzer"
}

您現在應該會看到由電話號碼生成的標記集合。

{
    "tokens": [
        {
            "token": "132",
            "startOffset": 1,
            "endOffset": 17,
            "position": 0
        },
        {
            "token": "1321",
            "startOffset": 1,
            "endOffset": 17,
            "position": 0
        },
        {
            "token": "13215",
            "startOffset": 1,
            "endOffset": 17,
            "position": 0
        },
        ...
        ...
        ...
    ]
}

修改自訂分析器以處理誤報

使用自定義分析器對索引進行範例查詢之後,您應該會看到召回已改善,而且現在會傳回所有相符的電話號碼。 不過,n 元權杖篩選器也會傳回一些誤判。 這是 n 元權杖篩選器的常見副作用。

若要防止誤判,請建立個別的分析器進行查詢。 此分析器與上一個分析器相同,不同之處在於它會省略 custom_ngram_filter

    {
      "@odata.type": "#Microsoft.Azure.Search.CustomAnalyzer",
      "name": "phone_analyzer_search",
      "tokenizer": "custom_tokenizer_phone",
      "tokenFilters": [],
      "charFilters": [
        "phone_char_mapping"
      ]
    }

在索引定義中,同時指定 indexAnalyzersearchAnalyzer

    {
      "name": "phone_number",
      "type": "Edm.String",
      "sortable": false,
      "searchable": true,
      "filterable": false,
      "facetable": false,
      "indexAnalyzer": "phone_analyzer",
      "searchAnalyzer": "phone_analyzer_search"
    }

完成此變更後,您就已準備就緒。 以下是後續步驟:

  1. 刪除索引。

  2. 在您新增自定義分析器 (phone_analyzer-search) 並將該分析器指派給 phone-number 字段的 searchAnalyzer 屬性之後,重新建立索引。

  3. 重新載入資料。

  4. 重新測試查詢,以確認搜尋是否如預期般運作。 如果您使用範例檔案,此步驟會建立名為 phone-number-index-3 的第三個索引。

替代方法

上一節中所述的分析器設計旨在將搜尋的彈性最大化。 不過,這樣做的代價是,索引中會儲存許多可能不重要的詞彙。

下列範例顯示替代分析器,在令牌化方面更有效率,但它有缺點。

假設輸入 14255550100,分析器無法以邏輯方式將電話號碼區塊化。 例如,其無法將國碼 (地區碼) (1) 與區碼 (425) 進行區隔。 如果使用者未在其搜尋中包含國碼 (地區碼),這項差異會導致系統未傳回電話號碼。

"analyzers": [
  {
    "@odata.type": "#Microsoft.Azure.Search.CustomAnalyzer",
    "name": "phone_analyzer_shingles",
    "tokenizer": "custom_tokenizer_phone",
    "tokenFilters": [
      "custom_shingle_filter"
    ]
  }
],
"tokenizers": [
  {
    "@odata.type": "#Microsoft.Azure.Search.StandardTokenizerV2",
    "name": "custom_tokenizer_phone",
    "maxTokenLength": 4
  }
],
"tokenFilters": [
  {
    "@odata.type": "#Microsoft.Azure.Search.ShingleTokenFilter",
    "name": "custom_shingle_filter",
    "minShingleSize": 2,
    "maxShingleSize": 6,
    "tokenSeparator": ""
  }
]

在下列範例中,電話號碼會分割成您通常預期使用者要搜尋的區塊。

輸入 輸出
(321) 555-0199 [321, 555, 0199, 321555, 5550199, 3215550199]

根據您的需求,這可能是更有效率的問題解決方法。

重要心得

本教學課程示範建置和測試自定義分析器的程式。 您已建立索引、為資料編制索引,然後針對索引進行查詢,以查看傳回的搜尋結果。 您從該處使用了分析 API 來查看語彙分析程序的實際運作過程。

雖然本教學課程中定義的分析器提供了簡單的解決方案來針對電話號碼進行搜尋,但您也可以使用同樣的程序來為共用類似特性的任何案例建立自訂分析器。

清除資源

當您使用自己的訂用帳戶時,最好在專案結束後移除不再需要的資源。 讓資源繼續執行可能會產生費用。 您可以個別刪除資源,或刪除資源群組以刪除整組資源。

您可以使用左側瀏覽窗格中的 [所有資源] 或 [資源群組] 連結,在 Azure 入口網站 中找到和管理資源。

下一步

既然您已瞭解如何建立自定義分析器,請查看所有可用於建置豐富搜尋體驗的不同篩選、令牌化程式和分析器: