やあ、業務自動化の世界へようこそ!毎日大量のデータ処理、本当にお疲れ様です。
Excelで開こうとした瞬間に画面が真っ白になり、「応答なし」の文字に絶望する……数十万行を超えるような巨大なCSVファイルを扱っていると、誰もが一度は通る道ですよね。
「VBAのマクロだと重すぎて動かない」「Pythonを入れようにも会社のPCセキュリティでインストーラーが弾かれてしまう」——そんな絶望的な状況を、追加ツール一切なしでスマートに解決してくれるのが、Windowsに標準搭載されているVBScript (Visual Basic Scripting Edition) です。
今回は、実務で即戦力となる「単一の巨大CSVを特定キー(支社コードや日付など)ごとに動的に分割出力するバッチスクリプト」を一緒に作っていきましょう。
ここをクリアすれば、VBScriptの基本はもちろん、大容量データを安全に扱うプロのアルゴリズムまでバッチリ身につきますよ!
—
1. なぜVBScriptで巨大CSVを処理するのか?(本質の理解)
具体的なコードを見る前に、なぜ今回VBScriptとファイルシステムオブジェクト(FSO)を使うのか、そのアーキテクチャの理由を少しだけお話しさせてください。ここが分かっていると、将来別の言語を学ぶときにも強い武器になります。
「一括読み込み」対「ストリーミング処理」
初心者が陥りがちな落とし穴が、「ファイルを丸ごとメモリ(配列)に読み込んでから処理しようとする」ことです。
【ダメな例:メモリ一括読み込み】
巨大CSV(数GB) ➔ [ PCのメモリへ全部ロード! ] ➔ メモリ不足でフリーズ!
VBScriptの`FileSystemObject`(以下、FSO)を使う最大のメリットは、データを1行ずつ読み込んでそのまま1行ずつ書き出す「ストリーミング処理」が簡単に書けることです。
【プロのやり方:ストリーミング処理】
巨大CSV ➔ [ 1行読む ] ➔ [ キー判定 ] ➔ [ 該当ファイルへ1行書く ] ➔ 次の行へ
この方法なら、ファイルサイズが100MBであろうと10GBであろうと、PCのメモリ消費量は「たったの1行分」で済みます。どんなに巨大なファイルでも、PCをフリーズさせずにサクサク処理できるのはこれが理由です。
—
2. 処理の全体イメージと設計図
今回のバッチ処理のロジックを図解すると、以下のようになります。
[ 入力ファイル: sales_data.csv ]
ヘッダー: 日付, 支社コード, 金額
行1: 2023/10/01, TOKYO, 1000
行2: 2023/10/01, OSAKA, 1500
行3: 2023/10/01, TOKYO, 2000
│ (1行ずつループ処理)
▼
【VBScript の制御タワー】
① ヘッダーを記憶
② 2列目(支社コード)を抽出
③ 初めて見るコードなら「新しい書き込みファイル」を作成し、ヘッダーを書き込む
④ 既に開いたファイルなら、そのまま追記
│
├───────────────────────┐
▼ ▼
[ output_TOKYO.csv ] [ output_OSAKA.csv ]
日付, 支社コード, 金額 日付, 支社コード, 金額
2023/10/01, TOKYO, 1000 2023/10/01, OSAKA, 1500
2023/10/01, TOKYO, 2000
ここで重要な技術的テクニックが「`Scripting.Dictionary` を使ったファイルハンドルのキャッシュ」です。ファイルを開いたり閉じたりする操作はPCにとって非常に重い処理です。そのため、一度開いた出力先ファイルは辞書(Dictionary)に保持しておき、最後まで閉じずに書き込み続けることで爆速処理を実現します。
—
3.【完成版】実務でそのまま使えるVBScriptコード
それでは、実際のスクリプトを見てみましょう。以下のコードをコピーして、テキストエディタ(メモ帳など)に貼り付け、ファイル名を `SplitCSV.vbs` として保存すれば、すぐに実行できます。
‘===============================================================================
‘ スクリプト名: SplitCSV.vbs
‘ 概要 : 巨大CSVファイルを特定列のキー(支社コード等)ごとに動的分割出力する
‘ 動作環境 : Windows(標準機能のみで動作)
‘===============================================================================
Option Explicit ‘ 未定義変数の使用を禁止(バグを防止するおまじない)
‘ — 設定エリア —
Const INPUT_FILE_PATH = “C:\Data\input_sales.csv” ‘ 分割したい巨大CSVのパス
Const OUTPUT_DIR = “C:\Data\Output\” ‘ 分割ファイルの保存先フォルダ
Const KEY_COLUMN_INDEX = 1 ‘ キーにする列の番号 (0始まり: 0=1列目, 1=2列目)
Const HAS_HEADER = True ‘ 1行目をヘッダーとして全ファイルに付与するか
‘ — メイン処理呼び出し —
Call Main()
Sub Main()
On Error Resume Next ‘ エラー検知用
Dim fso, dictStreams
Dim tsInput, tsOutput
Dim strLine, strHeader, strKey
Dim arrFields
Dim strOutputPath
Dim startTime, elapsedTime
startTime = Timer ‘ 処理時間計測用
‘ 1. オブジェクトの生成
Set fso = CreateObject(“Scripting.FileSystemObject”)
Set dictStreams = CreateObject(“Scripting.Dictionary”)
‘ 入力ファイルの存在チェック
If Not fso.FileExists(INPUT_FILE_PATH) Then
MsgBox “入力ファイルが見つかりません: ” & INPUT_FILE_PATH, vbCritical, “エラー”
Exit Sub
End If
‘ 出力フォルダが存在しない場合は自動作成
If Not fso.FolderExists(OUTPUT_DIR) Then
fso.CreateFolder(OUTPUT_DIR)
End If
‘ 2. 入力CSVファイルを読み込みモードで開く (1=ForReading, 0=ASCII/Shift-JIS)
Set tsInput = fso.OpenTextFile(INPUT_FILE_PATH, 1, False)
‘ ファイルが空でないか確認
If tsInput.AtEndOfStream Then
MsgBox “入力ファイルが空です。”, vbExclamation, “警告”
tsInput.Close
Exit Sub
End If
‘ 3. ヘッダー行の処理
If HAS_HEADER Then
strHeader = tsInput.ReadLine()
End If
‘ 4. データ行のストリーミング読み込み
Do Until tsInput.AtEndOfStream
strLine = tsInput.ReadLine()
‘ 空行はスキップ
If Trim(strLine) <> “” Then
‘ カンマで列に分解(簡易パース)
arrFields = Split(strLine, “,”)
‘ 指定列が存在するかチェック
If UBound(arrFields) >= KEY_COLUMN_INDEX Then
‘ キー文字を取得(前後空白除去、ファイル名に使えない文字対策は省略可)
strKey = Trim(arrFields(KEY_COLUMN_INDEX))
‘ ダブルクォーテーションで囲まれている場合の除去処理
strKey = Replace(strKey, “”””, “”)
‘ 5. 動的ファイル割り当てと書き込み
‘ まだこのキー用のファイルを開いていない場合
If Not dictStreams.Exists(strKey) Then
strOutputPath = fso.BuildPath(OUTPUT_DIR, “output_” & strKey & “.csv”)
‘ 新規作成・書き込みモードで開く (2=ForWriting, True=Create)
Set tsOutput = fso.CreateTextFile(strOutputPath, True, False)
‘ ヘッダーがあれば先頭に書き込む
If HAS_HEADER Then
tsOutput.WriteLine strHeader
End If
‘ 辞書オブジェクトにファイルハンドル(TextStream)を保持
Set dictStreams.Item(strKey) = tsOutput
End If
‘ 該当するキーのファイルストリームに1行書き込み
dictStreams.Item(strKey).WriteLine strLine
End If
End If
Loop
‘ 6. 後始末(リソースの確実に解放するライフサイクル管理)
tsInput.Close
Set tsInput = Nothing
‘ 辞書に溜め込んだすべての出力ファイルを閉じる
Dim key
For Each key In dictStreams.Keys
dictStreams.Item(key).Close
Set dictStreams.Item(key) = Nothing
Next
dictStreams.RemoveAll
Set dictStreams = Nothing
Set fso = Nothing
‘ エラーが発生していたか確認
If Err.Number <> 0 Then
MsgBox “処理中にエラーが発生しました: ” & Err.Description, vbCritical, “エラー”
Err.Clear
Else
elapsedTime = Round(Timer – startTime, 2)
MsgBox “分割処理が完了しました!” & vbCrLf & _
“処理時間: ” & elapsedTime & ” 秒”, vbInformation, “完了”
End If
End Sub
—
4. プロの視点!コードの重要ポイント解説
このコードには、実務でトラブルを起こさないための工夫が詰まっています。初心者の段階から知っておきたいポイントを3つ解説します。
ポイント①:`Option Explicit` でタイポを未然に防ぐ
スクリプトの1行目にある `Option Explicit`。これは「変数は必ず宣言してから使います」という宣言です。
VBScriptは変数宣言をしなくても動いてしまう緩い言語ですが、そのせいでスペルミス(例: `strLine` を `strLne` と打ち間違える)があっても気づけず、謎のバグに悩まされることになります。これを書く癖をつけるだけで、開発効率が劇的に上がります!
ポイント②:`Scripting.Dictionary` に「ファイルそのもの」を保存する
最もテクニカルな部分がここです。
‘ 辞書オブジェクトにファイルハンドル(TextStream)を保持
Set dictStreams.Item(strKey) = tsOutput
キー(支社コードなど)が登場するたびにファイルを開き直す(Open ➔ Write ➔ Close)と、何万回もハードディスクにアクセスすることになり、恐ろしく時間がかかります。
このコードでは、開いたファイルオブジェクト(TextStream)そのものを辞書に保管しています。2回目以降に同じ支社コードが出てきた時は、メモリ上の辞書から一瞬でファイルを取り出して `WriteLine` するため、超高速に動作するのです。
ポイント③:オブジェクトの明示的解放(ライフサイクル管理)
コードの終盤にある `Set … = Nothing` の山。これは一見面倒に見えますが、非常に重要です。
VBScriptが確保したメモリやファイルへのアクセス権(リソース)は、使い終わったら明示的に破棄(`Nothing`)するのが鉄則です。これを怠ると、スクリプトが終わった後もファイルがロックされたままになり、「ファイルが開けません」というエラーを引き起こす原因になります。
—
5. 実務で陥りやすい落とし穴と対策
実務のデータは、常に綺麗な状態とは限りません。現場でよく遭遇する「罠」と対策を知っておきましょう。
罠1:CSVのデータ内に「カンマ」が含まれている場合
今回のコードは `Split(strLine, “,”)` というシンプルな方法でカンマ区切りを判定しています。
しかし、CSVのデータ本文に `”東京都千代田区1-1,ビル5F”` のようにカンマが含まれ、全体がダブルクォーテーションで囲まれている場合、単純な Split だと列がズレてしまいます。
- 対策: もしデータ内にカンマが含まれる超複雑なCSVを扱う場合は、正規表現(`VBScript.RegExp`)を使ったパース関数を自作するか、キーとなる列だけでもカンマが含まれない運用ルール(またはTSV形式・タブ区切り)にするのが実務的な解決策です。
罠2:分割するキーの種類が多すぎる場合(数千〜数万種類)
今回のスクリプトは、「開いたファイルを閉じずに保持する」ことで高速化しています。しかし、Windows OSには「同時に開けるファイルの上限数」が存在します。
もし分割キー(支社コード等)の種類が数千種類に及ぶ場合、ファイルを開きすぎてエラーになることがあります。
- 対策: 支社コードや都道府県コード程度(数十〜数百種類)であれば全く問題ありません。もし数万種類に及ぶ場合は、一度全データをソート(並び替え)してから処理するか、キーが変わるたびにファイルを一回一回閉じる設計に変更する必要があります。
—
6. まとめ:ここをクリアすればVBScriptの基本はバッチリ!
今回は、FSOを使った大容量CSVの動的分割テクニックを解説しました。
1. ストリーミング処理(1行読み・1行書き)でメモリ負荷を最小化する
2. Dictionaryを使ってファイルハンドルを使い回し、処理速度を爆発的に上げる
3. 使い終わったオブジェクトは `Set … = Nothing` で綺麗に後始末する
この3つの原則さえマスターしてしまえば、VBScriptを使ったファイル操作バッチはもう怖いものなしです。
「プログラムで業務がこんなに速くなるんだ!」という感動を、ぜひご自身のPCで体験してみてください。あなたが手作業で行っていた数時間のデータ整理が、ボタン一発・数秒で終わる快感が待っていますよ!
何か分からない点や「自分のデータ構造の場合はどう書けばいい?」といった疑問があれば、いつでも気軽に聞いてくださいね。応援しています!
