Bing Search APIには何種類かのAPIがあります。今回は画像を検索するBing Image Searchを使って、推しメン画像の自動収集をしてみました。
実行結果はこんな感じです。フォルダ内が可愛い推しメン(乃木坂46の大園桃子さん)画像で溢れて幸せになれます。
Bing Search API v7
まずはBing Search v7を検索して選びます。
Bingリソースを作成します。
準備はこれだけです。キーが発行されるので1か2いずれかをコピーします。
ハマったこと
先にハマったことを書きたいと思います。最初、以下の記事を参考に進めていました。
ライブラリがあるんだな、と何も考えずに以下を追加しました。
<dependency>
<groupId>com.microsoft.azure.cognitiveservices</groupId>
<artifactId>azure-cognitiveservices-imagesearch</artifactId>
<version>1.0.2</version>
</dependency>
記事のコードを参考にキーを設定したのですが、以下のエラーで認証が通りませんでした。
Exception in thread "main" com.microsoft.azure.cognitiveservices.search.imagesearch.models.ErrorResponseException: Status code 401, {"error":{"code":"401","message":"Access denied due to invalid subscription key or wrong API endpoint. Make sure to provide a valid key for an active subscription and use a correct regional API endpoint for your resource."}}
キーの指定方法は正しく、何が原因がさっぱりわかりませんでした。仕方なく認証の部分で使っているBingImageSearchManagerクラスのソースを確認したところ、ライブラリの中で使っているエンドポイントが以下になっていました。
https://api.cognitive.microsoft.com/bing/v7.0/
Bingリソースの画面上では以下のエンドポイントになっています。
https://api.bing.microsoft.com/
ドキュメントを見ると以下のように「Bing Search API は、Cognitive Services プラットフォームから Microsoft.com の下の新しい領域に移行されています。」と警告があるので、古い情報をみてしまっていたようです。
HttpClientでAPIを呼び出す
先ほどのライブラリを使うのは諦めて、HttpClientで呼び出すことにしました。
レスポンスのJSONは以下のような形で色々な情報が含まれています。
{
"_type": "Images",
"instrumentation": {
"_type": "ResponseInstrumentation"
},
"readLink": "images/search?q=大園桃子",
"webSearchUrl": "https://www.bing.com/images/search?q=大園桃子&FORM=OIIARP",
"queryContext": {
"originalQuery": "大園桃子",
"alterationDisplayQuery": "大園桃子",
"alterationOverrideQuery": "+大園桃子",
"alterationMethod": "AM_JustChangeIt",
"alterationType": "CombinedAlterationsChained"
},
"totalEstimatedMatches": 997,
"nextOffset": 38,
"currentOffset": 0,
"value": [
{
"webSearchUrl": "https://www.bing.com/images/search?view=detailv2&FORM=OIIRPO&q=%E5%A4%A7%E5%9C%92%E6%A1%83%E5%AD%90&id=C98F2558D4D0436AFB3E601D2AF41B57CA13EDEC&simid=608037150088111611",
"name": "大園玲のかわいい性格の秘密や大園桃子との関係は?推しメン ...",
"thumbnailUrl": "https://tse1.mm.bing.net/th?id=OIP.GazlCdtTHvJoKftuEEnDbgHaEu&pid=Api",
"datePublished": "2020-05-16T22:19:00.0000000Z",
"isFamilyFriendly": true,
"contentUrl": "https://img.ananweb.jp/2019/12/29195005/2183-beauty1.jpg",
"hostPageUrl": "https://idolevery.com/oozonorei-12180",
"contentSize": "286430 B",
"encodingFormat": "jpeg",
"hostPageDisplayUrl": "https://idolevery.com/oozonorei-12180",
"width": 1300,
"height": 831,
"hostPageDiscoveredDate": "2020-04-20T00:00:00.0000000Z",
"thumbnail": {
"width": 474,
"height": 302
},
"imageInsightsToken": "ccid_GazlCdtT*cp_15E9A25A3E3827AB4EAE1B6B87C4DB2C*mid_C98F2558D4D0436AFB3E601D2AF41B57CA13EDEC*simid_608037150088111611*thid_OIP.GazlCdtTHvJoKftuEEnDbgHaEu",
"insightsMetadata": {
"pagesIncludingCount": 11,
"availableSizesCount": 4
},
"imageId": "C98F2558D4D0436AFB3E601D2AF41B57CA13EDEC",
"accentColor": "A1382A"
},
{
..省略..
}
]
}
とりあえず、contentUrlだけ引っ張れれば良いので、GSONでマッピングするための簡単なクラスを2つ作りました。
package com.example;
public class Value {
private String contentUrl;
public Value(String contentUrl) {
this.contentUrl = contentUrl;
}
public String getContentUrl() {
return contentUrl;
}
public void setContentUrl(String contentUrl) {
this.contentUrl = contentUrl;
}
}
package com.example;
import java.util.List;
public class Result {
private List<Value> value;
public Result() {
}
public Result(List<Value> value) {
this.value = value;
}
public List<Value> getValue() {
return value;
}
public void setValue(List<Value> value) {
this.value = value;
}
}
実装は以下のとおりです。Bing Image Searchで取得したcontentUrlの画像ファイルをダウンロードします。
package com.example;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpClient.Version;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.Paths;
import com.google.gson.Gson;
public class App
{
public static void main( String[] args ) throws URISyntaxException, IOException, InterruptedException
{
HttpClient client = HttpClient.newBuilder()
.version(Version.HTTP_1_1)
.build();
HttpRequest req = HttpRequest.newBuilder(new URI("https://api.bing.microsoft.com/v7.0/images/search?q="
+ URLEncoder.encode("大園桃子", "UTF-8")))
.header("Ocp-Apim-Subscription-Key", "xxxxxxxx")
.GET()
.build();
HttpResponse<String> ret = client.send(req, HttpResponse.BodyHandlers.ofString());
Gson gson = new Gson();
Result result = gson.fromJson(ret.body(), Result.class);
result.getValue().stream().forEach(v -> {
try {
URL url = new URL(v.getContentUrl());
String fileName = Paths.get(url.getPath()).getFileName().toString();
ReadableByteChannel readableByteChannel = Channels.newChannel(url.openStream());
FileOutputStream fileOutputStream = new FileOutputStream(fileName);
fileOutputStream.getChannel().transferFrom(readableByteChannel, 0, Long.MAX_VALUE);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
}
}
実行すると最初に紹介したように画像が取得できます。