我可以使用 Text::CSV_XS 解析 csv 格式的字符串而不将其写入磁盘吗?

Posted

技术标签:

【中文标题】我可以使用 Text::CSV_XS 解析 csv 格式的字符串而不将其写入磁盘吗?【英文标题】:Can I use Text::CSV_XS to parse a csv-format string without writing it to disk? 【发布时间】:2020-07-04 05:35:56 【问题描述】:

我从供应商处获得了一个“csv 文件”(使用他们的 API),但他们所做的只是将整个内容都注入到他们的响应中。这不会是一个大问题,当然,除了一些讨厌的人输入数据并放入“特征”,如换行符。我现在正在做的是为原始数据创建一个文件,然后重新打开它来读取数据:

open RAW, ">", "$rawfile" or die "ERROR: Could not open $rawfile for write: $! \n";
print RAW $response->content;
close RAW;

my $csv = Text::CSV_XS->new( binary=>1,always_quote=>1,eol=>$/ );
open my $fh, "<", "$rawfile" or die "ERROR: Could not open $rawfile for read: $! \n";

while ( $line = $csv->getline ($fh) )  ...

不知怎的,这似乎……不雅。看来我应该能够从 $response->content (多行字符串)中读取数据,就好像它是一个文件一样。但我对如何做到这一点完全空白。 一个指针将不胜感激。 谢谢, 保罗

【问题讨论】:

【参考方案1】:

你可以使用字符串文件句柄:

my $data = $response->content;
open my $fh, "<", \$data or croak "unable to open string filehandle : $!";
my $csv = Text::CSV_XS->new( binary=>1,always_quote=>1,eol=>$/ );
while ( $line = $csv->getline ($fh) )  ... 

【讨论】:

这是我在 Perl 中最喜欢的技巧之一,我在 Effective Perl Programming 中写了很多关于它的内容。将许多事物视为文件句柄意味着您拥有一个更简单且熟悉的界面。反过来也一样;您可以写入文件句柄,但将其显示在字符串中。 是的,很好,我也使用它——只是不要忘记它不是一个合适的文件句柄,所以不要遇到麻烦;例如,请参阅this post。 好的,谢谢!这正是我一直在寻找但没有得到的。我已经记不清我尝试过哪些组合了,但我显然很接近,但语法不太正确。【参考方案2】:

是的,您可以通过函数接口在字符串上使用Text::CSV_XS

use warnings;
use strict;
use feature 'say';

use Text::CSV_XS qw(csv);  # must use _XS version

my $csv = qq(a,line\nand,another);

my $aoa = csv(in => \$csv) 
    or die Text::CSV->error_diag; 

say "@$_" for @aoa;    

请注意,这确实需要Text::CSV_XS(通常Text::CSV 有效,但不适用于此)。

我不知道为什么这在 OO 界面中不可用(或者可能是但没有记录)。


虽然上面直接按要求解析字符串,但也可以通过在获取文件时直接将内容写入文件来减少示例中的“不优雅”方面,大多数库都支持LWP::UserAgent::get 中的:content_file 选项方法。

我还要注意,大多数时候您希望库对内容进行解码,因此LWP::UA 使用decoded_content(请参阅HTTP::Response)。

【讨论】:

【参考方案3】:

我用Mojo::UserAgent 编写了这个示例。对于 CSV 输入,我使用了来自 NYC Open Data 的各种数据集。这也将出现在Mojo Web Clients 的下一次更新中。

我在没有立即发出请求的情况下构建请求,这给了我事务对象$tx。然后我可以替换read 事件,这样我就可以立即将这些行发送到Text::CSV_XS:

#!perl

use v5.10;
use Mojo::UserAgent;

my $ua = Mojo::UserAgent->new;

my $url = ...;
my $tx = $ua->build_tx( GET => $url );

$tx->res->content->unsubscribe('read')->on(read => sub 
    state $csv = do 
        require Text::CSV_XS;
        Text::CSV_XS->new;
        ;
    state $buffer;
    state $reader = do 
        open my $r, '<:encoding(UTF-8)', \$buffer;
        $r;
        ;

    my ($content, $bytes) = @_;
    $buffer .= $bytes;
    while (my $row = $csv->getline($reader) ) 
        say join ':', $row->@[2,4];
        
    );

$tx = $ua->start($tx);

这并不像我希望的那样好,因为所有数据仍显示在缓冲区中。这有点吸引人,但在我在 cmets 中注意到的方式中它很脆弱。我现在懒得让它变得更好,因为当你知道什么时候有足够的数据来处理记录时,它会很快变得毛茸茸。我的特定代码并不像您可以随心所欲地做任何事情的想法那么重要,因为交易者会读取数据并将其传递给内容处理程序:

use v5.10;
use strict;
use warnings;
use feature qw(signatures);
no warnings qw(experimental::signatures);

use Mojo::UserAgent;

my $ua = Mojo::UserAgent->new;

my $url = ...;
my $tx = $ua->build_tx( GET => $url );

$tx->res->content
    ->unsubscribe('read')
    ->on( read => process_bytes_factory() );

$tx = $ua->start($tx);

sub process_bytes_factory 
    return sub ( $content, $bytes ) 
        state $csv = do 
            require Text::CSV_XS;
            Text::CSV_XS->new(  decode_utf8 => 1  );
            ;
        state $buffer = '';
        state $line_no = 0;

        $buffer .= $bytes;
        # fragile if the entire content does not end in a
        # newline (or whatever the line ending is)
        my $last_line_incomplete = $buffer !~ /\n\z/;

        # will not work if the format allows embedded newlines
        my @lines = split /\n/, $buffer;
        $buffer = pop @lines if $last_line_incomplete;

        foreach my $line ( @lines ) 
            my $status = $csv->parse($line);
            my @row = $csv->fields;
            say join ':', $line_no++, @row[2,4];
            
        ;
    

【讨论】:

以上是关于我可以使用 Text::CSV_XS 解析 csv 格式的字符串而不将其写入磁盘吗?的主要内容,如果未能解决你的问题,请参考以下文章

如何从 Perl 快速访问许多大型 CSV 文件中的数据?

Perl 最佳实践(节选) --- 08

解析单个 CSV 字符串?

使用 C# 使用 OleDb 解析 CSV

使用 Scala 解析器组合器解析 CSV 文件

如何解析 CSV 文件,以便可以被 Mahout 分类