副業・収益化

KDP自動出版が3日詰まった原因はカテゴリー「場所」チェックボックスだった

Ichinose Taito — MBTI × 心理学 × AI

心理学で、
人間関係を
ちょっとラクにする。

MBTIとアドラー心理学を軸に、
自分と他者を理解するヒントを発信しています。

記事を読む ›
ちのくん
ちのくん
— こんな活動をしています —
KDP自動出版が3日詰まった原因はカテゴリー「場所」チェックボックスだった

これで実際のスクリーンショット・コードの内容が確認できた。記事を書く。


KDP自動出版パイプラインが3日連続で詰まった。原因はカテゴリー設定の「場所」チェックボックスという、地味すぎる一点だった。

何が起きたか

深夜5時に自動起動した kdp_auto_publish.py が、毎回「コンテンツ」か「詳細」ページで止まって出版まで届かない。ログには category_placement_fail_050131.png というスクリーンショットが残っていて、モーダルが開いたまま保存できていないのはわかった。でも「なぜ開いたままなのか」がしばらく読めなかった。

環境

macOS Sequoia 15.x / MacBook Pro M5 32GB
Python 3.12 + Playwright (asyncio)
スクリプト: 01_Scripts/kdp/kdp_auto_publish.py  v3.0
カテゴリー設定フィールド: category_path / category_path_2 / category_placement

詰まったポイント

KDPのカテゴリーモーダルは2段階構造になっている。

  1. ドロップダウンで「コンピューター」「Python」と絞り込む
  2. そのあと「場所」というチェックボックス群が出てきて、コンピュータ・IT > Python みたいなノードを選択する

スクリプトの category_placement というフィールド、デフォルト値がこうなっていた。

placement = config.get("category_placement", "日本の小説・文芸")

技術書のキューに category_placement を指定し忘れると、スクリプトが「日本の小説・文芸」というテキストをモーダルの中から探す。Pythonの技術書ページにそんなテキストは当然ない。見つからないので category_placement_fail のスクリーンショットを保存してフォールバック処理に入る——が、フォールバックも「最初の未チェックのチェックボックスを選択」という実装で、タイミングによってはモーダル外のDOM要素を掴んでいた。

このモーダルが閉じないまま「次へ」ボタンをクリックしようとして、コンテンツページで止まる。

EPUBアップロード自体は成功していた。「表紙のアップロードに成功しました」は出ている。でもその後の「保存しています…」スピナーが永遠に回り続けた。

解決までの手順

ステップ1: デバッグスクリーンショットで詰まり箇所を特定する

04_Config/kdp_debug/ に保存された category_placement_fail_050131.png を開いて、カテゴリーモーダルが開いたままになっているのを確認した。ログの category_placement_fail というファイル名はコード内でこのタイミングにだけ使われているので、即座に行番号まで辿れた。

# kdp_auto_publish.py:1639
await save_debug(page, "category_placement_fail")

ステップ2: placement のデフォルト値を疑う

コード上のデフォルト "日本の小説・文芸" が、技術書カテゴリーのモーダルに存在しないと確認した。KDPの実UIで手動確認したところ、Pythonカテゴリーの「場所」テキストは コンピュータ・IT だった。

ステップ3: キューJSONに category_placement を追加する

{
  "category_path": ["コンピュータ・IT", "Python"],
  "category_path_2": ["コンピュータ・IT", "プログラミング"],
  "category_placement": "コンピュータ・IT"
}

category_path で絞り込んだ後に出てくる「場所」チェックボックスのテキストと一致させる必要がある。「コンピューター」じゃなくて「コンピュータ・IT」——中黒と表記揺れが普通にある。

ステップ4: フォールバックのスコープを修正する

フォールバック処理が document 全体のチェックボックスを走査していたのが根本の問題だった。モーダルが閉じていてもページ内の別チェックボックスを掴んでいた。

# 修正前: document 全体
var container = modal || document;

# 修正後: aria-hidden="false" のモーダルに限定
var modal = document.querySelector(
    '.a-popover-modal[aria-hidden="false"], [role="dialog"][aria-hidden="false"]'
);
if (!modal) return {status: 'modal_not_found'};
var container = modal;

モーダルが存在しない場合は即 modal_not_found を返して処理を中断する。ゾンビクリックが消えた。

ステップ5: 再実行して通過確認

コンテンツページで詰まらずに価格設定ページまで進むことを確認した。

コード/設定の抜粋

キューJSONのカテゴリー設定テンプレ(技術書版):

{
  "category_path": ["コンピュータ・IT", "Python"],
  "category_path_2": ["コンピュータ・IT", "データサイエンス・機械学習"],
  "category_placement": "コンピュータ・IT"
}

フォールバック修正の核心部分:

fallback_result = await page.evaluate("""() => {
    var modal = document.querySelector(
        '.a-popover-modal[aria-hidden="false"], [role="dialog"][aria-hidden="false"]'
    ) || document.querySelector('.a-popover-modal[style*="visible"]');
    if (!modal) return {status: 'modal_not_found'};
    var cbs = modal.querySelectorAll('input[type="checkbox"]');
    for (var i = 0; i < cbs.length; i++) {
        if (!cbs[i].checked) {
            cbs[i].click();
            return {status: 'checked', index: i};
        }
    }
    return {status: 'none_available'};
}""")

launchd で定時起動する場合の plist 抜粋:

<key>StartCalendarInterval</key>
<dict>
    <key>Hour</key><integer>5</integer>
    <key>Minute</key><integer>0</integer>
</dict>

試してわかったこと

category_placement のデフォルト値を小説前提にしていたのが根本ミスだった。KDPのカテゴリーツリーは「場所」という概念で、選択した階層に対して「どのノードに置くか」を別途チェックさせる構造になっている。ドロップダウンで選んだ値と「場所」のテキストが必ずしも一致しない——これがわかっていなかった。

あとモーダルの aria-hidden 属性を信頼するようにしたら、「開いてないモーダルを操作しようとする」問題が全体的に減った。Playwrightで動的モーダルを扱うときは aria-hidden="false" を必ずスコープに含めるべきだと思った。

デバッグスクリーンショットの命名規則は最初から入れておいて本当によかった。ログの文字列だけだと「どの画面で止まったか」の特定に時間がかかる。

まとめ

  • category_placement のデフォルトが小説前提で技術書に全滅していた
  • フォールバックが document 全体を走査していてゾンビクリックが発生していた
  • モーダルスコープを aria-hidden="false" に限定したら解決した