[{"data":1,"prerenderedAt":494},["ShallowReactive",2],{"content-/book-ocr-and-note-extension":3,"all-pages-for-dir":492,"og-image-/book-ocr-and-note-extension":493},{"id":4,"title":5,"body":6,"category":471,"description":472,"extension":473,"meta":474,"navigation":475,"path":476,"project_name":269,"published":477,"publishedAt":478,"seo":479,"stem":480,"tags":481,"todo":489,"unpublished":477,"updatedAt":490,"__hash__":491},"pages/2026-04/2026-04-21/book-ocr-and-note-extension.md","書籍OCRパイプライン構築とnote.com Chrome拡張の内部API解析",{"type":7,"value":8,"toc":453},"minimark",[9,13,17,22,27,30,34,37,60,63,67,70,73,77,81,84,88,91,102,109,112,127,218,221,225,228,238,248,254,260,264,275,278,285,288,291,293,296,430,432,435,449],[10,11,5],"h1",{"id":12},"書籍ocrパイプライン構築とnotecom-chrome拡張の内部api解析",[14,15,16],"p",{},"専門書287ページをOCRでMarkdownに変換してDBに流し込むパイプラインを組んだ。その傍らでnote.comの内部APIをChrome DevTools MCPで傍受し、下書き保存が201を返すまで叩き続けた。2つのプロジェクトが交互に手を動かした一日。",[18,19,21],"h2",{"id":20},"_1-書籍ocrdb格納パイプライン","1. 書籍OCR→DB格納パイプライン",[23,24,26],"h3",{"id":25},"yomitoku-ocrでpdfをmarkdownに変換","yomitoku OCRでPDFをMarkdownに変換",[14,28,29],{},"専門書のPDF（287ページ）をyomitoku OCRに通してMarkdownに変換した。テキストだけでなく図表も画像として抽出され、220枚の画像が出てきた。",[23,31,33],{"id":32},"_220枚53枚-画像の自動選別","220枚→53枚: 画像の自動選別",[14,35,36],{},"220枚の中身を見ると、装飾画像やページ区切りのバナーが大量に混ざっていた。手で1枚ずつ消すのは現実的でないので、サイズと縦横比で自動分類するスクリプトを組んだ。",[38,39,40,48,54],"ul",{},[41,42,43,47],"li",{},[44,45,46],"strong",{},"装飾画像 113件を削除",": ファイルサイズが小さいチェックマークやアイコン類",[41,49,50,53],{},[44,51,52],{},"横長バナー 54件を削除",": 縦横比で判別したページ区切りの帯画像",[41,55,56,59],{},[44,57,58],{},"実図 53件を残す",": グラフ、表、フローチャートなど内容のある図",[14,61,62],{},"ユーザーが画像を見ながら「チェックマーク消して」「バナーも消して」と指示を出し、それをサイズ閾値と縦横比の条件に変換して一括処理した。",[23,64,66],{"id":65},"turso-dbに280チャンク格納","Turso DBに280チャンク格納",[14,68,69],{},"変換したMarkdownをチャンクに分割し、Turso DBに280チャンク格納した。Embedded Replicaの同期も実施して、ローカルからの読み取りが即座に反映される状態を確認した。",[71,72],"hr",{},[18,74,76],{"id":75},"_2-notecom-chrome拡張機能-スキル開発","2. note.com Chrome拡張機能 & スキル開発",[23,78,80],{"id":79},"chrome-devtools-mcpで内部apiを調査","Chrome DevTools MCPで内部APIを調査",[14,82,83],{},"note.comの公開APIドキュメントは存在しないため、Chrome DevTools MCPでネットワークリクエストを傍受して内部APIの挙動を調べた。エディタ上で下書き保存ボタンを押したときのリクエストを捕まえるところから始めた。",[23,85,87],{"id":86},"下書き保存apiの発見","下書き保存APIの発見",[14,89,90],{},"ネットワークログから下書き保存のエンドポイントを特定した。",[92,93,98],"pre",{"className":94,"code":96,"language":97},[95],"language-text","POST /api/v1/text_notes → 201 Created\n","text",[99,100,96],"code",{"__ignoreMap":101},"",[14,103,104,105,108],{},"認証まわりで一つ重要な発見があった。XSRF-TOKENは不要で、",[99,106,107],{},"X-Requested-With: XMLHttpRequest"," ヘッダーだけで認証が通る。Cookieのセッション情報と組み合わせれば、Chrome拡張からそのまま叩ける。",[23,110,111],{"id":111},"リクエストフォーマットの試行錯誤",[14,113,114,115,118,119,122,123,126],{},"最初は ",[99,116,117],{},"{note: {name: \"...\", body: \"...\"}}"," とラップして送ったが、400エラーが返ってきた。DevToolsで実際のリクエストボディを見直すと、トップレベルに ",[99,120,121],{},"name",", ",[99,124,125],{},"body"," 等を直接配置する形式だった。",[92,128,132],{"className":129,"code":130,"language":131,"meta":101,"style":101},"language-json shiki shiki-themes vitesse-light vitesse-light","{\n  \"name\": \"タイトル\",\n  \"body\": \"\u003Cp>本文HTML\u003C/p>\",\n  \"status\": \"draft\"\n}\n","json",[99,133,134,143,172,192,212],{"__ignoreMap":101},[135,136,139],"span",{"class":137,"line":138},"line",1,[135,140,142],{"class":141},"shFtX","{\n",[135,144,146,150,153,156,159,163,167,169],{"class":137,"line":145},2,[135,147,149],{"class":148},"sqvqQ","  \"",[135,151,121],{"class":152},"sz8Xr",[135,154,155],{"class":148},"\"",[135,157,158],{"class":141},":",[135,160,162],{"class":161},"sMJiu"," \"",[135,164,166],{"class":165},"sdGka","タイトル",[135,168,155],{"class":161},[135,170,171],{"class":141},",\n",[135,173,175,177,179,181,183,185,188,190],{"class":137,"line":174},3,[135,176,149],{"class":148},[135,178,125],{"class":152},[135,180,155],{"class":148},[135,182,158],{"class":141},[135,184,162],{"class":161},[135,186,187],{"class":165},"\u003Cp>本文HTML\u003C/p>",[135,189,155],{"class":161},[135,191,171],{"class":141},[135,193,195,197,200,202,204,206,209],{"class":137,"line":194},4,[135,196,149],{"class":148},[135,198,199],{"class":152},"status",[135,201,155],{"class":148},[135,203,158],{"class":141},[135,205,162],{"class":161},[135,207,208],{"class":165},"draft",[135,210,211],{"class":161},"\"\n",[135,213,215],{"class":137,"line":214},5,[135,216,217],{"class":141},"}\n",[14,219,220],{},"ラップを外したら201が返り、下書きがnote.comのダッシュボードに現れた。",[23,222,224],{"id":223},"画像アップロードの壁-3段階の試行錯誤","画像アップロードの壁: 3段階の試行錯誤",[14,226,227],{},"画像アップロードで3回壁にぶつかった。",[14,229,230,233,234,237],{},[44,231,232],{},"試行1: アイキャッチ用エンドポイント","\n最初に見つけた ",[99,235,236],{},"/v1/image_upload/note_eyecatch"," を使った。アップロード自体は成功したが、画像が正方形にクロップされてしまう。アイキャッチ専用のリサイズ処理が走っていた。",[14,239,240,243,244,247],{},[44,241,242],{},"試行2: 本文画像用のpresigned URL方式","\n本文中の画像は別の経路を使っていた。まず ",[99,245,246],{},"/v3/images/upload/presigned_post"," を叩いてS3の署名付きURLを取得し、そのURLにmultipart/form-dataで画像をPUTする2段階方式。",[92,249,252],{"className":250,"code":251,"language":97},[95],"1. POST /v3/images/upload/presigned_post → S3署名付きURL + fields\n2. POST S3のURL → 画像アップロード完了\n",[99,253,251],{"__ignoreMap":101},[14,255,256,259],{},[44,257,258],{},"試行3: CORS制限との戦い","\nステップ2のS3直接アップロードでCORS制限に引っかかった。Chrome拡張のcontent scriptからS3のオリジンへのリクエストがブロックされる。最終的にcurlコマンドを使ってアップロードする方式で回避した。ブラウザのJavaScript実行環境を経由しないので、CORSの制約を受けない。",[23,261,263],{"id":262},"chrome拡張のリポジトリ分離","Chrome拡張のリポジトリ分離",[14,265,266,267,270,271,274],{},"当初 ",[99,268,269],{},"chrome-extension-x"," リポジトリ内で開発していたが、note.com専用の機能が増えたため ",[99,272,273],{},"chrome-extension-note"," として独立リポジトリに分離し、GitHubにpushした。",[23,276,277],{"id":277},"note-draftスキルの定義",[14,279,280,281,284],{},"MDX記事をnote.comの下書きとして自動保存するスキルを、ユーザーレベル（",[99,282,283],{},"~/.claude/skills/","）で定義した。MDXファイルを読み取り、MarkdownをHTMLに変換し、note.com APIで下書き保存するまでの一連の流れを自動化した。",[23,286,287],{"id":287},"ドラフトロックの知見",[14,289,290],{},"note.comのエディタを開いたまま、APIから同じノートを更新しようとすると変更が反映されない。エディタ側がWebSocketでロックを取得しているらしく、APIからの書き込みが上書きされる。回避策として、既存ノートの更新ではなく毎回新規ノートを作成する方式にした。",[71,292],{},[18,294,295],{"id":295},"今日の試行錯誤",[297,298,299,321],"table",{},[300,301,302],"thead",{},[303,304,305,309,312,315,318],"tr",{},[306,307,308],"th",{},"#",[306,310,311],{},"テーマ",[306,313,314],{},"試したこと",[306,316,317],{},"結果",[306,319,320],{},"気づき",[322,323,324,342,359,379,396,413],"tbody",{},[303,325,326,330,333,336,339],{},[327,328,329],"td",{},"1",[327,331,332],{},"画像選別",[327,334,335],{},"220枚を目視確認",[327,337,338],{},"非現実的",[327,340,341],{},"サイズ・縦横比で自動分類する方が速い",[303,343,344,347,350,353,356],{},[327,345,346],{},"2",[327,348,349],{},"note認証",[327,351,352],{},"XSRF-TOKEN取得を試行",[327,354,355],{},"不要だった",[327,357,358],{},"X-Requested-Withだけで通る",[303,360,361,364,367,373,376],{},[327,362,363],{},"3",[327,365,366],{},"リクエスト形式",[327,368,369,372],{},[99,370,371],{},"{note: {...}}"," でラップ",[327,374,375],{},"400エラー",[327,377,378],{},"トップレベルに直接配置が正解",[303,380,381,384,387,390,393],{},[327,382,383],{},"4",[327,385,386],{},"アイキャッチAPI",[327,388,389],{},"eyecatch用エンドポイントで本文画像UP",[327,391,392],{},"クロップされる",[327,394,395],{},"アイキャッチ専用のリサイズが走る",[303,397,398,401,404,407,410],{},[327,399,400],{},"5",[327,402,403],{},"S3アップロード",[327,405,406],{},"content scriptからfetch",[327,408,409],{},"CORSブロック",[327,411,412],{},"curlで回避、ブラウザを経由しない",[303,414,415,418,421,424,427],{},[327,416,417],{},"6",[327,419,420],{},"ドラフト更新",[327,422,423],{},"既存ノートをAPI更新",[327,425,426],{},"エディタのロックで上書き",[327,428,429],{},"新規ノート作成で回避",[71,431],{},[18,433,434],{"id":434},"今日の学び",[38,436,437,440,443,446],{},[41,438,439],{},"画像の自動選別はサイズと縦横比の2軸で大半をカバーできる。220枚を手で見るより、閾値を決めてスクリプトを回す方が速くて正確だった",[41,441,442],{},"Chrome DevTools MCPでネットワークタブを覗き、実際のリクエスト/レスポンスをそのまま再現した。公式ドキュメントがないAPIの調査手段として定着しそう",[41,444,445],{},"CORS制限はブラウザの制約なので、curlなどブラウザ外の手段を使えば迂回できる。Chrome拡張ではbackground scriptからのfetchやnative messagingも選択肢になる",[41,447,448],{},"エディタを開いたままAPIを叩いたら変更が消えた。WebSocketのロック機構はAPIレスポンスには現れないので、実際に踏むまで気づけない",[450,451,452],"style",{},"html pre.shiki code .shFtX, html code.shiki .shFtX{--shiki-default:#999999;--shiki-dark:#999999}html pre.shiki code .sqvqQ, html code.shiki .sqvqQ{--shiki-default:#99841877;--shiki-dark:#99841877}html pre.shiki code .sz8Xr, html code.shiki .sz8Xr{--shiki-default:#998418;--shiki-dark:#998418}html pre.shiki code .sMJiu, html code.shiki .sMJiu{--shiki-default:#B5695977;--shiki-dark:#B5695977}html pre.shiki code .sdGka, html code.shiki .sdGka{--shiki-default:#B56959;--shiki-dark:#B56959}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);}",{"title":101,"searchDepth":145,"depth":145,"links":454},[455,460,469,470],{"id":20,"depth":145,"text":21,"children":456},[457,458,459],{"id":25,"depth":174,"text":26},{"id":32,"depth":174,"text":33},{"id":65,"depth":174,"text":66},{"id":75,"depth":145,"text":76,"children":461},[462,463,464,465,466,467,468],{"id":79,"depth":174,"text":80},{"id":86,"depth":174,"text":87},{"id":111,"depth":174,"text":111},{"id":223,"depth":174,"text":224},{"id":262,"depth":174,"text":263},{"id":277,"depth":174,"text":277},{"id":287,"depth":174,"text":287},{"id":295,"depth":145,"text":295},{"id":434,"depth":145,"text":434},"dev","yomitoku OCRで専門書287ページをMarkdown変換しTurso DBに格納、note.comの内部APIをChrome DevTools MCPで解析してChrome拡張+スキルを開発した記録","md",{},true,"/book-ocr-and-note-extension",false,"2026-04-21T00:00:00.000Z",{"title":5,"description":472},"2026-04/2026-04-21/book-ocr-and-note-extension",[482,483,484,485,486,487,488],"OCR","yomitoku","TursoDB","Chrome拡張","note.com","API解析","MCP","memo",null,"UjgufuX7bzw4obl-DxAFoxfqlQBvi5xyLDJ86Q_3Cl0",[],"https://log.eurekapu.com/og/blog/book-ocr-and-note-extension.png?v=2026-04-21T00%3A00%3A00.000Z&title=%E6%9B%B8%E7%B1%8DOCR%E3%83%91%E3%82%A4%E3%83%97%E3%83%A9%E3%82%A4%E3%83%B3%E6%A7%8B%E7%AF%89%E3%81%A8note.com%20Chrome%E6%8B%A1%E5%BC%B5%E3%81%AE%E5%86%85%E9%83%A8API%E8%A7%A3%E6%9E%90&author=Kei%20Komatsu&sig=05f554601f711b45",1780786053682]