Извлечение данных из веб-ресурсов. Формат CSV
В продолжении темы «Извлечение данных и из-ресурсов» поговорим о таком формате данных, как CSV, а также об инструментах и способах обработки.
Название CSV расшифровывается как comma-separated values – т.е. значения, разделенные запятой. На самом деле, разделитесь (delimeter) может быть абсолютно любой.
Пример CSV файла:
Id, Name
1, Ukraine
2, Russia
3, Angola
Базовая реализация CSV парсера достаточно банальная:
namespace CsvParser { using System; class Program { static void Main(string[] args) { string fileName = "C://temp/data.csv"; string separator = ", "; foreach (var line in System.IO.File.ReadAllLines(fileName)) { string[] values = line.Split(new string[] { separator }, StringSplitOptions.RemoveEmptyEntries); foreach (var value in values) { Console.WriteLine(value); } } Console.ReadKey(); } } }
На экране увидим такой результат:
Id
Name
1
Ukraine
2
Russia
3
Angola
Но на самом деле простота решения в данном случае – обманчивая.
Рассмотрим проблемы, с которыми может столкнуться разработчик в процессе парсинга CSV файлов.
Файл может иметь большой размер. Это может привести к тому, что File.ReadAllLines() придется заменить на StreamReader для построчного чтения (так как при использовании File.ReadAllLines() весь файл может не поместиться в памяти):
namespace CsvParser { using System; using System.IO; class Program { static void Main(string[] args) { string fileName = "C://temp/data.csv"; string separator = ", "; using (StreamReader reader = new StreamReader(fileName)) { string line; while ((line = reader.ReadLine()) != null) { string[] values = line.Split(new string[] { separator }, StringSplitOptions.RemoveEmptyEntries); foreach (var value in values) { Console.WriteLine(value); } } } Console.ReadKey(); } } }
На самом деле, такой вариант тоже не дает 100% гарантию, так как отдельные строчки также могут быть большого размера. В таком случае необходимо использовать буфер при чтении файла и читать файл «порциями».
Следующая проблема, которая может возникнуть – сами данные. Например, если у нас запятая выступает в качестве разделителя, а одно (или более) значений содержат запятые, то при разборе количество элементов будет отличаться от необходимого.
Например:
Id, Name
1, Ukraine
2, Russia
3, Angola
4, Gambia, The
В таких случаях принято данные брать в скобки (так делает, например, Microsoft Excel):
4, “Gambia, The”
Таким образом, необходимо обрабатывать также и скобки, если такие будут найдены.
Следующий момент – перенос новой строки. Очень часто вместо «\n» для переноса строки используется «\r\n». Проще всего в коде использовать выражение System.Environment.NewLine.
Очень часто данные, хотелось бы получать не только по индексу, что неудобно (представьте, что у вас 100 полей), а также по имени столбца, например:
values[“Id”] и values[“Name”]
Нужно помнить, что файл не всегда содержит строку с названиями столбцов.
Наконец, если вы обрабатываете большой файл, в котором всего несколько строчек с неправильными данными и вам не хотелось бы останавливать весь процесс ради этих нескольких строк, то, скорее всего, правильным решением будет игнорировать такие строки (реализовать логику по принципу SkipLineOnError = true).
На самом деле есть и другие сложности, связанные с платформами, где выполняются задачи, кодировками и т.д. Более детально можно почитать здесь.
Убедились, что разбор CSV файла – не тривиальная задача? Тогда рассмотрим уже написанные средства для работы с CSV файлами.
Парсить CSV файлы можно с помощью ODBC Microsoft Text Driver :
string path = @"C:\"; using (OdbcConnection conn = new OdbcConnection( "Driver={Microsoft Text Driver (*.txt; *.csv)};Dbq=" + path + ";Extensions=asc,csv,tab,txt")) { using (OdbcCommand cmd = new OdbcCommand("SELECT * FROM verylarge.csv", conn)) { conn.Open(); using (OdbcDataReader dr = cmd.ExecuteReader(CommandBehavior.SequentialAccess)) { while (dr.Read()) { int test1 = dr.GetInt32(0); int test2 = dr.GetInt32(1); int test3 = dr.GetInt32(2); int test4 = dr.GetInt32(3); int test5 = dr.GetInt32(4); } } } }
А также с помощью Microsoft Jet OLE Driver:
string path = @"C:\"; using (OleDbConnection conn = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + path + @";Extended Properties=""Text;HDR=No;FMT=Delimited""")) { using (OleDbCommand cmd = new OleDbCommand("SELECT * FROM verylarge.csv", conn)) { conn.Open(); using (OleDbDataReader dr = cmd.ExecuteReader(CommandBehavior.SequentialAccess)) { while (dr.Read()) { int test1 = dr.GetInt32(0); int test2 = dr.GetInt32(1); int test3 = dr.GetInt32(2); int test4 = dr.GetInt32(3); int test5 = dr.GetInt32(4); } } } }
Это не самые быстрые способы, поэтому использовать их вряд ли стоит.
Регулярные выражения (Regex) Почему то все считают, что если есть задача распарсить какие-то данные, то регулярные выражения – это идеальный инструмент. Боюсь разочаровать, но это не так. Регулярные выражения работают долго, и ими можно покрыть только базовые сценарии.
Класс TextFieldParser в Microsoft.VisualBasic.FileIO. Этот класс находится в сборке Microsoft.VisualBasic.dll, т.е. в C# проектах этого класса нет по умолчанию.
Работает таким образом:
Using MyReader As New Microsoft.VisualBasic.FileIO. TextFieldParser("c:\logs\bigfile") MyReader.TextFieldType = Microsoft.VisualBasic.FileIO.FieldType.Delimited MyReader.Delimiters = New String() {vbTab} Dim currentRow As String() 'Loop through all of the fields in the file. 'If any lines are corrupt, report an error and continue parsing. While Not MyReader.EndOfData Try currentRow = MyReader.ReadFields() ' Include code here to handle the row. Catch ex As Microsoft.VisualBasic.FileIO.MalformedLineException MsgBox("Line " & ex.Message & " is invalid. Skipping") End Try End While End Using
Вроде все просто и понятно. Остается загадкой, почему такой же функционал не был включен по умолчанию в System.IO.
FileHelpers Library
Пусть у нас есть файл в таком формате:
1732,Juan Perez,435.00,11-05-2002
554,Pedro Gomez,12342.30,06-02-2004
112,Ramiro Politti,0.00,01-02-2000
924,Pablo Ramirez,3321.30,24-11-2002
...............
Создаем класс, который будет описывать структуру этих данных:
[DelimitedRecord(",")] public class Customer { public int CustId; public string Name; public decimal Balance; [FieldConverter(ConverterKind.Date, "dd-MM-yyyy")] public DateTime AddedDate; }
Дальше необходимо создать объект FileHelperEngine и открыть файл:
FileHelperEngine engine = new FileHelperEngine(typeof(Customer)); // To Read Use: Customer[] res = engine.ReadFile("FileIn.txt") as Customer[]; // To Write Use: engine.WriteFile("FileOut.txt", res); Дальнейшее использование уже заполненных объектов очень простое: foreach (Customer cust in res) { Console.WriteLine("Customer Info:"); Console.WriteLine(cust.Name + " - " + cust.AddedDate.ToString("dd/MM/yy")); }
Очень удобно, согласитесь. Библиотека распространяется бесплатно с исходными кодами под лицензией LGPL. Скачать можно на сайте http://www.filehelpers.com/.
A Fast CSV Reader
Сайт: http://www.codeproject.com/KB/database/CsvReader.aspx
Библиотека поддерживает все описанные мной выше сценарии, использование библиотеки достаточно простое:
using System.IO; using LumenWorks.Framework.IO.Csv; void ReadCsv() { // open the file "data.csv" which is a CSV file with headers using (CsvReader csv = new CsvReader(new StreamReader("data.csv"), true)) { int fieldCount = csv.FieldCount; string[] headers = csv.GetFieldHeaders(); while (csv.ReadNextRecord()) { for (int i = 0; i < fieldCount; i++) Console.Write(string.Format("{0} = {1};", headers[i], csv[i])); Console.WriteLine(); } } }
В ASP.NET приложениях использование библиотеки может быть таким:
using System.IO; using LumenWorks.Framework.IO.Csv; void ReadCsv() { // open the file "data.csv" which is a CSV file with headers using (CsvReader csv = new CsvReader( new StreamReader("data.csv"), true)) { myDataRepeater.DataSource = csv; myDataRepeater.DataBind(); } }
Библиотека поддерживает события обработки ошибок: DefaultParseErrorAction и ParseError, правила обработки данных, например:
csv.MissingFieldAction = MissingFieldAction.ReplaceByNull;
Эту библиотеку я активно использую в своем проекте и нахожу ее очень удобным инструментом.
Если вам необходимо распарсить CSV файл прямо в базу данных, то необходимо использовать утилиту bcp.
На сегодня все.
Компании из статьи
Microsoft Украина | Украинское подразделение компании Microsoft. |