画像RSS

2025年12月14日日曜日

C#でnoteの検索結果を全て取得する

noteで「禍話」に関する記事を全て取得するためにC#でプログラムを作成しました。noteのAPIの扱い方の参考になるかと思い、汎用的になるように加工して公開します。

noteでは、次のURLから検索結果をJSON形式で取得できます。

https://note.com/api/v3/searches?context=note&q=(検索ワード)&sort=(ソート方法)&size=(結果の数)&start=(開始番号)

各種のパラメータの意味は以下の通りです。

q=(検索ワード)
検索したい言葉。URL用にエンコードが必要です。「禍話」を検索する場合は「%E7%A6%8D%E8%A9%B1」となります。
sort=(ソート方法)
検索結果のソート方法。新しい順であれば「new」と書きます。
size=(結果の数)
検索結果の取得数。最大値は20のはずです。
start=(開始番号)
検索結果全体のうち、どこから取得するか。

次のプログラムは、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();
}

0 件のコメント:

コメントを投稿