如何在Go脚本中读取CSV文件前清理双引号以解决解析错误
解决Go encoding/csv解析非标准CSV(含未转义双引号)的问题
我来帮你搞定这个问题——要在Go脚本里直接移除CSV中的所有双引号,替代终端的s/"//g命令,最优雅高效的方式是给原始文件Reader做一个流式包装,在数据传给csv.Reader之前过滤掉所有双引号。这种流式处理适合大型CSV文件,不会占用过多内存,同时完美解决解析错误和字段不匹配的问题。
完整实现代码
package main import ( "io" "log" "os" "encoding/csv" ) // quoteStripper 实现io.Reader接口,用于过滤掉所有双引号字符 type quoteStripper struct { r io.Reader } func (qs *quoteStripper) Read(p []byte) (n int, err error) { // 从原始Reader读取数据 n, err = qs.r.Read(p) if n > 0 { // 遍历字节数组,移除所有双引号 j := 0 for i := 0; i < n; i++ { if p[i] != '"' { p[j] = p[i] j++ } } // 返回过滤后的有效字节数 n = j } return n, err } func processCSV(filepath string) { file, err := os.Open(filepath) if err != nil { log.Fatal(err) } defer file.Close() // 别忘了关闭文件,避免资源泄漏 // 用quoteStripper包装原始文件Reader,自动过滤双引号 strippedReader := "eStripper{r: file} parser := csv.NewReader(strippedReader) parser.Comma = '|' // 现在不需要设置LazyQuotes了,因为双引号已经被完全移除 // parser.LazyQuotes = true // 跳过表头行 _, err = parser.Read() if err != nil { log.Fatal(err) } for { record, err := parser.Read() if err == io.EOF { break } if err != nil { log.Fatal(err) } // 在这里处理你的record log.Printf("处理完成的记录: %v", record) } } func main() { processCSV("foo.csv") }
关键细节解释
流式过滤双引号:
quoteStripper实现了io.Reader接口,它会在每次读取数据时,自动过滤掉所有"字符。这种方式是流式处理,不需要把整个大文件加载到内存,非常适合处理大型CSV。- 相比逐行读取替换(比如用
bufio.Scanner),流式处理的内存效率更高,性能也更好。
为什么不用
LazyQuotes:- 你之前设置
LazyQuotes = true后出现字段不匹配,是因为当CSV中有未转义的双引号时,csv.Reader会把引号后面的内容错误地识别为新字段的开始,导致字段数量和预期不符。而完全移除双引号后,CSV就变成了标准的|分隔格式,不会有解析歧义。
- 你之前设置
补充的最佳实践:
- 原代码中没有关闭文件,我加上了
defer file.Close(),这是Go处理文件的基本好习惯,避免资源泄漏。
- 原代码中没有关闭文件,我加上了
备选方案(逐行处理)
如果你的CSV文件每行长度不大,也可以用bufio.Scanner逐行读取并替换双引号,虽然效率略低,但代码更直观:
func processCSV(filepath string) { file, err := os.Open(filepath) if err != nil { log.Fatal(err) } defer file.Close() scanner := bufio.NewScanner(file) // 针对大型文件,可增大缓冲区 buf := make([]byte, 1024*1024) scanner.Buffer(buf, bufio.MaxScanTokenSize) parser := csv.NewReader(strings.NewReader("")) parser.Comma = '|' // 跳过表头 if scanner.Scan() { cleanLine := strings.ReplaceAll(scanner.Text(), "\"", "") parser = csv.NewReader(strings.NewReader(cleanLine)) _, err = parser.Read() if err != nil { log.Fatal(err) } } for scanner.Scan() { cleanLine := strings.ReplaceAll(scanner.Text(), "\"", "") parser = csv.NewReader(strings.NewReader(cleanLine)) record, err := parser.Read() if err != nil { log.Fatal(err) } // 处理record log.Printf("处理完成的记录: %v", record) } if err := scanner.Err(); err != nil { log.Fatal(err) } }
内容的提问来源于stack exchange,提问作者DevonDahon
相关产品推荐
相关产品推荐

