💀 はじめに:退職者が作ったツールが消えた
ある日、社内で「例のツール、もう動かないんだけど?」と現場から問い合わせが来ました。
話を聞くと、別部署のスーパーハカーが以前作った、「CSV整理ツール」があったらしい。
(そういえば作ってもらった気がする)
だがしかしその本人はすでに退職済み。
引き継ぎ項目にも残っておらず、アカウント削除とともにツールが消滅。
GWSのアカウント消す時に引き継ぐ項目に入ってないから、個別にやっとかなきゃいけなかったらしい!盲点すぎるだろ!
Colabで作ってたらしいことと、残されたマニュアルから、ツールを復元してみようの回です
当方、パイソン チョトワカル プログラミング チョトワカル 程度の情シス要員です
コード自体はちゃっぴー(ChatGPT)に命令して作ってもらいました
💡 そもそもGoogle Colabってなに?
「Google Colab(コラボ)」は、Googleが提供している ブラウザ上でPythonを実行できる無料環境。(らしい)(知らんかった)
正式名称:GoogleColaboratory
- インストール不要(ブラウザだけで動く)
- Googleアカウントさえあれば無料で使える
- GPUなどのリソースも利用可能
- Googleドライブと連携できる
つまり、「ちょっとPythonで処理したい」をすぐに試せる環境です。
特に社内PCにPythonを入れられない環境で使えたりする。
でもそんなにPython入れるのって手間ではなくない?
特に現場の人にWebアプリ見たいな感じで簡単に使えるツールを提供できるのが便利だな~って実感しました
⚠️ 注意:Google Workspaceでは使えないことも
会社や学校などの Google Workspace アカウント(@会社名ドメイン) では、
管理者がColabを許可していないと利用できません。
このサービスは管理者によって無効にされています。
と出る場合は、 管理者に「Colaboratoryの使用許可」を依頼してください。
🤔 GAS(Google Apps Script)じゃだめなの?
てかGASの方が手軽だしGASじゃだめなの?
駄目らしい
| 項目 | GAS | Colab |
|---|---|---|
| ファイル操作 | DriveApp経由で都度ファイル取得が必要 | FileUploadウィジェットで即アップロード可 |
| 日本語文字コード(Shift_JISなど) | やや扱いにくい | pandasでエンコーディング指定が容易 |
| UI(ボタン・進行表示) | HTMLServiceが必要 | ipywidgetsで即作成可 |
| 実行速度 | やや遅め(制限あり) | ローカル実行に近い速度 |
| 実行環境 | Googleサーバー内、Apps Script制限下 | Pythonそのもの、柔軟 |
つまり、
「現場向けに、ドラッグ&ドロップで完結するCSVクリーナーを作りたい」
という要件では、Colabが圧倒的に速かったんです。
🧰 作ったもの:Shift_JIS非対応文字クリーナー
以下が復元したツールの実体。
CSVをアップロードすると、
- A〜K列の重複削除
- Shift_JISで保存できない文字の検出と除去
- 処理済みCSVを自動ダウンロード
を自動で行います。
ブラウザ上でGUI操作できるので、Pythonを知らなくても扱えます。
何故これを作りたかったといえば、皆さんご存知の通りCSVって開いて保存するときにコツがいるじゃん
特に小売りの人はあるあるだと思うんですけど、CSV内のJANコードが変になっちゃったりしたいね
なので、現場の人にはCSVを開かずに、作業を簡潔させるツールを作りたかったってわけです
💻 コード全文
Colabの基本的な使い方はここでは省略します
各自調べて使ってみてください
私もなんとなくでやりました
それで紆余曲折あーでもないこーでもない格闘して
こちらが出来上がったものです
Colabでそのまま貼り付けて動かせます。
初回は「初回のみ押す」ボタンでライブラリを自動インストール。
# @title Shift_JIS非対応文字クリーナー(Colab用)
import pandas as pd
import ipywidgets as widgets
from IPython.display import display, clear_output
import io, sys, subprocess
# --- chardetインストールボタン ---
install_log = widgets.Output()
def install_chardet(b):
install_log.clear_output()
with install_log:
print("chardetをインストールしています...")
try:
subprocess.check_call([sys.executable, "-m", "pip", "install", "chardet"])
print("chardetのインストールが完了しました!")
except Exception as e:
print("インストールに失敗しました:", e)
install_button = widgets.Button(description='初回のみ押す', button_style='warning', icon='download')
install_button.on_click(install_chardet)
# --- Shift_JIS判定関数 ---
def can_encode_shift_jis(char):
try:
char.encode('shift_jis')
return True
except UnicodeEncodeError:
return False
# --- UI構成 ---
upload_status = widgets.HTML(value='<span style="color:gray;">アップロード待機中</span>')
uploader = widgets.FileUpload(accept='.csv', multiple=False, description='ファイルを選択またはドロップ',
layout=widgets.Layout(width='360px', height='80px', border='2px dashed #aaa'))
start_button = widgets.Button(description='削除開始', button_style='success', icon='check')
help_button = widgets.Button(description='スタートボタン', button_style='info', icon='info-circle')
download_link = widgets.HTML(value='ダウンロードリンクはここに表示')
result_log = widgets.Output()
def on_help_clicked(b):
result_log.clear_output()
with result_log:
print("【使い方ガイド】")
print("1. 『初回のみ押す』でchardetを導入。")
print("2. CSVをアップロード。")
print("3. 『削除開始』でShift_JIS非対応文字と重複を除去。")
print("4. ダウンロードリンクから取得。")
help_button.on_click(on_help_clicked)
left_box = widgets.VBox([
install_button, install_log, help_button,
widgets.Label('【1】ファイルアップロード'),
upload_status, uploader,
widgets.Label('【2】下のボタンで削除開始'),
start_button
], layout=widgets.Layout(width='400px'))
right_box = widgets.VBox([
widgets.Label('【3】ダウンロード'),
download_link,
widgets.Label('【4】ログ'),
result_log
], layout=widgets.Layout(width='540px'))
display(widgets.HBox([left_box, right_box]))
# --- 処理ロジック ---
def on_upload_change(change):
if uploader.value:
upload_status.value = '<span style="color: blue;">アップロード完了!</span>'
else:
upload_status.value = '<span style="color: gray;">アップロード待機中</span>'
uploader.observe(on_upload_change, names='value')
def on_start_clicked(b):
result_log.clear_output()
if not uploader.value:
with result_log: print('⚠️ ファイルをアップロードしてください')
return
upload_status.value = '<span style="color: orange;">処理中...</span>'
uploaded_file = list(uploader.value.values())[0]
filename = uploaded_file['metadata']['name']
raw_data = uploaded_file['content']
try:
text_data = raw_data.decode('shift_jis', errors='ignore')
df = pd.read_csv(io.StringIO(text_data))
except Exception as e:
with result_log: print(f'読み込み失敗: {e}')
upload_status.value = '<span style="color: red;">ファイル読み込み失敗</span>'
return
df = df.drop_duplicates(subset=df.columns[:11], keep='last')
logs = []
df_cleaned = df.copy()
for i, row in df.iterrows():
for col in df.columns:
val = str(row[col])
try:
val.encode('shift_jis')
except UnicodeEncodeError:
cleaned = ''.join([c for c in val if can_encode_shift_jis(c)])
invalid = ''.join([c for c in val if not can_encode_shift_jis(c)])
df_cleaned.at[i, col] = cleaned
logs.append((i+2, col, invalid))
output_filename = f'processed_{filename.replace(".csv", "")}_sjis_clean.csv'
try:
df_cleaned.to_csv(output_filename, index=False, encoding='shift_jis')
from google.colab import files
files.download(output_filename)
upload_status.value = '<span style="color: green;">完了!</span>'
except Exception as e:
with result_log: print(f"保存エラー: {e}")
upload_status.value = '<span style="color: red;">保存エラー</span>'
return
with result_log:
if logs:
print("\n【Shift_JIS非対応文字 削除ログ】")
for r, c, inv in logs:
print(f"行{r}・列「{c}」: 非対応文字「{inv}」を削除しました。")
else:
print("Shift_JIS非対応文字は検出されませんでした。")
start_button.on_click(on_start_clicked)
全然身になった感じは全くないんだけど、少し賢くなった気がする!
おわり