SearchGalleryQueryService と NuGet.Services.Search.Client
https://api.nuget.org/v3/index.json に書いてはあるけど実態がよくわかっていなかった SearchGalleryQueryService についての話。以前の検索 API の話と同じくソースコードは NuGet.Services.Search にあります。
で、 SearchQueryService と SearchGalleryQueryService の違いですが、前者は NuGet.Services.BasicSearch、後者は NuGet.Services.Search というプロジェクト名になっています。指定できるパラメータやレスポンスに違いはありますが、検索システム自体は同じものを使っています。
SearchGalleryQueryService 下のエンドポイント
index.json によると、公式の SearchGalleryQueryService は https://api-search.nuget.org/ で公開されています。その JSON を見てみると
{ "self": "https://api-search.nuget.org:443/", "services": { "search": "https://api-search.nuget.org/search" }, "versions": { "search": { "branch": "master", "commit": "dcc9fd6b93ff385369e7cf70558c8da71efe4b65", "buildDateUtc": "2014-08-27T00:09:33Z", "version": "3.0.43-r-master" } } }
とあるので、さらに https://api-search.nuget.org/search を見てみると
{ "name": "nuget-prod-0-search_IN3-search", "service": "search", "resources": { "range": "https://api-search.nuget.org/search/range", "fields": "https://api-search.nuget.org/search/fields", "console": "https://api-search.nuget.org/search/console", "diagnostics": "https://api-search.nuget.org/search/diag", "segments": "https://api-search.nuget.org/search/segments", "query": "https://api-search.nuget.org/search/query" } }
と API の一覧を取得することが出来ました。というわけで、これらを順番に解説していきます。
query
メインの API です。というかこれ以外は // Admin endpoints
と書かれているのでこれ以外を使うことはないと思います。
パラメータ
- q
- 検索クエリ。省略すると sortBy に応じてすべてのパッケージからいい感じに取得してきます
- projectType
- Azure のストレージに保存されているファイルのデータからランキングを取得してくるのに使うっぽいが正体をつかめなかった……
- sortBy
- 指定なし、または lastEdited, published, title-asc, title-desc のいずれか
- luceneQuery
- false を指定すると(つまりデフォルト true)複雑なクエリが使えるようになります。ここに書くと厄介なので下に解説書いておきました。
- prerelease
- true を指定すると pre-release パッケージも検索結果に含まれます。と思ったら指定しなくても入ってきてるんだけどなんでだ……
- countOnly
- true を指定すると data フィールドが返ってこなくなります
- feed
- https://www.nuget.org/api/v2/curated-feeds/microsoftdotnet/ とかのやつ。
microsoftdotnet
と指定したらうまく動いたけど、そもそもこの curated-package とやらを一般人が作る方法がわからない…… - skip
- 最初に飛ばす件数
- take
- 取得する件数。デフォルト 20
- explanation
- true を指定すると executedQuery, diagnostics フィールドが追加されます
- ignoreFilter
- true を指定すると prerelease と feed を無視します。後で書くクライアントライブラリのほうでは getAllVersions という表記になってるのですが、すべてのバージョンが入ってくるかどうかは luceneQuery のほうが影響していて、こっちで挙動変わらないんですがこれは一体……
- supportedFramework
- 対応しているフレームワークから絞り込みます。ちゃんと動きます
luceneQuery=false 時の q
このとき通常の文字列を入れればタイトル検索になりますが、 property:value
という形式でプロパティの値で検索することができます。空白で区切られます。指定できるプロパティは id, packageid, version, author, authors, owner, owners, tag, tags です。 : がプロパティ指定になるのを回避するには "" で括ります。
例
https://api-search.nuget.org/search/query?q=author:azyobuzin&luceneQuery=false&sortBy=published&explanation=true&take=1
{ "totalHits": 6, "timeTakenInMs": 3, "index": "ng-search-index-aug26-2014", "indexTimestamp": "3/12/2015 2:50:08 PM", "executedQuery": "rank(+(+Authors:azyobuzin) +Facet:l_s<>)", "data": [ { "Key": 492105, "PackageRegistrationKey": 56163, "PackageRegistration": { "Key": 56163, "Id": "NuGetCalc", "DownloadCount": 19, "Owners": [ "azyobuzin" ] }, "Version": "1.3", "NormalizedVersion": "1.3.0", "Title": "NuGetCalc", "Description": "PS functions to find dependency information of nupkgs", "Summary": null, "Authors": "azyobuzin", "Copyright": "Copyright 2014-2015 azyobuzin", "Language": null, "Tags": "NuGet", "ReleaseNotes": null, "ProjectUrl": "https://github.com/azyobuzin/NuGetCalc", "IconUrl": null, "IsLatest": true, "IsLatestStable": true, "Listed": true, "Created": "2015-03-07T16:01:48.957", "Published": "2015-03-07T16:01:48.973", "LastUpdated": "2015-03-07T16:05:49.387", "LastEdited": "2015-03-07T16:05:49.387", "DownloadCount": 19, "FlattenedDependencies": "", "Dependencies": [], "SupportedFrameworks": [], "MinClientVersion": null, "Hash": "10htu6SSb2sZ0b4HThaO0UrfYmmxjXy9Cw56NSJM/qcZ1tMQANBOZtBNuIPwFq1w3WP1/ZG0Nqzu4F3Whnyj/A==", "HashAlgorithm": "SHA512", "PackageFileSize": 645232, "LicenseUrl": "https://github.com/azyobuzin/NuGetCalc/blob/26f96bcb60988d1eee60a7171afb92702b76379f/LICENSE.md", "RequiresLicenseAcceptance": false, "LicenseNames": null, "LicenseReportUrl": null, "HideLicenseReport": false, "Installs": 0, "Updates": 0, "diagnostics": { "Score": "NaN", "Explanation": "11.73407 = (MATCH) rank(+(+Authors:azyobuzin) +Facet:l_s<>), product of:\n 11.73407 = (MATCH) sum of:\n 11.08049 = (MATCH) weight(Authors:azyobuzin in 3668), product of:\n 0.9717512 = queryWeight(Authors:azyobuzin), product of:\n 11.40259 = idf(docFreq=14, maxDocs=494175)\n 0.08522194 = queryNorm\n 11.40259 = (MATCH) fieldWeight(Authors:azyobuzin in 3668), product of:\n 1 = tf(termFreq(Authors:azyobuzin)=1)\n 11.40259 = idf(docFreq=14, maxDocs=494175)\n 1 = fieldNorm(field=Authors, doc=3668)\n 0.6535816 = (MATCH) weight(Facet:l_s<> in 3668), product of:\n 0.2360074 = queryWeight(Facet:l_s<>), product of:\n 2.769327 = idf(docFreq=84230, maxDocs=494175)\n 0.08522194 = queryNorm\n 2.769327 = (MATCH) fieldWeight(Facet:l_s<> in 3668), product of:\n 1 = tf(termFreq(Facet:l_s<>)=1)\n 2.769327 = idf(docFreq=84230, maxDocs=494175)\n 1 = fieldNorm(field=Facet, doc=3668)\n 1 = queryBoost\n", "IdTerms": [ "nugetcalc (0,9)" ], "TokenizedIdTerms": [ "calc (5,9)", "get (2,5)", "getcalc (2,9)", "nu (0,2)", "nuget (0,5)" ], "ShingledIdTerms": [ "nugetcalc (0,9)" ], "VersionTerms": [ "1.3.0 (0,3)" ], "TitleTerms": [ "calc (5,9)", "get (2,5)", "getcalc (2,9)", "nu (0,2)", "nuget (0,5)" ], "TagsTerms": [ "nuget (0,5)" ], "DescriptionTerms": [ "dependency (21,31)", "find (16,20)", "functions (3,12)", "information (32,43)", "nupkgs (47,53)", "ps (0,2)" ], "AuthorsTerms": [ "azyobuzin (0,9)" ], "OwnersTerms": [ "azyobuzin (0,9)" ], "PublishedDate": 20150307, "EditedDate": 20150307, "CuratedFeed": [], "Key": 492105, "Checksum": 17428532, "ProjectGuidRankings": [ { "Id": 0 }, { "Version": 0 }, { "Checksum": 17428532 }, { "Data": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 }, { "Facet": 0 } ] } } ], "answeredBy": "nuget-prod-0-search_IN2-search" }
console
query API をブラウザでテストできる便利ツールです。
https://api-search.nuget.org/search/console/
その他
内部情報過ぎてよくわかりませんでした。。。
NuGet.Services.Search.Client
そんなこんなであんまり理解できなかった API 構成ですが、ラッパーライブラリがあります。
使い方は単機能なので難しいことはないです。説明することも特に無いので CoreTweet が Silverlight5 にインストールできた最後のバージョンでも眺めてみることにしましょう。なお、 SearchClient.Search の引数のデフォルト値は API のデフォルト値とは違う場合があるので注意してください。
using System; using Newtonsoft.Json.Linq; using NuGet.Services.Client; using NuGet.Services.Search.Client; using NuGet.Services.Search.Models; var client = new SearchClient(new Uri("https://api.nuget.org/v3/index.json")); ServiceResponse<SearchResults> result = client.Search("CoreTweet", supportedFramework: "sl5").Result; SearchResults content = result.ReadContent().Result; foreach (JObject item in content.Data) Console.WriteLine("{0} {1}", (string)item["Title"], (string)item["Version"]);
CoreTweet 0.3.4 CoreTweet.Streaming.Reactive 0.3.4 CoreTweetSupplement 1.1
はい、簡単ですね。しかしなぜ検索 API が 2 種類もあるのでしょうね……。謎。