見出し画像

生成AIで爆速ツール作成!noteのPVとスキを一気に集計してみた【#今年学んだこと】

こんにちは、@ultaroです!

今年のハイライト、それはもう生成AIの活用です!
「AIと一緒にコーディングしたら、めちゃくちゃ速くて便利…!」と気づいたのが、個人的にはかなりの衝撃でした。

さて、そんな生成AIとタッグを組んで作ったのが、noteの週間のPVとスキを集計できるツールです。やろうと思えば手動で集計できなくもないけど、面倒ですから、こういうことは自動化が一番です。しかも生成AIを使ったら、自動化ツールがさくっとできてしまいました!

この記事では、ツールを作った経緯や手順を紹介しつつ、生成AIと爆速開発した感想を共有します。


noteの集計ツールを作った経緯

noteでブログを書いていると、気になるのがPV数(ページビュー)とスキの数ですよね。それぞれの遷移を知るためにマイページのダッシュボードで週間のPV数とスキ数を見ることはできますが、以下の点で結構ストレスが溜まってました。

  • 週間ごとのPVとスキが見づらい → ページ送りしまくる必要がある

  • 集計範囲が変わる → 曜日によってデータの区切りがズレる

「これ、毎回手動で確認するの超ダルくない?」
「もうツールで全部取っちゃえばいいじゃん!」

そんな思いつきから、サクッと作ることにしました。さらに、「どうせなら生成AIと一緒にやれば、爆速でいけるかも?」という発想が出てきたわけです。

noteのAPI実行は要注意!!

さて自動化するにはAPIを叩く必要がありますが、noteのAPIは、実は公式に公開されているわけではありません。ブラウザの開発ツール(F12)で見つけたAPIを「お借りする」形になります。

だからこそ、ここはリスペクトの気持ちが大事!

  • APIを叩きすぎない(noteサーバーに負荷をかけない)

  • 公式の迷惑になるような使い方は絶対NG

このブログを読んで真似してnoteのAPIを叩いてみようと思う方は、「借りてる立場だぞ」ということを肝に銘じて、節度を守って使いましょうね。

生成AIと一緒にツールを作る

手順1: APIの認証の仕方を確認

ダッシュボード情報は認証なしでは取得できないため、まずnoteのAPIの認証の仕方を確認します。これは以前個人のエンジニアの方が書いていたnoteの記事で発見しました。しかし現在この記事は残念ながら削除されてしまっているようです。またアカウントも削除されているようなのですが、もしかすると著者がこのブログを見てくれるかもしれないので、コードをお借りしたことに対するお礼をさせてください。ありがとうございます、非常に参考になりました。

以下が認証のためのソースコードです。前述の記事から抜粋してお借りしています。

user_name = "ユーザー名" 
email_address = "メールアドレス"
password = "パスワード"

####################################################
# Function 
####################################################

# noteの認証を実施する
def note_auth(session):
    
    user_data = {
        "login":    email_address,
        "password": password
    }
    # 認証
    url = 'https://note.com/api/v1/sessions/sign_in'
    r = session.post(url,json=user_data)
    # ログインエラー判定
    r2 = json.loads(r.text)
    if "error" in r2:
        raise Exception("Login Error")
    else:
        return session

# APIからデータを取得する関数
def get_api_data(session,url,method="get",headers=""):
    # メソッド変更
    if method == "post":
        r = session.post(url,headers=headers)
    else:
        r = session.get(url,headers=headers)
    return r.json()

####################################################
# MAIN
####################################################
if __name__ == '__main__':
    try:
        now = datetime.datetime.now()
        print('---Start Script---')
        print(f'start :{now}')
        print('------')
        # sessionオブジェクト生成
        session = requests.session()
        # 認証
        session = note_auth(session)
        # ダッシュボードを取得する
        # TODO:「ダッシュボードを取得する」を実装する
    except Exception as e:
        print(f'cauth {type(e)}: {e}')
        print(traceback.format_exc())
    finally:
        finish = datetime.datetime.now()
        print(' ---Finish---')
        print(f'start :{finish}')


手順2: APIのエンドポイントを発見

次にAPIのURLを調査します。noteのwebサイトはSPAなので、フロントからバックエンドのAPIを叩いて動いています。ということは、ブラウザの開発ツールを開けば、ダッシュボードが何のAPI叩いているか確認できます。

これだー!

開発ツール画面

手順3: 生成AIにサンプルコードを作ってもらう

さてお次はAPIのエンドポイントとやりたいことを生成AIに丸投げします。
以下のプロンプトを与えると、一瞬でベースのコードを出してくれました。
ちなみに集計期間が2024/8/2からなのは、このブログがリニューアルされたのが2024/8/2だからです。集計のend_dateを日曜日にしたのは、月曜~日曜の集計がわかりやすくていいかなって思ったからです。

pythonでnoteというブログのAPIを叩いて、ダッシュボード情報を取得するツールを作ります。
以下の情報を元にコードを書いてください。

## 認証部分を含めたのベースのソースコード
※ここには手順1のソースコードを貼り付け

## 実行したいnoteのAPI
https://note.com/api/v1/stats/pv?end_date={sunday}&filter=weekly&page=1&sort=pv

## 補足
{sunday}は2024/8/2から本日までの日曜日の日付で「yyyy-MM-dd」形式の文字列。
{sunday}を計算してAPIを複数回実行すること。
APIで何が取得できるか確認したいので、いったん取得結果をログに出力すること。

プロンプトの内容から分かる通り、これは完成形を見越した命令ではなくいったんAPIからどんな応答があるのか確認するためのものです。
出力を確認して、次に生成AIにこんなプロンプトを与えました。

APIの取得結果は以下のような出力が出ました。
集計開始の日付(start_date_str)と記事の名前(name)とPV数(read_count)と
スキの数(like_count)を一覧でファイルに出力するようにソースコードを修正してください。
出力順は、start_date_strの昇順でお願いします。

# 実行結果抜粋
取得結果 (2024-11-17):
{
    "data": {
        "start_date_str": "2024/11/11",
        "start_date": "2024-11-11T00:00:00.000+09:00",
        "end_date_str": "2024/11/17",
        "end_date": "2024-11-17T00:00:00.000+09:00",
        "last_page": true,
        "note_stats": [
            {
                "id": dummy,
                "key": "dummy",
                "name": "dummy",        
                "body": null,
                "type": "TextNote",
                "status": "published",
                "read_count": 9999,
                "like_count": 9999,
                "comment_count": 9999,
                "user": {
                    "urlname": "ulsystems"
                },
                "custom_domain": null
            },
            {
                …(略)…
            }
        "total_pv": 999,
        "total_like": 999,
        "total_comment": 999,
        "filter": "weekly",
        "last_calculate_at": "2024/12/17 09:26"
        }
}

そして出力されたのがこちら。これでAPIを叩くツールは完成です!
お気づきかもしれませんがAPIのパラメータがpage=1なので、記事が多くなってくるとこれじゃ全件取得できないんですが、改善は追々やることにします。

import requests
import json
import datetime
import traceback
import csv

# 認証情報
user_name = "ユーザー名" 
email_address = "メールアドレス"
password = "パスワード"

####################################################
# Function 
####################################################

# noteの認証を実施する
def note_auth(session):
    user_data = {
        "login": email_address,
        "password": password
    }
    # 認証
    url = 'https://note.com/api/v1/sessions/sign_in'
    r = session.post(url, json=user_data)
    # レスポンス確認
    r2 = json.loads(r.text)
    if "error" in r2:
        raise Exception("Login Error: " + r2["error"])
    else:
        print("認証成功")
        return session

# APIからデータを取得する関数
def get_api_data(session, url, method="get", headers=""):
    try:
        if method == "post":
            r = session.post(url, headers=headers)
        else:
            r = session.get(url, headers=headers)
        return r.json()
    except Exception as e:
        print(f"API取得エラー: {e}")
        return None

# 2024/08/02から本日日付までの日曜日を計算する関数
def get_sundays_since(start_date):
    sundays = []
    today = datetime.date.today()
    date = start_date

    while date <= today:
        if date.weekday() == 6:  # 日曜日
            sundays.append(date.strftime("%Y-%m-%d"))
        date += datetime.timedelta(days=1)
    return sundays

# データをCSVファイルに出力する関数
def export_to_csv(data_list, file_name):
    with open(file_name, 'w', newline='', encoding='utf-8') as csvfile:
        fieldnames = ['start_date_str', 'name', 'read_count', 'like_count']
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        
        writer.writeheader()
        for row in data_list:
            writer.writerow(row)

####################################################
# MAIN
####################################################
if __name__ == '__main__':
    try:
        now = datetime.datetime.now()
        print('---Start Script---')
        print(f'start : {now}')
        print('------')
        
        # sessionオブジェクト生成
        session = requests.session()

        # 認証
        session = note_auth(session)

        # 2024/08/02から日曜日を取得
        start_date = datetime.date(2024, 8, 2)
        sundays = get_sundays_since(start_date)
        print(f"対象の日曜日一覧: {sundays}")

        # APIデータ格納リスト
        all_data = []

        # APIリクエストとデータ抽出
        for sunday in sundays:
            api_url = f"https://note.com/api/v1/stats/pv?end_date={sunday}&filter=weekly&page=1&sort=pv"
            print(f"APIリクエスト: {api_url}")
            response = get_api_data(session, api_url)

            if response and "data" in response:
                start_date_str = response["data"]["start_date_str"]
                note_stats = response["data"].get("note_stats", [])

                for note in note_stats:
                    # 必要なデータを取得
                    name = note.get("name", "No Name")
                    read_count = note.get("read_count", 0)
                    like_count = note.get("like_count", 0)

                    # データを追加
                    all_data.append({
                        "start_date_str": start_date_str,
                        "name": name,
                        "read_count": read_count,
                        "like_count": like_count
                    })

        # データを日付順にソート
        all_data.sort(key=lambda x: x['start_date_str'])

        # データをCSVに出力
        output_file = "note_dashboard_output.csv"
        export_to_csv(all_data, output_file)
        print(f"データをファイルに出力しました: {output_file}")

    except Exception as e:
        print(f'catch {type(e)}: {e}')
        print(traceback.format_exc())
    finally:
        finish = datetime.datetime.now()
        print('---Finish---')
        print(f'finish : {finish}')

実行するとこんな感じで週間の記事ごとのPV数とスキ数がCSV出力されます。

start_date_str,name,read_count,like_count
2024/07/29,TECH BLOGリニューアル!,999,999
2024/08/05,TECH BLOGリニューアル!,999,999
・・・

手順4: データ整形も生成AI先生にお任せ

取得したデータを人間が見やすい形に整形するのも生成AIにお願いしました。具体的には、

  • X軸 → 日付

  • Y軸 → 記事名

  • コンテンツ → PV数 / スキの数

「記事名の順番は最初の出現順で固定ね」というこだわりにも、しっかり応えてくれました。与えたプロンプトは以下の通り。

今のファイルの出力結果をさらに加工するソースコードを書いてください。
加工結果は2つのファイルに出力されます。
Y軸の記事の名前の並びは、note_dashboard_output.csvに最初に出てくるものから順に並べてください。

# 1つ目
X軸が日付、Y軸が記事の名前、コンテンツがスキの数になるCSV

#2つ目
X軸が日付、Y軸が記事の名前、コンテンツがPV数になるCSV

整形ツールはこれで完成です!

import pandas as pd

# 入力ファイル名
input_file = "note_dashboard_output.csv"

# 出力ファイル名
output_file_likes = "likes_by_date_and_article.csv"
output_file_pvs = "pvs_by_date_and_article.csv"

try:
    # CSVファイルを読み込む
    print("データを読み込んでいます...")
    df = pd.read_csv(input_file)

    # 記事名の順序を固定するために、最初に出現する順序を取得
    article_order = df['name'].drop_duplicates().tolist()

    # 記事名の列をカテゴリカル型に変換し、順序を指定
    df['name'] = pd.Categorical(df['name'], categories=article_order, ordered=True)

    # データをピボットテーブルに変換(スキの数)
    print("スキの数のピボットテーブルを生成しています...")
    pivot_likes = df.pivot_table(
        index='name',            # Y軸: 記事の名前(順序固定)
        columns='start_date_str',# X軸: 日付
        values='like_count',     # コンテンツ: スキの数
        aggfunc='sum',           # 同じ記事・日付の集計方法: 合計
        fill_value=0             # データが存在しない場合は0を埋める
    )

    # スキの数のピボットテーブルをCSVに出力
    pivot_likes.to_csv(output_file_likes)
    print(f"スキの数のファイルを出力しました: {output_file_likes}")

    # データをピボットテーブルに変換(PV数)
    print("PV数のピボットテーブルを生成しています...")
    pivot_pvs = df.pivot_table(
        index='name',            # Y軸: 記事の名前(順序固定)
        columns='start_date_str',# X軸: 日付
        values='read_count',     # コンテンツ: PV数
        aggfunc='sum',           # 同じ記事・日付の集計方法: 合計
        fill_value=0             # データが存在しない場合は0を埋める
    )

    # PV数のピボットテーブルをCSVに出力
    pivot_pvs.to_csv(output_file_pvs)
    print(f"PV数のファイルを出力しました: {output_file_pvs}")

except Exception as e:
    print(f"エラーが発生しました: {e}")

ツールを動かした出力結果はこんな感じ。

name,2024/07/29,2024/08/05,2024/08/12,2024/08/19,2024/08/26,2024/09/02・・・
TECH BLOGリニューアル!,・・・
生成AIChatBot『@ultaro』誕生秘話,・・・・

手順5: Excelでヒートマップ化して完成!

最後はデータをExcelに貼り付けて、ヒートマップで可視化。
「あ、この記事めっちゃ伸びてる!」と一目で分かるようになりました。

ぼやっとしてるのはわざとです 

生成AIと一緒なら爆速開発できる!

ゼロから作ったらそこそこ時間かかるところですが、生成AIのおかげでサクッと完成しました。

私が行ったのは最初の認証の仕方とAPIのエンドポイントの調査、それから途中のソースの仕上がりを見てプロンプトを調整するだけで、全体作業の80%程度を生成AIがやってくれました。ソースコードに至っては100%生成AIが作ったものです!特に苦労を感じたところもなく、めちゃくちゃ楽にツールができちゃいました。

でも生成AIとの付き合い方に慣れないうちはプロンプトでの命令の出し方に苦労するかもしれません。一つアドバイスするとしたら、生成AIでの開発のコツは、ズバリやりたいことを明確に伝えること。何を求めているかがはっきりしていて、それを生成AIに伝えることができれば、生成AIとお友達になれます!

まとめ

生成AIを使った開発は手動でやるより100倍ラク、そして速い!
ツール作成くらいなら生成AIが最適だと感じました!

でも「ツール作成くらいなら」と書いたのには理由があります。
ちょっとしたツールを作るだけなのであまり深く考えず生成AIが書いてくれたコードをそのまま動かしてみましたが、大規模な開発などに取り入れる時は、プロジェクトのルールを守れているか、保守性が高いコードなのか、などチェックするポイントがたくさんあります。

使い捨てのツール開発に生成AIを使うのは超気軽にできるんですが、本当の開発ではそうもいかない・・・。だからこそ、開発プロセスの中でどこにどのように生成AIを取り入れていくのかを考えていくことが重要です。エンプラ開発向けの生成AI活用の仕方については、まだまだですが、調査をしているところです。

それを踏まえて、来年も生成AIとタッグを組んで、爆速開発ライフを送りたいと思います。

この記事が参加している募集