noteで「禍話」に関する記事を全て取得するためにC#でプログラムを作成しました。noteのAPIの扱い方の参考になるかと思い、汎用的になるように加工して公開します。
noteでは、次のURLから検索結果をJSON形式で取得できます。
https://note.com/api/v3/searches?context=note&q=(検索ワード)&sort=(ソート方法)&size=(結果の数)&start=(開始番号)
各種のパラメータの意味は以下の通りです。
- 検索ワード
- 検索したい言葉。URL用にエンコードが必要です。「禍話」を検索する場合は「%E7%A6%8D%E8%A9%B1」となります。
- ソート方法
- 検索結果のソート方法。新しい順であれば「new」と書きます。
- 結果の数
- 検索結果の取得数。最大値は20のはずです。
- 開始番号
- 検索結果全体のうち、どこから取得するか。
次のプログラムは、noteの検索結果をすべて取得し、テキストファイルに出力します。 短期間で連続してnoteのAPIを叩くと、noteのサーバから怒られてしまいます。そのため、時間はかかりますが、Thread.Sleepを挟む必要があります。
using System.IO;
using System.Net.Http;
using System.Text.Json;
using System.Web;
// 待機時間生成用
var random = new Random();
// HTTPクライアント
using var httpClient = new HttpClient();
// note検索の基準値
int start = 0;
// startは20ずつ増加する
const int START_STEP = 20;
// 検索ワード
string? searchQuery = HttpUtility.UrlEncode("検索ワード");
// 出力先
using var sw = new StreamWriter("output.txt");
while(true)
{
// 検索APIを呼び出す
string noteUrlText = $@"https://note.com/api/v3/searches?context=note&q={searchQuery}&sort=new&size=20&start={start}";
HttpResponseMessage httpResponseMessage = await httpClient.GetAsync(noteUrlText);
Console.WriteLine(noteUrlText);
// ステータスコードを出力
Console.WriteLine($"Status Code: {httpResponseMessage.StatusCode}");
// JSONデータを取得
string? jsonContent = await httpResponseMessage.Content.ReadAsStringAsync();
// noteの検索結果を解析する
JsonDocument searchResult = JsonDocument.Parse(jsonContent);
// note_cursor
int cursor = 0;
// dataエレメントがあるか確認
if (searchResult.RootElement.TryGetProperty("data", out JsonElement dataElement))
{
// note_cursorエレメントを確認
if (dataElement.TryGetProperty("note_cursor", out JsonElement noteCursorElement))
{
if (Int32.TryParse(noteCursorElement.GetString(), out int cursorConv))
{
cursor = cursorConv;
}
}
else
{
// note_cursorエレメント取得失敗時はエラーと見なし、中断
Console.WriteLine("失敗");
break;
}
Console.WriteLine($"cursor: {cursor}");
// notesエレメントを確認
if (dataElement.TryGetProperty("notes", out JsonElement notesElement))
{
// contentsエレメントを確認
if (notesElement.TryGetProperty("contents", out JsonElement contentsElement))
{
for (int i = 0; i < (cursor - start); i++)
{
JsonElement contentElementChild = contentsElement[i];
string? authorId = null;
// 題名を取得
if (contentElementChild.TryGetProperty("name", out JsonElement nameElement))
{
sw.WriteLine($"題名\t{nameElement}");
}
// 記事投稿日時を取得
if (contentElementChild.TryGetProperty("publish_at", out JsonElement publishAtElement))
{
if (publishAtElement.TryGetDateTimeOffset(out DateTimeOffset dateTime))
{
sw.WriteLine($"投稿日時\t{dateTime.ToString("yyyy-MM-ddTHH:mm:sszzz")}");
}
}
// userエレメントを取得
if (contentElementChild.TryGetProperty("user", out JsonElement userElement))
{
// ユーザ名を取得
if (userElement.TryGetProperty("nickname", out JsonElement nicknameElement))
{
sw.WriteLine($"作者名\t{nicknameElement.ToString()}");
}
// ユーザのnoteページを取得
if (userElement.TryGetProperty("urlname", out JsonElement urlNameElement))
{
sw.WriteLine($"作者noteページ\thttps://note.com/{urlNameElement}");
authorId = urlNameElement.ToString();
}
}
// note記事のURLを取得
if (contentElementChild.TryGetProperty("key", out JsonElement keyElement))
{
sw.WriteLine($"noteのURL\thttps://note.com/{authorId}/n/{keyElement}");
}
}
}
}
}
// cursorはstartから20増加する値になるはず
if (cursor >= (start + START_STEP))
{
// 次のstartは20増加
start = start + START_STEP;
}
else
{
// 20増加した値にならなかった場合、この周回で検索を終える
break;
}
// 短期間でのnoteへのアクセスを防止するため、ランダム時間だけ待機
Thread.Sleep(random.Next(10000, 15000));
sw.WriteLine();
}