CSVファイルをSqlite にインポートする
最初に
import sqlite3
import csv
# CSVとDBファイルのパス
csv_file = 'receipts.csv'
db_file = 'receipts.db'
# データベース接続とテーブル作成
conn = sqlite3.connect(db_file)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS purchases (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT,
store TEXT,
item TEXT,
price INTEGER,
quantity INTEGER
)
''')
# CSV読み込み&データ挿入
with open(csv_file, newline='', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
cursor.execute('''
INSERT INTO purchases (date, store, item, price, quantity)
VALUES (?, ?, ?, ?, ?)
''', (row['日付'], row['店舗'], row['商品名'], int(row['単価']), int(row['数量'])))
conn.commit()
conn.close()
print("CSVファイルからSQLiteにデータを取り込みました。")
としたけど
毎日買い物するためCSVファイルは 20250618_create.csv のように ファイル名は 日付_店舗名.csv となる このファイルが複数になる
✅ 目的
すべての *.csv をスキャンし、SQLiteに取り込む。
なので
./csv_files/
から取り込むようにするので
mkdir csv_files
mv *.csv csv_files
で移動しておく
そしてこれを取り込むようにコード変更
import sqlite3
import csv
import os
import re
# フォルダとDBファイルのパス
csv_folder = './csv_files'
db_file = 'receipts.db'
# データベース接続とテーブル作成
conn = sqlite3.connect(db_file)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS purchases (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT,
store TEXT,
item TEXT,
price INTEGER,
quantity INTEGER
)
''')
# ファイル名のパターン: 20250618_イオン.csv
pattern = re.compile(r"(\d{8})_(.+)\.csv")
# フォルダ内のCSVファイルをループ処理
for filename in os.listdir(csv_folder):
match = pattern.match(filename)
if not match:
continue # 無効なファイル名はスキップ
date = match.group(1)
store = match.group(2)
filepath = os.path.join(csv_folder, filename)
with open(filepath, newline='', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
try:
cursor.execute('''
INSERT INTO purchases (date, store, item, price, quantity)
VALUES (?, ?, ?, ?, ?)
''', (
date,
store,
row['商品名'],
int(row['単価']),
int(row['数量'])
))
except Exception as e:
print(f"[エラー] {filename} の {row} に問題があります: {e}")
conn.commit()
conn.close()
print("すべてのCSVファイルをSQLiteに取り込みました。")
✅ 今後追加可能な機能
* すでに取り込んだCSVファイルを記録してスキップ(import_log.dbなどで管理)
* 重複チェック(同じ日付・店舗・商品・価格が存在すればスキップ)
すでに取り込んだCSVファイルを記録してスキップ(import_log.dbなどで管理) 重複チェック(同じ日付・店舗・商品・価格が存在すればスキップ) を追加する
✅ 追加機能の詳細
1. 取り込み済みCSVファイルを記録してスキップ
→ 別テーブル import_log を用意し、同じファイルは2回処理しないようにする。
2. 同一商品の重複データ(同じ日付+店舗+商品+単価)をスキップ
→ purchases テーブルに一意制約(UNIQUE)を追加。
これで
import sqlite3
import csv
import os
import re
# フォルダとDBファイルのパス
csv_folder = './csv_files'
db_file = 'receipts.db'
# データベース接続とテーブル作成
conn = sqlite3.connect(db_file)
cursor = conn.cursor()
# 商品データ用テーブル(重複防止のためUNIQUE制約付き)
cursor.execute('''
CREATE TABLE IF NOT EXISTS purchases (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT,
store TEXT,
item TEXT,
price INTEGER,
quantity INTEGER,
UNIQUE(date, store, item, price)
)
''')
# CSVインポート済みファイルの記録テーブル
cursor.execute('''
CREATE TABLE IF NOT EXISTS import_log (
filename TEXT PRIMARY KEY
)
''')
# ファイル名のパターン: 20250618_イオン.csv
pattern = re.compile(r"(\d{8})_(.+)\.csv")
# フォルダ内のCSVファイルをループ処理
for filename in os.listdir(csv_folder):
match = pattern.match(filename)
if not match:
continue # 無効なファイル名はスキップ
# すでに取り込み済みかチェック
cursor.execute('SELECT 1 FROM import_log WHERE filename = ?', (filename,))
if cursor.fetchone():
print(f"[スキップ] すでに取り込み済み: {filename}")
continue
date = match.group(1)
store = match.group(2)
filepath = os.path.join(csv_folder, filename)
with open(filepath, newline='', encoding='utf-8') as f:
reader = csv.DictReader(f)
inserted = 0
for row in reader:
try:
cursor.execute('''
INSERT OR IGNORE INTO purchases (date, store, item, price, quantity)
VALUES (?, ?, ?, ?, ?)
''', (
date,
store,
row['商品名'],
int(row['単価']),
int(row['数量'])
))
if cursor.rowcount > 0:
inserted += 1
except Exception as e:
print(f"[エラー] {filename} の {row} に問題があります: {e}")
# 成功したらimport_logに記録
cursor.execute('INSERT OR IGNORE INTO import_log (filename) VALUES (?)', (filename,))
print(f"[完了] {filename} を取り込みました(新規 {inserted} 件)")
conn.commit()
conn.close()
print("すべてのCSVファイルの取り込みが完了しました。")
とする
そして
セルフメディケーション対応商品の商品名と金額、今年購入した合計金額を表示
✅ やりたいことまとめ
* 商品名に「セルフメディケーション対象」とわかるものをリストアップ(例:風邪薬、胃腸薬など)
* 購入履歴のうち、対象商品の2025年の購入金額合計を商品別に表示
* 合計金額も出す
DBで購入管理したい そのために 購入店舗名 購入年月日 商品名 価格 個数 をCSVファイルにしたい また食品なども同時に購入することがあるため セルフメディケーション対象商品を識別できるようにしたい
最初にGPTで提案してきたのは
対象商品のリスト(キーワードまたは手動リスト)
たとえば以下のように事前定義します:
self_medication_keywords = [
"風邪薬", "解熱剤", "鎮痛剤", "胃薬", "整腸剤", "花粉症", "鼻炎薬", "目薬", "かゆみ止め", "湿布", "トローチ"
]
これだとリストを手動で追加する必要がある
DBで購入管理したい そのために 購入店舗名 購入年月日 商品名 価格 個数 をCSVファイルにしたい また食品なども同時に購入することがあるため セルフメディケーション対象商品を識別できるようにしたい 現在購入している店舗ては ◆ もしくは セ] が商品名の先頭についている
この場合、◆をCSVにいれる以外の方法で何かあるか?
に対して
方法1:**商品名とは別に「分類」カラムを持つ(推奨)
方法2:対象商品のリストを別ファイル or 別テーブルで管理する
方法3:商品名はそのまま、別フラグ列 is_self_med を追加
✅ 結論:どの方法がいい?
方法 長所 短所 おすすめ度
方法1: 分類列 視認性と拡張性◎、CSVで完結 手入力ミスの可能性 ⭐⭐⭐⭐☆
方法2: マスタ マスタ更新で一括制御できる(大量商品向け) 初期セットアップが必要 ⭐⭐⭐⭐☆
方法3: フラグ 分類が2値なら簡単 他カテゴリ分類に弱い ⭐⭐⭐☆☆
方法0: 記号 手軽・一目でわかる データが汚れ、分類が混在する可能性 ⭐⭐☆☆☆
が回答
結論として
購入店舗名,購入年月日,商品名,単品税抜価格,価格,個数,セルフメディケーション対象金額
ドラッグストア クリエイト 袋井山名店,2025/06/21,ロイヤルブレッド(6),139,139,1,0
ドラッグストア クリエイト 袋井山名店,2025/06/21,日興フーズ 王様のデーツ,298,298,1,0
ドラッグストア クリエイト 袋井山名店,2025/06/21,家族の潤いマスカット,108,108,1,0
ドラッグストア クリエイト 袋井山名店,2025/06/21,家族の潤い白桃ミックス,108,216,2,0
ドラッグストア クリエイト 袋井山名店,2025/06/21,ビオヨーグルト プレーン加糖,178,356,2,0
ドラッグストア クリエイト 袋井山名店,2025/06/21,雪印メグミルク 牧場の朝ヨー,99,198,2,0
ドラッグストア クリエイト 袋井山名店,2025/06/21,ブルボン デザートマルシェ白,93,278,3,0
ドラッグストア クリエイト 袋井山名店,2025/06/21,フルーツフェスタフルーツミックス,198,396,2,0
というように
カラムの追加とその値に金額を入れることにした
CSVデータ作成を依頼すると重複データを送られることがある。
実際のデータレシートは1枚のため
同一のレシート内容がある場合、削除する機能が欲しい
ハッシュで重複検出
をGPTは提案してくるが
CSVファイル全体の内容が完全一致した場合だけ「重複」とみなしてスキップ
つまり差分のチェックはこれだとできない
基本的に購入管理なので商品名と日付の判定で十分。 同じ日付で同一店舗で複数購入することはまず無いため
これを元に再度作成する
✔ 重複判定キー:
sql
コピーする編集する
UNIQUE(date, store, item)
これにより、
* 同じ日付
* 同じ店舗
* 同じ商品名
が1回しか登録されないようになる
✅ 補足:複数買い(同じ商品を同日に2回買う)を防ぎたい場合
万が一「同日に同じ商品を複数回買う」ことが発生してしまうと、2回目は登録されません(意図どおり)。
でもその場合も quantity や total_price を工夫すれば、「まとめて登録したことにできる」ので問題ありません。
ということ
この制約で動くインポートスクリプト全体を提供し
重複スキップされた商品名を CSV記録する
✅ 要件まとめ
* csv_files/ フォルダに複数のCSVがあり、すべて処理対象
* CSV形式:
購入店舗名,購入年月日,商品名,単品税抜価格,価格,個数,セルフメディケーション対象金額
* UNIQUE制約:購入店舗名, 購入年月日, 商品名 の3つで一意
* 重複データは登録せずスキップ
* スキップされた商品情報をCSVに記録(例:skipped.csv)
vim import_receipts_with_dedup_and_log.py
でファイルを作成
import sqlite3
import csv
import os
from datetime import datetime
import re
# 設定
csv_folder = './csv_files'
db_file = 'receipts.db'
skipped_log = 'skipped.csv'
# SQLite接続
conn = sqlite3.connect(db_file)
cursor = conn.cursor()
# purchasesテーブル(UNIQUE制約付き)
cursor.execute('''
CREATE TABLE IF NOT EXISTS purchases (
id INTEGER PRIMARY KEY AUTOINCREMENT,
store TEXT,
date TEXT,
item TEXT,
unit_price INTEGER,
total_price INTEGER,
quantity INTEGER,
self_med_amount INTEGER,
UNIQUE(date, store, item)
)
''')
# import_logテーブル
cursor.execute('''
CREATE TABLE IF NOT EXISTS import_log (
filename TEXT PRIMARY KEY
)
''')
# skipped.csv が存在していたら上書き、なければ新規作成
with open(skipped_log, 'w', newline='', encoding='utf-8') as skipfile:
skip_writer = csv.writer(skipfile)
skip_writer.writerow(['購入店舗名', '購入年月日', '商品名', '単品税抜価格', '価格', '個数', 'セルフメディケーション対象金額'])
for filename in os.listdir(csv_folder):
if not filename.endswith('.csv'):
continue
filepath = os.path.join(csv_folder, filename)
# ファイルの重複チェック
cursor.execute('SELECT 1 FROM import_log WHERE filename = ?', (filename,))
if cursor.fetchone():
print(f"[スキップ] {filename}(すでにインポート済み)")
continue
print(f"[処理中] {filename}")
try:
with open(filepath, newline='', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
store = row['購入店舗名'].strip()
date = datetime.strptime(row['購入年月日'], '%Y/%m/%d').strftime('%Y%m%d')
item = row['商品名'].strip()
unit_price = int(row['単品税抜価格'])
total_price = int(row['価格'])
quantity = int(row['個数'])
self_med_amount = int(row['セルフメディケーション対象金額'])
try:
cursor.execute('''
INSERT INTO purchases
(store, date, item, unit_price, total_price, quantity, self_med_amount)
VALUES (?, ?, ?, ?, ?, ?, ?)
''', (store, date, item, unit_price, total_price, quantity, self_med_amount))
except sqlite3.IntegrityError:
# 重複データをskipped.csvに記録
skip_writer.writerow([
store, row['購入年月日'], item,
unit_price, total_price, quantity, self_med_amount
])
# ファイル名をインポート済みとして記録
cursor.execute('INSERT INTO import_log (filename) VALUES (?)', (filename,))
print(f"[完了] {filename} をインポートしました。")
except Exception as e:
print(f"[エラー] {filename}: {e}")
conn.commit()
conn.close()
print("✅ すべてのCSVの処理が完了しました。スキップされた商品は skipped.csv に保存されています。")
これを実行したら
python import_receipts_with_dedup_and_log.py
[処理中] 20250621.csv
[エラー] 20250621.csv: time data '購入年月日' does not match format '%Y/%m/%d'
[処理中] 20240618-19.csv
[完了] 20240618-19.csv をインポートしました。
[処理中] 20250612.csv
[完了] 20250612.csv をインポートしました。
[処理中] 20250613.csv
[完了] 20250613.csv をインポートしました。
[処理中] 20250614-19.csv
[完了] 20250614-19.csv をインポートしました。
[処理中] 20250605.csv
[完了] 20250605.csv をインポートしました。
[処理中] 20250611.csv
[完了] 20250611.csv をインポートしました。
✅ すべてのCSVの処理が完了しました。スキップされた商品は skipped.csv に保存されています。
となる
cat skipped.csv
で中身を確認
購入店舗名,購入年月日,商品名,単品税抜価格,価格,個数,セルフメディケーション対象金額
杏林堂 袋井旭町店,2025/06/18,サンテビオ 15mL,968,871,1,871
ピアゴ 袋井店,2025/04/18,タイレノールA 30錠,1790,1772,1,1772
COOP ユーコープ 袋井町店,2025/06/19,COゼリーエネルギー,118,354,3,0
COOP ユーコープ 袋井町店,2025/06/19,鮭・くず桜,238,190,1,0
COOP ユーコープ 袋井町店,2025/06/19,天ぷら用えび,398,238,1,0
COOP ユーコープ 袋井町店,2025/06/19,たい,398,398,1,0
COOP ユーコープ 袋井町店,2025/06/19,塩銀鮭切身,398,398,1,0
COOP ユーコープ 袋井町店,2025/06/19,真あじ開き,398,796,2,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,日興フーズ 王様のチーズ,298,298,1,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,ロイヤルブレッド山型(5),139,139,1,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,家族の潤い白桃ミックス,108,216,2,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,鶏卵卵パック MS 10個,228,228,1,0
で調べるとスキップされている
cat csv_files/20240618-19.csv
で中身を確認
購入店舗名,購入年月日,商品名,単品税抜価格,価格,個数,セルフメディケーション対象金額
杏林堂 袋井旭町店,2025/06/18,サンテビオ 15mL,968,871,1,871
ピアゴ 袋井店,2025/04/18,タイレノールA 30錠,1790,1772,1,1772
COOP ユーコープ 袋井町店,2025/06/19,COゼリーエネルギー,118,354,3,0
COOP ユーコープ 袋井町店,2025/06/19,鮭・くず桜,238,190,1,0
COOP ユーコープ 袋井町店,2025/06/19,天ぷら用えび,398,238,1,0
COOP ユーコープ 袋井町店,2025/06/19,たい,398,398,1,0
COOP ユーコープ 袋井町店,2025/06/19,塩銀鮭切身,398,398,1,0
COOP ユーコープ 袋井町店,2025/06/19,真あじ開き,398,796,2,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,日興フーズ 王様のチーズ,298,298,1,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,ロイヤルブレッド山型(5),139,139,1,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,家族の潤い白桃ミックス,108,216,2,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,鶏卵卵パック MS 10個,228,228,1,0
で調べると
これは row[‘購入年月日’] の値が本来 “2025/06/18” のような日付になるはずのところ、
誤って “購入年月日”(つまりヘッダーの文字列)が入っているため、datetime.strptime() が失敗しています。
とのこと
CSVファイル内に ヘッダー行がもう一度繰り返されている可能性が非常に高いです(コピペミス、マージ時の事故など)。
✅ 解決策:誤って混入したヘッダー行をスキップ
以下の1行を for row in reader: の直後に追加
# ヘッダー行がデータ行に混入していたらスキップ
if row['購入年月日'] == '購入年月日':
continue
これを処理に追加して
with open(filepath, newline='', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
if row['購入年月日'] == '購入年月日':
continue # ← ここで2回目以降のヘッダー行を除外
store = row['購入店舗名'].strip()
date = datetime.strptime(row['購入年月日'], '%Y/%m/%d').strftime('%Y%m%d')
...
とすることで
✅ 結果
* ✔ ヘッダーが再登場しても自動でスキップ
* ✔ 異常な日付データがあってもクラッシュせず処理継続
保存して再度実行
python import_receipts_with_dedup_and_log.py
[処理中] 20250621.csv
[完了] 20250621.csv をインポートしました。
[スキップ] 20240618-19.csv(すでにインポート済み)
[スキップ] 20250612.csv(すでにインポート済み)
[スキップ] 20250613.csv(すでにインポート済み)
[スキップ] 20250614-19.csv(すでにインポート済み)
[スキップ] 20250605.csv(すでにインポート済み)
[スキップ] 20250611.csv(すでにインポート済み)
✅ すべてのCSVの処理が完了しました。スキップされた商品は skipped.csv に保存されています。
となって無事に成功
次に
セルフメディケーション対象商品の合計金額(2025年) 商品別セルフメディケーション金額ランキング を表示したい
SQLiteに保存された購入データ(テーブル名:purchases)から、以下の2つを表示
セルフメディケーション対象商品の合計金額(2025年)
SELECT SUM(self_med_amount) AS total_amount
FROM purchases
WHERE date BETWEEN '20250101' AND '20251231'
AND self_med_amount > 0;
商品別セルフメディケーション金額ランキング(2025年)
SELECT item, SUM(self_med_amount) AS total_amount
FROM purchases
WHERE date BETWEEN '20250101' AND '20251231'
AND self_med_amount > 0
GROUP BY item
ORDER BY total_amount DESC;
これがSQLになる
vim medication_summary.py
import sqlite3
# SQLiteに接続
conn = sqlite3.connect("receipts.db")
cursor = conn.cursor()
print("📋 セルフメディケーション対象商品の合計金額(2025年)")
cursor.execute('''
SELECT SUM(self_med_amount)
FROM purchases
WHERE date BETWEEN '20250101' AND '20251231'
AND self_med_amount > 0
''')
total = cursor.fetchone()[0] or 0
print(f"▶ 合計金額:¥{total:,}")
print()
print("📊 商品別セルフメディケーション対象金額ランキング(2025年)")
cursor.execute('''
SELECT item, SUM(self_med_amount) AS total
FROM purchases
WHERE date BETWEEN '20250101' AND '20251231'
AND self_med_amount > 0
GROUP BY item
ORDER BY total DESC
''')
rows = cursor.fetchall()
if not rows:
print("(対象商品はありません)")
else:
print("商品名\t\t金額")
print("-" * 30)
for item, amount in rows:
print(f"{item[:14]:<14} ¥{amount:,}")
conn.close()
これを実行すると
python medication_summary.py
📋 セルフメディケーション対象商品の合計金額(2025年)
▶ 合計金額:¥2,643
📊 商品別セルフメディケーション対象金額ランキング(2025年)
商品名 金額
------------------------------
タイレノールA 30錠 ¥1,772
サンテビオ 15mL ¥871
となる