[{"data":1,"prerenderedAt":546},["ShallowReactive",2],{"content-/yomitoku-kindle-pipeline":3,"all-pages-for-dir":544,"og-image-/yomitoku-kindle-pipeline":545},{"id":4,"title":5,"body":6,"category":526,"description":527,"extension":528,"meta":529,"navigation":490,"ogImage":530,"path":531,"project_name":209,"published":532,"publishedAt":533,"seo":534,"stem":535,"tags":536,"todo":530,"unpublished":532,"updatedAt":530,"__hash__":543},"pages/2026-06/2026-06-18/yomitoku-kindle-pipeline.md","Kindle Cloud Readerを巡回スクショ→OCR→Tursoまで自動化する/yomitoku-kindleパイプライン完成",{"type":7,"value":8,"toc":513},"minimark",[9,22,26,78,82,101,120,124,130,144,191,195,218,222,233,247,253,257,264,267,271,283,286,397,401,415,422,425,431,434,476,479,509],[10,11,12,13,17,18,21],"p",{},"朝、未コミットの作業を見たら昨日のyomitoku-kindleがStep 10まで書きかけで止まっていた。今日は「URLを渡したら最後まで通す」をゴールに据えて手を動かした。最終的に、Chrome拡張でKindle Cloud Readerを巡回スクショ→yomitokuでOCR→Turso book-knowledge-base DBに投入、最後に",[14,15,16],"code",{},"/restructure-book","で章節整形まで通る",[14,19,20],{},"/yomitoku-kindle","スラッシュコマンドが完成した。",[23,24,25],"h2",{"id":25},"やったこと",[27,28,29,39,47,54,65,68,75],"ul",{},[30,31,32,35,36,38],"li",{},[14,33,34],{},"/yomitoku-kindle \u003CASIN>"," で巡回スクショ→OCR→Turso投入→",[14,37,16],{},"チェーン実行までを1コマンドで完走させた",[30,40,41,42,46],{},"Chrome拡張のUIをポップアップから",[43,44,45],"strong",{},"Floating Panel","に移し、拡張アイコンを毎回クリックする運用を捨てた",[30,48,49,50,53],{},"撮影の瞬間だけFloating Panelを",[14,51,52],{},"display:none","でDOMから外し、OCRノイズを消した",[30,55,56,57,60,61,64],{},"read.amazon.co.jp/notebookの内部APIからハイライトとメモを取得し、",[14,58,59],{},"kindle_library"," / ",[14,62,63],{},"kindle_highlights"," テーブルにUPSERTで投入した",[30,66,67],{},"KU（Kindle Unlimited）絞り込み27件をkindle_libraryに反映した",[30,69,70,71,74],{},"コミック157件に",[14,72,73],{},"comic","タグを付与し、星評価ランキングから除外運用に切り替えた",[30,76,77],{},"ビューアー側に表紙画像を追加、グローバルナビでbooks / shelves / highlightsへ全ページから1クリック遷移できるようにした",[23,79,81],{"id":80},"floating-panel化で拡張アイコンを毎回クリックする運用を捨てる","Floating Panel化で「拡張アイコンを毎回クリックする」運用を捨てる",[10,83,84,85,88,89,92,93,96,97,100],{},"最初の壁はChrome拡張のUI形態だった。MV3のポップアップはアイコンクリックがないと開かないので、自動化と相性が悪い。content.jsの中でKindleページに",[43,86,87],{},"floating panel","を直接埋め込む構造に書き換えた。これでClaude Codeから",[14,90,91],{},"evaluate_script","で",[14,94,95],{},"#kindle-capturer-start","を",[14,98,99],{},"click()","するだけで巡回が始まる。",[10,102,103,104,107,108,111,112,115,116,119],{},"ところがpanel経由で起動した瞬間、",[14,105,106],{},"captureVisibleTab","が",[14,109,110],{},"Either the '\u003Call_urls>' or 'activeTab' permission is required.","で落ちた。MV3の",[14,113,114],{},"activeTab","はユーザーのアイコンクリック起点でしか降りない仕様だった。manifestの権限を",[14,117,118],{},"\u003Call_urls>","を持つ形に切り替えて回避した。昨日まで動いていたのに今朝動かなかったのは、起動経路がポップアップからfloating panelに変わったからだった。",[23,121,123],{"id":122},"撮影瞬間のpanel非表示ocrノイズを消す","撮影瞬間のpanel非表示——OCRノイズを消す",[10,125,126,127,129],{},"巡回を回しはじめて、ユーザーから「panelが画面に映り込んでる」と指摘された。",[14,128,106],{},"はChromeのビューポート全体を撮るのでFloating Panel自体が黒背景の上に重なって入る。OCRノイズになる。",[10,131,132,133,136,137,139,140,143],{},"最初は",[14,134,135],{},"visibility:hidden","で逃がしたが、それでも領域が残る。完全に消すために",[14,138,52],{},"に切り替え、撮影直後に復元する方式に変えた。さらに、keepalive用の",[14,141,142],{},"setInterval","が撮影中に新しいpanelを再注入する競合があったので、巡回中はkeepalive自体を止めた。134枚撮り終えて確認したら、Floating Panelの映り込みは一切なし。OCR精度の文脈で「白背景＋黒文字」を意識してKindleのReader設定を反転させる運用も追加した。",[145,146,151],"pre",{"className":147,"code":148,"language":149,"meta":150,"style":150},"language-js shiki shiki-themes vitesse-light vitesse-light","// 撮影直前にpanelをDOMから外す\nconst panel = document.getElementById('kindle-capturer-panel')\nconst parent = panel?.parentNode\npanel?.remove()\nawait chrome.runtime.sendMessage({ type: 'CAPTURE_VISIBLE_TAB' })\nif (panel && parent) parent.appendChild(panel)\n","js","",[14,152,153,161,167,173,179,185],{"__ignoreMap":150},[154,155,158],"span",{"class":156,"line":157},"line",1,[154,159,160],{},"// 撮影直前にpanelをDOMから外す\n",[154,162,164],{"class":156,"line":163},2,[154,165,166],{},"const panel = document.getElementById('kindle-capturer-panel')\n",[154,168,170],{"class":156,"line":169},3,[154,171,172],{},"const parent = panel?.parentNode\n",[154,174,176],{"class":156,"line":175},4,[154,177,178],{},"panel?.remove()\n",[154,180,182],{"class":156,"line":181},5,[154,183,184],{},"await chrome.runtime.sendMessage({ type: 'CAPTURE_VISIBLE_TAB' })\n",[154,186,188],{"class":156,"line":187},6,[154,189,190],{},"if (panel && parent) parent.appendChild(panel)\n",[23,192,194],{"id":193},"restructure-bookのチェーン実行を組み込む","/restructure-bookのチェーン実行を組み込む",[10,196,197,199,200,202,203,206,207,210,211,210,214,217],{},[14,198,20],{},"の末尾で",[14,201,16],{},"を必ず呼ぶように直した。マークダウンのスラッシュコマンドはチェーン実行を明示的に書かないと走らないので、Step 16として",[14,204,205],{},"/restructure-book \u003Cbook_id>","をinline実行する手順を末尾に追加した。コミット履歴で追えるよう、",[14,208,209],{},"book-knowledge-base","・",[14,212,213],{},"chrome-extension-kindle",[14,215,216],{},"~/.claude","の3リポジトリそれぞれにコミットを切った。",[23,219,221],{"id":220},"目次照準が逆バグ画像順を反転して再ocr","「目次照準が逆」バグ——画像順を反転して再OCR",[10,223,224,225,228,229,232],{},"別の本（東洋経済の和書）を流したら、章番号が",[43,226,227],{},"第7章→第1章","の降順で並ぶ妙な構造になった。最初は「目次ページがある変な本」と思って章名割り当てで凌いだが、ユーザーから「いや、照準なんでこれ逆なんですか」と一言。原因はKindleメタの",[14,230,231],{},"page-progression-direction: rtl","。横書きビジネス書でもKindle側は「和書」扱いで、ページ送りキーがltrと逆向きに作用していた。",[10,234,235,236,239,240,243,244,246],{},"ここで「再撮影せず、画像の順序をプログラムで反転して再OCRすればいい」とユーザーから案を貰った。",[14,237,238],{},"page_0001.png","〜",[14,241,242],{},"page_0124.png","をリネームで逆順に並べ替え、yomitokuに再投入。DB側の旧チャンクを削除して再投入したら、第1章→第7章の論理順に並んだ。",[14,245,16],{},"もセクション単位で全部走らせ、87→68チャンクに統合できた。",[10,248,249,250,252],{},"この学びは",[14,251,20],{},"のStep 5「方向判定」と、末尾の「方向誤判定リカバリ」セクションに書き残した。次から「論理順がおかしい」と気づいた瞬間、撮り直しではなくスクリプトで反転すれば数分で復旧できる。",[23,254,256],{"id":255},"画像が全部xタブだった事件別ウィンドウ分離","「画像が全部Xタブだった」事件——別ウィンドウ分離",[10,258,259,260,263],{},"別の本（新書）で巡回を回したら、撮れていた100枚が全部XのタブUIだった。原因は単純で、撮影中に元ウィンドウでX（Twitter）タブをアクティブにしたから。",[14,261,262],{},"captureVisibleTab(windowId)","は「指定ウィンドウのアクティブタブ」を撮るので、別タブに切り替えた瞬間そっちが入る。",[10,265,266],{},"対策として、background.jsに「Kindleタブを新ウィンドウに分離して撮影する」ハンドラを追加し、content.jsには**「別ウィンドウで開始」ボタン**を新設した。これで撮影専用ウィンドウは独立して動き、元ウィンドウではTwitterでも調べ物でも自由にできる。新書98ページを再撮影したら、まえがき→第1章→と論理順で揃った。",[23,268,270],{"id":269},"kindleノートブックの内部api2801件のハイライトを取り込む","Kindleノートブックの内部API——2,801件のハイライトを取り込む",[10,272,273,274,277,278,60,280,282],{},"別セッションでread.amazon.co.jp/notebookの内部APIを観察した。",[14,275,276],{},"/notebook?asin=...&contentLimitState="," がハイライト本体（HTML 116KB）を返し、annotation IDと位置・色・本文・メモが全部DOMから取れる。Chrome DevTools MCP経由でログイン済みChromeから叩いて、ASIN 196冊分のハイライトをfetchしてTursoの",[14,279,59],{},[14,281,63],{},"テーブルにUPSERTで投入した。",[10,284,285],{},"最初の取り込みでcp932エンコーディングのprintで落ちて0件になったが、出力をUTF-8固定＆書籍ごとcommitに変えて、196冊・2,801件のハイライトをDBに収めた。",[145,287,291],{"className":288,"code":289,"language":290,"meta":150,"style":150},"language-sql shiki shiki-themes vitesse-light vitesse-light","-- kindle_highlights テーブル設計（差分判定用にlast_annotated_atを持つ）\nCREATE TABLE kindle_highlights (\n  annotation_id TEXT PRIMARY KEY,\n  asin TEXT NOT NULL,\n  location INTEGER,\n  color TEXT,\n  highlight TEXT,\n  note TEXT,\n  annotated_at TEXT\n);\n","sql",[14,292,293,299,316,331,343,353,362,372,382,391],{"__ignoreMap":150},[154,294,295],{"class":156,"line":157},[154,296,298],{"class":297},"sxvE3","-- kindle_highlights テーブル設計（差分判定用にlast_annotated_atを持つ）\n",[154,300,301,305,308,312],{"class":156,"line":163},[154,302,304],{"class":303},"sHkkW","CREATE",[154,306,307],{"class":303}," TABLE",[154,309,311],{"class":310},"senZ8"," kindle_highlights",[154,313,315],{"class":314},"sG7-3"," (\n",[154,317,318,321,325,328],{"class":156,"line":169},[154,319,320],{"class":314},"  annotation_id ",[154,322,324],{"class":323},"stQ0i","TEXT",[154,326,327],{"class":323}," PRIMARY KEY",[154,329,330],{"class":314},",\n",[154,332,333,336,338,341],{"class":156,"line":175},[154,334,335],{"class":314},"  asin ",[154,337,324],{"class":323},[154,339,340],{"class":303}," NOT NULL",[154,342,330],{"class":314},[154,344,345,348,351],{"class":156,"line":181},[154,346,347],{"class":303},"  location",[154,349,350],{"class":323}," INTEGER",[154,352,330],{"class":314},[154,354,355,358,360],{"class":156,"line":187},[154,356,357],{"class":314},"  color ",[154,359,324],{"class":323},[154,361,330],{"class":314},[154,363,365,368,370],{"class":156,"line":364},7,[154,366,367],{"class":314},"  highlight ",[154,369,324],{"class":323},[154,371,330],{"class":314},[154,373,375,378,380],{"class":156,"line":374},8,[154,376,377],{"class":314},"  note ",[154,379,324],{"class":323},[154,381,330],{"class":314},[154,383,385,388],{"class":156,"line":384},9,[154,386,387],{"class":314},"  annotated_at ",[154,389,390],{"class":323},"TEXT\n",[154,392,394],{"class":156,"line":393},10,[154,395,396],{"class":314},");\n",[23,398,400],{"id":399},"ビューアー側表紙画像とグローバルナビ","ビューアー側——表紙画像とグローバルナビ",[10,402,403,404,406,407,410,411,414],{},"mdx-playgroundではなく",[14,405,209],{},"のNuxt側ビューアーに、",[14,408,409],{},"amazon_metadata.image_url_large","から表紙画像を引っ張る修正を入れた。258冊中233冊に画像URLがあったので、",[14,412,413],{},"/books","の4カラムグリッドが一気に華やかになった。",[10,416,417,418,421],{},"そのあとユーザーから「ヘッダーのどこかからbooks/shelves/highlightsに直接遷移できるようにして」と指示。Nuxtのレイアウトで全ページ共通のグローバルナビバーを追加し、Kindleハイライトページの独自topbarが重ならないよう",[14,419,420],{},"top: 100px","にずらした。",[23,423,424],{"id":424},"コミック除外",[10,426,427,428,430],{},"KU・全468冊の星評価を取り終わって★順ランキングを見たら、トップ10がほぼコミックで埋まっていた。ユーザーから「コミックは星が高くなりがちだから",[14,429,73],{},"タグで除外して」と指示。タイトル・著者・カテゴリ・amazon_categoriesからヒューリスティック判定して157件にcomicタグを付与した。誤判定1件（橘玲の評論本がWPB eBooksレーベルでヒット）はユーザー目視で除外。",[23,432,433],{"id":433},"学びメモ",[27,435,436,442,451,457,465],{},[30,437,438,441],{},[43,439,440],{},"MV3のactiveTabは起動経路で挙動が変わる","。Floating Panel化で動かなくなった原因はここだった。ポップアップ起動と同じ感覚で進めると黙って落ちる",[30,443,444,447,448,450],{},[43,445,446],{},"OCR元画像にUI要素を映さない","。",[14,449,52],{},"＋keepalive停止までやって、ようやく黒背景に本文だけの画像が撮れる",[30,452,453,456],{},[43,454,455],{},"論理順が逆だと気づいたら撮り直さない","。リネームで反転して再OCRすれば数分で済む。今回これに30分かけたが、次は5分で復旧できる",[30,458,459,464],{},[43,460,461,463],{},[14,462,106],{},"はアクティブタブを撮る","。撮影専用ウィンドウを別に分離するのが唯一の正解。撮影中は元ウィンドウで自由に作業できるメリットも大きい",[30,466,467,447,470,472,473,475],{},[43,468,469],{},"チェーン実行はマークダウンに書き残す",[14,471,20],{},"末尾に「最後に",[14,474,16],{},"を必ず呼ぶ」を明文化して、未来の自分が忘れない構造にした",[23,477,478],{"id":478},"明日やること",[27,480,483,493,503],{"className":481},[482],"contains-task-list",[30,484,487,492],{"className":485},[486],"task-list-item",[488,489],"input",{"disabled":490,"type":491},true,"checkbox"," kindle_libraryの467冊から、まだOCR未取り込みの本を優先順位順にOCR投入する運用フローを書く",[30,494,496,498,499,502],{"className":495},[486],[488,497],{"disabled":490,"type":491}," ハイライトとOCR本文を横断検索できる",[14,500,501],{},"/kindle-search","コマンドを設計する",[30,504,506,508],{"className":505},[486],[488,507],{"disabled":490,"type":491}," /yomitoku-kindleの巡回中に「Oops... Something Went Wrong」が出たら自動でdone.jsonにerror記録する処理を実機で確認する",[510,511,512],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sxvE3, html code.shiki .sxvE3{--shiki-default:#A0ADA0;--shiki-dark:#A0ADA0}html pre.shiki code .sHkkW, html code.shiki .sHkkW{--shiki-default:#1E754F;--shiki-dark:#1E754F}html pre.shiki code .senZ8, html code.shiki .senZ8{--shiki-default:#59873A;--shiki-dark:#59873A}html pre.shiki code .sG7-3, html code.shiki .sG7-3{--shiki-default:#393A34;--shiki-dark:#393A34}html pre.shiki code .stQ0i, html code.shiki .stQ0i{--shiki-default:#AB5959;--shiki-dark:#AB5959}",{"title":150,"searchDepth":163,"depth":163,"links":514},[515,516,517,518,519,520,521,522,523,524,525],{"id":25,"depth":163,"text":25},{"id":80,"depth":163,"text":81},{"id":122,"depth":163,"text":123},{"id":193,"depth":163,"text":194},{"id":220,"depth":163,"text":221},{"id":255,"depth":163,"text":256},{"id":269,"depth":163,"text":270},{"id":399,"depth":163,"text":400},{"id":424,"depth":163,"text":424},{"id":433,"depth":163,"text":433},{"id":478,"depth":163,"text":478},"dev","Chrome拡張機能でKindle本を1ページ目から最終ページまで巡回スクショし、yomitokuでOCRしてTurso DBに投入する/yomitoku-kindleスラッシュコマンドを作った。Floating Panel化・別ウィンドウ分離・撮影瞬間の非表示など、自動化の壁を1つずつ潰した記録。","md",{},null,"/yomitoku-kindle-pipeline",false,"2026-06-18T00:00:00.000Z",{"title":5,"description":527},"2026-06/2026-06-18/yomitoku-kindle-pipeline",[537,538,539,540,541,542],"Chrome拡張","Kindle","OCR","Turso","yomitoku","スラッシュコマンド","lF7LvcgOfsp3JSCQCZ61AIG91a_JPS44_uxX6bcUGeg",[],"https://log.eurekapu.com/og/blog/yomitoku-kindle-pipeline.png?v=2026-06-18T00%3A00%3A00.000Z&title=Kindle%20Cloud%20Reader%E3%82%92%E5%B7%A1%E5%9B%9E%E3%82%B9%E3%82%AF%E3%82%B7%E3%83%A7%E2%86%92OCR%E2%86%92Turso%E3%81%BE%E3%81%A7%E8%87%AA%E5%8B%95%E5%8C%96%E3%81%99%E3%82%8B%2Fyomitoku-kindle%E3%83%91%E3%82%A4%E3%83%97%E3%83%A9%E3%82%A4%E3%83%B3%E5%AE%8C%E6%88%90&author=Kei%20Komatsu&sig=d2a372fd3936022a",1782176330933]