CosenseでKindleハイライトとメモを抽出するブックマークレットを作ってみた

当ページのリンクにはプロモーションが含まれています。

こんにちは、まなてぃです。

Cosense(旧: scrapbox)を使い始めて、気軽に読書記録をつけられるようになってきました!

まなてぃ

ズボラな私はKindleのハイライトやメモを手動でコピペするのが面倒だったので、楽にコピペできないか考えました。

今回は解決方法として、Kindleで読んだ本のハイライトとメモを簡単に抽出できるブックマークレットを作ってみたので、コードと利用イメージを共有します。

もしよろしければ使ってみてください。

ご注意

  • コードの利用は自己責任でお願いいたします。本コードの利用によるトラブル等の責任は負いかねます。
  • このコードは素人ノンプログラマーがchatGPTの協力を得て作成したものです。 完璧なものではありません
    動けばヨシ!の精神で作成しております。
  • コードはアレンジしてご活用いただいて問題ありません
  • 本コードを販売することはご遠慮ください。
  • Kindleの「メモとハイライト」のページの仕様変更が行われた場合、正常にコードが動かなくなる可能性があります。
  • コードの詳しい解説については、Claude先生、chatGPT先生に聞いた方が正確な回答が得られると思います。コードのアレンジ等もAIなどに相談してご活用ください。

(スポンサーリンク)

目次

Cosense(旧: scrapbox)とは

Cosense(旧: Scrapbox)は、ノート共有・情報整理プラットフォームです。 ユーザーが自由に情報を記録し、リンクを張ることで知識を柔軟に管理できるツールとして知られています。

説明するよりも実物を見ていただいた方が早い気がします。

公式ホームページ: https://scrapbox.io/product

Scrapboxは、従来のような階層(フォルダ)分類ではなく、様々な情報を整理できるまったく新しいツールです。単語をカッコで囲むだけで情報をネットワーク化できるので、無秩序なフォルダに頭を抱えることはありません。また、同時編集、画像やYoutube動画の貼り付けといった多彩な機能があるので、これ1つであらゆる情報を集約できます。
https://corp.helpfeel.com/news/scrapbox-25million

筆者が最近はじめた公開プロジェクトはこんな感じです。

ブックマークレットとは

ブックマークレット(Bookmarklet)とは、ブックマークとして保存されたJavaScriptのコードです。Webページ上でそのブックマークをクリックすると、JavaScriptが実行されます。

以下のYouTube動画の解説がイメージしやすく、わかりやすかったです。リンクさせていただきます。

作成したコード

Kindleの「メモとハイライト」ページのハイライトをScrapbox用に出力するブックマークレットを作った(テストウフ様)のコードを参考にさせていただきました。ありがとうございます。

ブックマーク登録用(1行版)

javascript:(function(){'use strict';function escapeHTML(str){return str.replace(/[&<>'"]/g,tag=>({'&':'&','<':'<','>':'>',"'":''','"':'"'}[tag]));}const base=document.getElementById("annotation-scroller");if(!base){alert("このページでは実行できません。Kindleのハイライトページを開いてください。");return;}function extractBookInfo(){let title="タイトル不明";let author="著者不明";const h3Metadata=document.querySelector("h3.kp-notebook-metadata");if(h3Metadata&&h3Metadata.innerText.trim()){title=h3Metadata.innerText.trim();}if(title==="タイトル不明"){const metadata=Array.from(document.querySelectorAll("p.kp-notebook-metadata"));if(metadata.length>=2){title=metadata[0]?.innerText.trim()||title;author=metadata[1]?.innerText.trim()||author;}}if(title==="タイトル不明"){const printable=document.querySelector(".kp-notebook-printable");if(printable){const textNodes=Array.from(printable.childNodes).filter(node=>node.nodeType===Node.TEXT_NODE&&node.textContent.trim()).map(node=>node.textContent.trim());if(textNodes.length>0){title=textNodes[0];}}}if(title==="タイトル不明"){const pageTitle=document.title;if(pageTitle&&pageTitle!=="Your Highlights"){const parts=pageTitle.split(" - ");if(parts.length>=2){title=parts[0].trim();if(author==="著者不明"){author=parts[1].trim();}}else{title=pageTitle.trim();}}}if(title==="タイトル不明"){const selectors=[".kp-notebook-metadata","[data-testid='book-title']",".a-text-bold","h1","h2","h3",".kp-notebook-title"];for(const selector of selectors){const element=document.querySelector(selector);if(element&&element.innerText&&element.innerText.trim()){title=element.innerText.trim();break;}}}if(author==="著者不明"){const metadata=Array.from(document.querySelectorAll("p.kp-notebook-metadata"));if(metadata.length>=2&&metadata[1]?.innerText.trim()){author=metadata[1].innerText.trim();}else{const authorSelectors=[".kp-notebook-author","[data-testid='book-author']",".a-text-normal","p.kp-notebook-metadata"];for(const selector of authorSelectors){const elements=document.querySelectorAll(selector);for(const element of elements){if(element&&element.innerText&&element.innerText.trim()&&element.innerText.trim()!==title){author=element.innerText.trim();break;}}if(author!=="著者不明")break;}}}return{title,author};}function extractHighlights(){const items=base.querySelectorAll("#highlight, #note");return Array.from(items).map(el=>{const text=el.innerText.trim();if(text.length<=2)return null;if(el.id==="note"){return`✏ ${text}`;}else{return`> ${text}`;}}).filter(text=>text!==null).join("\n\n");}const bookInfo=extractBookInfo();const highlights=extractHighlights();console.log("=== Kindleハイライト抽出 デバッグ情報 ===");console.log("取得したタイトル:",bookInfo.title);console.log("取得した著者:",bookInfo.author);console.log("ページタイトル:",document.title);console.log("ハイライト数:",base.querySelectorAll("#highlight").length);console.log("メモ数:",base.querySelectorAll("#note").length);const cover=document.querySelector(".kp-notebook-printable img")?.src||null;const fullText=`${bookInfo.title}\n#${bookInfo.author}\n\n${highlights}`;const resultWindow=window.open("","_blank");const htmlContent=`<html><head><title>Kindleハイライト - ${escapeHTML(bookInfo.title)}</title><style>body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;padding:20px;max-width:800px;margin:auto;line-height:1.6;background-color:#fafafa;}.container{background:white;padding:30px;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,0.1);}.book-info{margin-bottom:20px;padding-bottom:20px;border-bottom:2px solid #eee;}.book-title{font-size:24px;font-weight:bold;color:#333;margin-bottom:8px;}.book-author{font-size:18px;color:#666;font-style:italic;}.cover-image{max-height:150px;margin:15px 0;border-radius:4px;box-shadow:0 2px 8px rgba(0,0,0,0.2);}textarea{width:100%;height:400px;font-size:14px;font-family:'Courier New',monospace;border:2px solid #ddd;border-radius:4px;padding:15px;resize:vertical;}.button-group{margin-bottom:15px;}button{padding:12px 20px;font-size:14px;margin-right:10px;margin-bottom:10px;border:none;border-radius:4px;cursor:pointer;transition:background-color 0.3s;}.copy-button{background-color:#007bff;color:white;}.copy-button:hover{background-color:#0056b3;}.debug-button{background-color:#6c757d;color:white;}.debug-button:hover{background-color:#545b62;}.copied-msg{color:#28a745;font-weight:bold;margin-top:10px;display:none;}.debug-info{background:#f8f9fa;padding:15px;margin-bottom:15px;font-size:12px;border-radius:4px;border-left:4px solid #6c757d;display:none;}.stats{background:#e7f3ff;padding:10px;border-radius:4px;margin-bottom:15px;font-size:14px;}</style></head><body><div class="container"><div class="book-info">${cover?`<img src="${escapeHTML(cover)}" alt="Book Cover" class="cover-image">`:""}<div class="book-title">${escapeHTML(bookInfo.title)}</div><div class="book-author">${escapeHTML(bookInfo.author)}</div></div><div class="stats">📚 ハイライト数: ${base.querySelectorAll("#highlight").length}件 | ✏ メモ数: ${base.querySelectorAll("#note").length}件</div><div class="button-group"><button class="copy-button" onclick="copyToClipboard()">📋 ハイライトをコピー</button><button class="debug-button" onclick="toggleDebugInfo()">🔍 デバッグ情報</button></div><div class="debug-info" id="debug-info"><strong>🔍 デバッグ情報:</strong><br>ページタイトル: ${escapeHTML(document.title)}<br>取得したタイトル: ${escapeHTML(bookInfo.title)}<br>取得した著者: ${escapeHTML(bookInfo.author)}<br>URL: ${escapeHTML(window.location.href)}<br>ハイライト要素数: ${base.querySelectorAll("#highlight").length}<br>メモ要素数: ${base.querySelectorAll("#note").length}</div><div class="copied-msg" id="copied-msg">✅ クリップボードにコピーしました!</div><textarea id="highlights-text" readonly>${escapeHTML(fullText)}</textarea></div><script>function copyToClipboard(){const textarea=document.getElementById('highlights-text');textarea.select();textarea.setSelectionRange(0,99999);navigator.clipboard.writeText(textarea.value).then(()=>{const msg=document.getElementById('copied-msg');msg.style.display='block';setTimeout(()=>msg.style.display='none',3000);}).catch(()=>{document.execCommand('copy');const msg=document.getElementById('copied-msg');msg.style.display='block';setTimeout(()=>msg.style.display='none',3000);});}function toggleDebugInfo(){const debugInfo=document.getElementById('debug-info');debugInfo.style.display=debugInfo.style.display==='none'?'block':'none';}</script></body></html>`;resultWindow.document.write(htmlContent);resultWindow.document.close();})();

開発・デバッグ用(整理された版)

アレンジはClaude先生、chatGPT先生に聞きならがらご自由にどうぞ

クリックして開きます(長いよ)
// ========================================
// Kindleハイライト抽出ブックマークレット
// ========================================
// 開発・デバッグ用(整理された版)
(function() {
    'use strict';
    
    // HTMLエスケープ関数
    function escapeHTML(str) {
        return str.replace(/[&<>'"]/g, tag => ({
            '&': '&',
            '<': '<',
            '>': '>',
            "'": ''',
            '"': '"'
        }[tag]));
    }
    
    // ページがKindleハイライトページかチェック
    const base = document.getElementById("annotation-scroller");
    if (!base) {
        alert("このページでは実行できません。Kindleのハイライトページを開いてください。");
        return;
    }
    
    // 書籍情報を取得する関数
    function extractBookInfo() {
        let title = "タイトル不明";
        let author = "著者不明";
        
        // 方法1: h3.kp-notebook-metadataから取得
        const h3Metadata = document.querySelector("h3.kp-notebook-metadata");
        if (h3Metadata && h3Metadata.innerText.trim()) {
            title = h3Metadata.innerText.trim();
        }
        
        // 方法2: p.kp-notebook-metadataから取得
        if (title === "タイトル不明") {
            const metadata = Array.from(document.querySelectorAll("p.kp-notebook-metadata"));
            if (metadata.length >= 2) {
                title = metadata[0]?.innerText.trim() || title;
                author = metadata[1]?.innerText.trim() || author;
            }
        }
        
        // 方法3: .kp-notebook-printableのテキストノードから取得
        if (title === "タイトル不明") {
            const printable = document.querySelector(".kp-notebook-printable");
            if (printable) {
                const textNodes = Array.from(printable.childNodes)
                    .filter(node => node.nodeType === Node.TEXT_NODE && node.textContent.trim())
                    .map(node => node.textContent.trim());
                if (textNodes.length > 0) {
                    title = textNodes[0];
                }
            }
        }
        
        // 方法4: ページタイトルから取得
        if (title === "タイトル不明") {
            const pageTitle = document.title;
            if (pageTitle && pageTitle !== "Your Highlights") {
                const parts = pageTitle.split(" - ");
                if (parts.length >= 2) {
                    title = parts[0].trim();
                    if (author === "著者不明") {
                        author = parts[1].trim();
                    }
                } else {
                    title = pageTitle.trim();
                }
            }
        }
        
        // 方法5: 一般的なセレクターで再試行
        if (title === "タイトル不明") {
            const selectors = [
                ".kp-notebook-metadata",
                "[data-testid='book-title']",
                ".a-text-bold",
                "h1", "h2", "h3",
                ".kp-notebook-title"
            ];
            
            for (const selector of selectors) {
                const element = document.querySelector(selector);
                if (element && element.innerText && element.innerText.trim()) {
                    title = element.innerText.trim();
                    break;
                }
            }
        }
        
        // 著者名の再取得
        if (author === "著者不明") {
            const metadata = Array.from(document.querySelectorAll("p.kp-notebook-metadata"));
            if (metadata.length >= 2 && metadata[1]?.innerText.trim()) {
                author = metadata[1].innerText.trim();
            } else {
                const authorSelectors = [
                    ".kp-notebook-author",
                    "[data-testid='book-author']",
                    ".a-text-normal",
                    "p.kp-notebook-metadata"
                ];
                
                for (const selector of authorSelectors) {
                    const elements = document.querySelectorAll(selector);
                    for (const element of elements) {
                        if (element && element.innerText && element.innerText.trim() && 
                            element.innerText.trim() !== title) {
                            author = element.innerText.trim();
                            break;
                        }
                    }
                    if (author !== "著者不明") break;
                }
            }
        }
        
        return { title, author };
    }
    
    // ハイライトを抽出する関数
    function extractHighlights() {
        const items = base.querySelectorAll("#highlight, #note");
        return Array.from(items)
            .map(el => {
                const text = el.innerText.trim();
                if (text.length <= 2) return null; // 空のハイライトを除外
                
                // メモかハイライトかを判定
                if (el.id === "note") {
                    return `✏ > ${text}`;
                } else {
                    return `> ${text}`;
                }
            })
            .filter(text => text !== null) // nullを除外
            .join("\n\n"); // 各ハイライトの間に改行を追加
    }
    
    // メイン処理
    const bookInfo = extractBookInfo();
    const highlights = extractHighlights();
    
    // デバッグ情報をコンソールに出力
    console.log("=== Kindleハイライト抽出 デバッグ情報 ===");
    console.log("取得したタイトル:", bookInfo.title);
    console.log("取得した著者:", bookInfo.author);
    console.log("ページタイトル:", document.title);
    console.log("ハイライト数:", base.querySelectorAll("#highlight").length);
    console.log("メモ数:", base.querySelectorAll("#note").length);
    
    // カバー画像を取得
    const cover = document.querySelector(".kp-notebook-printable img")?.src || null;
    
    // 最終テキストを作成
    const fullText = `【${bookInfo.title}】\n#${bookInfo.author}\n\n${highlights}`;
    
    // 結果表示用の新しいウィンドウを作成
    const resultWindow = window.open("", "_blank");
    const htmlContent = `
    <html>
    <head>
        <title>Kindleハイライト - ${escapeHTML(bookInfo.title)}</title>
        <style>
            body {
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
                padding: 20px;
                max-width: 800px;
                margin: auto;
                line-height: 1.6;
                background-color: #fafafa;
            }
            .container {
                background: white;
                padding: 30px;
                border-radius: 8px;
                box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            }
            .book-info {
                margin-bottom: 20px;
                padding-bottom: 20px;
                border-bottom: 2px solid #eee;
            }
            .book-title {
                font-size: 24px;
                font-weight: bold;
                color: #333;
                margin-bottom: 8px;
            }
            .book-author {
                font-size: 18px;
                color: #666;
                font-style: italic;
            }
            .cover-image {
                max-height: 150px;
                margin: 15px 0;
                border-radius: 4px;
                box-shadow: 0 2px 8px rgba(0,0,0,0.2);
            }
            textarea {
                width: 100%;
                height: 400px;
                font-size: 14px;
                font-family: 'Courier New', monospace;
                border: 2px solid #ddd;
                border-radius: 4px;
                padding: 15px;
                resize: vertical;
            }
            .button-group {
                margin-bottom: 15px;
            }
            button {
                padding: 12px 20px;
                font-size: 14px;
                margin-right: 10px;
                margin-bottom: 10px;
                border: none;
                border-radius: 4px;
                cursor: pointer;
                transition: background-color 0.3s;
            }
            .copy-button {
                background-color: #007bff;
                color: white;
            }
            .copy-button:hover {
                background-color: #0056b3;
            }
            .debug-button {
                background-color: #6c757d;
                color: white;
            }
            .debug-button:hover {
                background-color: #545b62;
            }
            .copied-msg {
                color: #28a745;
                font-weight: bold;
                margin-top: 10px;
                display: none;
            }
            .debug-info {
                background: #f8f9fa;
                padding: 15px;
                margin-bottom: 15px;
                font-size: 12px;
                border-radius: 4px;
                border-left: 4px solid #6c757d;
                display: none;
            }
            .stats {
                background: #e7f3ff;
                padding: 10px;
                border-radius: 4px;
                margin-bottom: 15px;
                font-size: 14px;
            }
        </style>
    </head>
    <body>
        <div class="container">
            <div class="book-info">
                ${cover ? `<img src="${escapeHTML(cover)}" alt="Book Cover" class="cover-image">` : ""}
                <div class="book-title">${escapeHTML(bookInfo.title)}</div>
                <div class="book-author">${escapeHTML(bookInfo.author)}</div>
            </div>
            
            <div class="stats">
                📚 ハイライト数: ${base.querySelectorAll("#highlight").length}件 | 
                ✏ メモ数: ${base.querySelectorAll("#note").length}件
            </div>
            
            <div class="button-group">
                <button class="copy-button" onclick="copyToClipboard()">
                    📋 ハイライトをコピー
                </button>
                <button class="debug-button" onclick="toggleDebugInfo()">
                    🔍 デバッグ情報
                </button>
            </div>
            
            <div class="debug-info" id="debug-info">
                <strong>🔍 デバッグ情報:</strong><br>
                ページタイトル: ${escapeHTML(document.title)}<br>
                取得したタイトル: ${escapeHTML(bookInfo.title)}<br>
                取得した著者: ${escapeHTML(bookInfo.author)}<br>
                URL: ${escapeHTML(window.location.href)}<br>
                ハイライト要素数: ${base.querySelectorAll("#highlight").length}<br>
                メモ要素数: ${base.querySelectorAll("#note").length}
            </div>
            
            <div class="copied-msg" id="copied-msg">✅ クリップボードにコピーしました!</div>
            
            <textarea id="highlights-text" readonly>${escapeHTML(fullText)}</textarea>
        </div>
        
        <script>
            function copyToClipboard() {
                const textarea = document.getElementById('highlights-text');
                textarea.select();
                textarea.setSelectionRange(0, 99999);
                
                navigator.clipboard.writeText(textarea.value).then(() => {
                    const msg = document.getElementById('copied-msg');
                    msg.style.display = 'block';
                    setTimeout(() => msg.style.display = 'none', 3000);
                }).catch(() => {
                    // フォールバック: 古いブラウザ対応
                    document.execCommand('copy');
                    const msg = document.getElementById('copied-msg');
                    msg.style.display = 'block';
                    setTimeout(() => msg.style.display = 'none', 3000);
                });
            }
            
            function toggleDebugInfo() {
                const debugInfo = document.getElementById('debug-info');
                debugInfo.style.display = debugInfo.style.display === 'none' ? 'block' : 'none';
            }
        </script>
    </body>
    </html>`;
    
    resultWindow.document.write(htmlContent);
    resultWindow.document.close();
})();

ブックマークレットの登録方法

ブックマークレットを使用するためには、以下の手順で登録します。今回はGoogleChromeでのやり方を例に書きます。

  • Chromeで任意のページを開く
     まずはどのページでもよいので、ChromeでWebページを開いておきます。
  • ブックマークバーを表示する
     Ctrl + Shift + B(Macの場合は Cmd + Shift + B)でブックマークバーを表示できます。表示されていない場合はこの操作を行ってください。
  • ブックマークを追加する
     ブックマークバー上で右クリック →「ページを追加」を選択。
  • 名前とURLを入力する
    • 名前:わかりやすい名前を設定(例:Kindleハイライトとメモを取得するブックマークレット)
    • URL:javascript:で始まるJavaScriptコード(ブックマーク登録用(1行版))のコードを入力
  • 保存する
     「保存」ボタンをクリックして完了です。

注意点

ブックマークレットは1行のコードにする必要があります。複数行のコードを書く場合は、すべて1行に圧縮してください(改行やコメントは不可)。

先頭に必ず javascript: がついているか確認してください。これがないと通常のURLとして解釈されてしまうようです。

作成したブックマークレットの使い方

Kindleのメモとハイライトを確認できるページを開きます。

https://read.amazon.co.jp/notebook

メモとハイライトを取得したい本のページを開き、登録したブックマークを、ブックマークバーからクリックします。指定のJavaScriptが実行されます。

コードを実行すると別タブが開いて、そこにハイライト内容が表示されます。

テキストをコピーし、コピーしたテキストをCosenseに貼り付けます。

Image from Gyazo

まとめ

ブックマークレットは、ちょっとした自動化に便利な仕組みだと勉強になりました。

Kindleのメモやハイライトを簡単に抽出できるブックマークレットが、読書好きの方の情報整理が少しでも楽になると幸いです。

(スポンサーリンク)

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

訪問いただきありがとうございます。
まなてぃと申します。
元IT企業の人→フリーランス→派遣OL&主婦。ブログを書いています。最近はデータ集計や分析のお仕事をしています。
このブログでは、主婦(主夫)さんに向けた、ライフスタイルに関すること、自身のスキルアップのことなど、ざっくばらんに発信できればいいなと思っています。よろしければご覧ください。

目次