見出し画像

Rakuten(楽天)の領収書のPDFから必要情報を抽出しCSVデータでダウンロードするツール

楽天で購入した多くの領収書を経費処理する際、インボイスの登録番号等、ひとつひとつの領収書から情報を取り出す作業がとても面倒でした。
過去にExcel VBAを使って自動化ツールを作成したことがあるのですが、今回は同じことをWeb上で簡単にできるようにしてみました。
このツールを使えば、経理作業が大幅に楽になります。

当然無料です。 今のところ広告も無しです。
ご自由にお使いください!
https://www.dopnet.jp/sys/rakuten_pdf_extractor.html

PDFの領収書は、Rakutenの「購入履歴」からダウンロードできます。

https://order.my.rakuten.co.jp/?fidomy=1&l2-id=shop_header_purchase


楽天の領収書PDFから必要情報を自動抽出し、CSVデータとしてダウンロードするツールの解説と使い方

このツールは、楽天市場の領収書PDFから
「注文番号」
「店舗名」
「領収日」
「領収者」
「登録番号」
「総合計」
「消費税」
などの、必要な情報を自動的に抽出し、CSVファイル形式で保存できる便利なツールです。
複数のPDFファイルを一度に処理することもでき、業務の効率化に貢献します。

主な機能の概要

  • PDFファイルの自動解析: 複数の楽天領収書PDFを一括で読み込み、必要な情報を自動的に抽出します。

  • CSVファイル形式で保存: 抽出された情報をCSV形式でダウンロード可能。データ管理や分析に役立ちます。

  • シンプルな操作性: ファイルをドラッグ&ドロップするだけで簡単に利用できるため、専門知識がなくても使用可能です。


使い方ガイド


1. PDFファイルの準備
まず、楽天で発行された領収書のPDFファイルを準備します。
ツールは複数のファイルを一度に処理することができるため、複数のPDFをまとめて用意すると便利です。


2. PDFファイルの読み込み

  • ツールの中央にある「ここにPDFファイルをドロップするか、クリックして複数のファイルを選択してください」と表示された領域に、領収書PDFファイルをドラッグ&ドロップします。

  • または、領域をクリックするとファイル選択ダイアログが表示されるので、そこからPDFファイルを選択することも可能です。


3. 情報の抽出と表示

PDFファイルが読み込まれると、ツールは自動的に情報を解析し、以下の情報を画面上に表示します。

  • 注文番号: 楽天の注文ごとに付与される一意の番号

  • 店舗名: 商品を販売した店舗の名前

  • 領収日: 領収書が発行された日付

  • 領収者: 領収書の受領者

  • 登録番号:インボイス制度の「登録番号」

  • 総合計: 支払った総額

  • 消費税: 総合計に含まれる消費税額

これらの情報は、表形式で表示され、簡単に確認できます。

4. CSVファイルのダウンロード

  • 抽出された情報が正しく表示されたら、「CSVをダウンロード」リンクをクリックして、データをCSV形式で保存します。

  • CSVファイルはExcelなどの表計算ソフトで開くことができ、後で簡単に分析や編集が可能です。

5. データのクリア

  • 抽出された情報をクリアして、新しいPDFを処理したい場合は、「クリア」ボタンをクリックします。これにより、表示されていたデータとダウンロードリンクがリセットされます。


コード は下記になります 

html / javascript / css 面倒なので分けておりません
使えればいいんじゃないでしょうか

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PDF情報抽出</title>
    <style>
        body {
            font-family: 'Helvetica Neue', sans-serif;
            text-align: center;
            margin: 0;
            padding: 0;
            background-color: #f7f7f7;
        }
        h1 {
            font-size: 24px;
            margin: 40px 0;
            color: #333;
        }
        #drop-zone {
            width: 80%;
            height: 200px;
            border: 2px dashed #4CAF50;
            margin: 20px auto;
            display: flex;
            align-items: center;
            justify-content: center;
            color: #888;
            font-size: 18px;
            border-radius: 10px;
            background-color: #fff;
            transition: background-color 0.3s ease, transform 0.3s ease;
        }
        #drop-zone:hover {
            background-color: #e6ffe6;
            transform: scale(1.02);
        }
        #output {
            margin-top: 20px;
            font-size: 16px;
            color: #333;
            display: flex;
            justify-content: center;
        }
        table {
            border-collapse: collapse;
            width: 80%;
            margin: 20px auto;
            background-color: #fff;
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
            border-radius: 10px;
            overflow: hidden;
        }
        table, th, td {
            border: none;
            padding: 15px;
        }
        th {
            background-color: #4CAF50;
            color: white;
            font-size: 18px;
        }
        td {
            text-align: center;
            background-color: #f9f9f9;
        }
        tr:nth-child(even) td {
            background-color: #f1f1f1;
        }
        #download-link {
            margin-top: 20px;
            font-size: 18px;
            color: #4CAF50;
            text-decoration: none;
            display: inline-block;
            transition: color 0.3s ease;
        }
        #download-link:hover {
            color: #388E3C;
        }
        #clear-button {
            margin-top: 20px;
            padding: 10px 20px;
            font-size: 16px;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            transition: background-color 0.3s ease, transform 0.3s ease;
        }
        #clear-button:hover {
            background-color: #388E3C;
            transform: scale(1.05);
        }
        @media (max-width: 1500px) {
            table {
                display: block;
            }
            #extracted-table thead {
                display: none;
            }
            tbody {
                display: flex;
                flex-wrap: wrap;
                justify-content: center;
            }
            tr {
                display: flex;
                flex-direction: column;
                border: 1px solid #ccc;
                margin: 10px;
                padding: 20px;
                box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
                width: 100%;
                max-width: 400px;
                background-color: #fff;
                transition: transform 0.3s ease;
            }
            tr:hover {
                transform: scale(1.05);
            }
            th, td {
                display: block;
                text-align: left;
                padding: 5px 0;
            }
            td {
                font-size: 16px;
                font-weight: normal;
            }
            tr td:before {
                content: attr(data-label);
                font-weight: bold;
                color: #333;
                margin-right: 10px;
                display: inline-block;
                width: 120px;
            }
        }
    </style>
</head>
<body>

<h1>PDFファイルから情報を抽出</h1>
<div id="drop-zone">ここにPDFファイルをドロップするか、クリックして複数のファイルを選択してください</div>
<div id="output">
    <table id="extracted-table">
        <thead>
            <tr>
                <th>注文番号</th>
                <th>店舗名</th>
                <th>領収日</th>
                <th>領収者</th>
                <th>登録番号</th>
                <th>総合計</th>
                <th>消費税</th>
            </tr>
        </thead>
        <tbody></tbody>
    </table>
</div>
<a id="download-link" style="display:none;">CSVをダウンロード</a>

<!-- クリアボタン -->
<br><br>
<button id="clear-button">クリア</button>

<!-- pdf.js ライブラリ -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.10.377/pdf.min.js"></script>
<script>
    const dropZone = document.getElementById('drop-zone');
    const tableBody = document.querySelector('#extracted-table tbody');
    const downloadLink = document.getElementById('download-link');
    const clearButton = document.getElementById('clear-button');

    dropZone.addEventListener('dragover', (e) => {
        e.preventDefault();
        dropZone.style.backgroundColor = '#e6ffe6';
    });

    dropZone.addEventListener('dragleave', () => {
        dropZone.style.backgroundColor = '#fff';
    });

    dropZone.addEventListener('drop', (e) => {
        e.preventDefault();
        dropZone.style.backgroundColor = '#fff';
        handleFiles(e.dataTransfer.files);
    });

    dropZone.addEventListener('click', () => {
        const fileInput = document.createElement('input');
        fileInput.type = 'file';
        fileInput.multiple = true;
        fileInput.accept = 'application/pdf';
        fileInput.addEventListener('change', (e) => handleFiles(e.target.files));
        fileInput.click();
    });

    function handleFiles(files) {
        const promises = [];
        const extractedData = [];

        for (const file of files) {
            if (file.type === 'application/pdf') {
                const reader = new FileReader();
                const filePromise = new Promise((resolve) => {
                    reader.onload = function() {
                        const typedarray = new Uint8Array(this.result);
                        pdfjsLib.getDocument(typedarray).promise.then((pdf) => {
                            let textContent = '';
                            let pagePromises = [];

                            for (let i = 1; i <= pdf.numPages; i++) {
                                pagePromises.push(pdf.getPage(i).then((page) => {
                                    return page.getTextContent().then((content) => {
                                        let text = content.items.map(item => item.str).join(' ');
                                        textContent += text + '\n';
                                    });
                                }));
                            }

                            Promise.all(pagePromises).then(() => {
                                const data = extractInfo(textContent);
                                extractedData.push(data);
                                resolve();
                            });
                        });
                    };
                    reader.readAsArrayBuffer(file);
                });

                promises.push(filePromise);
            }
        }

        Promise.all(promises).then(() => {
            createCSV(extractedData);
            displayExtractedData(extractedData);
        });
    }

    function extractInfo(text) {
        const orderNumberRegex = /注文番号:\s*([^\s]+)/;
        const storeNameRegex = /店舗名:\s*([^\s]+)/;
        const deliveryDateRegex = /初回領収日:\s*([^\s]+)/;
        const receiptNameRegex = /領収者:\s*([^\s]+)/;
        const registrationNumberRegex = /登録番号:\s*([^\s]+)/;
        const totalAmountRegex = /%対象\s*([^\s]+)/;
        const taxRegex = /%対象消費税\s*([^\s]+)/;

        const orderNumber = text.match(orderNumberRegex);
        const storeName = text.match(storeNameRegex);
        const deliveryDate = text.match(deliveryDateRegex);
        const receiptName = text.match(receiptNameRegex);
        const registrationNumber = text.match(registrationNumberRegex);
        const totalAmount = text.match(totalAmountRegex);
        const tax = text.match(taxRegex);

        const formatValue = (value) => value ? value.replace(/,/g, '') : '見つかりませんでした';

        return {
            "注文番号": formatValue(orderNumber ? orderNumber[1] : null),
            "店舗名": formatValue(storeName ? storeName[1] : null),
            "領収日": formatValue(deliveryDate ? deliveryDate[1] : null),
            "領収者": formatValue(receiptName ? receiptName[1] : null),
            "登録番号": formatValue(registrationNumber ? registrationNumber[1] : null),
            "総合計": formatValue(totalAmount ? totalAmount[1] : null),
            "消費税": formatValue(tax ? tax[1] : null)
        };
    }

    function displayExtractedData(dataArray) {
        dataArray.forEach(data => {
            const row = document.createElement('tr');
            for (const key in data) {
                const cell = document.createElement('td');
                cell.textContent = data[key];
                cell.setAttribute('data-label', key);
                row.appendChild(cell);
            }
            tableBody.appendChild(row);
        });
    }

    function createCSV(dataArray) {
        const BOM = '\uFEFF';
        let csvContent = BOM + `注文番号,店舗名,領収日,領収者,登録番号,総合計,消費税\n`;

        dataArray.forEach(data => {
            csvContent += `${data["注文番号"]},${data["店舗名"]},${data["領収日"]},${data["領収者"]},${data["登録番号"]},${data["総合計"]},${data["消費税"]}\n`;
        });

        const blob = new Blob([csvContent], { type: 'text/csv' });
        const url = URL.createObjectURL(blob);
        downloadLink.href = url;
        downloadLink.download = 'extracted_data.csv';
        downloadLink.style.display = 'inline';
        downloadLink.textContent = 'CSVをダウンロード';
    }

    clearButton.addEventListener('click', () => {
        tableBody.innerHTML = '';
        downloadLink.style.display = 'none';
    });
</script>

</body>
</html>

この記事が気に入ったらサポートをしてみませんか?