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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?