見出し画像

note-API(非公式)で記事情報の取得

1.概要

 noteで記事を記載するモチベーションとして「スキをもらう」、「有料記事を買ってもらう」、「多くの人に見てもらう」などありますが、そのためにどのような記事を記載したらよいかデータ分析が必要です。
 私もそろそろデータ分析をしたいと思ったのですが、noteのダッシュボードはデータをダウンロードできず、またnoteのスクレイピングは結構手間です(element名が変わる、JavaScriptで動くなど)。
(下記記事も公開当時から要素名の変更があるため修正が必要です)

 APIが無いか調べていると非公式でAPIがあるらしいので試してみました。「サーバーに負荷をかける行為」は禁止されているため注意が必要です。

【noteロゴ】
 noteのロゴデータは下記からダウンロードしました。

ロゴデータ・ガイドラインのダウンロード

2.note API一覧:全体情報

2-1.カテゴリー一覧:v2/categories

 note記事のカテゴリ一覧を取得できます。

[IN]
import requests
import json

url = 'https://note.com/api/v2/categories'
res = requests.get(url)
data_json = json.loads(res.text)
data_json
[OUT]
{'data': {'categories': [{'name': '注目',
    'order': 0,
    'endpoint': 'https://note.com/api/v1/top_articles'},
   {'id': 1,
    'name': 'マンガ',
    'engName': 'manga',
    'order': 1.0,
    'endpoint': 'https://note.com/api/v1/categories/manga?note_intro_only=true',
    'url': '/categories/manga'},
   {'id': 2,
    'name': 'コラム・エッセイ',
    'engName': 'column',
    'order': 2.0,
    'endpoint': 'https://note.com/api/v1/categories/column?note_intro_only=true',
    'url': '/categories/column'},
   {'id': 3,
    'name': '小説',
    'engName': 'novel',
    'order': 3.0,
    'endpoint': 'https://note.com/api/v1/categories/novel?note_intro_only=true',
    'url': '/categories/novel'},
   {'id': 4,
    'name': '写真',
    'engName': 'photo',
    'order': 4.0,
    'endpoint': 'https://note.com/api/v1/categories/photo?note_intro_only=true',
    'url': '/categories/photo'},
   {'id': 134,
    'name': 'ラジオ',
    'engName': 'radio',
    'order': 5.0,
    'endpoint': 'https://note.com/api/v1/categories/radio?note_intro_only=true',
    'url': '/categories/radio'},
   {'id': 5,
    'name': '音楽',
    'engName': 'music',
    'order': 6.0,
    'endpoint': 'https://note.com/api/v1/categories/music?note_intro_only=true',
    'url': '/categories/music'},
   {'id': None,
    'name': 'ビジネス',
    'engName': 'business',
    'order': 6,
    'endpoint': None,
    'url': 'topic/business'},
   {'id': None,
    'name': 'ライフスタイル',
    'engName': 'lifestyle',
    'order': 7,
    'endpoint': None,
    'url': 'topic/lifestyle'},
   {'id': 8,
    'name': 'テクノロジー',
    'engName': 'tech',
    'order': 9.0,
    'endpoint': 'https://note.com/api/v1/categories/tech?note_intro_only=true',
    'url': '/categories/tech'},
   {'id': None,
    'name': 'デザイン',
    'engName': 'design',
    'order': 10,
    'endpoint': None,
    'url': 'topic/design'}]}}

2-2.ユーザー一覧:v2/users

 ユーザー一覧を取得でき1ページに20ユーザーずつ表示されます。クエリパラメータを「?page=2」に渡すと各ページのユーザーを照会できます。

[IN]
import requests
import json

url = 'https://note.com/api/v2/users'
res = requests.get(url)
data_json = json.loads(res.text)
print(len(data_json['data']['contents']))
data_json
[OUT]
20


{'data': {'contents': [{'id': 120,
    'nickname': 'note公式',
    'urlname': 'info',
    'profile': 'ミッションは「だれもが創作をはじめ、続けられるようにする」。noteの最新情報や新機能(カイゼン)を紹介します。\n編集部アカウント : https://note.com/notemag || イベントアカウント : https://note.com/events',
    'followingCount': -1,
    'followerCount': -1,
    'isFollowing': False,
    'isMyself': False,
    'profileImageUrl': 'https://assets.st-note.com/production/uploads/images/93752369/profile_ae0f62081ec839762d6e066ef222a3d6.png?fit=bounds&format=jpeg&quality=85&width=330',
    'isBlocked': False,
    'isOfficial': True,
    'showFollowCount': False,
    'customDomain': None,
    'followAppealText': 'noteへのご意見・ご要望は、フッターの「フィードバック」へ👇',
    'followAppealImage': 'https://assets.st-note.com/production/uploads/images/44189616/dce9dee798ef6fd864d52bb352b52607.gif?fit=bounds&format=jpeg&height=500&quality=85&width=500'},
   {'id': 879565,
    'nickname': 'しいたけ占いのしいたけ.',
    'urlname': 'shiitakeofficial',
    'profile': '占い師、作家。早稲田大学大学院政治学研究科修了。2014年から『VOGUE GIRL』で週刊・上半期・下半期の「しいたけ占い」を連載中。近著に『しいたけ.のやさしいお守りBOOK』『しいたけ.の小さな開運BOOK』など。お仕事の依頼は下のほうにある専用フォームからお願いします。',
    'followingCount': 2,
    'followerCount': 90291,
    'isFollowing': False,
    'isMyself': False,
    'profileImageUrl': 'https://assets.st-note.com/production/uploads/images/6085570/profile_db6fc656cf36e9f6faf936c4c311dc26.jpg?fit=bounds&format=jpeg&quality=85&width=330',
    'isBlocked': False,
    'isOfficial': True,
    'showFollowCount': True,
    'customDomain': {'id': 173,
     'tls': True,
     'host': 'shiitakeofficial.com',
     'type': 'creator',
     'key': 'shiitakeofficial'},
    'followAppealText': 'フォローありがとうございます。ますます頑張ります。',
    'followAppealImage': None},
   {'id': 197,
    'nickname': 'けんすう',
    'urlname': 'kensuu',
    'profile': 'インターネットでコミュニティサービスとかをよく作っています。最近だとナマケモノをモチーフにした、きせかえできるNFT「sloth(すろーす)」というのを作ったりしています。https://sloth-nft.life',
    'followingCount': 84,
    'followerCount': 89640,
    'isFollowing': False,
    'isMyself': False,
    'profileImageUrl': 'https://assets.st-note.com/production/uploads/images/23325052/profile_bcb7d8e8147bde6e15fd4e193ffb16c3.png?fit=bounds&format=jpeg&quality=85&width=330',
    'isBlocked': False,
    'isOfficial': True,
    'showFollowCount': True,
    'customDomain': {'id': 64,
     'tls': True,
     'host': 'kensuu.com',
     'type': 'creator',
     'key': 'kensuu'},
    'followAppealText': 'フォローありがとうございます!😍スキ',
    'followAppealImage': None},
   {'id': 38342,
    'nickname': '広瀬隆雄',
    'urlname': 'hirosetakao',
    'profile': 'Market Hack編集長',
    'followingCount': 13,
    'followerCount': 84084,
    'isFollowing': False,
    'isMyself': False,
    'profileImageUrl': 'https://assets.st-note.com/production/uploads/images/127874/profile_8e5fe69083ad95056c450bd02f445219.jpg?fit=bounds&format=jpeg&quality=85&width=330',
    'isBlocked': False,
    'isOfficial': True,
    'showFollowCount': True,
    'customDomain': None,
    'followAppealText': None,
    'followAppealImage': None},
   {'id': 542782,
    'nickname': '落合陽一',
    'urlname': 'ochyai',
    'profile': 'メディアアーティストで光や音や物性や計算機メディアの研究をしているような感覚的物書きで博士持ちのスナップ写真家です.多様性社会を目指す波動使いの准教授.noteは作家としての個人的な発信です.ご連絡はリンク先のお問い合わせまで.\u3000https://yoichiochiai.com',
    'followingCount': 3,
    'followerCount': 74584,
    'isFollowing': False,
    'isMyself': False,
    'profileImageUrl': 'https://assets.st-note.com/production/uploads/images/3306384/profile_1a6b756c512ecf5dfff1e22f7328ac70.jpeg?fit=bounds&format=jpeg&quality=85&width=330',
    'isBlocked': False,
    'isOfficial': True,
    'showFollowCount': True,
    'customDomain': None,
    'followAppealText': '落合陽一です! SNSでシェアいただけると喜びます!',
    'followAppealImage': None},
・・・

3.note API一覧:個別情報

3-1.特定ユーザーの基本情報:creators/<id>

 エンドポイントにユーザー名(urlname)を渡すことでそのユーザーの基本情報を取得できます。私のユーザー名は"kiyo_ai_note"でありコードは下記の通りです。

[IN]
import requests
import json

url = 'https://note.com/api/v2/creators/kiyo_ai_note'
res = requests.get(url)
data_json = json.loads(res.text)
data_json
[OUT]
{'data': {'id': 2345636,
  'key': '72e203ff3d89e4e47a25289ef6a1bbea',
  'nickname': 'KIYO',
  'urlname': 'kiyo_ai_note',
  'profile': '普段は製造業で設計しておりますが、Python・プログラミング・機械学習関係の記事をメインで作成します。\n内容は学術的な説明より実装・アウトプット(ほしくなるもの)を重視して作成しています。\n\nhttps://github.com/KIYO-note',
  'noteCount': 208,
  'magazineCount': 21,
  'followingCount': 14,
  'followerCount': 129,
  'isFollowing': False,
  'isFollowed': False,
  'isMyself': False,
  'originalHeaderImageUrl': 'https://assets.st-note.com/production/uploads/images/62046865/f4d4230fde49a1929c492ad856b29401.png',
  'headerImageUrl': 'https://assets.st-note.com/production/uploads/images/62046865/wallpaper_landscape_f4d4230fde49a1929c492ad856b29401.png',
  'headerImageKey': 'f4d4230fde49a1929c492ad856b29401',
  'profileImageUrl': 'https://assets.st-note.com/production/uploads/images/69162928/profile_ddaa6e9e8da326c91bb6732f45a090ed.jpg?fit=bounds&format=jpeg&quality=85&width=330',
  'profileImageKey': 'ddaa6e9e8da326c91bb6732f45a090ed',
  'isBlocked': False,
  'blocksYou': False,
  'isAdmin': False,
  'socials': {'twitter': {'id': 2014986,
    'nickname': 'kyonsy69',
    'name': '',
    'uid': ''}},
  'isOfficial': False,
  'showFollowCount': True,
  'externalLinks': {},
  'disableSupport': False,
  'hasStore': False,
  'hasCircle': False,
  'customDomain': None,
  'followAppealText': None,
  'followAppealImage': None,
  'isCreatorLikesTabEnabled': True,
  'isCreatorMagazinesTabEnabled': True,
  'isCreatorCirclesTabEnabled': True,
  'isCreatorArchivesTabEnabled': True,
  'isCreatorProfileTabEnabled': False,
  'isCreatorJobOfferTabEnabled': False,
  'isCreatorFollowNumberDisplayed': True,
  'style': 'streem',
  'proUserId': None,
  'tlMagazines': [],
  'isMedia': False,
  'storeUrl': '',
  'isHavingSubscribableMagazines': False}}

3-2.特定ユーザーの記事情報

 ユーザー名(urlname)とページ数を渡すことでユーザーが作成した記事を取得できます。1ページ当たり6個の記事が取得可能です。

[IN]
import requests
import json

url = 'https://note.com/api/v2/creators/kiyo_ai_note/contents?kind=note&page=1'
res = requests.get(url)
data_json = json.loads(res.text)
print(len(data_json['data']['contents']))
data_json
[OUT]
{'data': {'contents': [{'id': 38911034,
    'type': 'TextNote',
    'status': 'published',
    'name': '作成ノート(+予定)一覧 by KIYO',
    'description': None,
    'likeCount': 3,
    'price': 0,
    'key': 'nd49cc7237ceb',
    'slug': 'slug-nd49cc7237ceb',
    'publishAt': '2021-10-14T22:20:53+09:00',
    'thumbnailExternalUrl': '',
    'eyecatch': 'https://assets.st-note.com/production/uploads/images/63469381/rectangle_large_type_2_eda03a8afb916b60b7dbef9ef4459c71.jpg?fit=bounds&quality=85&width=1280',
    'user': {'id': 2345636,
     'key': '72e203ff3d89e4e47a25289ef6a1bbea',
     'name': 'KIYO',
     'urlname': 'kiyo_ai_note',
     'nickname': 'KIYO',
     'userProfileImagePath': 'https://assets.st-note.com/production/uploads/images/69162928/profile_ddaa6e9e8da326c91bb6732f45a090ed.jpg?fit=bounds&format=jpeg&quality=85&width=330',
     'customDomain': None,
     'disableSupport': False,
     'disableGuestPurchase': False,
     'emailConfirmedFlag': True,
     'likeAppealText': None,
     'likeAppealImage': None,
     'purchaseAppealTextNote': None,
     'twitterNickname': 'kyonsy69',
     'shareAppeal': {'text': None, 'image': None},
     'magazineAddAppeal': {'text': None, 'image': None}},
    'canRead': True,
    'isAuthor': False,
    'externalUrl': None,
    'customDomain': None,
    'body': '概要私が作成予定のノートのご紹介です。気分屋ですが適宜作成します。需要があれば収益化とかもしたいです(そっちの方がやる気がでるので)。\n\n1.Python基礎下記予定(一部作成済み)。本や動画で勉強しても十分に理解できなかったり、体系的に理解できなかった部分を記載していきます。\n\n2.Python応用・実践具体的にPythonでできることや、実際にPythonで何かをやってみます。\n\n3.Pythonライブラリライブラリを使用して仕事に生かせそうなものを紹介予定。できればあま',
    'separator': None,
    'isLimited': False,
    'isTrial': False,
    'canUpdate': False,
    'tweetText': '作成ノート(+予定)一覧 by KIYO|KIYO @kyonsy69 #note',
    'isRefund': False,
    'isLiked': False,
    'commentCount': 0,
    'likes': [],
    'anonymousLikeCount': 0,
    'disableComment': False,
    'hashtags': [{'hashtag': {'name': '#Blender'}},
     {'hashtag': {'name': '#KIYO'}},
     {'hashtag': {'name': '#Blenderアドオン'}}],
    'twitterShareUrl': 'https://twitter.com/intent/tweet?url=https://note.com/kiyo_ai_note/n/nd49cc7237ceb&text=%E4%BD%9C%E6%88%90%E3%83%8E%E3%83%BC%E3%83%88%28%2B%E4%BA%88%E5%AE%9A%29%E4%B8%80%E8%A6%A7+by+KIYO%EF%BD%9CKIYO+%40kyonsy69 %23note&related=kyonsy69,note_PR',
    'facebookShareUrl': 'https://www.facebook.com/share.php?u=https://note.com/kiyo_ai_note/n/nd49cc7237ceb&t=%E4%BD%9C%E6%88%90%E3%83%8E%E3%83%BC%E3%83%88%28%2B%E4%BA%88%E5%AE%9A%29%E4%B8%80%E8%A6%A7+by+KIYO%EF%BD%9CKIYO%EF%BD%9Cnote%EF%BC%88%E3%83%8E%E3%83%BC%E3%83%88%EF%BC%89',
    'lineShareUrl': 'https://line.naver.jp/R/msg/text/?%E4%BD%9C%E6%88%90%E3%83%8E%E3%83%BC%E3%83%88%28%2B%E4%BA%88%E5%AE%9A%29%E4%B8%80%E8%A6%A7+by+KIYO%EF%BD%9CKIYO%EF%BD%9Cnote%EF%BC%88%E3%83%8E%E3%83%BC%E3%83%88%EF%BC%89%0Ahttps://note.com/kiyo_ai_note/n/nd49cc7237ceb',
    'audio': {},
    'pictures': [],
    'limitedMessage': None,
    'labels': [],
    'priorSale': None,
    'canMultipleLimitedNote': False,
    'isMembershipConnected': False,
    'hasAvailableCirclePlans': False,
    'isPinned': True,
    'pinnedUserNoteId': 176534,
    'spEyecatch': 'https://assets.st-note.com/production/uploads/images/63469381/square_large_eda03a8afb916b60b7dbef9ef4459c71.jpg?fit=bounds&format=jpeg&quality=85&width=360',
    'enableBacktoDraft': False,
    'notificationMessages': [],
    'isProfiled': False,
    'isForWork': False,
    'isCircleDescription': False,
    'noteDraft': None,
    'noteUrl': 'https://note.com/kiyo_ai_note/n/nd49cc7237ceb',
    'imageCount': 3,
    'format': '4.0',
    'capabilities': {'rubyText': True,
     'formulaText': True,
     'duplication': True}},
   {'id': 65343203,
・・・・・

3-3.記事の詳細:notes/<id>

 記事のkeyを入力することでその記事の情報を取得できます。

[IN]
import requests
import json

url = 'https://note.com/api/v1/notes/n4e23ee767fa3'
res = requests.get(url)
data_json = json.loads(res.text)
data_json
[OUT]
長いので省略

4.全記事の情報取得

 データ分析に使用するために、私の記事の全情報を抽出してDataFrameに格納していきます。

4-1.データ抽出

 データ抽出のための関数を作成します。設計思想は下記の通りです。

  • データをAPIで抽出してJSON->DataFrameに変換

  • サーバーに負荷をかけないようにwait時間を設定(Default:1.0s)

  • 取得したDataFrameを行方向に結合

  • 指定ページ数、またはデータの中身が空になったら処理終了

  • 最後にIndexをリセット

[IN]
import time
import requests
import json
import pandas as pd

def getAllnotes(username:str, maxpage:int, waittime:int=1.0):
    output = None
    
    for numpage in range(1, maxpage+1):
        time.sleep(waittime) #サーバー負荷軽減のため
        res = requests.get(f'https://note.com/api/v2/creators/{username}/contents?kind=note&page={numpage}')
        data_json = json.loads(res.text) #JSON->Dict
        content = data_json['data']['contents'] #データ抽出
        
        if content: #データが空でない場合
            df = pd.DataFrame(content) #Dict->DataFrame
            if numpage == 1:
                output = df
            else:
                output = pd.concat([output, df], axis=0) #axis=0:行方向に結合
        else:
            print(f'page {numpage} は空のため処理完了!')
            output = output.reset_index(drop=True, inplace=False) #indexを振り直す
            return output
        
    output = output.reset_index(drop=True, inplace=False) #indexを振り直す
    return output

df = getAllnotes('kiyo_ai_note', 100)
df


[OUT]

 処理が終わったら再利用できるようにPickleファイルに保管します。

[IN]
df.to_pickle('note記事データ_KIYO.pkl')
df = pd.read_pickle('note記事データ_KIYO.pkl')

[OUT]

4-2.中身の確認

 カラムは下記の通りです。非公式とのことですので全てが何か分かりませんが下記情報は確認できます。

  • id: ID

  • type: 記事の種類(テキスト、画像、つぶやきなど)

  • status: 記事の状態(下書き・投稿など)

  • name: 記事のタイトル

  • description: 詳細

  • likeCount: いいね数

  • price: 価格

  • key: キー

  • slug: スラッグ

  • publishAt: 公開日時

  • body: 本文

  • commentCount: コメント数

[IN]
df.columns
[OUT]
Index(['id', 'type', 'status', 'name', 'description', 'likeCount', 'price',
       'key', 'slug', 'publishAt', 'thumbnailExternalUrl', 'eyecatch', 'user',
       'canRead', 'isAuthor', 'externalUrl', 'customDomain', 'body',
       'separator', 'isLimited', 'isTrial', 'canUpdate', 'tweetText',
       'isRefund', 'isLiked', 'commentCount', 'likes', 'anonymousLikeCount',
       'disableComment', 'hashtags', 'twitterShareUrl', 'facebookShareUrl',
       'lineShareUrl', 'audio', 'pictures', 'limitedMessage', 'labels',
       'priorSale', 'canMultipleLimitedNote', 'isMembershipConnected',
       'hasAvailableCirclePlans', 'isPinned', 'pinnedUserNoteId', 'spEyecatch',
       'enableBacktoDraft', 'notificationMessages', 'isProfiled', 'isForWork',
       'isCircleDescription', 'noteDraft', 'noteUrl', 'imageCount', 'format',
       'capabilities'],
      dtype='object')

 簡単にいいね数の高い順に記事を可視化しました。

[IN]
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import japanize_matplotlib

def plot_barh_likes(df: pd.DataFrame):
    fig, ax = plt.subplots(figsize=(10, 5), facecolor="white")

    ax.barh(df["name"], df["likeCount"], color='blue')

    ax.set(xlabel="likeCount", ylabel="記事名")
    ax.xaxis.set_major_locator(ticker.MultipleLocator(1))  # 1単位で目盛りを設定
    ax.xaxis.set_major_formatter(ticker.FuncFormatter(lambda x, pos: f'{int(x):,}'))
    ax.grid(axis='x', linestyle='--', alpha=0.7)  # 縦軸に補助線を追加
    plt.tight_layout()
    plt.show()

# 関数を実行してグラフを表示
df_sorted = df.sort_values('likeCount', ascending=False) # 降順にソート
df_sorted = df_sorted.iloc[:12, :]
df_sorted.sort_values('likeCount', ascending=True, inplace=True) # 昇順にソート
plot_barh_likes(df_sorted)

[OUT]

5.所感

 このAPIだとダッシュボードで確認できるビュー数(閲覧数)と、データの日時(月)がないため、解析には不向きでした。ビュー数のデータがあれば確率の計算から記事が書けるくらいのデータ分析ができますが、現状では非公式ということもあり一時中断したいと思います。


参考記事

あとがき

 公式のAPI求む。

いいなと思ったら応援しよう!