我可以使用 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 格式的字符串而不将其写入磁盘吗?的主要内容,如果未能解决你的问题,请参考以下文章