如何将数据从自定义格式转换为CSV?

时间:2014-08-11 11:50:29

标签: csv etl

我有文件内容如下文件,我这里只输出两条记录,但单个文件中有大约1000条记录:

           Record type : GR
            address : 62.5.196
             ID : 1926089329
     time : Sun Aug 10 09:53:47 2014
               Time zone : + 16200 seconds
         address [1] : 61.5.196
            PN ID : 412 1
          ---------- Container #1 (start) -------
          inID : 101
          ---------- Container #1 (end) -------
          timerecorded: Sun Aug 10 09:51:47 2014
          Uplink data volume : 502838
          Downlink data volume : 3133869
          Change condition : Record closed

--------------------------------------------------------------------
    Record type : GR
            address : 61.5.196
             ID : 1926089327
     time : Sun Aug 10 09:53:47 2014
               Time zone : + 16200 seconds
         address [1] : 61.5.196
            PN ID : 412 1
          ---------- Container #1 (start) -------
          intID : 100
          ---------- Container #1 (end) -------
          timerecorded: Sun Aug 10 09:55:47 2014
          Uplink data volume : 502838
          Downlink data volume : 3133869
          Change condition : Record closed
--------------------------------------------------------------------
    Record type : GR
            address : 63.5.196
             ID : 1926089328
     time : Sun Aug 10 09:53:47 2014
              Time zone : + 16200 seconds
         address [1] : 61.5.196
            PN ID : 412 1
          ---------- Container #1 (start) -------
          intID : 100
          ---------- Container #1 (end) -------
          timerecorded: Sun Aug 10 09:55:47 2014
          Uplink data volume : 502838
          Downlink data volume : 3133869
          Change condition : Record closed

我的目标是将其转换为CSV或txt文件,如bellow

Record type| address |ID | time | Time zone| address [1] | PN ID 
GR |61.5.196 |1926089329 |Sun Aug 10 09:53:47 2014 |+ 16200 seconds |61.5.196 |412 1

任何指南都会很好,你认为这是最好的方式来开始这个,我提供的样本我认为会给出清晰的想法,但在文字中我想读取每个记录的标题一次并放置他们的数据在输出标题下。

感谢您的时间和帮助或建议

2 个答案:

答案 0 :(得分:1)

您正在做的是创建Extract / Transform脚本( ETL ET 部分)。我不知道您打算使用哪种语言,但基本上可以使用任何语言。就个人而言,除非这是一个庞大的文件,否则我推荐使用Python,因为使用附带的csv module很容易理解和编写。

首先,您需要彻底了解格式。

  1. 记录如何分开?
  2. 字段如何分开?
  3. 是否有可选的字段?
  4. 如果是,那么可选字段是否重要,或者它们是否需要丢弃?
  5. 不幸的是,这完全是头脑:没有神奇的代码解决方案可以让这更容易。然后,一旦你弄清楚了格式,你就会想开始编写代码。这基本上是一系列数据转换:

    1. 阅读文件。
    2. 将其拆分为记录。
    3. 对于每条记录,将字段转换为适当的数据结构。
    4. 将数据结构序列化为CSV。
    5. 如果你的文件大于内存,这会变得更复杂;例如,您可能希望按顺序读取文件并在每次检测到记录分隔符时创建Record对象,而不是读取然后拆分。如果您的文件甚至更大,您可能希望使用具有更好多线程功能的语言来并行处理转换;但那些比你现在需要的声音更先进。

答案 1 :(得分:-1)

这是一个简单的PHP脚本,它将读取包含您的数据的文本文件并编写带有结果的csv文件。如果您使用的是安装了命令行PHP的系统,只需将其保存到某个目录中的文件中,将其旁边的数据文件重命名为“your_data_file.txt”并在命令行上调用“php whatever_you_named_the_script.php”从该目录。

<?php
$text = file_get_contents("your_data_file.txt");

$matches;
preg_match_all("/Record type[\s\v]*:[\s\v]*(.+?)address[\s\v]*:[\s\v]*(.+?)ID[\s\v]*:[\s\v]*(.+?)time[\s\v]*:[\s\v]*(.+?)Time zone[\s\v]*:[\s\v]*(.+?)address \[1\][\s\v]*:[\s\v]*(.+?)PN ID[\s\v]*:[\s\v]*(.+?)/su", $text, $matches, PREG_SET_ORDER);

$csv_file = fopen("your_csv_file.csv", "w");
if($csv_file) {
    if(fputcsv($csv_file, array("Record type","address","ID","time","Time zone","address [1]","PN ID"), "|") === FALSE) {
        echo "could not write headers to csv file\n";
    }
    foreach($matches as $match) {
        $clean_values = array();
        for($i=1;$i<8;$i++) {
            $clean_values[] = trim($match[$i]);
        }
        if(fputcsv($csv_file, $clean_values, "|") === FALSE) {
            echo "could not write data to csv file\n";
        }
    }
    fclose($csv_file);
} else {
    die("could not open csv file\n");
}

此脚本假定您的数据记录的格式始终与您发布的示例类似,并且所有值始终存在。如果数据文件可能具有这些规则的例外,则可能必须相应地调整脚本。但它应该让你知道如何做到这一点。

<强>更新

修改脚本以处理更新问题中提供的完整格式。正则表达式现在匹配单个数据行(提取它们的值)以及由短划线组成的记录分隔符。循环已经改变了一点,现在逐字段地填充缓冲区数组,直到遇到记录分隔符。

<?php

$text = file_get_contents("your_data_file.txt");

// this will match whole lines
// only if they either start with an alpha-num character
// or are completely made of dashes (record separator)
// it also extracts the values of data lines one by one
$regExp = '/(^\s*[a-zA-Z0-9][^:]*:(.*)$|^-+$)/m';

$matches;
preg_match_all($regExp, $text, $matches, PREG_SET_ORDER);

$csv_file = fopen("your_csv_file.csv", "w");
if($csv_file) {

    // in case the number or order of fields changes, adapt this array as well
    $column_headers = array(
        "Record type",
        "address",
        "ID",
        "time",
        "Time zone",
        "address [1]",
        "PN ID",
        "inID",
        "timerecorded",
        "Uplink data volume",
        "Downlink data volume",
        "Change condition"
    );

    if(fputcsv($csv_file, $column_headers, "|") === FALSE) {
        echo "could not write headers to csv file\n";
    }

    $clean_values = array();
    foreach($matches as $match) {

        // first entry will contain the whole line
        // remove surrounding whitespace
        $whole_line = trim($match[0]);

        if(strpos($whole_line, '-') !== 0) {
            // this match starts with something else than -
            // so it must be a data field, store the extracted value
            $clean_values[] = trim($match[2]);
        } else {
            // this match is a record separator, write csv line and reset buffer
            if(fputcsv($csv_file, $clean_values, "|") === FALSE) {
                echo "could not write data to csv file\n";
            }
            $clean_values = array();
        }
    }
    if(!empty($clean_values)) {
        // there was no record separator at the end of the file
        // write the last entry that is still in the buffer
        if(fputcsv($csv_file, $clean_values, "|") === FALSE) {
            echo "could not write data to csv file\n";
        }
    }

    fclose($csv_file);

} else {
    die("could not open csv file\n");
}

使用正则表达式进行数据提取是一种可能的方法,对于具有清晰结构且没有意外的简单数据格式非常有用。正如syrion在他的回答中指出的那样,事情会变得复杂得多。在这种情况下,您可能需要编写比此更复杂的脚本。