miso_soup3 Blog

主に ASP.NET 関連について書いています。

Azure Search で自炊本 PDF を検索

by:dots.女子部 Advent Calendar 2016 - Qiita

自炊した本PDFファイルをいくつか用意し、Azure Search で中身を検索する実装を試してみました。

大まかな流れは、次の通りです。

  • Azure Blob Storage に PDF ファイルを入れる
  • Azure Search を用意する
  • 検索する Web アプリを作成し、HTTP で Azure Search の API から本を検索する

試してみた結果ですが、なぜかいくつかの PDF がインデックスする際にエラーとなってしまい、 原因が分からず、結局 6 冊分しか検索できないアプリになってしまいましたorz

今回使用したツールなど:

  • OS: Windows 10
  • Azure で使うサービス
    • Azure Blob Storage
    • Azure Search
  • IDE: Visual Studio 2017 RC
  • Web Framework: ASP.NET MVC(ASP.NET Code ではなく)
  • C#

GitHub サンプルはこちらgist

作成した Webアプリ

画面

f:id:miso_soup3:20161208170936p:plain

「深層」で検索してみる

f:id:miso_soup3:20161208170943p:plain

Azure Search

Azure Search とは、Azure のサービスの1つで検索 SaaS です。 Azure Search では検索対象となるデータソースを指定できるのですが、そのうちの1つとして、Azure Blob Storage があります。 PDF・Office ファイル・CSV・JSON などを入れた Azure Blob Storage を指定すると、それを検索対象としてインデックスを作成し、HTTP 経由で検索を提供します。 この Azure Blob を対象とする機能は、今までプレビューだったのですが、11月末に一部GAとなりました。

また、PDF の場合は Azure Search が PDF の中のテキストを抽出してくれます。

詳しくはこちら:

以下の表は、各ドキュメント形式に関して実行される処理と、Azure Search によって抽出されるメタデータのプロパティをまとめたものです。 Azure Blob Storage のインデックスを Azure Search で作成する

サンプル PDF ファイルの用意

まず、Azure Blob Storage を用意し、そこにサンプル PDF ファイルをアップロードします。

ローカルにて用意した PDF ファイル群:

f:id:miso_soup3:20161208171025p:plain

アップロードには、「AzCopy」コマンドが便利です。AzCopy を使用してストレージにデータをコピーまたは移動する | Microsoft Docs

AzCopy をインストールし、次のようにコマンドを実行します。

AzCopy /Source:C:\Users\hhyyg\Desktop\books2{←PDFがあるフォルダパス} /Dest:https://{your account}.blob.core.windows.net/{conainer 名} /DestKey:{Azure Blob Storage の key}} /S /SetContentType

アップロード後、Microsoft Azure Storage Explorer 等のツールで Blob の中身を確認します。

f:id:miso_soup3:20161208171035p:plain

なぜここで、ファイル名が数字になっていて、ファイル数が6つしかないのか、というと、この後の「インデックス」作成の手順でエラーが発生したからです。 エラーについては最後に記載しました。

Azure Search の用意

次に Azure Search の用意です。Azure ポータルにて、Azure Search を作成した後、「データのインポート」を選択します。

f:id:miso_soup3:20161208171135p:plain

データの接続で、先ほど PDF をアップロードした Azure Blob Storage を選択します。

f:id:miso_soup3:20161208171146p:plain

すると、通知欄に”データソースをサンプリングしています”と表示されます。

f:id:miso_soup3:20161208171154p:plain

ここで成功すると、「インデックス」を設定するフォームが表示されます。

f:id:miso_soup3:20161208171201p:plain

↑の画像では、インデックス名が「temp」となっていますが、「booksindex」みたいに分かりやすい名前を付けます。この「インデックス名」は、後のコードで参照します。

設定の内容ですが、画像のように、「content」以外を「取得可能」にチェック、「content」のみを「検索可能」にチェックします。

f:id:miso_soup3:20161208171210p:plain

「content」には、PDF から抽出したテキストが入るため、膨大なサイズの値になります。これを「取得可能」にしてしまうと HTTP でアクセスした際に膨大な値がレスポンスに入ってしまいます。なのでチェックを外します。 また、「content」の「検索可能」にチェックを入れるのは、PDFから抽出したテキストを検索対象とするためです。

次に、「アナライザー」タブをクリックし、「content」の「アナライザー」を、「日本語 - Lucene」に設定します。これは、「日本語 - Micorosoft」でもかまいません。 PDF の内容が日本語であれば、このどちらかを選択するのが良いと思います。

f:id:miso_soup3:20161208171218p:plain

次は、「インデクサー」の作成です。ここでは、「1度」を選択します。 Azure Blob Storage の内容が変更になる可能性がある場合は、「毎時間」や「毎日」を選択し、インデックスの内容を更新するようにします。

f:id:miso_soup3:20161208171225p:plain

操作が完了すると、「インデクサー」の画面から成否が表示されます。ここで成功となれば、Azure Search の準備は完了です。

f:id:miso_soup3:20161208171229p:plain

検索してみる

検索の結果は Azure のポータルから確認できます。Azure Search の「Search エクスプローラー」を開き、「検索」をクリックすると検索結果を確認できます。

f:id:miso_soup3:20161208171235p:plain

結果として、metadata_storage_name PDF のファイル名や、metadata_storage_path Azure Blob Storage の URL(base64で変換されています。)が格納されていることを確認できます。 アプリを作成する場合は、この URL を SDK などで叩いて HTTP アクセスにより検索結果を得ます。

ここで、実際の PDF のテキスト内容が格納されていない理由は、先ほどの「インデックス」の設定で「content」の「取得可能」にチェックを入れなかったためです。 もし、チェックと入れていると、ここで大量のテキストデータがここに出力されることになります。

Web アプリを作成

検索を試してみたところで、Web アプリを作成します。今回は ASP.NET MVC 5 で作成しましたが、HTTP 経由で Azure Search にアクセスするので他のプラットフォーム・フレームワークでも作成できます。

Visual Studio で ASP.NET MVC 5 のベースを作成した後、NuGet パッケージ「Microsoft.Azure.Search」をインストールします。 インストール後、Web.config にて、次のようにキーを設定します。

このキーの名前は、後の実装によるので、実装とともに変更して構いません。キーの値は、ポータルより確認できます。

f:id:miso_soup3:20161208171244p:plain

サンプルは GitHub にありますので、ここでは注意点だけ記載します。

ハイライト

検索結果をハイライトして表示するには、リクエストを送信する際にどのフィールドをハイライトしたいか指定します。 Azure Search SDK を使った今回は、次のように「content」をハイライト対象に設定しました。

return new SearchParameters()
{
    SearchMode = SearchMode.Any,
    IncludeTotalResultCount = true,
    HighlightFields = new string[] { "content" },
    Top = 20
};
base64 デコード

レスポンスにある「metadata_storage_path」フィールドには、Azure Blob Storage の URL がエンコードされて格納されます。 実際に PDF を取得するには、この Azure Blob Storage の URL にアクセスする必要があるのですが、デコードは次のようにします。

System.Text.Encoding.UTF8.GetString(HttpServerUtility.UrlTokenDecode("{metadata_storage_path の値}".ToString()));

エラー

インデックスの際のエラーですが、発生した原因は分からず、以下の可能性が怪しいとみています。

  • ファイル名が日本語だから?
  • PDF ファイル内のコンテンツが日本語?変な値がある?

f:id:miso_soup3:20161208171041p:plain

エラーメッセージ: 説明データ ソースからインデックス スキーマを削除するときにエラーが発生しました: "Error processing blob 'https://misobooksst.blob.core.windows.net/mycontainer/180日でグローバル人材になる方法.pdf' with content type '': 422

Azure Search では日本語関連の問題に出会うことが多いです。 現在このエラーの原因の調査をお願いしています。(12/9)

Azure Search で CSV 検索

azure.microsoft.com

Azure Search にて、Azure Storage Blob においてある CSV ファイルの行検索が可能になりました(version 2015-02-28-Preview にて可能、プレビューでの提供です)。 今までは行・列単位ではなくファイル単位での検索しかできませんでした。

主な機能
  • Azure Storage の Blob にある CSV ファイルを対象にできる。
    • コンテナー、フォルダーの指定が可能です。
  • コンテナーまたはフォルダー内にある CSV はすべて対象となります。(複数ファイルごそっと配置できます)
  • UTF-8、カンマ(,)区切りのみ対応。
  • CSVは、ヘッダー行有り・無し、の両方対応しています。
注意
  • Shift-JIS で保存すると日本語が文字化けします
  • 指定したコンテナー、またはフォルダ内には、CSVファイル以外のファイルを混在できません。
  • プレビューなので、URL に ?api-version=2015-02-28-Preview を付けることを忘れない。
  • CR, LF のみの改行コードだとダブルクォーテーションで囲ったとしてもエラーになります。
主な流れ
  • データソースの作成(CSV ファイルのある Azure Blog Storage、コンテナー、フォルダー名の指定)
  • インデックスの作成(CSV ファイルの列名をフィールドとして設定します)
  • インデクサーの作成(CSV ファイルとインデックスをつなげるインデクサーを作成します)
  • 検索実行

実際にやってみたので手順を記載します。

サンプルCSVの用意

サンプルとして下の場所から郵便番号のCSVファイルを用意しました。

vallog: 無料CSVデータ 住所データCSV【住所.jp】

このCSVファイルは、ヘッダー行が日本語になっています。Azure Search では対応していないので、英数のヘッダーに置き換えました。 この英数のヘッダー名は後の手順で参照することになります。

f:id:miso_soup3:20160708182535p:plain f:id:miso_soup3:20160708182538p:plain

CSV ファイルは、Azure Storage の Blog に配置します。 ここでは、コンテナー名「yago-test」の「zipcode」フォルダ内に配置しました。

f:id:miso_soup3:20160708182708p:plain

データソースの作成

Azure Search をプロビジョニングした後、データソースを作成します。 API は次のようになります。 Azure Search の API については、Azure Search Service REST API バージョン 2015-02-28-Preview) | Microsoft Azure | Azure Search Preview API こちらを参照します。

POST https://[サービス名].search.windows.net/datasources?api-version=2015-02-28-Preview
Content-Type: application/json
api-key: キー

{
    "name" : "my-blob-zipcode(データソースの名前。なんでもよいですがあとで参照します。)"",
    "type" : "azureblob",
    "credentials" : { "connectionString" : "Azure Storage の接続文字列" },
    "container" : { "name" : "yago-test(コンテナ名)", "query" : "zipcode(フォルダ名(ある場合))" }
}

作成後、Azure ポータルではこのように表示されます。

f:id:miso_soup3:20160708182551p:plain

インデックスの作成

つぎにインデックスを作成します。 ここで、CSV ファイルの各列について記述することになります。CSV ファイルの全ての列を定義する必要はありません。 取得可能や検索可能といった仕様に応じて定義します。

また、CSV ファイルにヘッダー行がある場合は、このヘッダー名とここで定義するフィールド名は同じにしなければいけません。

例として、次のようにインデックスを作成しました。

POST https://[サービス名].search.windows.net/indexes?api-version=2015-02-28-Preview
Content-Type: application/json
api-key: キー

{ "name": "zipcode-index", "fields": 
 [  {"name": "AddressCode", "type": "Edm.String", "key": true },
    { "name": "Prefecture", "type": "Edm.String" },
    { "name": "ZipCode", "type": "Edm.String" },
    { "name": "PrefectureKana", "type": "Edm.String" },
    { "name": "City", "type": "Edm.String" },
    { "name": "CityKana", "type": "Edm.String" },
    { "name": "Area", "type": "Edm.String" },
    { "name": "AreaKana", "type": "Edm.String" }
  ]
}

f:id:miso_soup3:20160708182600p:plain

インデクサーの作成

次にインデクサーの作成です。ヘッダー行がある場合・無い場合で Body の Json が変わります。

ヘッダー行がある場合
POST https://[service name].search.windows.net/indexers?api-version=2015-02-28-Preview
Content-Type: application/json
api-key: [admin key]

{
  "name" : "my-zipcode-indexer",
  "dataSourceName" : "my-blob-zipcode",
  "targetIndexName" : "zipcode-index",
  "parameters" : { "configuration" : { "parsingMode" : "delimitedText", "firstLineContainsHeaders" : true } }
}

"firstLineContainsHeaders" : true が、ヘッダー行があることを意味します。

ヘッダー行がない場合
POST https://[service name].search.windows.net/indexers?api-version=2015-02-28-Preview
Content-Type: application/json
api-key: [admin key]

{
  "name" : "my-zipcode-indexer",
  "dataSourceName" : "my-blob-zipcode",
  "targetIndexName" : "zipcode-index",
  "parameters" : { "configuration" : { "parsingMode" : "delimitedText", "delimitedTextHeaders" : "AddressCode,PrefectureCode,CityCode,Address1Code,ZipCode,Flag1,Flag2,Prefecture,PrefectureKana,City,CityKana,Area,AreaKana,AreaNote,Torina,Cho,ChoKana,Note,Office,OfficeKana,OfficeAddress,NewAddressCode" } }
}

delimitedTextHeaders には、CSV ファイルの全ての列名を定義する必要があります。 この列名とインデックスのフィールド名が照会し検索が動作します。

インデクサーの実行

インデクサーが作成されると、データのインデックスが行われます。 Azure ポータルから結果を確認できます。エラーがある場合はエラーの内容を確認します。

f:id:miso_soup3:20160708182610p:plain

検索

これで検索の準備ができました。 「富山市」で検索すると、結果が返ってきました。

GET https://yagosear2.search.windows.net/indexes/zipcode-index/docs?api-version=2015-02-28&search=フチュウ
Content-Type: application/json
api-key: キー

f:id:miso_soup3:20160708183507p:plain

検索は Azure ポータルからも試すことができます。(日本語での検索は結果を確認できなかったので API で確認しました。)

f:id:miso_soup3:20160708182646p:plain

(日本語での検索は、アナライザーを日本語用のアナライザーにすることを忘れないようにします。)

カンマ以外の対応が欲しい場合、ほか要望は https://feedback.azure.com/forums/263029-azure-search こちらでフィードバックします。