- 技術 💻
VBAでCSVを読み込み・出力する方法|UTF-8の文字化け対策と手法比較
VBAでCSVを扱う方法は、「Shift_JISで単純な表ならLine InputとSplit、UTF-8ならADODB.Stream、列の型を指定して取り込むならQueryTablesかOpenText」と選びます。 どの方法でも読み書きはできますが、文字コード・引用符・先頭ゼロの扱いがそれぞれ違うため、ファイルの中身に合わせて選ぶと文字化けや列ずれを避けられます。
この記事では、読み込み5手法と出力3手法のコードを、同じ架空の商品CSVで並べます。文字化け・先頭ゼロ落ち・日付への自動変換・列ずれの4つの症状について、原因と直し方も表にしました。
確認環境:Excel VBA のコードは、Microsoft Learn の VBA リファレンスと ADO リファレンスで各メソッド・プロパティ・引数を確かめて書いています。Line Input・Split・Print # と、引用符を考慮して分割・出力する関数は、LibreOffice 24.2.7.2 の Basic(VBA互換モード)で動かした結果を載せています。掲載したコード全体も同じ環境で構文エラーが無いことを確かめています(Excel固有のオブジェクトを使う部分の動作は、リファレンスの記載にもとづきます)。文字コードのバイト列は Python 3.11 で確かめました(確認日 2026-10-05)。
この記事の結論
- Shift_JISで、値にカンマや改行を含まない表:Line Input と Split。コードが短く、追加の部品も要りません。
- UTF-8のCSV:ADODB.Stream で Charset を "UTF-8" にして読み書きします。
- 先頭ゼロや日付を列ごとに指定して取り込む:QueryTables か OpenText で、列の型を「文字列」に指定します。
- 出力先がExcel以外のシステム:BOMの要否を先に確かめます。ADODB.Stream はBOMあり・BOMなしの両方を書けます。
VBAのCSV読み込み・出力の手法比較表
読み込み5手法
| 手法 | 文字コード | 引用符内カンマ | 先頭ゼロ | 速度の目安 | コード量 |
|---|---|---|---|---|---|
| Workbooks.Open | Origin引数はプラットフォーム(xlWindows など)の指定 | Excelが解釈する | 数値として読まれ、消える | Excel本体が一括で処理 | 3行ほど |
| Workbooks.OpenText | Origin引数にコードページ番号を書ける(UTF-8は65001) | TextQualifierで指定 | FieldInfoで列を文字列に指定 | Excel本体が一括で処理 | 10行ほど |
| Line Input+Split | Windowsの既定の文字コード(日本語環境ではShift_JIS) | Splitだけでは列がずれる。分割用の関数を足す | 書式を文字列にしてから書けば残る | 1行ずつ処理。行数に比例 | 20行ほど |
| ADODB.Stream | Charsetで指定("UTF-8"、"Shift_JIS" など) | 同上(文字列を自分で分割) | 同上 | ファイル全体を一度に読む。分割は行数に比例 | 30行ほど |
| QueryTables | TextFilePlatformで指定 | TextFileTextQualifierで指定 | TextFileColumnDataTypesで列を文字列に指定 | Excel本体が一括で処理 | 15行ほど |
出力3手法とSaveAs
| 手法 | 文字コード | BOM | 引用符の付け方 | コード量 |
|---|---|---|---|---|
| Print # | Windowsの既定の文字コード(日本語環境ではShift_JIS) | なし | 自分で付ける(下の CsvField 関数) | 20行ほど |
| ADODB.Stream(そのまま保存) | UTF-8 | あり | 自分で付ける | 25行ほど |
| ADODB.Stream(先頭3バイトを除いて保存) | UTF-8 | なし | 自分で付ける | 35行ほど |
| Workbook.SaveAs(xlCSV/xlCSVUTF8) | 形式の定数で決まる | 形式による | Excelが付ける | 3行ほど |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「VBAでCSVを読み込み・出力する方法|UTF-8の文字化け対策と手法比較」
どの手法を選ぶかの判断表
| 条件 | 選ぶ手法 |
|---|---|
| Shift_JISで、値にカンマ・改行・引用符が無い | Line Input+Split |
| UTF-8のファイルを読む・書く | ADODB.Stream |
| 商品コード「00123」のような先頭ゼロを残したい | QueryTables(列を文字列に指定) |
| 値の中にカンマや改行がある | QueryTables、またはこの記事の ParseCsvLine 関数 |
| 読みながら行ごとに判定・加工したい | Line Input か ADODB.Stream |
| シートの内容をそのままCSVにしたい | Workbook.SaveAs |
| 受け取る側がBOMなしのUTF-8を求めている | ADODB.Stream(BOMなし) |
この記事で使う商品CSV
次の5行のCSV(items.csv)を使います。先頭ゼロのコード、値の中のカンマ、値の中の引用符、日付に見える文字、値の中の改行を1つずつ入れてあります。
商品コード,商品名,単価,入荷日,備考
00123,ノート A4,180,2026/10/01,
00456,"ペン,黒",110,2026/10/02,10本入り
07890,マグカップ,680,2026/10/03,"ロゴ ""R"" 入り"
01-02,付箋,220,2026/10/04,"1行目
2行目"
CSVでは、値にカンマ・改行・引用符が入るときは全体を二重引用符で囲み、値の中の引用符は2つ重ねて書きます。
文字コードとBOMをバイト列で見る
「商品コード,商品名」と「00123,ノート」の2行(改行はCRLF)を、3通りの文字コードで保存したときの大きさと先頭のバイト列です(Pythonで確認)。
| 文字コード | 大きさ | 先頭のバイト列(16進) |
|---|---|---|
| Shift_JIS(CP932) | 33バイト | 8f a4 95 69 83 52 … |
| UTF-8(BOMなし) | 44バイト | e5 95 86 e5 93 81 … |
| UTF-8(BOMあり) | 47バイト | ef bb bf e5 95 86 … |
BOMは、UTF-8のファイルの先頭に付く3バイト(EF BB BF)の目印です。 中身は同じでも、BOMの有無で3バイトの差が出ます。
共通で使う関数(標準モジュールに貼る)
読み込みと出力の両方で使う関数を先にまとめます。標準モジュールの先頭に貼り付けてください。ADODB.Stream は CreateObject で作るので、参照設定は要りません。そのかわり、ADOの定数は自分で定義します(値は ADO リファレンスの各列挙型のページのもの)。
Option Explicit
' ADO の定数(参照設定をしない場合)
Private Const adTypeBinary As Long = 1
Private Const adTypeText As Long = 2
Private Const adReadAll As Long = -1
Private Const adWriteLine As Long = 1
Private Const adSaveCreateOverWrite As Long = 2
Private Const adCRLF As Long = -1
' 1レコード分の文字列を、引用符を考慮して列に分ける
Function ParseCsvLine(ByVal s As String) As Variant
Dim result() As String, n As Long, i As Long
Dim ch As String, buf As String, inQ As Boolean
ReDim result(0 To 0)
i = 1
Do While i <= Len(s)
ch = Mid$(s, i, 1)
If inQ Then
If ch = """" Then
If Mid$(s, i + 1, 1) = """" Then
buf = buf & """" ' "" は引用符1文字
i = i + 1
Else
inQ = False ' 引用符の終わり
End If
Else
buf = buf & ch ' カンマも改行もそのまま値
End If
ElseIf ch = """" Then
inQ = True
ElseIf ch = "," Then
result(n) = buf
buf = ""
n = n + 1
ReDim Preserve result(0 To n)
Else
buf = buf & ch
End If
i = i + 1
Loop
result(n) = buf
ParseCsvLine = result
End Function
' 引用符が閉じていない(=値の途中で改行している)かを調べる
Function QuoteIsOpen(ByVal s As String) As Boolean
QuoteIsOpen = ((Len(s) - Len(Replace(s, """", ""))) Mod 2 = 1)
End Function
' 値をCSVの1項目に直す(必要なときだけ引用符で囲む)
Function CsvField(ByVal v As Variant) As String
Dim s As String
s = CStr(v)
If InStr(s, ",") > 0 Or InStr(s, """") > 0 _
Or InStr(s, vbCr) > 0 Or InStr(s, vbLf) > 0 Then
s = """" & Replace(s, """", """""") & """"
End If
CsvField = s
End Function
' シートの1行をCSVの1行に直す
Function RowToCsv(ByVal ws As Worksheet, ByVal r As Long, ByVal lastCol As Long) As String
Dim c As Long, v As Variant, s As String
For c = 1 To lastCol
v = ws.Cells(r, c).Value
If VarType(v) = vbDate Then v = Format$(v, "yyyy/mm/dd")
If c > 1 Then s = s & ","
s = s & CsvField(v)
Next
RowToCsv = s
End Function
ParseCsvLine・QuoteIsOpen・CsvField の3つは、LibreOffice Basic のVBA互換モードで商品CSVを使って動かしました。結果は次の「読み込み3」と「出力1」に載せています。
CSVを読み込む5つの方法
読み込み1:Workbooks.Open でそのまま開く
Sub ReadByOpen()
Dim wb As Workbook
Set wb = Workbooks.Open(Filename:="C:\work\items.csv", ReadOnly:=True)
wb.Worksheets(1).UsedRange.Copy ThisWorkbook.Worksheets("取込").Range("A1")
wb.Close SaveChanges:=False
End Sub
ExcelでCSVをダブルクリックして開くのと同じ動きです。コードは最短ですが、列ごとの型を指定する引数がありません。Microsoft サポートの「Keeping leading zeros and large numbers」のページには、Excelは先頭のゼロを自動的に取り除き、大きな数値を指数表記に変えると書かれています。商品コードの「00123」を残したい表には、次の OpenText か QueryTables が向いています。
リファレンスでは、Open の Origin 引数は xlMacintosh・xlWindows・xlMSDOS のいずれかで、テキストファイルの出どころを示す引数と説明されています。
読み込み2:Workbooks.OpenText で列の型を指定する
Sub ReadByOpenText()
Workbooks.OpenText Filename:="C:\work\items.txt", _
Origin:=65001, _
StartRow:=1, _
DataType:=xlDelimited, _
TextQualifier:=xlTextQualifierDoubleQuote, _
Comma:=True, _
FieldInfo:=Array(Array(1, xlTextFormat), Array(2, xlGeneralFormat), _
Array(3, xlGeneralFormat), Array(4, xlYMDFormat), _
Array(5, xlTextFormat))
ActiveWorkbook.Worksheets(1).UsedRange.Copy ThisWorkbook.Worksheets("取込").Range("A1")
ActiveWorkbook.Close SaveChanges:=False
End Sub
引数の意味は次のとおりです(Workbooks.OpenText メソッドのリファレンス)。
| 引数 | 指定した値 | 意味 |
|---|---|---|
| Origin | 65001 | ファイルの出どころ。XlPlatform の定数のほか、コードページ番号の整数も指定できる。65001 は UTF-8、932 は Shift_JIS |
| DataType | xlDelimited | 区切り文字で分ける |
| TextQualifier | xlTextQualifierDoubleQuote | 二重引用符で囲まれた部分を1つの値として扱う |
| Comma | True | カンマを区切り文字にする(既定は False) |
| FieldInfo | Array(Array(列番号, 型), …) | 列ごとの型。指定しなかった列は「標準」で読まれる |
FieldInfo に指定できる型(XlColumnDataType)の主なものです。
| 定数 | 値 | 意味 |
|---|---|---|
| xlGeneralFormat | 1 | 標準 |
| xlTextFormat | 2 | 文字列(先頭ゼロや「01-02」をそのまま残す) |
| xlMDYFormat | 3 | 月日年の日付 |
| xlDMYFormat | 4 | 日月年の日付 |
| xlYMDFormat | 5 | 年月日の日付 |
| xlSkipColumn | 9 | その列を取り込まない |
この例ではファイル名を items.txt にしています。Microsoft のリファレンスの使用例も拡張子 .txt のファイルを渡す形です。拡張子が .csv のまま列の型を指定して取り込みたい場合は、「読み込み5」の QueryTables を使うと、拡張子に関係なく同じ書き方で指定できます。
読み込み3:Line Input と Split で1行ずつ読む
Shift_JISのCSVを、1行ずつ読んでカンマで分ける方法です。まず Split だけを使った最小の形で、商品CSVの各行が何列に分かれるかを調べます。
Function CountColumns() As String
Dim out As String, f As Integer, s As String, a As Variant
f = FreeFile
Open "C:\work\items_sjis.csv" For Input As #f
Do While Not EOF(f)
Line Input #f, s
a = Split(s, ",")
out = out & (UBound(a) + 1) & "列 | " & s & vbLf
Loop
Close #f
CountColumns = out
End Function
LibreOffice Basic で動かした結果です。
5列 | 商品コード,商品名,単価,入荷日,備考
5列 | 00123,ノート A4,180,2026/10/01,
6列 | 00456,"ペン,黒",110,2026/10/02,10本入り
5列 | 07890,マグカップ,680,2026/10/03,"ロゴ ""R"" 入り"
5列 | 01-02,付箋,220,2026/10/04,"1行目
1列 | 2行目"
「ペン,黒」の行が6列に分かれ、値の中に改行がある行は2行に割れました。 Split は引用符を見ないので、値にカンマや改行が入るCSVでは列がずれます。引用符も「"ペン」のように値に残ります。
共通の関数 ParseCsvLine と QuoteIsOpen を使うと、次のように直せます。
Sub ReadByLineInput()
Dim ws As Worksheet, f As Integer
Dim rec As String, more As String
Dim fields As Variant, r As Long, c As Long
Set ws = ThisWorkbook.Worksheets("取込")
ws.Cells.Clear
ws.Cells.NumberFormat = "@" ' 全セルを文字列にして先頭ゼロを残す
f = FreeFile
Open "C:\work\items_sjis.csv" For Input As #f
Do While Not EOF(f)
Line Input #f, rec
Do While QuoteIsOpen(rec) And Not EOF(f)
Line Input #f, more
rec = rec & vbLf & more ' 値の途中の改行なので次の行をつなぐ
Loop
fields = ParseCsvLine(rec) ' 単純な表なら Split(rec, ",") でもよい
r = r + 1
For c = 0 To UBound(fields)
ws.Cells(r, c + 1).Value = fields(c)
Next
Loop
Close #f
End Sub
同じ読み取りと分割の部分を LibreOffice Basic で動かし、各レコードの列を角かっこで囲んで表示した結果です(値の中の改行は <LF> と表示)。
5列 [商品コード] [商品名] [単価] [入荷日] [備考]
5列 [00123] [ノート A4] [180] [2026/10/01] []
5列 [00456] [ペン,黒] [110] [2026/10/02] [10本入り]
5列 [07890] [マグカップ] [680] [2026/10/03] [ロゴ "R" 入り]
5列 [01-02] [付箋] [220] [2026/10/04] [1行目<LF>2行目]
すべての行が5列になり、引用符も外れました。
Line Input # ステートメントのリファレンスには、キャリッジリターン(Chr(13))か、キャリッジリターンとラインフィードの組(Chr(13)+Chr(10))までを1行として読むと書かれています。改行がLFだけのファイルは行の区切りとして扱われないため、次の ADODB.Stream で読み、テキストをLFで分ける方法が合います。
読み込み4:ADODB.Stream でUTF-8を読む
UTF-8のCSVは、ADODB.Stream で Charset を指定して読みます。ファイル全体を文字列で受け取り、レコードに分けてからシートに書きます。
' UTF-8のテキストファイルを丸ごと読む
Function ReadTextUtf8(ByVal path As String) As String
Dim st As Object, s As String
Set st = CreateObject("ADODB.Stream")
st.Type = adTypeText
st.Charset = "UTF-8"
st.Open
st.LoadFromFile path
s = st.ReadText(adReadAll)
st.Close
If Left$(s, 1) = ChrW$(&HFEFF) Then s = Mid$(s, 2) ' 先頭にBOMの文字が残っていたら外す
ReadTextUtf8 = s
End Function
' テキスト全体を、引用符内の改行を保ったままレコードに分ける
Function SplitCsvRecords(ByVal text As String) As Variant
Dim lines As Variant, recs() As String
Dim n As Long, i As Long, rec As String
text = Replace(Replace(text, vbCrLf, vbLf), vbCr, vbLf)
If text = "" Then
SplitCsvRecords = Array()
Exit Function
End If
lines = Split(text, vbLf)
ReDim recs(0 To UBound(lines))
i = 0
Do While i <= UBound(lines)
rec = lines(i)
Do While QuoteIsOpen(rec) And i < UBound(lines)
i = i + 1
rec = rec & vbLf & lines(i) ' 値の途中の改行なので次の行をつなぐ
Loop
If Not (i = UBound(lines) And rec = "") Then ' 末尾の空行は数えない
recs(n) = rec
n = n + 1
End If
i = i + 1
Loop
If n = 0 Then
SplitCsvRecords = Array()
Else
ReDim Preserve recs(0 To n - 1)
SplitCsvRecords = recs
End If
End Function
Sub ReadByAdoStream()
Dim ws As Worksheet, recs As Variant, fields As Variant
Dim r As Long, c As Long
Set ws = ThisWorkbook.Worksheets("取込")
ws.Cells.Clear
ws.Cells.NumberFormat = "@"
recs = SplitCsvRecords(ReadTextUtf8("C:\work\items.csv"))
For r = 0 To UBound(recs)
fields = ParseCsvLine(recs(r))
For c = 0 To UBound(fields)
ws.Cells(r + 1, c + 1).Value = fields(c)
Next
Next
End Sub
SplitCsvRecords は改行を LF にそろえてから分けるので、CRLF・LF・CR のどれでも同じ結果になります。商品CSVの内容を文字列で渡して LibreOffice Basic で動かすと、レコード数は5、各レコードは「読み込み3」と同じ5列でした。LFだけで区切った2行の文字列も2レコードに分かれました。
Stream オブジェクトで使ったプロパティとメソッドです(ADO リファレンス)。
| 名前 | 役割 | 補足 |
|---|---|---|
| Type | テキスト(adTypeText=2)かバイナリ(adTypeBinary=1)か | 既定はテキスト。変更できるのは位置が先頭(Position=0)のときだけ |
| Charset | テキストの文字コード | 既定は Unicode。開いた後に変える場合は位置が先頭のときだけ |
| LoadFromFile | ファイルの内容を読み込む | Open の後に呼ぶ。読み込み後の位置は先頭 |
| ReadText | 文字を読む | adReadAll(-1)で全部、adReadLine(-2)で1行 |
| LineSeparator | 行の区切り | adCRLF(-1、既定)、adLF(10)、adCR(13) |
Shift_JISのファイルを同じコードで読むときは、Charset を "Shift_JIS" に変えます。
読み込み5:QueryTables で列の型を指定して取り込む
「データの取り込み」と同じ仕組みを使う方法です。文字コード・引用符・列の型をプロパティで指定できます。
Sub ReadByQueryTable()
Dim ws As Worksheet, qt As QueryTable
Set ws = ThisWorkbook.Worksheets("取込")
ws.Cells.Clear
Set qt = ws.QueryTables.Add( _
Connection:="TEXT;C:\work\items.csv", _
Destination:=ws.Range("A1"))
With qt
.TextFilePlatform = 65001 ' UTF-8。Shift_JISなら 932
.TextFileParseType = xlDelimited
.TextFileCommaDelimiter = True
.TextFileTextQualifier = xlTextQualifierDoubleQuote
.TextFileColumnDataTypes = Array(xlTextFormat, xlGeneralFormat, _
xlGeneralFormat, xlYMDFormat, xlTextFormat)
.Refresh BackgroundQuery:=False
.Delete ' 接続の定義だけを消す
End With
End Sub
| プロパティ・メソッド | 役割(リファレンスの説明) |
|---|---|
| QueryTables.Add | Connection に「TEXT;」に続けてファイルのパスを書くと、テキストファイルを取り込み元にできる |
| TextFilePlatform | 取り込むテキストファイルの出どころ。取り込みに使うコードページがこれで決まる |
| TextFileParseType | xlDelimited(区切り文字)か xlFixedWidth(固定幅) |
| TextFileCommaDelimiter | True でカンマ区切り。既定は False |
| TextFileTextQualifier | 文字列を囲む記号。xlTextQualifierDoubleQuote は二重引用符 |
| TextFileColumnDataTypes | 列の型を左から順に並べた配列。既定は各列とも「標準」 |
| Refresh | 取り込みを実行する。成功すると True を返す |
TextFilePlatform の型は、リファレンスでは XlPlatform と書かれています。上のコードは、OpenText の Origin 引数と同じ考え方でコードページ番号(65001)を渡しています。
1列目と5列目を xlTextFormat にしているので、商品コードの先頭ゼロと「01-02」が文字列のまま入ります。4列目は年月日の日付として読みます。
CSVを出力する3つの方法
出力1:Print # でShift_JISのCSVを書く
Sub WriteByPrint()
Dim ws As Worksheet, f As Integer
Dim r As Long, lastRow As Long, lastCol As Long
Set ws = ThisWorkbook.Worksheets("商品")
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
f = FreeFile
Open "C:\work\out_sjis.csv" For Output As #f
For r = 1 To lastRow
Print #f, RowToCsv(ws, r, lastCol)
Next
Close #f
End Sub
Print # は、行の最後に改行を付けて書き出します。値をCSVの項目に直す CsvField の動きを、LibreOffice Basic で確かめました。6つの値(00456、ペン,黒、110、ロゴ "R" 入り、改行を含む値、空文字)をつないだ結果です。
00456,"ペン,黒",110,"ロゴ ""R"" 入り","1行目
2行目",
カンマ・引用符・改行を含む値だけが引用符で囲まれ、値の中の引用符は2つに増えています。
よく似た Write # ステートメントは、文字列を必ず引用符で囲み、日付を #1994-07-13# の形、真偽値を #TRUE# の形で書くと、リファレンスに説明があります。Input # で読み戻すための形式なので、ほかのシステムに渡すCSVには Print # を使います。
出力2:ADODB.Stream でUTF-8(BOMあり)のCSVを書く
' シートの内容を Stream に書き込む
Sub WriteSheetToStream(ByVal st As Object, ByVal ws As Worksheet)
Dim r As Long, lastRow As Long, lastCol As Long
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
For r = 1 To lastRow
st.WriteText RowToCsv(ws, r, lastCol), adWriteLine
Next
End Sub
Sub WriteUtf8WithBom()
Dim st As Object
Set st = CreateObject("ADODB.Stream")
st.Type = adTypeText
st.Charset = "UTF-8"
st.LineSeparator = adCRLF
st.Open
WriteSheetToStream st, ThisWorkbook.Worksheets("商品")
st.SaveToFile "C:\work\out_utf8_bom.csv", adSaveCreateOverWrite
st.Close
End Sub
WriteText の第2引数に adWriteLine(1)を渡すと、LineSeparator で指定した改行が行末に付きます。SaveToFile の adSaveCreateOverWrite(2)は、同じ名前のファイルがあれば上書きする指定です。既定の adSaveCreateNotExist(1)は、ファイルが無いときだけ新しく作ります。
ADODB.Stream は、Charset を "UTF-8" にして保存すると、ファイルの先頭にBOM(EF BB BF)を付けます。Excelで開くCSVにはこの形が合います。
出力3:ADODB.Stream でUTF-8(BOMなし)のCSVを書く
BOMなしのUTF-8を求めるシステムに渡すときは、書き終えた Stream をバイナリに切り替え、先頭3バイトを除いた部分を別の Stream にコピーして保存します。
Sub WriteUtf8NoBom()
Dim st As Object, bin As Object, head As Variant
Set st = CreateObject("ADODB.Stream")
st.Type = adTypeText
st.Charset = "UTF-8"
st.LineSeparator = adCRLF
st.Open
WriteSheetToStream st, ThisWorkbook.Worksheets("商品")
st.Position = 0
st.Type = adTypeBinary ' 位置が先頭のときだけ切り替えられる
head = st.Read(3) ' 先頭3バイトを読む
If Not (head(0) = &HEF And head(1) = &HBB And head(2) = &HBF) Then
st.Position = 0 ' BOMでなければ先頭からコピーする
End If
Set bin = CreateObject("ADODB.Stream")
bin.Type = adTypeBinary
bin.Open
st.CopyTo bin ' 現在の位置から最後までをコピー
bin.SaveToFile "C:\work\out_utf8_nobom.csv", adSaveCreateOverWrite
bin.Close
st.Close
End Sub
先頭3バイトが EF BB BF かどうかを調べてから読み飛ばすので、BOMが付いていない場合でもデータの先頭が欠けません。CopyTo は、Position で示される現在の位置から終わりまでをコピーするメソッドです(第2引数の既定値 -1)。
Workbook.SaveAs の xlCSV と xlCSVUTF8 の違い
シートの見た目どおりにCSVへ保存するなら、SaveAs が最短です。
Sub SaveSheetAsCsv()
ThisWorkbook.Worksheets("商品").Copy ' シートを新しいブックにコピー
With ActiveWorkbook
.SaveAs Filename:="C:\work\out_saveas.csv", FileFormat:=xlCSVUTF8, Local:=True
.Close SaveChanges:=False
End With
End Sub
XlFileFormat 列挙型のリファレンスに載っているCSVとテキストの形式です。
| 定数 | 値 | リファレンスの説明 | 拡張子 |
|---|---|---|---|
| xlCSV | 6 | CSV | .csv |
| xlCSVUTF8 | 62 | UTF8 CSV | .csv |
| xlCSVWindows | 23 | Windows CSV | .csv |
| xlCSVMSDOS | 24 | MSDOS CSV | .csv |
| xlCSVMac | 22 | Macintosh CSV | .csv |
| xlUnicodeText | 42 | Unicode Text | .txt |
| xlCurrentPlatformText | -4158 | Current Platform Text | .txt |
xlCSV は Windows の既定の文字コード(日本語環境では Shift_JIS)、xlCSVUTF8 は UTF-8 で保存する形式です。Shift_JIS で表せない文字が入る表は xlCSVUTF8 を選びます。
SaveAs の Local 引数は、True にすると Excel の言語(コントロールパネルの設定を含む)に合わせて保存し、False(既定)では VBA の言語(通常は米国英語)に合わせて保存する、とリファレンスに書かれています。日付の列がある表は、Local:=True を付けておくと画面と同じ並びで保存しやすくなります。
SaveAs はセルの値をExcelがCSVに直すので、引用符の付け方や日付の書式を細かく決めたいときは、出力1〜3のように自分で1行ずつ組み立てます。
症状別:原因と直し方
| 症状 | 原因 | 直し方 |
|---|---|---|
| 文字化けする(「繝」「蜩」などが並ぶ) | UTF-8のファイルを Shift_JIS として読んでいる | ADODB.Stream で Charset="UTF-8"、または Origin/TextFilePlatform に 65001 |
| 文字化けする(「�」が並ぶ) | Shift_JISのファイルを UTF-8 として読んでいる | Charset="Shift_JIS"、または 932 を指定 |
| 先頭の見出しだけ一致しない、先頭に見えない文字が入る | BOM(EF BB BF)が1文字目に残っている | 読む側で先頭の ChrW(&HFEFF) を外す。書く側はBOMなしで保存する |
| 絵文字などが「?」になる | Shift_JIS に無い文字を Shift_JIS で書いている | UTF-8で出力する(出力2・3、xlCSVUTF8) |
| 先頭ゼロが消える(00123 → 123) | Excelが数値として読んでいる | 列を xlTextFormat で取り込む。自分で書く場合は書式を "@" にしてから値を入れる |
| 日付に変わる(01-02 → 1月2日) | Excelが日付として読んでいる | 同上(列を文字列として取り込む) |
| 桁の多い番号が指数表記になる | 大きな数値として読まれている(有効桁は15桁まで) | 同上(列を文字列として取り込む) |
| 列がずれる | 値の中のカンマを Split が区切りとして扱っている | ParseCsvLine で分ける。または QueryTables/OpenText に任せる |
| 1件が2行に割れる | 値の中の改行で行が切れている | QuoteIsOpen で引用符が閉じるまで次の行をつなぐ |
| ファイル全体が1行で読まれる | 改行がLFだけで、Line Input が行末として扱わない | ADODB.Stream で読み、LFで分ける |
| 空行でエラーになる | Split("", ",") の結果は要素数0の配列 | UBound が -1 のときは読み飛ばす。ParseCsvLine は空文字でも要素1つを返す |
文字化けの見え方は、Pythonで同じバイト列を別の文字コードとして読んで確かめたものです。「商品コード」をUTF-8で保存して Shift_JIS として読むと「蝠�蜩√さ繝シ繝�」、Shift_JIS で保存して UTF-8 として読むと「���i�R�[�h」になりました。
確かめて分かったこと
- Split だけで読むと、「"ペン,黒"」の行は6列になった。 ParseCsvLine に替えると5列で、引用符も外れました。
- 値の中に改行があるレコードは、1行ずつ読むと2行に割れる。 引用符の数が奇数のあいだ次の行をつなぐ処理(QuoteIsOpen)で、1レコードに戻せました。
- Split("", ",") は要素数0の配列を返し、UBound は -1 になった。 空行があるファイルで
For c = 0 To UBound(a)は何もせずに抜けますが、a(0)を直接読むコードは止まります。 - BOMの有無で、同じ内容のファイルが44バイトと47バイトになった。 見た目が同じでも、先頭3バイトの差で見出しの比較が合わなくなります。
- Line Input の行末は CR か CRLF。 リファレンスの記載です。LFだけのファイルは、テキスト全体を読んでからLFで分けるほうが確実です。
- Write # は日付を #年-月-日# の形で書く。 ほかのシステムに渡すCSVは Print # で組み立てます。
よくある質問
Q. VBAでUTF-8のCSVを読み込むと文字化けするのはなぜですか? A. Shift_JISとして読まれているためです。ADODB.Stream の Charset を "UTF-8" にするか、OpenText の Origin に 65001 を指定します。
Q. Line Input と ADODB.Stream の違いは何ですか? A. Line Input は Windows の既定の文字コードで1行ずつ読み、ADODB.Stream は文字コードを指定してファイル全体を読めます。Shift_JISで単純な表なら Line Input、UTF-8なら ADODB.Stream を選びます。
Q. CSVを読み込むと先頭の0が消えるときは? A. その列を文字列として取り込みます。QueryTables の TextFileColumnDataTypes か OpenText の FieldInfo に xlTextFormat を指定します。自分でセルに書く場合は、先に書式を "@" にします。
Q. BOMありとBOMなしはどちらで出力すればよいですか? A. 受け取る側に合わせます。Excelで開くファイルはBOMあり、ほかのシステムに取り込むファイルは仕様を確かめて、指定が無ければBOMなしを先に試すと進めやすくなります。
Q. xlCSV と xlCSVUTF8 の違いは何ですか? A. 保存する文字コードの違いです。xlCSV(6)は Windows の既定の文字コード、xlCSVUTF8(62)は UTF-8 で保存します。
まとめ
- Shift_JISで単純な表は Line Input と Split、UTF-8は ADODB.Stream、列の型を指定するなら QueryTables か OpenText。
- 値にカンマ・改行・引用符が入るCSVは、Split だけでは列がずれる。 引用符を考慮する関数か、Excelの取り込み機能に任せる。
- 先頭ゼロと「01-02」は、列を文字列(xlTextFormat、書式 "@")にして守る。
- UTF-8のコードページ番号は 65001、Shift_JIS は 932。
- BOMは先頭3バイト(EF BB BF)。 ADODB.Stream はそのまま保存するとBOMあり、先頭3バイトを除いてコピーするとBOMなし。
- SaveAs の xlCSV は既定の文字コード、xlCSVUTF8 は UTF-8。
- 出力は CsvField のように「必要なときだけ引用符で囲み、引用符は2つ重ねる」。
参考・出典
- Microsoft Learn「Workbooks.Open method (Excel)」https://learn.microsoft.com/en-us/office/vba/api/excel.workbooks.open
- Microsoft Learn「Workbooks.OpenText method (Excel)」https://learn.microsoft.com/en-us/office/vba/api/excel.workbooks.opentext
- Microsoft Learn「QueryTables.Add method (Excel)」https://learn.microsoft.com/en-us/office/vba/api/excel.querytables.add
- Microsoft Learn「QueryTable.TextFilePlatform property (Excel)」https://learn.microsoft.com/en-us/office/vba/api/excel.querytable.textfileplatform
- Microsoft Learn「QueryTable.TextFileColumnDataTypes property (Excel)」https://learn.microsoft.com/en-us/office/vba/api/excel.querytable.textfilecolumndatatypes
- Microsoft Learn「XlColumnDataType enumeration (Excel)」https://learn.microsoft.com/en-us/office/vba/api/excel.xlcolumndatatype
- Microsoft Learn「XlFileFormat enumeration (Excel)」https://learn.microsoft.com/en-us/office/vba/api/excel.xlfileformat
- Microsoft Learn「Workbook.SaveAs method (Excel)」https://learn.microsoft.com/en-us/office/vba/api/excel.workbook.saveas
- Microsoft Learn「Line Input # statement」https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/line-inputstatement
- Microsoft Learn「Print # statement」https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/printstatement
- Microsoft Learn「Write # statement」https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/writestatement
- Microsoft Learn「Stream Object (ADO)」https://learn.microsoft.com/en-us/sql/ado/reference/ado-api/stream-object-ado
- Microsoft Learn「Charset Property (ADO)」https://learn.microsoft.com/en-us/sql/ado/reference/ado-api/charset-property-ado
- Microsoft Learn「Code Page Identifiers」https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers
- Microsoft サポート「Keeping leading zeros and large numbers」https://support.microsoft.com/en-us/office/keeping-leading-zeros-and-large-numbers-1bf7b935-36e1-4985-842f-5dfa51f85fe7
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「VBAでCSVを読み込み・出力する方法|UTF-8の文字化け対策と手法比較」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。