API自動化 Python

スキ・フォローをPythonで操作する前に読む記事|自動化の境界線

境界線

実装は10行で終わる。問題は、それを動かしていいのかという判断のほうだ。

仕様とリスクの両方を、はっきり書きます

📋 この記事でわかること

  • スキ・フォローAPIの仕様と実装
  • urlnameから数値IDを引く手順
  • 機械的な大量実行がなぜ危険なのか
  • スパム判定される具体的な条件
  • 安全に使える範囲(自分のデータの整理)
  • noteとアメブロで自動化の扱いがどう違うか

「自動いいねツールを作りたい」という需要は確実にあります。技術的には、この記事で紹介するコードを数行組み合わせれば実現できます。しかし結論から書くと、他人への機械的なスキ・フォローの自動実行は推奨できません。規約上のリスクがあり、得られる効果も一時的です。この記事では、APIの仕様を正確に解説したうえで、なぜ推奨しないのか、では何なら安全なのかを整理します。判断材料を提供することが目的です。

この記事の立場

  1. APIの仕様は正確に解説します。知らないまま使うほうが危険だからです
  2. ただし、他人への機械的な大量実行は明確に推奨しません
  3. 安全な用途(自分のデータの整理・分析)は積極的に紹介します
  4. 最終的な判断は読者に委ねます。そのための材料を出します

APIの仕様

スキとフォローのエンドポイントは、どちらもシンプルです。

操作 メソッド・パス 識別子
スキを付ける POST /api/v3/notes/{key}/likes 記事キー(n形式)
スキを外す DELETE /api/v3/notes/{key}/likes 記事キー(n形式)
スキした人の一覧 GET /api/v3/notes/{key}/likes 記事キー(n形式)
フォローする POST /api/v3/users/{id}/following 数値のユーザーID
フォローを外す DELETE /api/v3/users/{id}/following 数値のユーザーID

注意点は、フォロー系が数値のユーザーIDを要求することです。プロフィールURLに現れる urlname とは別物なので、変換が必要になります。

social_client.py
"""
スキ・フォローの操作をまとめたクライアント。

【重要】
このクラスは仕様の理解と、自分のデータ整理のために提供する。
他人への機械的な大量実行には使わないこと。
間隔を意図的に長く設定しているのはそのため。
"""
import logging
import time

from note_auth_client import NoteAuthClient

logger = logging.getLogger(__name__)

# 意図的に長めの間隔にしている
MIN_INTERVAL = 5.0
# 1回の実行での上限も低く抑える
MAX_ACTIONS_PER_RUN = 20


class SocialClient:

    def __init__(self, client: NoteAuthClient,
                 interval: float = MIN_INTERVAL):
        self.client = client
        self.interval = max(interval, MIN_INTERVAL)
        self._actions = 0

    def _guard(self):
        """実行回数の上限を守る"""
        if self._actions >= MAX_ACTIONS_PER_RUN:
            raise RuntimeError(
                f"1回の実行での上限({MAX_ACTIONS_PER_RUN}件)に達しました。"
                "時間をおいて実行してください"
            )
        self._actions += 1
        time.sleep(self.interval)

    # ---- ユーザーIDの解決 ----

    def resolve_user_id(self, urlname: str) -> int:
        """urlname から数値のユーザーIDを取得する"""
        data = self.client.get_json(f"/api/v2/creators/{urlname}")

        user_id = data.get("id")
        if not user_id:
            raise ValueError(f"ユーザーIDを取得できません: {urlname}")

        return int(user_id)

    # ---- スキ ----

    def get_likers(self, note_key: str) -> list:
        """記事にスキを付けた人の一覧(読み取りのみ・安全)"""
        data = self.client.get_json(
            f"/api/v3/notes/{note_key}/likes"
        )

        if isinstance(data, list):
            return data

        return data.get("likes", data.get("contents", []))

    def like(self, note_key: str) -> bool:
        """記事にスキを付ける"""
        self._guard()

        res = self.client.request(
            "POST", f"/api/v3/notes/{note_key}/likes"
        )

        if res.status_code not in (200, 201):
            logger.warning("スキに失敗 HTTP %s(%s)",
                           res.status_code, note_key)
            return False

        return True

    def unlike(self, note_key: str) -> bool:
        """スキを取り消す"""
        self._guard()

        res = self.client.request(
            "DELETE", f"/api/v3/notes/{note_key}/likes"
        )

        return res.status_code in (200, 204)

    # ---- フォロー ----

    def follow(self, user_id: int) -> bool:
        """フォローする(数値IDを渡す)"""
        self._guard()

        res = self.client.request(
            "POST", f"/api/v3/users/{user_id}/following"
        )

        if res.status_code not in (200, 201):
            logger.warning("フォローに失敗 HTTP %s(id=%s)",
                           res.status_code, user_id)
            return False

        return True

    def unfollow(self, user_id: int) -> bool:
        """フォローを外す"""
        self._guard()

        res = self.client.request(
            "DELETE", f"/api/v3/users/{user_id}/following"
        )

        return res.status_code in (200, 204)


if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)

    client = NoteAuthClient()
    client.verify()

    social = SocialClient(client)

    # 読み取りのみの例(安全)
    likers = social.get_likers("nXXXXXXXXXXXX")
    print(f"スキを付けた人: {len(likers)} 人")

    for u in likers[:10]:
        user = u.get("user", u)
        print(f"  {user.get('nickname', '')}"
              f"(@{user.get('urlname', '')})")

クラス側で制限をかけている理由

このクラスは、間隔5秒・1回の実行で20件までという制限を内部に持っています。使う側が制限を忘れても、大量実行にならないようにするためです。自動化のコードを書くとき、こうした安全装置を仕組みとして組み込んでおくと、判断ミスによる事故を防げます。

なぜ機械的な実行が危険なのか

「1日に数百件のスキを自動で付ける」といった運用が、なぜ問題なのかを整理します。

リスク 内容
規約違反の可能性 スパム行為として利用規約に抵触しうる
アカウント制限 一時的な機能制限や、利用停止の対象になる
サーバー負荷 人間には不可能な頻度のリクエストが発生する
評判の低下 機械的な行動は読者に見抜かれる
数字の無意味化 相互スキで増えた数字は実態を反映しない
改善の材料を失う どの記事が良かったのか判断できなくなる

最後の2つは軽視されがちですが、実は最も本質的な問題です。人工的に増やした数字は、判断材料としての価値を失います。スキが100付いていても、そのうち90が相互目的なら、その記事が読者に響いたかどうかは分かりません。改善のための情報が消えるということです。

効果も限定的

自動いいねで得られるのは、一時的な訪問だけです。訪問した人が記事を読み、フォローするかどうかは記事の内容次第です。中身が伴わなければ、リスクを負った割に何も残りません。同じ時間を記事の質に使うほうが、確実にリターンがあります。この点はスキがつかないときの記事でも触れています。

スパム判定される条件

明確な基準は公開されていませんが、一般的にスパムとみなされやすいパターンを整理します。

  • 1
    短時間の大量実行——1分間に何十件もの操作は、人間の行動として不自然です。
  • 2
    一定間隔での規則的な実行——きっちり3秒おきといった動きは、機械であることが明白です。
  • 3
    記事を開かずにスキだけ付ける——閲覧履歴と行動が一致しない状態です。
  • 4
    フォローと解除の繰り返し——いわゆるフォロー・アンフォローの手法です。
  • 5
    無関係なジャンルへの一律実行——興味がないはずの記事にも機械的に反応している状態です。
  • 6
    複数アカウントからの相互操作——自作自演として明確に問題視されます。

▶ コメントの自動化についても、同じ観点で整理しています。
取得は自動化、返信は人が書くという線引きです。

コメントAPIの記事を見る

安全に使える範囲

では何なら問題ないのか。自分のデータを読み取る用途と、自分のフォローを整理する用途です。ここは積極的に自動化してよい領域です。

01_analyze_likers.py
"""
自分の記事にスキを付けてくれた人を分析する。
読み取りのみなので安全。

- 繰り返しスキをくれる人が誰か
- どの記事に反応が集まるか
"""
import time
from collections import Counter

from note_auth_client import NoteAuthClient, CookieExpiredError
from social_client import SocialClient

URLNAME = "your_note_id"
SLEEP_SEC = 1.5
MAX_ARTICLES = 60


def fetch_my_notes(client: NoteAuthClient, urlname: str,
                   max_pages: int = 30) -> list:
    items = []

    for page in range(1, max_pages + 1):
        data = client.get_json(
            f"/api/v2/creators/{urlname}/contents",
            params={"kind": "note", "page": page},
        )
        contents = data.get("contents", [])
        if not contents:
            break

        items.extend(contents)

        if data.get("isLastPage") or data.get("is_last_page"):
            break

        time.sleep(1.2)

    return items


def analyze(client: NoteAuthClient, notes: list):
    social = SocialClient(client)

    liker_counter = Counter()
    liker_names = {}
    by_article = []

    targets = notes[:MAX_ARTICLES]

    for i, note in enumerate(targets, start=1):
        note_key = note.get("key")
        title = note.get("name", "")

        if not note_key:
            continue

        likers = social.get_likers(note_key)

        for entry in likers:
            user = entry.get("user", entry)
            urlname = user.get("urlname", "")
            if not urlname:
                continue

            liker_counter[urlname] += 1
            liker_names[urlname] = user.get("nickname", urlname)

        by_article.append((len(likers), title))
        print(f"[{i}/{len(targets)}] {title[:40]}"
              f"({len(likers)} 人)")

        time.sleep(SLEEP_SEC)

    print("\n=== 繰り返しスキをくれる方 ===")
    for urlname, count in liker_counter.most_common(20):
        name = liker_names.get(urlname, urlname)
        print(f"  {count:>3} 回  {name[:24]}(@{urlname})")

    print("\n=== スキが多い記事 ===")
    for count, title in sorted(by_article, reverse=True)[:10]:
        print(f"  {count:>4} 人  {title[:44]}")

    print(f"\nスキをくれた人の総数(重複なし): {len(liker_counter)} 人")


def main():
    try:
        client = NoteAuthClient()
        client.verify()
    except CookieExpiredError as e:
        print("認証エラー:", e)
        return

    notes = fetch_my_notes(client, URLNAME)
    analyze(client, notes)


if __name__ == "__main__":
    main()

繰り返しスキをくれる人が見えると、その人たちがどんな記事に反応しているかが分かります。これは読者像を掴むうえで有用な情報です。相互目的でスキを返すためではなく、書く内容を考えるために使ってください。

自分のフォローを整理する

フォローが増えすぎてタイムラインが機能しなくなったときの、整理を支援するスクリプトです。判断は人が行い、実行だけを補助します。

02_review_following.py
"""
自分のフォロー一覧を確認し、整理の候補を提示する。

判定するのはスクリプト、決めるのは人間。
実際の解除は確認したうえで手動または少数ずつ行う。
"""
import time
from datetime import datetime, timezone

from note_auth_client import NoteAuthClient, CookieExpiredError

SLEEP_SEC = 1.5
STALE_DAYS = 365


def fetch_following(client: NoteAuthClient, urlname: str,
                    max_pages: int = 40) -> list:
    """自分がフォローしている人の一覧"""
    users = []

    for page in range(1, max_pages + 1):
        data = client.get_json(
            f"/api/v2/creators/{urlname}/followings",
            params={"page": page},
        )

        items = data.get("contents", data.get("users", []))
        if not items:
            break

        users.extend(items)
        print(f"  page {page}: 累計 {len(users)} 人")

        if data.get("isLastPage") or data.get("is_last_page"):
            break

        time.sleep(SLEEP_SEC)

    return users


def parse_date(value: str):
    if not value:
        return None
    try:
        return datetime.fromisoformat(value.replace("Z", "+00:00"))
    except ValueError:
        return None


def review(client: NoteAuthClient, users: list):
    """整理の候補を分類する"""
    now = datetime.now(timezone.utc)

    inactive = []
    no_notes = []
    active = []

    for u in users:
        urlname = u.get("urlname", "")
        nickname = u.get("nickname", urlname)
        note_count = u.get("noteCount", u.get("note_count", 0)) or 0
        last = parse_date(
            u.get("lastNoteAt") or u.get("last_note_at") or ""
        )

        if note_count == 0:
            no_notes.append((nickname, urlname))
            continue

        if last and (now - last).days > STALE_DAYS:
            days = (now - last).days
            inactive.append((nickname, urlname, days))
            continue

        active.append((nickname, urlname))

    print(f"\n=== フォロー {len(users)} 人の内訳 ===")
    print(f"  活動中           : {len(active)} 人")
    print(f"  1年以上更新なし  : {len(inactive)} 人")
    print(f"  記事が0本        : {len(no_notes)} 人")

    if inactive:
        print(f"\n--- 1年以上更新がない方(整理の候補)---")
        for nickname, urlname, days in inactive[:25]:
            print(f"  {days:>5}日前  {nickname[:24]}(@{urlname})")

    if no_notes:
        print(f"\n--- 記事が0本の方 ---")
        for nickname, urlname in no_notes[:15]:
            print(f"  {nickname[:24]}(@{urlname})")

    print("\n※ 解除するかどうかは、ご自身で判断してください")
    print("※ タイムラインに出てきたとき読みたいかが基準です")


def main():
    try:
        client = NoteAuthClient()
        user = client.verify()
    except CookieExpiredError as e:
        print("認証エラー:", e)
        return

    urlname = user.get("urlname", "")
    if not urlname:
        print("ユーザー情報を取得できません")
        return

    print(f"@{urlname} のフォロー一覧を取得します")
    users = fetch_following(client, urlname)

    review(client, users)


if __name__ == "__main__":
    main()

判定と実行を分ける

このスクリプトは候補を提示するだけで、フォロー解除は実行しません。判断が必要な操作を自動実行しないという設計です。実際に解除するかどうかは、一覧を見て自分で決めてください。フォロー解除の考え方はフォロー解除の記事で解説しています。

自分がスキした記事を記録する

もう1つの安全な用途は、自分の行動の記録です。「良いと思って保存したはずの記事が見つからない」という状況を防げます。

03_my_likes_log.py
"""
自分がスキした記事を記録し、あとから検索できるようにする。
読み取りのみなので安全。

noteの画面でもスキした記事は一覧できるが、
手元にCSVで持っておくと検索・分類がしやすい。
"""
import csv
import os
import time

from note_auth_client import NoteAuthClient, CookieExpiredError

OUTPUT = "my_likes.csv"
SLEEP_SEC = 1.5
FIELDS = ["key", "title", "author", "author_urlname",
          "price", "url", "recorded_at"]


def fetch_my_likes(client: NoteAuthClient, urlname: str,
                   max_pages: int = 40) -> list:
    """自分がスキした記事の一覧を取得する"""
    items = []

    for page in range(1, max_pages + 1):
        data = client.get_json(
            f"/api/v2/creators/{urlname}/likes",
            params={"page": page},
        )

        contents = data.get("contents", data.get("likes", []))
        if not contents:
            break

        items.extend(contents)
        print(f"  page {page}: 累計 {len(items)} 件")

        if data.get("isLastPage") or data.get("is_last_page"):
            break

        time.sleep(SLEEP_SEC)

    return items


def normalize(items: list) -> list:
    from datetime import datetime

    stamp = datetime.now().strftime("%Y-%m-%d")
    rows = []

    for entry in items:
        # note が入れ子になっている場合とそうでない場合に対応
        note = entry.get("note", entry)
        user = note.get("user") or {}
        key = note.get("key", "")
        author_url = user.get("urlname", "")

        rows.append({
            "key": key,
            "title": note.get("name", ""),
            "author": user.get("nickname", ""),
            "author_urlname": author_url,
            "price": note.get("price", 0) or 0,
            "url": f"https://note.com/{author_url}/n/{key}",
            "recorded_at": stamp,
        })

    return rows


def save(rows: list, path: str = OUTPUT):
    if not rows:
        print("記録対象がありません")
        return

    with open(path, "w", encoding="utf-8-sig", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=FIELDS)
        writer.writeheader()
        writer.writerows(rows)

    print(f"\n{path} に {len(rows)} 件を保存しました")


def summarize(rows: list):
    from collections import Counter

    authors = Counter(r["author"] for r in rows if r["author"])

    print("\n=== よくスキしている書き手 ===")
    for name, count in authors.most_common(15):
        print(f"  {count:>3} 件  {name[:24]}")

    paid = [r for r in rows if r["price"]]
    print(f"\n有料記事へのスキ: {len(paid)} 件")


if __name__ == "__main__":
    try:
        client = NoteAuthClient()
        user = client.verify()

        urlname = user.get("urlname", "")
        if not urlname:
            raise SystemExit("ユーザー情報を取得できません")

        print(f"@{urlname} がスキした記事を取得します")
        rows = normalize(fetch_my_likes(client, urlname))

        save(rows)
        summarize(rows)

    except CookieExpiredError as e:
        print("認証エラー:", e)

よくスキしている書き手が分かると、自分がどんな記事に惹かれているかが客観的に見えます。これは自分が何を書くべきかを考える材料にもなります。

相互フォローの状態を確認する

フォローとフォロワーを突き合わせて、片方向の関係を把握する処理です。これも読み取りのみです。

04_follow_status.py
"""
フォローとフォロワーを比較して、関係の内訳を確認する。

【注意】
この結果をもとに「フォローバックしてくれない人を外す」という
運用は推奨しない。フォローは一方向でも成立する関係である。
数字を把握するための情報として使うこと。
"""
import time

from note_auth_client import NoteAuthClient, CookieExpiredError

SLEEP_SEC = 1.5


def fetch_users(client: NoteAuthClient, urlname: str,
                kind: str, max_pages: int = 40) -> list:
    """
    kind: "followings"(フォロー中) / "followers"(フォロワー)
    """
    users = []

    for page in range(1, max_pages + 1):
        data = client.get_json(
            f"/api/v2/creators/{urlname}/{kind}",
            params={"page": page},
        )

        items = data.get("contents", data.get("users", []))
        if not items:
            break

        users.extend(items)

        if data.get("isLastPage") or data.get("is_last_page"):
            break

        time.sleep(SLEEP_SEC)

    return users


def to_map(users: list) -> dict:
    """urlname をキーにした辞書にする"""
    result = {}
    for u in users:
        urlname = u.get("urlname")
        if urlname:
            result[urlname] = u.get("nickname", urlname)
    return result


def report(following: dict, followers: dict):
    mutual = set(following) & set(followers)
    only_following = set(following) - set(followers)
    only_followers = set(followers) - set(following)

    print("\n=== 関係の内訳 ===")
    print(f"  フォロー中     : {len(following)} 人")
    print(f"  フォロワー     : {len(followers)} 人")
    print(f"  相互           : {len(mutual)} 人")
    print(f"  自分から片方向 : {len(only_following)} 人")
    print(f"  相手から片方向 : {len(only_followers)} 人")

    if following:
        rate = len(mutual) / len(following) * 100
        print(f"\n  フォロー中のうち相互の割合: {rate:.1f}%")

    if only_followers:
        print("\n=== フォローしてくれている方(自分は未フォロー)===")
        for urlname in list(only_followers)[:20]:
            print(f"  {followers[urlname][:24]}(@{urlname})")
        print("  → 記事を見て、読みたいと思う人だけフォローすれば十分です")


if __name__ == "__main__":
    try:
        client = NoteAuthClient()
        user = client.verify()

        urlname = user.get("urlname", "")
        if not urlname:
            raise SystemExit("ユーザー情報を取得できません")

        print("フォロー中を取得します")
        following = to_map(fetch_users(client, urlname, "followings"))

        print("フォロワーを取得します")
        followers = to_map(fetch_users(client, urlname, "followers"))

        report(following, followers)

    except CookieExpiredError as e:
        print("認証エラー:", e)

この結果で人を切り分けない

「フォローバックしてくれない人を外す」という運用は、数字のための行動であって、読みたいものを読むという本来の目的から外れています。フォローは一方向でも成立する関係です。この集計は、自分のタイムラインの構成を把握するために使ってください

操作ログを残す

投稿系の操作を行う場合、何をいつ実行したかを記録しておくべきです。問題が起きたときに説明できる状態にしておくためです。

action_log.py
"""
スキ・フォローなどの操作を記録するラッパー。

- 1日の実行上限を管理する
- 何をいつ実行したかCSVに残す
- 上限に達したら例外を投げて止まる
"""
import csv
import os
from datetime import date, datetime

LOG_FILE = "action_log.csv"
DAILY_LIMIT = 30
FIELDS = ["date", "time", "action", "target", "result"]


class DailyLimitExceeded(Exception):
    """1日の上限に達した"""


def count_today(path: str = LOG_FILE) -> int:
    """今日すでに何件実行したかを数える"""
    if not os.path.exists(path):
        return 0

    today = date.today().isoformat()
    count = 0

    with open(path, encoding="utf-8-sig", newline="") as f:
        for row in csv.DictReader(f):
            if row.get("date") == today:
                count += 1

    return count


def record(action: str, target: str, result: bool,
           path: str = LOG_FILE):
    """操作を記録する"""
    now = datetime.now()
    exists = os.path.exists(path)

    with open(path, "a", encoding="utf-8-sig", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=FIELDS)
        if not exists:
            writer.writeheader()

        writer.writerow({
            "date": now.date().isoformat(),
            "time": now.strftime("%H:%M:%S"),
            "action": action,
            "target": target,
            "result": "ok" if result else "failed",
        })


def guard(path: str = LOG_FILE, limit: int = DAILY_LIMIT):
    """上限に達していたら例外を投げる"""
    count = count_today(path)

    if count >= limit:
        raise DailyLimitExceeded(
            f"1日の上限({limit}件)に達しています。"
            f"今日はすでに {count} 件実行しました"
        )

    return count


def summary(path: str = LOG_FILE):
    """記録の集計を表示する"""
    if not os.path.exists(path):
        print("記録がありません")
        return

    from collections import Counter

    with open(path, encoding="utf-8-sig", newline="") as f:
        rows = list(csv.DictReader(f))

    by_date = Counter(r["date"] for r in rows)
    by_action = Counter(r["action"] for r in rows)
    failed = sum(1 for r in rows if r["result"] == "failed")

    print(f"=== 記録 {len(rows)} 件 ===")
    print(f"  失敗: {failed} 件")

    print("\n--- 操作の種類 ---")
    for action, count in by_action.most_common():
        print(f"  {action}: {count} 件")

    print("\n--- 日別の実行数(直近14日)---")
    for d in sorted(by_date)[-14:]:
        count = by_date[d]
        bar = "|" * count
        warn = "  ← 多い" if count > DAILY_LIMIT else ""
        print(f"  {d}  {count:>3} 件  {bar}{warn}")


if __name__ == "__main__":
    print(f"今日の実行数: {count_today()} / {DAILY_LIMIT}")
    summary()

1日の上限をコード側で管理しておけば、うっかり大量実行してしまう事故を防げます。実行前に guard() を呼ぶだけで、上限に達していれば例外で止まります。

▼ RECOMMENDATION ▼

自動いいねを使いたいなら、それが認められている場所で

noteでの機械的なスキ・フォローはリスクがあります。一方アメブロには、いいね・フォロー・アクセス獲得の自動化を前提とした専用ツールが以前から存在し、アメプレスProはその代表格です。12年連続でインフォトップ上位に入り、累計10万本の販売実績があります。プログラミング不要で、設定するだけで24時間稼働。ASPアフィリエイトも使えるため、集めたアクセスを収益に変えられます。月額2,980円、365日LINEサポート付きです。

✦ 自動いいね・フォロー ✦ プログラミング不要 ✦ ASPアフィリOK ✦ 365日LINEサポート
アメプレスPro 公式ページを確認する →

※ 当サイトはアフィリエイト広告を含みます。

noteとアメブロの違い

同じ「自動いいね」でも、プラットフォームによって扱いが異なります。この違いは知っておく価値があります。

観点 note アメブロ
自動化ツール 公式・非公式ともに提供なし 専用ツールが複数存在する
実装方法 自分でコードを書くしかない 既製ツールを設定するだけ
ASPアフィリエイト 原則禁止 規約上使える
文化 コンテンツの質を重視 交流からの流入が機能する
自動化の位置づけ グレーからブラック 広く使われている

noteは「読まれる記事を書けば読まれる」という思想が強く、交流による集客の比重が低い設計です。一方アメブロは、いいねや読者登録からの回遊が実際に機能する構造になっています。同じ手法でも、プラットフォームの設計思想に合っているかどうかで結果が変わります。詳しい比較はアメブロとnoteの比較記事を参照してください。

それでも実装する場合の最低限

推奨はしませんが、どうしても実装する場合に守るべき条件を整理します。

条件 目安 理由
実行間隔 最低5秒、可能なら10秒以上 人間の操作速度を超えない
間隔のばらつき 乱数で±3秒程度変動させる 規則的な動きを避ける
1日の上限 数十件まで 人間が手作業でできる範囲
対象の選定 実際に関心のあるジャンルに限る 無差別な実行を避ける
実行時間帯 深夜帯を避ける 生活時間と矛盾させない
ログの記録 いつ何をしたか残す 問題が起きたときに確認できる

それでもリスクはゼロにならない

これらの条件を守っても、規約上の問題が消えるわけではありません。アカウントを失った場合、それまで積み上げた記事とフォロワーがすべて失われます。数年かけて育てたアカウントを、一時的な数字のために危険に晒す価値があるか——この問いに自分で答えてから判断してください。リスクについてはnoteの危険性の記事でも扱っています。

代わりにやるべきこと

自動いいねに使う時間を、別のことに振り向けた場合の効果を比較します。

やること リスク 効果の持続
自動いいねを回す 高い 一時的
検索を意識したタイトルにする なし 長期的
プロフィールと固定記事を整える なし 継続的
SNSで告知する なし 即効性あり
マガジンで回遊を作る なし 継続的
1本の記事を丁寧に書く なし 長期的

自動いいねは、唯一「リスクが高く効果が一時的」な選択肢です。他の項目はすべてリスクゼロで、効果も持続します。合理的に考えれば、選ぶ理由はありません。伸ばし方の具体策は伸ばし方の記事にまとめています。

まとめ:実装できることと、すべきことは違う

スキ・フォローのAPIは単純で、実装自体は数行で終わります。だからこそ、技術的な難易度ではなく判断が問われます。

この記事の要点

  1. スキは /api/v3/notes/{key}/likes、記事キーを使う
  2. フォローは /api/v3/users/{id}/following、数値IDを使う
  3. urlnameから数値IDは /api/v2/creators/{urlname} で引く
  4. 他人への機械的な大量実行は規約・評判の両面でリスクがある
  5. 相互スキで増やした数字は、改善の材料にならない
  6. 安全なのは、自分のデータの読み取りと整理
  7. フォロー整理は「判定は機械、決定は人間」で分ける
  8. 同じ時間を記事の質に使うほうが確実にリターンがある

この記事のコードは、仕様の理解と自分のデータ整理のために提供しています。読み取り系のスクリプトから使ってみてください。繰り返しスキをくれる人が見えるだけでも、書く内容の判断が変わります。

FAQ|スキ・フォローの自動化についてよくある質問

Q. 自動いいねは規約違反ですか?

「自動いいねは禁止」と名指しされた条文があるわけではありませんが、スパム行為や不正な手段でサービスを利用する行為は規約で禁じられています。機械的な大量実行はこれに該当しうると考えられます。グレーゾーンで運用するリスクを負うかどうかは、失うものの大きさと比べて判断してください。

Q. 少量なら問題ありませんか?

「実際に読んで、良いと思った記事にスキを付ける」という行為は、それが手動でもプログラム経由でも本質は同じです。問題は、読まずに機械的に付けることです。少量であってもその性質は変わりません。判断の基準は量ではなく、実際の行動として自然かどうかです。

Q. 相互フォロー企画に参加するのはどうですか?

規約違反ではありませんが、効果は限定的です。相互目的でフォローした人は、あなたの記事を読みません。フォロワー数だけが増え、実際の読者は増えないという状態になります。さらに、フォロワー数と実際の反応が乖離すると、数字から状況を判断できなくなります。参加する前に、何を得たいのかを考えてください。

Q. アカウントが制限されたらどうなりますか?

程度によりますが、一時的な機能制限から、最悪の場合はアカウントの利用停止まであり得ます。停止された場合、記事もフォロワーも売上金も失う可能性があります。数年かけて積み上げたものが一度に消えるリスクを、一時的な数字と引き換えにする判断が妥当かどうか——冷静に考えれば答えは出るはずです。

Q. アメブロなら自動いいねを使っていいのですか?

アメブロには自動化ツールが以前から存在し、広く使われているという実態があります。プラットフォームの文化として、いいねや読者登録からの交流が集客手段として機能している面もあります。ただし、こちらも過度な使用は問題になり得ます。ツールを使う場合も、設定を極端にせず、常識的な範囲で運用してください。

Q. スキを付けてくれた人にスキを返すのは?

その人の記事を実際に読んで良いと思ったなら、自然な行動です。問題は、読まずに機械的に返す場合です。「スキを返さなければ」という義務感で動くと、そのうち負担になって続かなくなります。読みたいと思ったときに読む、という自然な形に留めるのが健全です。

Q. スキした人の一覧を取得するのは問題ありませんか?

自分の記事にスキを付けた人の一覧は、noteの画面上でも確認できる公開情報です。取得して集計すること自体に問題はありません。ただし、そのリストを使って個別に営業をかけるような使い方は、相手の期待に反します。読者像を把握するための情報として使ってください。

Q. フォロワーを増やす正攻法はありますか?

テーマを絞って記事を積むことです。「何の人か分かる」状態になると、その分野に関心のある人がフォローします。加えて、プロフィールと固定記事を整えること、検索から入ってきた人が他の記事も読める導線を作ることが効きます。時間はかかりますが、この方法で増えたフォロワーは実際に記事を読んでくれます。

Q. フォロー解除をスクリプトで一括実行できますか?

技術的には可能ですが、この記事のスクリプトは候補の提示までに留めています。フォロー解除は相手との関係に関わる判断であり、機械的に処理すべきものではないと考えるためです。また、短期間に大量の解除を行うと、それ自体が不自然な動きとして検知される可能性もあります。少数ずつ手動で行うのが安全です。

Q. この記事のコードで制限を外せますか?

コードを書き換えれば技術的には外せます。ただし、その制限は事故を防ぐために意図的に入れているものです。外して大量実行した結果については、この記事では責任を負えません。制限を外そうと思ったとき、なぜそれが必要なのかを一度考えてみてください。多くの場合、目的は別の方法でも達成できます。

アメプレスラボ編集部

AMEPRESS LAB EDITORIAL TEAM

アメブロ・note・WordPressを実際に運営し、それぞれの収益構造を数字で検証しているチーム。プラットフォームごとの得意・不得意を踏まえた、宣伝色に偏らない実践情報の発信を続けています。