Qiitaの記事一覧を認証付きのAPI以外で取得したい

## はじめに

自分が働いているチームの取り組みとして、「パブリックな場での発信を計測しよう」ということで同チームメンバーが直近2週間に投稿した記事を確認できる画面を作りたいと思いました。
自分的には気になった時、ちょろっと確認したいくらいなので`作成に30分以上はかけたくないな`と思っており、フロントエンドだけでサクッと作ることにしました。

## Atom フィードを使う
サクッと作るにあたらQiita APIだと認証やトークン管理などしんどいなと思いました。
なのでAtomフィードを使って記事の更新情報を取得することにしました。

### Atom フィードとは

Atomフィードとは、ウェブサイトの更新情報を配信するためのXMLベースのデータフォーマットで、RSSフィードと同様に新しい記事やコンテンツの更新情報を提供します。

例えばQiitaの場合、`https://qiita.com/{アカウント名}/feed.atom`のようにアクセスすると以下のようなXML構造のデータが取得できます。

```xml
<?xml version="1.0" encoding="UTF-8"?>
<feed xml:lang="ja-JP" xmlns="http://www.w3.org/2005/Atom">
  <id>tag:qiita.com,2005:/OtsukaTomoaki/feed</id>
  <link rel="alternate" type="text/html" href="https://qiita.com"/>
  <link rel="self" type="application/atom+xml" href="https://qiita.com/OtsukaTomoaki/feed.atom"/>
  <title>OtsukaTomoakiの記事 - Qiita</title>
  <description>QiitaでユーザーOtsukaTomoakiによる最近の記事</description>
  <updated>2024-11-02T18:38:36+09:00</updated>
  <link>https://qiita.com/OtsukaTomoaki</link>
  <entry>
    <id>tag:qiita.com,2005:PublicArticle/1930999</id>
    <published>2024-11-02T18:38:36+09:00</published>
    <updated>2024-11-04T17:11:55+09:00</updated>
    <link rel="alternate" type="text/html" href="https://qiita.com/OtsukaTomoaki/items/57032c0371663baf7313"/>
    <url>https://qiita.com/OtsukaTomoaki/items/57032c0371663baf7313</url>
    <title>Auth0 management APIでClientを登録する</title>
    <content type="html">0.はじめに
以前、こんな記事を書きましたが、Auth0でClient自体をAPIで登録する方法について追加で調べました。
1. management APIの Domain, Client ID,…</content>
    <author>
      <name>OtsukaTomoaki</name>
    </author>
  </entry>
  <entry>
    <id>tag:qiita.com,2005:PublicArticle/1930552</id>
    <published>2024-11-01T15:44:53+09:00</published>
    <updated>2024-11-03T12:59:02+09:00</updated>
    <link rel="alternate" type="text/html" href="https://qiita.com/OtsukaTomoaki/items/1b257e7ab17fe7dc4664"/>
    <url>https://qiita.com/OtsukaTomoaki/items/1b257e7ab17fe7dc4664</url>
    <title>そのクエリちゃんとインデックス使えている？</title>
    <content type="html">0.はじめに
最近開発中に特定の機能を使用するとDBのCPUが跳ね上がる現象に出くわしました。
詳しくSQLクエリを見ていくと「これインデックス貼っているのに正しく使われてねーじゃん」というオチだっ…</content>
    <author>
      <name>OtsukaTomoaki</name>
    </author>
  </entry>
  <entry>
    <id>tag:qiita.com,2005:PublicArticle/1908192</id>
    <published>2024-09-05T23:13:08+09:00</published>
    <updated>2024-09-06T09:01:18+09:00</updated>
    <link rel="alternate" type="text/html" href="https://qiita.com/OtsukaTomoaki/items/916be8ff24ecd822e2b5"/>
    <url>https://qiita.com/OtsukaTomoaki/items/916be8ff24ecd822e2b5</url>
    <title>Auth0でClientCredentialsFlowを試す</title>
    <content type="html">0.初めに
OAuth2.0ではauthorization code flowが有名ですが、認証するユーザが存在しない場合はClientID, ClientSecretを使ってアクセストークンを発行…</content>
    <author>
      <name>OtsukaTomoaki</name>
    </author>
  </entry>
  <entry>
    <id>tag:qiita.com,2005:PublicArticle/1908179</id>
    <published>2024-09-05T22:12:35+09:00</published>
    <updated>2024-09-06T12:41:52+09:00</updated>
    <link rel="alternate" type="text/html" href="https://qiita.com/OtsukaTomoaki/items/2f64ec208001855dd985"/>
    <url>https://qiita.com/OtsukaTomoaki/items/2f64ec208001855dd985</url>
    <title>ストラングラーフィグパターンとは？～レガシーシステムからの移行～</title>
    <content type="html">はじめに
チームが運用しているシステムが、何年も前に作られたレガシーなアプリケーションだとしたらどうでしょう？ユーザーは不便を感じ、開発者は技術的負債に悩まされ、新しい機能を追加するたびに思わぬバグ…</content>
    <author>
      <name>OtsukaTomoaki</name>
    </author>
  </entry>
</feed>
```

## ブラウザからリクエストしてAtomフィードを取得する
前述した通り、サーバーサイドでAPIを書くなんてことはしたくなかったのでフロントエンドのコードだけで（GPTが）書きました。
処理も簡易的なものなので懐かしのjQueryでAtomフィードの取得を行っています。
今回、普通に取得しようとするだけではうまくいかなかったので失敗パターンと成功パターンを記載します。

### 失敗パターン
feed.atomをajaxで直接呼び出しました。
エラーを見てみたら当たり前のことでしたが、CORSでエラーになっていました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2527942/7ed25d4a-821c-2613-4584-730d2ff3c793.png)


```html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Qiita Articles</title>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script>
    <script>
        $(document).ready(function() {
            const feedUrl = 'https://qiita.com/OtsukaTomoaki/feed.atom';

            $.ajax({
                url: feedUrl,
                type: 'GET',
                dataType: 'xml',
                success: function(data) {
                    const $entries = $(data).find('entry');
                    const $articlesDiv = $('#articles');
                    $articlesDiv.empty();

                    $entries.each(function() {
                        const $entry = $(this);
                        const title = $entry.find('title').text();
                        const link = $entry.find('link[rel="alternate"]').attr('href');
                        const published = $entry.find('published').text();
                        const summary = $entry.find('content').text();

                        const articleHtml = `
                            <div class="article">
                                <h2><a href="${link}" target="_blank">${title}</a></h2>
                                <p><strong>Published:</strong> ${published}</p>
                                <p>${summary}</p>
                            </div>
                            <hr>
                        `;

                        $articlesDiv.append(articleHtml);
                    });
                },
                error: function() {
                    $('#articles').html('<p>Failed to load articles.</p>');
                }
            });
        });
    </script>
</head>
<body>
    <h1>Qiita - Multiple Accounts Articles</h1>
    <div id="articles">
        <p>Loading...</p>
    </div>
</body>
</html>
```

### 成功パターン

調べてみると[rss2json](https://rss2json.com/)というサービスを使えばCORSに引っかからないようなのでこのAPIを利用してみます。
`rss_url`にatomフィードを取得するURLを指定すれば良さそうです。
レスポンスはjson形式になるようなのでjsonから取得するようにします。
（その他、細かい差分もありますが無視してください🙏）

```html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Qiita Articles</title>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script>
        $(document).ready(function() {
            const accounts = ['OtsukaTomoaki'];
            const $articlesDiv = $('#articles');
            $articlesDiv.empty();

            accounts.forEach(function(account, index) {
                const feedUrl = `https://qiita.com/${account}/feed.atom`;
                const apiUrl = `https://api.rss2json.com/v1/api.json?rss_url=` + encodeURIComponent(feedUrl);

                $.getJSON(apiUrl, function(data) {
                    if (data.items) {
                        const now = new Date();
                        const twoWeeksAgo = new Date();
                        twoWeeksAgo.setDate(now.getDate() - 14);

                        const filteredItems = data.items.filter(function(item) {
                            const pubDate = new Date(item.pubDate);
                            return pubDate >= twoWeeksAgo;
                        });

                        const articleCount = filteredItems.length;

                        if (articleCount > 0) {
                            const accountClass = `account-${index + 1}`;
                            const accountSection = `
                                <div class="account-section ${accountClass}">
                                    <div class="account-header">${account} - ${articleCount} articles in the last 2 weeks</div>
                                    <div class="account-articles"></div>
                                </div>
                            `;
                            $articlesDiv.append(accountSection);

                            const $accountArticlesDiv = $articlesDiv.find('.account-section:last .account-articles');
                            filteredItems.forEach(function(item) {
                                const articleHtml = `
                                    <div class="article">
                                        <h2><a href="${item.link}" target="_blank">${item.title}</a></h2>
                                        <p><strong>Published:</strong> ${item.pubDate}</p>
                                        <p>${item.description}</p>
                                    </div>
                                `;
                                $accountArticlesDiv.append(articleHtml);
                            });
                        } else {
                            $articlesDiv.append(`<p>No articles found for ${account} in the last 2 weeks.</p>`);
                        }
                    } else {
                        $articlesDiv.append(`<p>No articles found for ${account}.</p>`);
                    }
                }).fail(function() {
                    $articlesDiv.append(`<p>Failed to load articles for ${account}.</p>`);
                });
            });
        });
    </script>
</head>
<body>
    <h1>Qiita - Multiple Accounts Articles</h1>
    <div id="articles">
        <p>Loading...</p>
    </div>
</body>
</html>
```

無事取得できました！
記事を取得したいアカウントを追加してcssを整えてあげればある程度見られるようになりそうです...！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2527942/464b218f-6c46-8802-c061-68b6e8bf12d3.png)

## おわりに

RSSやAtomのこと詳しくないですが、存在だけ知っておくといざという時便利ですね。