freee API×Pythonで仕訳帳・試算表をダウンロードする方法|MCP・GASも紹介

freeeにログインしようとすると、
ログイン画面に広告が出たり、自分に関係のないお知らせが出たりして、
地味にめんどくささを感じます。

今回は、

freeeに毎回手動でログインせずに、会計データを取得する方法

を3つ紹介します。

なお、いずれの方法も、最初の連携設定ではfreeeへのログインや認証が必要です。
一度設定した後は、毎回freeeの画面を開いて手動でログインしなくても、データを取得できるようになります。

Contents

AIと対話しながら扱うなら「MCP」

まずはMCPについて。

無料版のClaudeを使ってみます。

MCPの設定は、いろいろ解説記事もありますが、公式HPでいいかなと。
freee-mcp(リモート版)を設定して利用する


設定ができたら、まず接続確認を兼ねて
「現在選択されているfreeeの事業所を教えてください。」
と聞いてみましょう。

Claudeに現在選択されているfreeeの事業所を教えてくださいと聞いている画面

想定している事業所名が表示されていればOKです。


次に、出力したいデータと対象期間を指定します。

「2026年7月分の仕訳帳を、freeeの新CSV形式、文字コードUTF-8で出力してください。」

このように依頼すると、ダウンロード用のCSVを準備してもらえます。


最初の設定を済ませておけば、このようにデータを取得することが可能です。

freeeのMCPを使うメリットとしては、以下のような点が挙げられます。

  • プログラミングが不要
  • Claudeから会話形式で会計データを取得できる
  • 最初にfreeeとの連携を済ませれば、毎回freeeへ手動ログインは不要

一方で、

  • 毎月1日に自動実行する
  • 決まったフォルダへ必ずCSVを保存する

といった完全自動化には向いていません。

会話形式で手軽にデータを取り出したい場合はMCP、
決まった形式・保存先へ安定して保存したい場合はPythonが向いています。

ExcelやCSVへ安定して保存するなら「Python」

毎月決まった日に自動実行することもできますが、
月初時点で前月の経理処理が終わっているとは限りません。

今回は、処理が終わったタイミングで実行できる形にしました。

具体的には、
BATファイルをダブルクリックし、対象月を指定すると、仕訳帳CSVと試算表Excelが指定フォルダへ保存されます。

※この記事のPython編はWindowsを前提にしています。

1.Pythonをインストールする

まず、Pythonをインストールします。

Pythonのインストール方法は、
以下の記事の「Pythonを動かすための最低限の準備」で解説しています。

そして、PowerShellまたはVS Codeのターミナルで、
次のコマンドを実行し、必要なライブラリをインストールします。

  • py -m pip install requests python-dotenv
  • py -m pip install openpyxl

これで、freee APIへ接続する機能と、認証情報を設定ファイルから読み込む機能、Excelにエクスポートする機能などがPythonで使えるようになります。


2.freeeでAPI連携用アプリを作成する

まずfreeeにログインして、freeeアプリストアに入ります。

次に、「開発者ページ」にアクセスし、
「今すぐアプリを作成」をクリックします。

アプリの基本情報などを入力します。
自分だけで使うのであれば「プライベートアプリ」を選択すればOKです。

対象の事業所を選択し、入力内容を下書き保存します。

権限設定で以下の参照権限を付与します。

  • 事業所情報:参照
  • 貸借対照表:参照
  • 仕訳帳:参照
  • 損益計算書:参照

基本情報タブの中に、以下の情報が表示されていればアプリの設定は完了です。

  • コールバックURL
  • Client IDとClient Secret
  • 認証用URL

※以下の画面の下の方に表示されます

3.freee APIの初回認証を行う

こんな形のフォルダとファイルを作っていきます。


まず「.env」ファイルを作ります。

メモ帳を開き、次の内容を入力して保存します。
※IDなどはご自身のものに差し替えてください

ファイル名は「.env」のみとしておきます。

FREEE_CLIENT_ID=ここにClient ID
FREEE_CLIENT_SECRET=ここにClient Secret
FREEE_REDIRECT_URI=urn:ietf:wg:oauth:2.0:oob

次に初回認証用のPythonのコードを組んでいきます。

以下のコードをメモ帳に貼り付け、「.py」ファイルとして保存します。

ファイル名は「freee_initial_auth.py」としておきます。

コード詳細
from __future__ import annotations

import json
import os
import sys
from datetime import datetime
from pathlib import Path

import requests
from dotenv import load_dotenv


BASE_DIR = Path(__file__).resolve().parent
ENV_FILE = BASE_DIR / ".env"
TOKEN_FILE = BASE_DIR / "tokens.json"
TOKEN_URL = "https://accounts.secure.freee.co.jp/public_api/token"


def get_required_env(name: str) -> str:
    """環境変数を取得し、未設定なら処理を終了する。"""
    value = os.getenv(name, "").strip()

    if not value:
        print(f"エラー:.envに{name}が設定されていません。")
        sys.exit(1)

    return value


def main() -> None:
    load_dotenv(ENV_FILE)

    client_id = get_required_env("FREEE_CLIENT_ID")
    client_secret = get_required_env("FREEE_CLIENT_SECRET")
    redirect_uri = get_required_env("FREEE_REDIRECT_URI")

    print("freeeの認可画面に表示された認可コードを貼り付けてください。")
    authorization_code = input("認可コード:").strip()

    if not authorization_code:
        print("エラー:認可コードが入力されていません。")
        sys.exit(1)

    response = requests.post(
        TOKEN_URL,
        data={
            "grant_type": "authorization_code",
            "client_id": client_id,
            "client_secret": client_secret,
            "code": authorization_code,
            "redirect_uri": redirect_uri,
        },
        timeout=30,
    )

    if not response.ok:
        print(f"トークンの取得に失敗しました:HTTP {response.status_code}")
        print(response.text)
        sys.exit(1)

    token_data = response.json()
    token_data["saved_at"] = datetime.now().astimezone().isoformat()

    TOKEN_FILE.write_text(
        json.dumps(token_data, ensure_ascii=False, indent=2),
        encoding="utf-8",
    )

    print()
    print("初回認証が完了しました。")
    print(f"保存先:{TOKEN_FILE}")
    print(f"事業所ID:{token_data.get('company_id', '取得できませんでした')}")
    print("アクセストークンなどの値は表示していません。")


if __name__ == "__main__":
    main()

PowerShellを立ち上げて、

まず、参照先の設定をします。

「cd “C:\Users\amehu\Desktop\freee_api”」
※ご自身のフォルダの場所を指定してください。


次に、「py .\freee_initial_auth.py」を実行します。
認可コードの入力を求められたら、freeeの画面に表示された認可コードを貼り付けてEnterを押します。

ここでの入力対象は「Webアプリ認証用URL」から得たコードです。
※Webアプリ認証用URLそのものを貼り付けるのではないため注意

このURLにアクセスして事業所を選択すると、以下のように認可コードが表示されますので、
これをPowerShellで入力していきます。

初回認証に成功すると、同じフォルダに「tokens.json」が作成されます。

4.仕訳帳・試算表をダウンロードする

まず、「.env」ファイルに保存先を追加しましょう。

FREEE_SAVE_FOLDER=●●(ご自身の会計データを保存したいフォルダのパスを記載してください)

次にダウンロードのPythonファイルを作っていきます。

以下のコードをメモ帳に貼り付け、「.py」ファイルとして保存しましょう。

ファイル名は「freee_download.py」としておきます。

コード詳細
from __future__ import annotations

import calendar
import json
import os
import sys
import tempfile
import time
from datetime import date, datetime
from pathlib import Path
from typing import Any

import requests
from dotenv import load_dotenv
from openpyxl import Workbook
from openpyxl.styles import Alignment, Font, PatternFill
from openpyxl.utils import get_column_letter


BASE_DIR = Path(__file__).resolve().parent
ENV_FILE = BASE_DIR / ".env"
TOKEN_FILE = BASE_DIR / "tokens.json"

API_BASE_URL = "https://api.freee.co.jp"
TOKEN_URL = "https://accounts.secure.freee.co.jp/public_api/token"

JOURNALS_URL = f"{API_BASE_URL}/api/1/journals"
COMPANY_URL = f"{API_BASE_URL}/api/1/companies/{{company_id}}"
TRIAL_BS_URL = f"{API_BASE_URL}/api/1/reports/trial_bs"
TRIAL_PL_URL = f"{API_BASE_URL}/api/1/reports/trial_pl"

REQUEST_TIMEOUT = 60
JOURNAL_POLL_SECONDS = 2
JOURNAL_MAX_WAIT_SECONDS = 180

TRIAL_COLUMNS = [
    ("account_item_id", "勘定科目ID"),
    ("account_item_name", "勘定科目名"),
    ("hierarchy_level", "階層"),
    ("parent_account_category_name", "上位カテゴリー"),
    ("account_category_name", "カテゴリー"),
    ("opening_balance", "期首残高"),
    ("debit_amount", "借方金額"),
    ("credit_amount", "貸方金額"),
    ("closing_balance", "期末残高"),
    ("composition_ratio", "構成比"),
]


class FreeeApiError(RuntimeError):
    """freee APIの処理に失敗した場合の例外。"""


def get_required_env(name: str) -> str:
    value = os.getenv(name, "").strip()
    if not value:
        raise FreeeApiError(f".envに{name}が設定されていません。")
    return value


def load_token_data() -> dict[str, Any]:
    if not TOKEN_FILE.exists():
        raise FreeeApiError(
            f"{TOKEN_FILE} が見つかりません。"
            "先に freee_initial_auth.py を実行してください。"
        )

    try:
        return json.loads(TOKEN_FILE.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError) as exc:
        raise FreeeApiError(f"tokens.jsonを読み込めませんでした:{exc}") from exc


def save_json_atomically(path: Path, data: dict[str, Any]) -> None:
    """途中で失敗しても既存ファイルを壊さないように保存する。"""
    path.parent.mkdir(parents=True, exist_ok=True)

    with tempfile.NamedTemporaryFile(
        mode="w",
        encoding="utf-8",
        delete=False,
        dir=path.parent,
        prefix=f"{path.stem}_",
        suffix=".tmp",
    ) as temp_file:
        json.dump(data, temp_file, ensure_ascii=False, indent=2)
        temp_path = Path(temp_file.name)

    temp_path.replace(path)


def refresh_access_token(
    token_data: dict[str, Any],
    client_id: str,
    client_secret: str,
) -> dict[str, Any]:
    refresh_token = str(token_data.get("refresh_token", "")).strip()
    if not refresh_token:
        raise FreeeApiError(
            "tokens.jsonにrefresh_tokenがありません。"
            "freee_initial_auth.pyを再実行してください。"
        )

    response = requests.post(
        TOKEN_URL,
        data={
            "grant_type": "refresh_token",
            "client_id": client_id,
            "client_secret": client_secret,
            "refresh_token": refresh_token,
        },
        timeout=REQUEST_TIMEOUT,
    )

    if not response.ok:
        raise FreeeApiError(
            "アクセストークンの更新に失敗しました。\n"
            "freee_initial_auth.pyで再認証してください。\n"
            f"HTTP {response.status_code}: {response.text}"
        )

    new_token_data = response.json()

    # トークン更新レスポンスに含まれない値は、既存データから引き継ぐ。
    if not new_token_data.get("company_id"):
        new_token_data["company_id"] = token_data.get("company_id")

    new_token_data["saved_at"] = datetime.now().astimezone().isoformat()
    save_json_atomically(TOKEN_FILE, new_token_data)

    return new_token_data


def make_headers(access_token: str) -> dict[str, str]:
    return {
        "Authorization": f"Bearer {access_token}",
        "Accept": "application/json",
    }


def api_get_json(
    url: str,
    access_token: str,
    *,
    params: dict[str, Any] | None = None,
    expected_statuses: tuple[int, ...] = (200,),
) -> dict[str, Any]:
    response = requests.get(
        url,
        headers=make_headers(access_token),
        params=params,
        timeout=REQUEST_TIMEOUT,
    )

    if response.status_code not in expected_statuses:
        raise FreeeApiError(
            f"APIの取得に失敗しました:HTTP {response.status_code}\n"
            f"{response.text}"
        )

    try:
        return response.json()
    except ValueError as exc:
        raise FreeeApiError("APIレスポンスをJSONとして読み込めませんでした。") from exc


def default_target_month() -> str:
    today = date.today()
    if today.month == 1:
        return f"{today.year - 1}-12"
    return f"{today.year}-{today.month - 1:02d}"


def input_target_month() -> tuple[int, int]:
    default_value = default_target_month()

    while True:
        raw_value = input(
            f"取得する年月を入力してください[{default_value}]:"
        ).strip()
        value = raw_value or default_value

        try:
            parsed = datetime.strptime(value, "%Y-%m")
            return parsed.year, parsed.month
        except ValueError:
            print("年月は 2026-07 のように入力してください。")


def input_download_mode() -> str:
    print()
    print("ダウンロードするデータを選択してください。")
    print("1:仕訳帳")
    print("2:試算表")
    print("3:仕訳帳と試算表の両方")

    while True:
        mode = input("番号[3]:").strip() or "3"
        if mode in {"1", "2", "3"}:
            return mode
        print("1、2、3のいずれかを入力してください。")


def month_period(year: int, month: int) -> tuple[date, date]:
    last_day = calendar.monthrange(year, month)[1]
    return date(year, month, 1), date(year, month, last_day)


def get_company(
    access_token: str,
    company_id: int,
) -> dict[str, Any]:
    response_data = api_get_json(
        COMPANY_URL.format(company_id=company_id),
        access_token,
    )
    company = response_data.get("company")

    if not isinstance(company, dict):
        raise FreeeApiError("事業所情報を取得できませんでした。")

    return company


def find_fiscal_year(
    company: dict[str, Any],
    target_end_date: date,
) -> tuple[date, date]:
    fiscal_years = company.get("fiscal_years", [])

    for fiscal_year in fiscal_years:
        try:
            start_date = date.fromisoformat(str(fiscal_year["start_date"]))
            end_date = date.fromisoformat(str(fiscal_year["end_date"]))
        except (KeyError, TypeError, ValueError):
            continue

        if start_date <= target_end_date <= end_date:
            return start_date, end_date

    raise FreeeApiError(
        f"{target_end_date:%Y-%m-%d}を含む会計年度が見つかりませんでした。"
    )


def warn_if_not_up_to_date(response_data: dict[str, Any], label: str) -> None:
    if response_data.get("up_to_date", True):
        return

    print(f"注意:{label}の集計結果が最新ではない可能性があります。")
    reasons = response_data.get("up_to_date_reasons", [])

    for reason in reasons:
        if isinstance(reason, dict):
            message = reason.get("message")
            if message:
                print(f"  - {message}")


def download_journal(
    access_token: str,
    company_id: int,
    start_date: date,
    end_date: date,
    save_folder: Path,
) -> Path:
    print()
    print("仕訳帳の作成をfreeeへ依頼しています…")

    request_data = api_get_json(
        JOURNALS_URL,
        access_token,
        params={
            "download_type": "generic_v2",
            "encoding": "utf-8",
            "company_id": company_id,
            "start_date": start_date.isoformat(),
            "end_date": end_date.isoformat(),
        },
        expected_statuses=(202,),
    )

    warn_if_not_up_to_date(request_data, "仕訳帳")

    journals = request_data.get("journals", {})
    report_id = journals.get("id")

    if not report_id:
        raise FreeeApiError("仕訳帳の受付IDを取得できませんでした。")

    status_url = (
        f"{API_BASE_URL}/api/1/journals/reports/{report_id}/status"
    )
    download_url = (
        f"{API_BASE_URL}/api/1/journals/reports/{report_id}/download"
    )

    deadline = time.monotonic() + JOURNAL_MAX_WAIT_SECONDS

    while True:
        status_data = api_get_json(
            status_url,
            access_token,
            params={"company_id": company_id},
        )
        status = status_data.get("journals", {}).get("status")

        if status == "uploaded":
            break

        if status == "failed":
            raise FreeeApiError("freee側で仕訳帳の作成に失敗しました。")

        if time.monotonic() >= deadline:
            raise FreeeApiError(
                "仕訳帳の作成が時間内に完了しませんでした。"
                "時間を空けて、もう一度実行してください。"
            )

        print(f"仕訳帳を作成中です…({status or '状態確認中'})")
        time.sleep(JOURNAL_POLL_SECONDS)

    response = requests.get(
        download_url,
        headers={
            "Authorization": f"Bearer {access_token}",
            "Accept": "text/csv",
        },
        params={"company_id": company_id},
        timeout=REQUEST_TIMEOUT,
    )

    if not response.ok:
        raise FreeeApiError(
            f"仕訳帳のダウンロードに失敗しました:"
            f"HTTP {response.status_code}\n{response.text}"
        )

    save_path = save_folder / (
        f"{start_date:%Y-%m}_freee仕訳帳.csv"
    )

    try:
        save_path.write_bytes(response.content)
    except PermissionError as exc:
        raise FreeeApiError(
            f"{save_path.name}を保存できませんでした。"
            "ファイルをExcelなどで開いている場合は閉じてください。"
        ) from exc

    print(f"仕訳帳を保存しました:{save_path}")
    return save_path


def get_trial_report(
    url: str,
    root_key: str,
    access_token: str,
    company_id: int,
    start_date: date,
    end_date: date,
    label: str,
) -> tuple[dict[str, Any], bool]:
    response_data = api_get_json(
        url,
        access_token,
        params={
            "company_id": company_id,
            "start_date": start_date.isoformat(),
            "end_date": end_date.isoformat(),
            "account_item_display_type": "account_item",
        },
    )

    warn_if_not_up_to_date(response_data, label)

    report_data = response_data.get(root_key)
    if not isinstance(report_data, dict):
        raise FreeeApiError(f"{label}のデータを取得できませんでした。")

    return report_data, bool(response_data.get("up_to_date", True))


def format_trial_sheet(
    worksheet: Any,
    title: str,
    report_data: dict[str, Any],
    is_up_to_date: bool,
    start_date: date,
    end_date: date,
) -> None:
    balances = report_data.get("balances", [])

    worksheet["A1"] = title
    worksheet["A1"].font = Font(size=14, bold=True)

    worksheet["A2"] = "対象期間"
    worksheet["B2"] = f"{start_date:%Y-%m-%d}{end_date:%Y-%m-%d}"
    worksheet["A3"] = "freee集計日時"
    worksheet["B3"] = report_data.get("created_at", "")
    worksheet["A4"] = "集計状態"
    worksheet["B4"] = "最新" if is_up_to_date else "最新ではない可能性あり"

    header_row = 6
    for column_index, (_, heading) in enumerate(TRIAL_COLUMNS, start=1):
        cell = worksheet.cell(row=header_row, column=column_index, value=heading)
        cell.font = Font(bold=True)
        cell.fill = PatternFill("solid", fgColor="D9EAF7")
        cell.alignment = Alignment(horizontal="center")

    for row_index, balance in enumerate(balances, start=header_row + 1):
        for column_index, (key, _) in enumerate(TRIAL_COLUMNS, start=1):
            value = balance.get(key)
            cell = worksheet.cell(
                row=row_index,
                column=column_index,
                value=value,
            )

            if key == "account_item_name":
                hierarchy_level = balance.get("hierarchy_level")
                if isinstance(hierarchy_level, int) and hierarchy_level > 1:
                    cell.alignment = Alignment(indent=min(hierarchy_level - 1, 10))

            if key in {
                "opening_balance",
                "debit_amount",
                "credit_amount",
                "closing_balance",
            }:
                cell.number_format = '#,##0;[Red]-#,##0'

            if key == "composition_ratio":
                cell.number_format = "0.00%"

    worksheet.freeze_panes = f"A{header_row + 1}"
    worksheet.auto_filter.ref = (
        f"A{header_row}:{get_column_letter(len(TRIAL_COLUMNS))}"
        f"{max(header_row, header_row + len(balances))}"
    )

    widths = {
        "A": 13,
        "B": 28,
        "C": 8,
        "D": 22,
        "E": 20,
        "F": 15,
        "G": 15,
        "H": 15,
        "I": 15,
        "J": 12,
    }
    for column_letter, width in widths.items():
        worksheet.column_dimensions[column_letter].width = width


def download_trial_balance(
    access_token: str,
    company_id: int,
    company: dict[str, Any],
    target_year: int,
    target_month: int,
    target_end_date: date,
    save_folder: Path,
) -> Path:
    fiscal_start_date, fiscal_end_date = find_fiscal_year(
        company,
        target_end_date,
    )

    print()
    print(
        "試算表を取得しています… "
        f"({fiscal_start_date:%Y-%m-%d}{target_end_date:%Y-%m-%d})"
    )

    bs_data, bs_is_up_to_date = get_trial_report(
        TRIAL_BS_URL,
        "trial_bs",
        access_token,
        company_id,
        fiscal_start_date,
        target_end_date,
        "貸借対照表",
    )
    pl_data, pl_is_up_to_date = get_trial_report(
        TRIAL_PL_URL,
        "trial_pl",
        access_token,
        company_id,
        fiscal_start_date,
        target_end_date,
        "損益計算書",
    )

    workbook = Workbook()
    default_sheet = workbook.active
    workbook.remove(default_sheet)

    bs_sheet = workbook.create_sheet("貸借対照表")
    pl_sheet = workbook.create_sheet("損益計算書")

    format_trial_sheet(
        bs_sheet,
        "貸借対照表",
        bs_data,
        bs_is_up_to_date,
        fiscal_start_date,
        target_end_date,
    )
    format_trial_sheet(
        pl_sheet,
        "損益計算書",
        pl_data,
        pl_is_up_to_date,
        fiscal_start_date,
        target_end_date,
    )

    save_path = save_folder / (
        f"{target_year}-{target_month:02d}_freee試算表.xlsx"
    )

    try:
        workbook.save(save_path)
    except PermissionError as exc:
        raise FreeeApiError(
            f"{save_path.name}を保存できませんでした。"
            "ファイルをExcelで開いている場合は閉じてください。"
        ) from exc

    print(f"試算表を保存しました:{save_path}")
    print(f"会計年度の期末日:{fiscal_end_date:%Y-%m-%d}")
    return save_path


def main() -> None:
    load_dotenv(ENV_FILE)

    client_id = get_required_env("FREEE_CLIENT_ID")
    client_secret = get_required_env("FREEE_CLIENT_SECRET")
    save_folder = Path(get_required_env("FREEE_SAVE_FOLDER")).expanduser()
    save_folder.mkdir(parents=True, exist_ok=True)

    token_data = load_token_data()

    print("freeeのアクセストークンを更新しています…")
    token_data = refresh_access_token(
        token_data,
        client_id,
        client_secret,
    )

    access_token = str(token_data.get("access_token", "")).strip()
    company_id_raw = token_data.get("company_id")

    if not access_token:
        raise FreeeApiError("アクセストークンを取得できませんでした。")

    try:
        company_id = int(company_id_raw)
    except (TypeError, ValueError) as exc:
        raise FreeeApiError("事業所IDを取得できませんでした。") from exc

    company = get_company(access_token, company_id)
    company_name = (
        company.get("display_name")
        or company.get("name")
        or str(company_id)
    )

    target_year, target_month = input_target_month()
    start_date, end_date = month_period(target_year, target_month)
    mode = input_download_mode()

    print()
    print(f"事業所:{company_name}")
    print(f"対象月:{target_year}-{target_month:02d}")
    print(f"保存先:{save_folder}")

    saved_files: list[Path] = []

    if mode in {"1", "3"}:
        saved_files.append(
            download_journal(
                access_token,
                company_id,
                start_date,
                end_date,
                save_folder,
            )
        )

    if mode in {"2", "3"}:
        saved_files.append(
            download_trial_balance(
                access_token,
                company_id,
                company,
                target_year,
                target_month,
                end_date,
                save_folder,
            )
        )

    print()
    print("処理が完了しました。")
    for saved_file in saved_files:
        print(f"  - {saved_file}")


if __name__ == "__main__":
    try:
        main()
    except FreeeApiError as exc:
        print()
        print(f"エラー:{exc}")
        sys.exit(1)
    except requests.RequestException as exc:
        print()
        print(f"通信エラー:{exc}")
        sys.exit(1)
    except KeyboardInterrupt:
        print()
        print("処理を中止しました。")
        sys.exit(1)

フォルダ内の構成はこんな形です。

PowerShellで「py .\freee_download.py」を実行しましょう。

年月とデータ種類を入力します。
何も入力せずEnterを押すと、初期値として「前月」「仕訳帳と試算表の両方」が選ばれます。

出力先のフォルダを見ると、このようにfreeeに手動ログインすることなく出力ができました。

5.ワンクリックで実行できるようにする

毎回PowerShellを起動してコードを入力するのは手間ですので、
Python実行用の「.bat」ファイルを作っておきましょう。

メモ帳に以下の内容を貼り付け、保存画面で「ファイルの種類」を「すべてのファイル」にします。
ファイル名は「freee_download.bat」として保存します。

コードの詳細
@echo off
chcp 65001 > nul
title freee仕訳帳・試算表ダウンロード
cd /d "%~dp0"

if not exist "%~dp0freee_download.py" (
    echo エラー:freee_download.pyが見つかりません。
    echo このBATファイルと同じフォルダに保存してください。
    echo.
    pause
    exit /b 1
)

where py > nul 2>&1
if %errorlevel% equ 0 (
    py "%~dp0freee_download.py"
) else (
    where python > nul 2>&1
    if %errorlevel% equ 0 (
        python "%~dp0freee_download.py"
    ) else (
        echo エラー:Pythonが見つかりません。
        echo Pythonをインストールしてから、もう一度実行してください。
        echo.
        pause
        exit /b 1
    )
)

echo.
pause

BATファイルとfreee_download.pyを同じフォルダに置けば、
BATファイル内のパスを書き換える必要はありません。

なお、以下の前提がありますので、ご注意ください。

  • Windowsを使用している
  • Pythonがインストールされている
  • requests、python-dotenv、openpyxlがインストールされている
  • freeeアプリを作成して初回認証が完了している
  • .envに保存先などを設定している

ちなみに今回はデスクトップで作成をしてきましたが、
フォルダごと移動するのであれば、任意の場所に置いていただいても問題なく起動します。


プログラム一式をOneDriveなどへ置くこともできます。
ただし、「.env」や「tokens.json」にはfreeeへ接続するための認証情報が含まれます。

本記事では、プログラムと認証情報はPC内に保存し、
取得した会計データだけをクラウドストレージへ保存する方法をおすすめします。

Googleスプレッドシートへ出すなら「GAS」

freee APIとGoogle Apps Script(GAS)を組み合わせれば、
freeeのデータをGoogleスプレッドシートへ取り込むこともできます。

当初は、この方法もゼロから作成して紹介しようと考えていました。

ただ、freee公式サイトですでに設定方法が詳しく解説されていますので、GASを使って連携したい方は、そちらをご参照ください。

この方法はPythonをインストールせず、
Googleスプレッドシート上でデータを取得・加工したい場合に向いている方法です。


ちなみに「freee Data Getter for Google Sheets」というアプリも存在します。

有料ですが、コードを書かずにGoogleスプレッドシートと連携したい場合の選択肢になります。

まとめ

紹介した3つの方法をまとめてみます。

方法向いている用途特徴
MCPその都度、AIに取得・集計を依頼するプログラミング不要で手軽
freee API+PythonCSV・Excelを決まったフォルダへ保存する自由度が高く、保存形式も調整できる
freee API+GASGoogleスプレッドシートへ取り込むPythonをインストールせず、クラウド上で扱える

用途に合った方法から、試してみていただければ。

では、また次回。

編集後記

◇日記
 昨日は秩父旅行2日目。
 埼玉県立自然の博物館へ。
 5年前も長男と3人で来ていましたが、家族が増えたなぁとしみじみとしました。
 帰宅して、夜は自宅近くの焼肉へ。

◇ブログネタ経緯
 freeeネタを書こうと思い、実際にPythonで試してみました。
 想像以上にPythonと格闘することになりましたが…。

◇1日1新
 道の駅 みなの
 みそポテト
 わらじカツ丼

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