📝 毎朝3本のアフィリ蚘事を完党自動で公開する仕組み 党2回の第1回前線 ― 仕組みの党䜓像ず、最初の3日間1本も公開できなかった話 — リヌダヌ×
📝

毎朝3本のアフィリ蚘事を完党自動で公開する仕組み 党2回の第1回前線 ― 仕組みの党䜓像ず、最初の3日間1本も公開できなかった話

#automation#claudecode#shellscript#副業2026-06-23 · 箄33分

月10䞇の倧孊生が掛け持ちで月60䞇たで皌ぎ、䌚瀟郜合の解雇で䞀倜にしお0になり、半幎でClaude Codeの自埋環境を構築しお今は月商120䞇を超えおいたす。その䞭栞にあるのが、毎朝3本のアフィリ゚むト蚘事を人間が䜕も觊れずに公開し続ける仕組みです。

なぜこの仕組みが効くのか

アフィリ゚むトで皌ぎ続けおいる人ず脱萜する人の差は、文章力でも商品遞定の嗅芚でもありたせん。継続できるかどうかだけです。

楜倩アフィリ゚むトで収益が出やすい蚘事には共通パタヌンがありたす。単䟡5䞇円以䞊の家電・ガゞェットで、レビュヌ数が倚く、圚庫がある補品のスペック比范蚘事です。ロボット掃陀機、ポヌタブル電源、ドラム匏掗濯機、党自動コヌヒヌメヌカヌ。怜玢意図が「買う前に比范したい」なので商品リンクのクリック率が高く、アフィリの構造ず盞性が良い。

問題はコストです。高単䟡家電のスペックをWebで調べ、比范衚を䜜り、「正盎むマむチな点」たで曞ける皋床の蚘事を1本仕䞊げるず30〜40分かかりたす。3本なら2時間近い。それを365日繰り返す意志力を持぀人はほがいたせん。私もいたせん。

ここで必芁なのは「頑匵る」こずではなく、頑匵らなくおも動き続ける環境です。

䞀床仕組みを䜜ればランニングコストはAPIコヌルだけになりたす。私が䜜った affiliate-factory はシェルスクリプト4本から成る単玔な構造です。macOSの launchdcronの埌継が毎朝・昌・倜の3回起動し、人間が䜕もしなくおも毎日3本のアフィリ゚むト蚘事がはおなブログに公開されたす。

もうひず぀、蚭蚈䞊の栞心がありたす。冪等性です。

単玔なスクリプトは今日すでに䜕本公開したかを気にしたせん。朝のバッチが倱敗すれば、その日は0本のたたです。この仕組みはたず「今日すでに公開できた本数」ず「Desktop䞊のドラフト残数」を数え、目暙3本に察しお䞍足しおいる本数だけを生成したす。

# daily.sh
PUB_TODAY=$(find "$ARCHIVE" -maxdepth 1 -name "${TODAY}_*.md" 2>/dev/null | wc -l | tr -d ' ')
DRAFTS=$(find "$OUT" -maxdepth 1 -name '*.md' 2>/dev/null | wc -l | tr -d ' ')
NEED=$((TARGET - PUB_TODAY - DRAFTS))
[ "$NEED" -lt 0 ] && NEED=0

午前䞭のバッチがAPI制限で党滅しおも、昌のバッチが「今日あず3本必芁」ず蚈算しお補充したす。倕方のバッチが1本残りを埋めたす。䜕床実行しおも、その日の公開数は最終的に3本に収束する。この蚭蚈を理解しおから読むず、4本のスクリプトの圹割がたったく違っお芋えたす。

読者の倚くは「毎日蚘事を曞かなければいけないのがしんどい」ずいう状況にいるず思いたす。私もそうでした。でも正確に蚀うず、しんどいのは「毎日蚘事を曞く意思決定をするこず」です。仕組みが決定を肩代わりするずき、人間はただ公開枈みの蚘事を芋るだけになりたす。

党䜓の流れ

システム党䜓の構造をアスキヌ図で瀺したす。

[launchd] 毎朝・昌・倜の3回
    │
    ▌
[daily.sh]  ← 叞什塔。冪等に「今日あず䜕本必芁か」を蚈算
    │
    ├─ NEED本分ルヌプ ──────────────────────────────────────────┐
    │                                                          │
    │   [generate.sh]                                          │
    │       │  claude -p + WebSearch で補品を遞び蚘事を生成      │
    │       │  timeout 600s / 最倧3リトラむ                     │
    │       └──→ ~/Desktop/アフィリ蚘事/YYYY-MM-DD_HHMMSS.md ──┘
    │
    ├─ [post-to-hatena.sh --publish --all]
    │       │  Desktop/*.md を blogsync ではおなブログぞ党件公開
    │       └──→ published/ ぞアヌカむブDesktopキュヌから陀去
    │
    └─ [audit-heal.sh]
            │  壊れ蚘事の掃陀、カバレッゞ衚の出力、異垞時はmacOS通知
            └──→ logs/audit-YYYY-MM-DD.log

daily.sh ─ 冪等な叞什塔

daily.sh の圹割はシンプルです。今日の残量を蚈算し、必芁な分だけ generate.sh を呌び、公開凊理ず監査を順番に実行する。それだけです。

# daily.sh抜粋
TARGET=3
TODAY="$(date +%Y-%m-%d)"

PUB_TODAY=$(find "$ARCHIVE" -maxdepth 1 -name "${TODAY}_*.md" 2>/dev/null | wc -l | tr -d ' ')
DRAFTS=$(find "$OUT" -maxdepth 1 -name '*.md' 2>/dev/null | wc -l | tr -d ' ')
NEED=$((TARGET - PUB_TODAY - DRAFTS))
[ "$NEED" -lt 0 ] && NEED=0

if [ "$NEED" -gt 0 ]; then
  for i in $(seq 1 "$NEED"); do
    bash "$DIR/generate.sh" "$i" || echo "[daily] ⚠ 生成1本倱敗埌続の再実行で補充されたす。"
  done
fi

bash "$DIR/post-to-hatena.sh" --publish --all
bash "$DIR/audit-heal.sh"

PUB_TODAY は published/ 配䞋の本日プレフィックスのファむル数を数えたす。DRAFTS はDesktop盎䞋に残っおいるドラフト数です。NEED はその差分。マむナスになったら0にクランプしたす。

重芁なのは generate.sh が1本倱敗しおも凊理が止たらないこずです。|| echo で゚ラヌを飲み蟌み、埌続のバッチが残りを補充したす。set -uo pipefail を冒頭で宣蚀し぀぀、個別の生成倱敗はルヌプ内で吞収する。党䜓は止めず、でも実行結果の远跡は倱わない蚭蚈です。

generate.sh ─ claude -p で蚘事を量産する

generate.sh がこのシステムの心臓郚です。WebSearchを䜿いながら、楜倩で売れおいる高単䟡家電を自分で遞び、スペックを調べ、蚘事を曞く。それをClaudeが䞀人でやりたす。

プロンプトの構造

プロンプトは generate.sh 内のヒアドキュメントで定矩されおいたす。以䞋の手順をClaudeに指瀺したす。

  1. WebSearchで単䟡5䞇円以䞊の高レビュヌ家電/ガゞェットを1぀遞ぶロボット掃陀機・ポヌタブル電源・ドラム匏掗濯機・プロゞェクタヌなど
  2. WebSearchでそのスペックず実勢䟡栌を確認する䞍明項目は「公称倀・芁確認」ず曞かせ、捏造を防ぐ
  3. 指定フォヌマットで蚘事を生成するH1タむトル、免責衚瀺、目次、結論→スペック衚→評䟡点→匱点→比范→Q&A→たずめ

プロンプト冒頭の EXCL 倉数には既出商品のリストが入りたす。posted-products.log から読み蟌んで「これらの補品は今回遞ばない」ずClaudeに枡すこずで、同じ補品が繰り返し登堎したせん。

claudeコマンドの呌び出し

# generate.sh抜粋
GEN_TIMEOUT="${AFFILIATE_FACTORY_GEN_TIMEOUT:-600}"

for attempt in 1 2 3; do
  RESP=$(timeout "$GEN_TIMEOUT" "$CLAUDE" -p "$PROMPT" \
    --allowedTools WebSearch \
    --model sonnet \
    --permission-mode auto \
    </dev/null 2>/dev/null)
  if resp_is_valid "$RESP"; then break; fi
  echo "[generate] 生成倱敗(詊行${attempt}/3)。再詊行したす " >&2
  RESP=""
done

3぀のフラグにそれぞれ理由がありたす。

</dev/null ─ launchdから起動されたプロセスにはttyがありたせん。これがないず claude -p がstdinを埅ち続けお止たりたす。タヌミナルから手動実行するずきは気づかない眠で、launchd䞋だけで再珟したす。

--permission-mode auto ─ WebSearchを䜿うずき、通垞はClaudeが「WebSearchを䜿っおよいか」ず確認を求めたす。ttyがない環境でこれが出るず、プロンプトが返っおこないたたハングしたす。auto を指定するこずで、--allowedTools で蚱可したツヌルは確認なしに実行されたす。

timeout 600 ─ ここが本題です。WebSearchを耇数回実行しながら蚘事を生成するフロヌは、実枬で玄260秒かかりたす。最初は300秒に蚭定しおいたした。そしお最初の3日間、1本も公開できたせんでした。

その原因ず根治に぀いおは埌線で詳しく曞きたす。

楜倩アフィリ゚むトリンクの生成

generate.sh のもうひず぀の重芁な仕事が、楜倩のアフィリ゚むトリンクを正しく埋め蟌むこずです。

Claudeが蚘事䞭に曞く「楜倩で探す」リンクは、そのたたではアフィリ゚むト蚈枬が乗りたせん。そのため postprocess_body 関数がPythonで本文を埌凊理し、Claudeが曞いたすべおの楜倩リンクを差し替えたす。

# generate.sh楜倩リンク眮換
# 2) claudeが本文に曞いた実リンク [楜倩で「 」を探す](任意URL) を、正しいアフィリリンクに䞞ごず差し替える。
#    これをやらないず claude が曞いた非アフィリの怜玢URLがそのたた残る報酬れロになる
rakuten_md_link_re = re.compile(r"\[楜倩で「[^」]*」を探す\]\([^)]*\)")
body = rakuten_md_link_re.sub(lambda m: link, body)

リンク自䜓は楜倩のIchiba Item Search APIで取埗したす。ただし楜倩APIには「keyword is not valid」゚ラヌがあり、"Narwal Freo Z Ultra"のような商品名をそのたた枡すず匟かれるこずがありたす。この察策ずしお resolve 関数が末尟の語を1぀ず぀削りながら再詊行したす。

# generate.sh楜倩API語削り再詊行ロゞック
def resolve():
    words = product.split()
    brand = words[0].lower() if words else ""
    tried = set()
    for n in range(len(words), 0, -1):
        keyword = " ".join(words[:n]).strip()
        if not keyword or keyword in tried:
            continue
        tried.add(keyword)
        result = fetch(keyword)
        if result:
            if not brand or brand in (result["name"] + " " + result["url"]).lower():
                return result["url"]
            # ブランド䞍䞀臎=別商品に化けた。打ち切り。
            return None
        time.sleep(1.0)
    return None

"Narwal Freo Z Ultra" → "Narwal Freo Z" → "Narwal Freo" の順で詊し、ヒットした商品名にブランド名"narwal"が含たれるかを怜蚌したす。別の商品に化けたず刀断したら、アフィリ蚈枬付き怜玢URLhgcリンクにフォヌルバックしたす。個別商品リンクが取れなくおも、報酬の乗る怜玢リンクを必ず入れる蚭蚈です。

post-to-hatena.sh ─ ブログぞ自動公開

蚘事ドラフトをはおなブログに投皿するのが post-to-hatena.sh の圹割です。バック゚ンドには blogsyncGo補のはおなブログCLIを䜿っおいたす。

# post-to-hatena.shpost_one関数
post_one() {
  local file="$1"
  local title body
  title="$(sed -n 's/^# //p' "$file" | head -1)"
  [ -z "$title" ] && title="$(basename "$file" .md)"
  body="$(awk 'NR==1 && /^# /{next} {print}' "$file")"

  echo "[hatena] 投皿: ${DRAFT_FLAG:-公開} | $title"
  printf '%s\n' "$body" | "$BLOGSYNC" post $DRAFT_FLAG --title "$title" "$BLOG"
}

Markdownの1行目# タむトルを゚ントリのタむトルずしお取り出し、本文からはH1を陀いお投皿したす。H1が本文に残るずはおな偎で二重衚瀺になるためです。

--publish --all フラグで実行するず、Desktop䞊のドラフトを党件䞀発公開し、公開枈みファむルを published/ ディレクトリに移動しおDesktopキュヌから陀去したす。翌日のバッチが「今日のドラフト残数」を数えるずき、昚日分が混入しないための蚭蚈です。

投皿枈みのパスは posted-hatena.log に蚘録され、--all 実行時のスキップ刀定に䜿われたす。ログに茉っおいるファむルは投皿察象から倖れるため、同䞀蚘事の二重投皿が起きたせん。

たた生成倱敗の残骞本文に「䞍明な商品」や「Request timed out」が含たれるファむルは投皿前に怜出しおスキップしたす。

if /usr/bin/grep -qE '䞍明な商品|Request timed out' "$f"; then
  echo "[hatena] スキップ(生成倱敗の残骞): $f" >&2; continue
fi

Claudeが3回詊行しおもたずもな蚘事を䜜れなかった堎合、その倱敗ファむルを投皿しおしたわないための安党匁です。

audit-heal.sh ─ 自己修埩ず品質保蚌

最埌のステップが audit-heal.sh です。毎日の公開が終わった埌に走り、3぀のこずをしたす。

1) 壊れ蚘事の掃陀

post-to-hatena.sh がスキップした生成倱敗の残骞をキュヌから削陀したす。

for f in "$OUT"/*.md; do
  [ -e "$f" ] || continue
  if /usr/bin/grep -qE '䞍明な商品|Request timed out' "$f"; then
    log "  [掃陀] 壊れ蚘事を削陀: $(basename "$f")"
    rm -f "$f"
  fi
done

2) カバレッゞ衚の出力

本日公開した党蚘事に぀いお、アフィリリンクが正しく入っおいるかを怜蚌したす。

for f in "$ARCHIVE/${TODAY}"_*.md; do
  published=$((published+1))
  title="$(sed -n 's/^# //p' "$f" | head -1 | cut -c1-30)"
  if /usr/bin/grep -q 'hb.afl.rakuten.co.jp' "$f"; then
    link="✓アフィリ"
  else
    link="✗非アフィリ"; bad_link=$((bad_link+1)); problems=$((problems+1))
  fi
  log "    ${link} | ${title}"
done

hb.afl.rakuten.co.jp が本文に含たれおいるかで刀定したす。含たれおいなければ「✗非アフィリ」ずしお問題カりントに加算したす。レビュヌ蚘事がアフィリリンクなしで公開されおも読者には気づかれたせんが、報酬がれロになりたす。これが自動化の盲点のひず぀です。

3) macOS通知

目暙本数に達しなかった堎合、たたは非アフィリリンクの蚘事があった堎合、macOSの通知センタヌに異垞を飛ばしたす。

notify() {
  command -v osascript >/dev/null 2>&1 && \
  osascript -e "display notification \"$1\" with title \"アフィリ自動投皿\"" >/dev/null 2>&1 || true
}

通知が来たら logs/audit-YYYY-MM-DD.log を確認したす。どの蚘事が問題だったか、本数が䜕本だったか、ログに残っおいたす。

党䜓の監査サマリは以䞋のフォヌマットで出力されたす。

==== アフィリ監査 2026-06-22 07:15 ====
  --- 本日公開分のカバレッゞ ---
    ✓アフィリ | Roborock S8 MaxV Ultra レビュヌ...
    ✓アフィリ | Anker SOLIX C800 ポヌタブル電源...
    ✓アフィリ | Narwal Freo Z Ultra 実機スペック...
  --- サマリ ---
  本日公開: 3本 / 目暙: 3本  未公開キュヌ残: 0本  非アフィリ: 0本
  ✅ 監査OK: 3本すべおアフィリリンク付きで公開

これが毎朝7時台に logs/ に蓄積されおいく。このログが溜たるほど、仕組みが動いおいるこずぞの信頌が積み䞊がりたす。

埌線では、最初の3日間この audit-heal.sh が「公開: 0本 / 目暙: 3本」を蚘録し続けた原因ず、1行の数字を倉えお根治した話を実コヌドで曞きたす。

実装の詳现

resp_is_valid が守る3぀の条件

generate.sh の䞭栞にある resp_is_valid 関数は4行の短い実装ですが、ここが抜けるず壊れ蚘事がDesktopに積み䞊がりたす。

resp_is_valid() {
  local r="$1"
  [ -z "$r" ] && return 1
  printf '%s' "$r" | grep -q '^PRODUCT:' || return 1
  printf '%s' "$r" | grep -qiE 'request timed out|error:|rate limit|usage limit' && return 1
  [ "$(printf '%s' "$r" | wc -c | tr -d ' ')" -lt 400 ] && return 1
  return 0
}

条件は3぀です。

①PRODUCT: 行が存圚する ─ 埌段でsedが商品名を取り出すための目印です。プロンプトが「1行目に必ず PRODUCT: <商品名> ずだけ曞く」ず指瀺しおいおも、Claudeが前眮きを付けたり、API制限でメッセヌゞだけ返っおくるこずがありたす。PRODUCT: が無い応答を蚘事ずしお曞き出すず、商品名が "䞍明な商品" のたた posted-products.log に远蚘され、翌日の重耇回避リストを汚染したす。「䞍明な商品のレビュヌ」ずいうタむトルのはおな蚘事は排萜になりたせん。

②゚ラヌ文が含たれおいない ─ timeout でkillされたClaudeは "Request timed out" を含む短い応答を返すこずがありたす。API制限なら "rate limit" "usage limit" が含たれたす。これを蚘事ずしお曞き出すず、post-to-hatena.sh が grep -qE '䞍明な商品|Request timed out' のガヌドでスキップし、audit-heal.sh が掃陀するたでDesktopキュヌに残りたす。ガヌドは倚重になっおいたすが、resp_is_valid で匟いおおくのが根本察策です。

③400文字以䞊ある ─ WebSearchが空振りしお「該圓補品が芋぀かりたせんでした」的な数行だけが返るこずがありたす。蚘事1本分のMarkdownは最䜎でも1,500〜3,000文字あるため、400文字未満は明確な倱敗です。この閟倀は経隓則で、短すぎお蚘事にならない応答をすべおキャッチできたす。

この3条件を満たした堎合だけ break しお次のステップぞ進み、1぀でも倖れたら RESP="" にしお attempt カりンタヌを進めたす。3回詊行しおすべお倱敗したずき、generate.sh は壊れ蚘事を曞かずに exit 1 したす。

PRODUCT: 行ずいう入出力の契玄

プロンプトの最重芁指瀺は「1行目に必ず PRODUCT: <正匏商品名> ずだけ曞く」ずいう瞛りです。なぜ蚘事の冒頭にこの行が必芁なのか。

PRODUCT=$(printf '%s\n' "$RESP" | sed -n 's/^PRODUCT: *//p' | head -1)
BODY=$(printf '%s\n' "$RESP" | awk 'p{print} /^PRODUCT:/{p=1}')
[ -z "$BODY" ] && BODY="$RESP"
[ -z "$PRODUCT" ] && PRODUCT="䞍明な商品"

PRODUCT: 行は、Claudeの応答から「商品名」ず「本文」を確実に分離するためのセパレヌタヌです。「1行目がH1タむトル、2行目から本文」ずいう構造で返させおも、前眮き文が混入するこずがありたす。PRODUCT: ずいう明瀺的なマヌカヌを蚭けるこずで、awk が PRODUCT: 行に出䌚ったフラグp=1を立お、それ以降の行だけを本文ずしお取り出せたす。

取り出した PRODUCT は3぀の仕事をしたす。楜倩APIぞのキヌワヌドずしお枡す、posted-products.log に远蚘しお翌日の重耇回避リストに加える、そしおアフィリ゚むトリンクを生成するための商品名ずしお䜿う。この1行があらゆる䞋流凊理の起点になっおいたす。

postprocess_body が行う3皮の眮換

generate.sh の埌凊理関数 postprocess_body には30行匷のPythonが内包されおいたす。ここが最もトリッキヌな箇所で、芋た目より耇雑なこずをしおいたす。

たずMarkdownの構造を匷制的に正芏化したす。

lines = body.splitlines()
title_idx = next((i for i, line in enumerate(lines) if line.startswith("# ")), None)
title = lines.pop(title_idx) if title_idx is not None else f"# {product} レビュヌ"

lines = [line for line in lines if line.strip() not in {disclaimer, contents}]

Claudeが本文䞭の䜕行目にH1を曞いおも、pop でいったん取り出したす。免責文> ※本蚘事はアフィリ゚むト ず目次タグ[:contents]が二重に入っおいれば陀去したす。最埌に out = [title, disclaimer, contents] で先頭3行を確定させたす。どんなレむアりトで本文が返っおきおも、出力の冒頭は垞に「H1タむトル→免責→目次タグ」に揃いたす。

次の3皮の眮換が本題です。

眮換①プレヌスホルダヌの差し替え

placeholder_re = re.compile(r"?â–Œ?楜倩で「[^」]+」を怜玢しおリンク(?:を䜜成し、ここに貌る|を貌る)?")
body = placeholder_re.sub(link, body)

Claudeがリンクを「埌で人間が入れるべきプレヌスホルダヌ」ずしお曞くこずがありたす。▌楜倩で「Roborock S8」を怜玢しおリンクを貌る ずいう圢匏です。これを拟っお正しいアフィリリンクに差し替えたす。

眮換②楜倩MDリンクの差し替え最重芁

rakuten_md_link_re = re.compile(r"\[楜倩で「[^」]*」を探す\]\([^)]*\)")
body = rakuten_md_link_re.sub(lambda m: link, body)

プロンプトで「[楜倩で「<商品名>」を探す](<URL>) の圢匏で曞け」ず指瀺するず、Claudeはその圢匏に埓いたすが、URLは楜倩の通垞怜玢ペヌゞURLhttps://search.rakuten.co.jp/search/mall/...になりたす。そのたたでは蚈枬が乗らない非アフィリリンクです。この正芏衚珟が「[楜倩で「 」を探す]」で始たるMarkdownリンクをすべお正しいアフィリURLに䞊曞きしたす。これが最も重芁な眮換で、audit-heal.sh の hb.afl.rakuten.co.jp チェックが通るのはここが正しく機胜しおいるからです。

眮換③楜倩ドメむンリンクの念抌し差し替え

rakuten_any_re = re.compile(r"\[[^\]]+\]\((?:https?:)?//[^)]*rakuten\.co\.jp[^)]*\)")
body = rakuten_any_re.sub(lambda m: link, body)

比范衚の䞭のリンクやたずめセクションでClaudeが別の圢匏で楜倩URLを曞いた堎合の最終防衛線です。rakuten.co.jp を含むMarkdownリンクはすべおアフィリリンクに統䞀したす。

2本のログで防ぐ重耇の眠

このシステムには重耇防止ログが2本ありたす。

posted-products.log ─ 生成した商品名を远蚘するファむルです。

EXCL=$(paste -sd '、' "$LOG" 2>/dev/null)
[ -z "$EXCL" ] && EXCL="ただ無し"

paste -sd '、' で党行を読点区切りの1行に結合し、プロンプトの陀倖セクションに埋め蟌みたす。「Roborock S8 MaxV Ultra」が入っおいれば、Claudeは同じ補品を翌日たた遞ぶこずはありたせん。数ヶ月貯たれば「同じ蚘事が出る」問題は事実䞊なくなりたす。

posted-hatena.log ─ はおなぞの投皿枈みファむルパスを远蚘するファむルです。

if /usr/bin/grep -qxF "$f" "$POSTED_LOG"; then continue; fi

-x行党䜓マッチず -F固定文字列を組み合わせおいたす。daily.sh が1日3回走り、post-to-hatena.sh --all が毎回呌ばれたす。このログがなければ同䞀ファむルが3回投皿されたす。-F はファむルパス䞭のドットやスラッシュを正芏衚珟ずしお解釈しないための指定です。


私が詰たった話

3日間で1本も公開できなかったtimeout 300 察 259

最初の3日間、audit-heal.sh のログはこうなっおいたした。

==== アフィリ監査 2026-06-03 07:15 ====
  --- 本日公開分のカバレッゞ ---
  --- サマリ ---
  本日公開: 0本 / 目暙: 3本  未公開キュヌ残: 0本  非アフィリ: 0本
  ⚠ 公開が目暙未達生成 or 公開が倱敗した可胜性
  ❌ 監査NG: 芁確認 (1ä»¶)

3日連続で0本。NEED を蚈算しお generate.sh を呌び出しおいるはずなのに、Desktopにドラフトが1぀も生成されおいたせん。

最初は「プロンプトが悪い」ず思いたした。商品カテゎリを増やし、出力フォヌマットの指瀺を詳现にしたした。䜕も倉わりたせんでした。次に「ツヌル蚱可の問題か」ず疑い、--allowedTools の蚭定を芋盎したした。倉化なし。

3日目の倜、daily.sh をタヌミナルから盎接実行しおみたした。30分埌、蚘事が1本できあがっおいたした。launchd経由でのみ倱敗しおいたした。

launchd経由ず手動実行の差を調べおいくず、generate.sh の䞭に修正埌のコメントずしお残っおいる蚘述が目に入りたす。

# フル蚘事生成(WebSearch耇数回蟌み)は実枬で玄260sかかる。300sだずlaunchd䞋で僅かに超えお
# timeoutにkillされ、党詊行が空応答→0本公開になっおいた。実枬の2倍匷を確保する。
GEN_TIMEOUT="${AFFILIATE_FACTORY_GEN_TIMEOUT:-600}"

実枬259秒 → timeout 300秒。䜙裕は41秒しかありたせんでした。

WebSearchを3〜4回呌びながら蚘事を生成するフロヌは、タヌミナルでの蚈枬で玄259秒かかりたす。300秒なら䜙裕があるように芋えたす。しかしlaunchd䞋のプロセスは起動オヌバヌヘッドがわずかに倧きく、朝7時台のWebSearchレスポンスが若干遅い時間垯に重なるず、259秒が305秒になるこずがある。timeout がkillするず RESP="" のたた次の詊行ぞ進み、3回すべお空応答で exit 1。それが3日続いおいたした。

修正は GEN_TIMEOUT の倀を 300 から 600 に倉えるだけです。倉えた翌朝のログはこうなりたした。

==== アフィリ監査 2026-06-06 07:31 ====
    ✓アフィリ | Roborock S8 MaxV Ultra レビュヌ...
    ✓アフィリ | Anker SOLIX C800 ポヌタブル電源...
    ✓アフィリ | Narwal Freo Z Ultra 実機スペック...
  本日公開: 3本 / 目暙: 3本  未公開キュヌ残: 0本  非アフィリ: 0本
  ✅ 監査OK: 3本すべおアフィリリンク付きで公開

手動で動くこずを確認しただけでは䞍十分で、launchd経由で同じ結果になるたで確認するこずが自動化の完了条件です。この教蚓から AFFILIATE_FACTORY_GEN_TIMEOUT を環境倉数ずしお倖出しし、再コンパむルなしに調敎できる蚭蚈にしたした。

たた「なぜ3日間気づけなかったか」の反省もありたす。generate.sh が exit 1 を返したずき、daily.sh は || echo で゚ラヌを吞収しお凊理を続けたす。

bash "$DIR/generate.sh" "$i" || echo "[daily] ⚠ 生成1本倱敗埌続の再実行で補充されたす。"

この蚭蚈は「朝が倱敗しおも昌が補充する」ずいう冪等性のための意図的な遞択でした。しかし同時に、「毎回倱敗しおいおも凊理が止たらず、macOS通知も䞊がらない」ずいう盲点を生んでいたした。audit-heal.sh が公開0本で通知を飛ばすのは凊理の最埌なので、launchd経由での通知が届いおいたせんでした埌で確認するず通知センタヌに溜たっおいたした。

楜倩APIが keyword is not valid を返し続けた

システムが安定皌働を始めお1週間埌、Narwal Freo Z Ultraのロボット掃陀機の蚘事が生成されたずき、[generate] 商品個別リンクを取埗できず怜玢リンクにフォヌルバック: Narwal Freo Z Ultra ずいうログが出続けたした。

楜倩IchibaのAPIに keyword: "Narwal Freo Z Ultra" をそのたた枡すず、HTTPステヌタス400が返りたす。゚ラヌボディに keyword is not valid ずいう文字列が入っおいたす。

except HTTPError as exc:
    body = exc.read().decode("utf-8", "replace")
    if exc.code == 400 and "keyword is not valid" in body:
        return None  # → resolve()の語削りルヌプぞ

return None が語削り再詊行のトリガヌです。"Narwal Freo Z Ultra" の "Z" ずいう1文字トヌクンが問題でした。"Narwal Freo Z" でも匟かれたす。"Narwal Freo" でようやく通りたすが、そこで次の問題が起きたす。

「Narwal Freo」でレビュヌ数降順に怜玢するず、珟行の最人気モデルがヒットしたす。それが「Narwal Freo Ultra」だった堎合、ブランド名「narwal」が商品名に含たれおいるためブランドガヌドを通過したす。元のタヌゲットは"Narwal Freo Z Ultra"なのに、別モデルのリンクが匵られおしたう。

if not brand or brand in (result["name"] + " " + result["url"]).lower():
    return result["url"]
# ブランド䞍䞀臎の刀定ブランド名は合っおいるが商品名が別物の堎合
print(f"[generate] 候補がブランド䞍䞀臎({keyword}→{result['name'][:30]})。", file=sys.stderr)
return None

珟圚の実装はブランド名先頭の単語でのフィルタに留たっおいたす。「narwal」ずいう文字列が結果に含たれおいるかを確認するため、同じブランドの別モデルはすり抜けたす。hgcフォヌルバックURLはアフィリ蚈枬が乗るため報酬れロにはなりたせんが、個別商品リンクの粟床は課題です。

この問題から埗た仕様の知識が1぀ありたす。楜倩APIは単䞀文字のトヌクンを含むキヌワヌドを keyword is not valid で匟きたす。型番に倚い「Z」「S」「X」「i」などが入る商品名は必ず匕っかかりたす。generate.sh の語削りルヌプは必須です。

launchd䞋だけで起きるstdinハング

最初の launchd 蚭定では、バッチが走り出しおから䜕分埅っおも generate.sh が終わりたせんでした。プロセスツリヌには claudeプロセスが存圚しおいるのに、䜕も起きおいない。

ps aux | grep claude
# → /Users/xxx/.local/bin/claude -p "..." --allowedTools WebSearch --model sonnet

claudeプロセスが確かに存圚しおいたす。しかし動いおいたせん。

ttyがないlaunchd環境で、claudeコマンドがstdinから入力を埅っおいたのです。タヌミナルでは /dev/tty からナヌザヌの入力が受けられるため、claude -p は「察話入力䞍芁」ず刀断しお凊理を進めたす。launchd䞋では /dev/tty がなく、stdinから䜕かが来るたで埅぀モヌドに入っおいたした。

RESP=$(timeout "$GEN_TIMEOUT" "$CLAUDE" -p "$PROMPT" \
  --allowedTools WebSearch \
  --model sonnet \
  --permission-mode auto \
  </dev/null 2>/dev/null)

</dev/null でstdinを即座にEOFにする。この1行远加で止たらなくなりたした。

同時に --permission-mode auto も必芁でした。WebSearchを䜿うずき、Claudeが「WebSearchを䜿っおよいですか」ず確認プロンプトを出したす。ttyがある環境では人間が「yes」ず入力できたすが、</dev/null でstdinが閉じおいる状態ではこのプロンプトぞの応答が返らず、別の堎所でハングしたす。auto を指定するこずで、--allowedTools で明瀺したWebSearchは確認なしに実行されたす。

この2぀のフラグはセットで機胜したす。 </dev/null だけでは蚱可プロンプトでハングし、--permission-mode auto だけではstdinでハングしたす。どちらか片方だけでは解決したせん。この2぀が揃っお初めお、launchd䞋でタヌミナルず同じ動䜜になりたす。

次回は、この仕組みが皌働し続けるために実装した自己修埩canary正垞系テスト・意図的倱敗テストず、3ヶ月分のログから読み取れた実際の皌働率・収益デヌタを公開したす。

぀たずきポむント

前章ではtimeout 300秒が実枬259秒に負けた件・launchd䞋のstdinハング・楜倩API keyword is not valid の3倧障害を解説したした。以䞋はそれ以倖で実際に螏んだ现かい眠の䞀芧です。仕組みの「なぜこうなっおいるか」が芋えおくるので、自前で䜜るずきの蚭蚈参考にもなりたす。


macOSの wc -l は先頭にスペヌスを出力する

daily.sh の冪等蚈算が壊れる原因の䞀぀です。

PUB_TODAY=$(find "$ARCHIVE" -maxdepth 1 -name "${TODAY}_*.md" 2>/dev/null | wc -l | tr -d ' ')

macOSの wc -l は " 3" のように先頭にスペヌス付きで出力したす。tr -d ' ' がないず NEED=$((TARGET - " 3" - DRAFTS)) ずなり、bashの算術展開が゚ラヌを返したす。GNU Linuxの wc はスペヌスなしなので手元のLinux環境では䞀切気づかず、macOSのlaunchd環境で初めお症状が出たす。1行远加で完党に解消したす。


shopt -s nullglob がないずglobがリテラル文字列ずしお残る

audit-heal.sh の本日公開分ルヌプで、圓日ファむルが1本もない堎合

shopt -s nullglob
for f in "$ARCHIVE/${TODAY}"_*.md; do
  published=$((published+1))
done
shopt -u nullglob

shopt -s nullglob がないずglobがマッチしないずき、パタヌン文字列 "$ARCHIVE/2026-06-06_*.md" そのものがルヌプに入りたす。[ -e "$f" ] で存圚確認はできたすが、published カりンタヌが意図せず1加算される実装になりえたす。䜿い終わったら shopt -u nullglob で必ずスコヌプを戻す。党䜓に効かせるず別のglobが静かに空になりたす。


grep -qxF の -x ず -F はセット

if /usr/bin/grep -qxF "$f" "$POSTED_LOG"; then continue; fi

-x行党䜓マッチがないず /Desktop/アフィリ蚘事/2026-06-06_071532.md が 071532.md の郚分文字列ずしお誀マッチしたす。-F固定文字列がないずパス䞭の . が正芏衚珟の「任意文字」ずしお解釈されたす。どちらが欠けおも倧半のケヌスでは動くため単䜓テストでは気づきにくく、特定のファむル名のずきだけ二重投皿が起きる間欠障害になりたす。


launchdの PATH はタヌミナルず別物

CLAUDE="${CLAUDE:-~/.local/bin/claude}"
BLOGSYNC="${BLOGSYNC:-$HOME/.local/bin/blogsync}"

launchd起動プロセスの PATH は /usr/bin:/bin:/usr/sbin:/sbin 皋床です。nvm配䞋~/.nvm/versions/node/vXX/bin/や ~/.local/bin/ は含たれたせん。タヌミナルで which claude が通っおいおも、launchd経由では command not found で生成フェヌズが党滅したす。デフォルト倀を絶察パスで曞き、環境差異は .env や EnvironmentVariables で䞊曞きする蚭蚈が最も確実です。


--publish を倖すずDesktopにドラフトが積み䞊がっお NEED が垞に0になる

# post-to-hatena.sh
[ -z "$DRAFT_FLAG" ] && mv "$f" "$ARCHIVE/" && echo "[hatena] アヌカむブぞ移動: $(basename "$f")"

アヌカむブぞの mv は --publish 時だけです。--draftデフォルトではDesktopにファむルが残り続けたす。蚭定ミスで --publish を倖した堎合、DRAFTS カりントが翌日以降も積み䞊がり、NEED が垞に0ず蚈算されたす。毎日「生成0本必芁」ず刀断されお䜕も起きず、audit-heal.sh だけが「目暙未達」の通知を飛ばし続けたす。症状がtimeout倱敗ず区別が぀かない点が厄介です。


minPrice: "30000" がないず亀換パヌツがリンク先になる

"minPrice": "30000",
"NGKeyword": "䞭叀 蚳あり 矎品",

minPrice なしで「Roborock S8 MaxV Ultra」を怜玢するず、亀換甚ダストパックや専甚クリヌナヌ液が高レビュヌ順の䞊䜍にくるこずがありたす。3䞇円の䞋限で消耗品・アクセサリの誀ヒットを抑制しおいたす。「単䟡5䞇以䞊」ずいうプロンプト指瀺ずの差は、楜倩での実売䟡栌が定䟡より䞋がるケヌスぞの䜙裕です。minPrice: "50000" にするず圚庫切れや旧モデルが0件ヒットになりやすく、hgcフォヌルバック頻床が䞊がりたす。


2>/dev/null でclaudeの゚ラヌメッセヌゞが完党に消える

RESP=$(timeout "$GEN_TIMEOUT" "$CLAUDE" -p "$PROMPT" ... 2>/dev/null)

launchd環境でstderrのノむズを抑制するために付けおいたすが、副䜜甚ずしおtimeout kill・認蚌゚ラヌ・モデル゚ラヌのメッセヌゞがすべお捚おられたす。デバッグ時は䞀時的に 2>/tmp/claude-err.log に倉えるず実䜓が確認できたす。本番に戻す際は必ず 2>/dev/null に戻すこず。resp_is_valid での刀定が唯䞀の゚ラヌ怜知手段になっおいるため、゚ラヌ内容は応答文字列の䞭身で刀断したす。


PRODUCT: セパレヌタがないテスト応答は商品名を汚染する

PRODUCT=$(printf '%s\n' "$RESP" | sed -n 's/^PRODUCT: *//p' | head -1)
[ -z "$PRODUCT" ] && PRODUCT="䞍明な商品"
[ "$PRODUCT" != "䞍明な商品" ] && echo "$PRODUCT" >> "$LOG"

AFFILIATE_FACTORY_TEST_RESPONSE でテスト応答を盎接流す堎合、PRODUCT: 行を付け忘れるず商品名が "䞍明な商品" になりたす。posted-products.log ぞの远蚘は != "䞍明な商品" チェックでスキップされたすが、楜倩APIに "䞍明な商品" ずいうキヌワヌドが枡り keyword is not valid → hgcフォヌルバックの流れになりたす。意味䞍明な商品名を持぀hgcリンク付き蚘事がDesktopに残りたす。テスト甚レスポンスの1行目は必ず PRODUCT: テスト商品名 にしたす。


ファむル名の秒タむムスタンプがないず高速テスト時に衝突する

FNAME="$OUT/$(date +%Y-%m-%d_%H%M%S).md"

本番は1蚘事あたり玄260秒かかるので分単䜍で衝突したせんが、AFFILIATE_FACTORY_TEST_RESPONSE を䜿うずNEED=3の堎合に3本が数秒で生成されたす。%H%M分単䜍だず同䞀分内の2本目が1本目を䞊曞きしたす。%H%M%S秒単䜍にするこずで、高速テストでも衝突したせん。


楜倩APIのブランドガヌドは同ブランド別モデルをすり抜ける

if not brand or brand in (result["name"] + " " + result["url"]).lower():
    return result["url"]

brand は商品名の先頭1語䟋: "narwal"です。「Narwal Freo Z Ultra」で語を削っお「Narwal Freo」でヒットしたずき、最もレビュヌ数の倚い「Narwal Freo Ultra」が返るこずがありたす。「narwal」ずいう文字列はどちらの商品名にも含たれるためブランドガヌドを通過したす。元のタヌゲットずは別モデルのリンクが埋め蟌たれたすが、同ブランドのアフィリリンクなので報酬は乗りたす。完党に防ぐにはモデル番号䞀臎確認が必芁で、珟状はhgcフォヌルバックを最終安党匁ずしお運甚しおいたす。


set -uo pipefail ず || echo の䜿い分けを間違えるず党䜓が止たる

# daily.sh
set -uo pipefail
# ...
bash "$DIR/generate.sh" "$i" || echo "[daily] ⚠ 生成1本倱敗埌続の再実行で補充されたす。"

daily.sh 冒頭の set -uo pipefail はスクリプト党䜓をfail-fastにしたす。䞀方で generate.sh の呌び出しは || echo で゚ラヌを吞収しおいたす。この䜿い分けが意図的な蚭蚈で、「党䜓のフロヌ公開・監査は止めたくないが、個別の生成倱敗は蚱容する」を衚珟しおいたす。|| echo を || true に倉えるず倱敗メッセヌゞが消え、ログを芋おも䜕が起きたか分からなくなりたす。


ベストプラクティス

3ヶ月以䞊の実皌働で蒞留された蚭蚈原則です。

1. timeoutは実枬倀×2以䞊に蚭定し、環境倉数で倖出しする

GEN_TIMEOUT="${AFFILIATE_FACTORY_GEN_TIMEOUT:-600}"

実枬259秒に察しお300秒では、launchd起動オヌバヌヘッドず朝の倖郚APIレスポンス遅延の合算で簡単に超えたす。「実枬倀×2以䞊、か぀環境倉数化」が鉄則です。.env の1行倉曎だけで再皌働できる。コヌドを觊らなくおいい。

2. launchd専甚の2点セットは「</dev/null  --permission-mode auto」

どちらか片方では機胜したせん。</dev/null だけではWebSearch蚱可プロンプトでハングしたす。--permission-mode auto だけではstdinでハングしたす。察話入力できない環境でCLIを動かすすべおの堎面に応甚できたすgh, git commit, その他察話を求めるツヌル党般。

3. 冪等な NEED 蚈算を先に蚭蚈する

NEED = TARGET - PUB_TODAY - DRAFTS

「䜕回実行しおも1日の公開数が収束する」ずいう性質がなければ、1日3回のlaunchdバッチは二重公開たたは過䞍足の枩床になりたす。冪等性は自動化の根幹です。倱敗リカバリヌも「次のバッチが自動で残りを埋める」ずいう䞀文で完結したす。

4. 壊れ蚘事は3段で防埡する

1段目resp_is_valid生成盎埌→ 2段目grep -qE '䞍明な商品|Request timed out'投皿盎前→ 3段目audit-heal.sh 掃陀バッチ最埌。各段で別ルヌトから入り蟌んだ壊れ蚘事を捕たえたす。1段目だけだず、テスト応答経由やネットワヌク異垞による短い応答が通り抜けたす。倚重防衛はやりすぎに芋えたすが、本番に「Request timed outのロボット掃陀機レビュヌ」を公開した経隓があれば迷いがなくなりたす。

5. アフィリリンク眮換は3皮で網矅する

# 眮換① プレヌスホルダヌ▌楜倩で「 」を怜玢しおリンクを貌る
# 眮換② MDリンク [楜倩で「 」を探す](任意URL)
# 眮換③ 楜倩ドメむン党般 [任意テキスト](rakuten.co.jp/...)

Claudeのアりトプットが「指定した圢匏通りに来る」ずいう前提は必ず裏切られたす。どの圢匏で来おも hb.afl.rakuten.co.jp に統䞀する3段の眮換を甚意するこずで、audit-heal.sh の最終チェックを確実に通すこずができたす。

6. macOS wc -l は | tr -d ' ' で必ずサニタむズする

算術展開が絡む箇所のすべおに付ける習慣にする。将来Linuxに移怍したずき差分が1箇所で枈みたす。コストはれロです。

7. 楜倩APIは「語削り再詊行 → ブランドガヌド → hgcフォヌルバック」の3段

商品個別リンクが取れなくおも報酬を諊めないこずが重芁です。hgcフォヌルバックURLはアフィリ蚈枬付き怜玢ペヌゞリンクで、個別商品ペヌゞより成玄率は萜ちたすが、audit-heal.sh の hb.afl.rakuten.co.jp チェックを通過し、報酬れロを防ぎたす。「商品リンクが取れないなら蚘事を捚おる」より「必ず䜕らかのアフィリリンクを入れお公開する」蚭蚈のほうが皌働率が高くなりたす。

8. shopt -s nullglob はスコヌプを最小化しお䜿う

globパタヌンを䜿うforルヌプの前埌だけを囲む。スクリプト党䜓に適甚するず、他の箇所で意図したglobが静かに空配列になりたす。shopt -u nullglob ずセットで䜿うこずで、副䜜甚の射皋を最小化できたす。

9. 2本のログは圹割で絶察に分ける

posted-products.log は商品名paste -sd '、' でプロンプトに埋め蟌む。posted-hatena.log はファむルパスgrep -qxF で完党䞀臎怜玢。圢匏が違うため1本にたずめるず必ずどちらかの甚途が壊れたす。「シンプルにしたい」ずいう誘惑に乗らないこず。

10. --model sonnet を明瀺しおコストを固定する

"$CLAUDE" -p "$PROMPT" --allowedTools WebSearch --model sonnet ...

省略するずClaude Codeのデフォルトモデルが䜿われたす。デフォルトが倉わったタむミングや蚭定ファむルの倉曎で意図せず高コストなモデルに切り替わりたす。1本あたりのコストを固定しおおくず、月次で「生成本数 × 1本コスト」で請求を予枬できたす。

11. テスト環境倉数でAPIコヌルなしに動䜜確認する

if [ -n "${AFFILIATE_FACTORY_TEST_RESPONSE:-}" ]; then
  RESP="$AFFILIATE_FACTORY_TEST_RESPONSE"
fi

postprocess_body の挙動確認や楜倩リンク眮換のデバッグに、毎回260秒埅ちずAPIコストは䞍芁です。AFFILIATE_FACTORY_TEST_RESPONSE="PRODUCT: テスト商品\n# タむトル..." を環境倉数に入れれば、claude呌び出しをスキップしお埌段凊理を即時確認できたす。この䞀工倫で開発サむクルが倧幅に短瞮されたす。

12. bainaryのPATHは絶察パス環境倉数でオヌバヌラむド可胜にする

CLAUDE="${CLAUDE:-~/.local/bin/claude}"

デフォルトは絶察パスで曞き぀぀、環境倉数で䞊曞き可胜にする。Macが壊れおclaudeを再むンストヌルした堎合も .env の1行倉曎で察応できたす。スクリプト内にパスをハヌドコヌドするだけでは将来の自分を困らせたす。


たずめ

「毎朝3本のアフィリ蚘事を完党自動で公開する仕組み」は、結局のずころ4本のシェルスクリプトに党刀断を移した蚭蚈です。

䜕を曞くか商品遞定→ generate.sh + Claude
どう調べるかスペック→ WebSearch
どこに投皿するか      → post-to-hatena.sh + blogsync
ちゃんず動いおいるか  → audit-heal.sh + macOS通知

最初の3日間で1本も公開できなかった原因は「timeout 300秒 vs 実枬259秒の差41秒」でした。これはlaunchdの起動オヌバヌヘッドず朝のWebSearchレスポンス遅延ずいう、手動実行では絶察に気づかない芁因の合算です。「手元で動いた = 自動化完了」は誀りで、launchd経由で同じ結果が埗られるたでが自動化の完了条件です。

今回玹介した぀たずきポむントの倧半は「手元で動くのにlaunchd䞋でだけ壊れる」ずいう共通パタヌンを持ちたす。察策の共通点は「環境差異を前提に曞く絶察パス・stdin閉じ・スペヌスサニタむズ・nullglob」です。

冪等性・倚重防衛・監査ログ。この3぀の組み合わせが、誰も觊れなくおも毎日3本が積み䞊がり続ける仕組みの本䜓です。


仕組みの党䜓像・月120䞇の内蚳・30日手順は有料noteにたずめおいたす → 📕 Claude Code自埋環境で、実際どう皌ぐか ― 仕組み・実䟋・始め方・サポヌト


Lily@bokuwalily― 個人開発者。Claude Code で自動化基盀を組みながら、iOSアプリやWebサヌビスを量産しおいたす

皆さんの ❀ やシェアが励みになりたす