【问题标题】:How to convert data from a custom format to CSV?如何将数据从自定义格式转换为 CSV?
【发布时间】:2014-08-11 11:50:28
【问题描述】:

我有文件,文件内容如下,我这里只输出了两条记录,但单个文件中有大约 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 文件,如下所示

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

任何指南都会很好地说明您认为如何最好地开始这一点,我提供的示例我认为会给出清晰的想法,但换句话说,我想阅读每条记录的标题一次并放置它们的数据在输出标题下。

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

【问题讨论】:

  • 请避免使用“所有可以使用的语言”进行标记。仅使用实际使用的语言进行标记。由于没有使用任何语言,而是对一种方法的建议,它可能会被关闭。
  • @user2864740 - 我不认为有关算法的问题是题外话,这是什么;他不是在寻求关于使用哪种工具的建议,而是如何开始,这实质上意味着他想要关于使用算法的建议。
  • @syrion 然后不要投票关闭它。对于这个网站,我发现这些问题通常过于宽泛。

标签: csv etl


【解决方案1】:

您正在做的是创建一个提取/转换脚本(ETLET 部分)。我不知道您打算使用哪种语言,但基本上可以使用任何语言。就个人而言,除非这是一个庞大的文件,否则我会推荐 Python,因为它很容易理解,并且使用包含的 csv module 易于编写。

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

  1. 如何分隔记录?
  2. 字段如何分隔?
  3. 是否有任何可选字段?
  4. 如果是,可选字段是否重要,或者是否需要丢弃?

不幸的是,这都是头疼的事情:没有神奇的代码解决方案可以让这更容易。然后,一旦你弄清楚了格式,你就会想要开始编写代码。这本质上是一系列数据转换:

  1. 读取文件。
  2. 将其拆分为记录。
  3. 对于每条记录,将字段转换为适当的数据结构。
  4. 将数据结构序列化为 CSV。

如果您的文件大于内存,这可能会变得更加复杂;例如,您可能希望顺序读取文件并在每次检测到记录分隔符时创建一个 Record 对象,而不是读取然后拆分。如果您的文件甚至更大,您可能希望使用具有更好多线程功能的语言来并行处理转换;但那些比听起来你现在需要去的更先进。

【讨论】:

    【解决方案2】:

    这是一个简单的 PHP 脚本,它将读取包含您的数据的文本文件并将结果写入 csv 文件。如果您在安装了命令行 PHP 的系统上,只需将其保存到某个目录中的文件中,复制旁边的数据文件,将其重命名为“your_data_file.txt”并在命令行上调用“phpwhat_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 在他的回答中指出的那样,事情可能会变得更加复杂。在这种情况下,您可能需要编写一个比这个更复杂的脚本。

    【讨论】:

    • 谢谢@janwschaefer 我正在寻找如何开始,但您提供了解决方案,感谢您的时间和解决方案:D
    • 很抱歉打扰您,但它适用于示例文件,但是当我对具有 33 个字段的原始文件进行更改并运行时,它不起作用它写入带有标题的文件但内容未写入,原始文件的唯一区别是(a:它有 33 列 b:在这 33 个字段之间还有另一行像这样“--------- Container #1 (end) ------ -") 我所做的将 for($i=1;$i
    • 使用正则表达式 (preg_match_all(...)) 从数据文件中解析数据。该表达式当前是静态的,它假定恰好遇到您问题中显示的数据格式。为了使其处理更多的字段,必须增强正则表达式。这可以通过添加缺少的字段以“愚蠢”的方式完成。或者您可以尝试使其更智能,以便它可以处理任意数量的字段。您能否发布一个完整的数据字段示例(可能是三个条目,包含所有分隔符和格式)?
    • 谢谢我已经更新了问题并按照说明添加了字段...感谢您的时间...由于字符限制,我无法在此处添加
    • 容器#1 是否只包含文件中所有条目的一个字段 intID?换句话说,字段的数量和顺序是否始终相同?
    猜你喜欢
    • 2012-06-08
    • 2015-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多