画像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=(開始番号)

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

検索ワード
検索したい言葉。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();
}

C#でBloggerページの自動更新

禍話リライトまとめを作る際に、Bloggerのページを自動で更新するプログラムが欲しくなり、Blogger APIを叩くプログラムを自作しました。

下準備: ページIDの取得

ページの自動更新のためには、ブログのIDとページのIDを知っている必要があるようです。

ブログのIDは簡単に確認できます。Bloggerのサイトを開いたときに、自動でリダイレクトされる先のURLを見れば解決します。URLは「https://www.blogger.com/blog/posts/(数字の羅列)」という形式であり、この数字の羅列がブログのIDです。

ただ、ページのIDは簡単には確認できないようでした。そのため、まずはページのIDを調べるプログラムを作成しました。

以下のプログラムは次の2点を予め実施する必要があります。

  • GoogleのAPIの認証のため、APIキーまたはOAuthクライアントIDを作成する (参考: アクセス認証情報を作成する)。作成したAPIキーまたはOAuthクライアントIDはすぐには有効にはならないため注意する。
  • NuGetパッケージで「Google.Apis.Blogger.v3」をインストールする。

このプログラムは、ページのキーを列挙し、そのID、題名、URLを出力します。 この例ではAPIキーを使用していますが、OAuthクライアントIDを使用しても良いです。どうせ必要になることを考えると、OAuthクライアントIDを作成した方が良いかもしれません。


using Google.Apis.Blogger.v3;
using Google.Apis.Services;

// Bloggerサービス
var service = new BloggerService(new BaseClientService.Initializer()
{
    ApiKey = "APIキー",
    ApplicationName = "アプリケーション名"
});

// リクエスト
var request = service.Pages.List("ブログのID");
request.MaxResults = 100;      // 結果の最大数

// レスポンス
var response = request.Execute();

// レスポンスを出力
using (var sw = new StreamWriter("output.txt"))
{
    foreach (var item in response.Items)
    {
        sw.WriteLine(item.Id);
        sw.WriteLine(item.Title);
        sw.WriteLine(item.Url);

        sw.WriteLine();
    }
}

本番: ページの更新

ようやく本題に入ります。以下のプログラムは次の2点を予め実施する必要があります。

  • GoogleのAPIの認証のため、OAuthクライアントIDを作成する (参考: アクセス認証情報を作成する)。“client_secret.json”をダウンロードしておくこと。作成したOAuthクライアントIDはすぐには有効にはならないため注意する。
  • NuGetパッケージで「Google.Apis.Blogger.v3」をインストールする。

このプログラムは、既存のBloggerのページの題名、内容を更新します。


using Google.Apis.Auth.OAuth2;
using Google.Apis.Blogger.v3;
using Google.Apis.Blogger.v3.Data;
using Google.Apis.Services;
using Google.Apis.Util.Store;
using static Google.Apis.Blogger.v3.Data.Page;

namespace BloggerPageUpdateProgram
{
    public class Program()
    {
        public static void Main()
        {
            new Program().Run().Wait();
        }
    
        private async Task Run()
        {
            // Googleの認証
            UserCredential credential;
            using (var stream = new FileStream("client_secret.jsonのパス", FileMode.Open, FileAccess.Read))
            {
                credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
                    GoogleClientSecrets.FromStream(stream).Secrets,
                    new[] { BloggerService.Scope.Blogger },
                    "user",
                    CancellationToken.None,
                    new FileDataStore(this.GetType().ToString())
                );
            }

            // Bloggerサービス
            var service = new BloggerService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName = "アプリケーション名"
            });

            // Blogger接続用のデータ
            var blog = new BlogData();
            blog.Id = "ブログのID";
            
            // Bloggerのページへの書き込みデータ
            var page = new Page();
            page.Kind = "blogger#page";
            page.Blog = blog;
            page.Id = "ページのID";
            page.Title = "ページの題名";
            page.Content = "ページの内容";

            // リクエスト
            var request = service.Pages.Update(page, "ブログのID", "ページのID");

            // レスポンス
            var response = request.Execute();
        }
    }
}