从我的Ruby脚本中我产生了一个PhantomJS进程。该进程将返回一个JSON字符串给Ruby,Ruby将解析它。
一切正常,但是当PhantomJS脚本返回一个巨大的JSON(超过10000个条目)时,似乎Ruby不知道如何处理它(它没有得到整个字符串,但它被切断了)。
JSON中的条目看起来像这样(但有些条目可以有大约5个属性):
{"id" : 3, "nSteps" : 5, "class" : "class-name", "width" : 300, "height" : 500, "source" : "this is a big string", "nMove" : 10, "id-name" : "this is a big string", "checked" : true, "visible" : false}
这是我现在的代码:
@pid = Process.spawn("phantomjs",
"myparser.js",
:out => pipe_cmd_out, :err => pipe_cmd_out
)
Timeout.timeout(400) do
Process.wait(@pid)
pipe_cmd_out.close
output = pipe_cmd_in.read
return JSON.parse(output)
end
有什么方法可以通过块读取JSON或以某种方式增加管道的缓冲区限制?
编辑:
为了将数据从PhantomJS发送到Ruby,我在PhantomJS脚本的最后有以下内容:
console.log(JSON.stringify(data));
phantom.exit();
如果我从终端启动PhantomJS脚本,我会正确获取JSON。但是,当我在Ruby中执行此操作时,响应会被切断。
console.log
中断时放入的字符串的大小为:132648
编辑:
我想我发现了什么是确切的问题。当我在生成进程时指定:out时,如果返回的JSON很大(132648长度),它将不会让Ruby读取它。所以在做的时候:
reader, writer = IO.pipe
pid = Process.spawn("phantomjs",
"script.js",
:out => writer
)
Timeout.timeout(100) do
Process.wait(pid)
writer.close
output = reader.read
json_output = JSON.parse(output)
end
它无效。
但是如果我让PhantomJS只是写入它的标准标准输出,它将正确输出JSON。所以,做:
reader, writer = IO.pipe
pid = Process.spawn("phantomjs",
"script.js"
)
Timeout.timeout(100) do
Process.wait(pid)
writer.close
output = reader.read
json_output = JSON.parse(output)
end
将正确输出终端中的结果。所以我认为问题在于,对于大JSON来说它不能正确编写,或者Ruby读者不知道如何阅读它。
答案 0 :(得分:1)
我怀疑问题不是Ruby,而是你在阅读时的方式或时间。
可能是PhantomJS在您阅读之前尚未完成发送输出,导致部分响应。尝试将输出路由到文件以确定其大小(以字节为单位)。这将告诉您PhantomJS 是否正在完成其任务并正确关闭JSON,并会告诉您可以在缓冲区中看到多少字节。
...连接Ruby和衍生进程的管道怎么样?我在哪里可以找到缓冲区的限制?
在我的记忆的黑暗角落里挖掘,以找出它是如何工作的......
根据我的记忆,这应该是相当准确的:TCP / IP堆栈将缓冲输入数据,直到其缓冲区已满,然后它将告诉发送方停止。缓冲区清除后,由于脚本已读取缓冲区,因此告知发送方继续发送。因此,即使有多GB GB待处理,也不会一次发送所有数据,除非脚本从缓冲区连续读取以保持清晰。
当脚本执行read
时,read
不会仅获取缓冲区中的内容,它希望看到EOF,并且在TCP / IP会话认为之前不应该看到它它收到发件人和会话/连接关闭的所有内容。 Ruby I / O子系统和TCP / IP子系统读取从发送方接收的数据块中的数据,并将其存储在变量中。换句话说,您的脚本应暂停并等待所有数据传输完毕,然后继续,因为read
是阻止操作。
处理I / O有不同的方法。你正在啜饮数据,这就是我们在步骤中阅读所有内容时所称的数据。这是不可扩展的,因为如果传入的数据大于Ruby的字符串可以存储,那么你就遇到了问题。您可能应该进行增量读取,然后将数据存储在临时文件中,然后使用类似YAJL的内容将其传输到JSON解析器中,但第一步是确定您是否实际获取了完整的JSON字符串。
处理问题的另一种方法是请求较小的数据集,然后在脚本中重新组合它们。就像通过SQL从数据库中请求每条记录一样,这是一个坏主意,因为它不可扩展并且打败了DBM,也许你应该在页面或块中请求你的JSON数据,并且只处理必要的结果。
这可能是......
@pid = Process.spawn("phantomjs",
"myparser.js",
:out => pipe_cmd_out, :err => pipe_cmd_out
)
Timeout.timeout(400) do
Process.wait(@pid)
pipe_cmd_out.close
output = pipe_cmd_in.read
return JSON.parse(output)
end
PhantomJS的STDOUT和STDERR被分配到pipe_cmd_out
,但您使用pipe_cmd_out.close
关闭该流,然后尝试读取未定义的pipe_cmd_in
。这一切似乎都错了。我认为你应该pipe_cmd_in.close
然后pipe_cmd_out.read
:
@pid = Process.spawn(
"phantomjs",
"myparser.js",
:in => pipe_cmd_in,
:out => pipe_cmd_out,
:err => pipe_cmd_out
)
Timeout.timeout(400) do
pipe_cmd_in.close
Process.wait(@pid)
output = pipe_cmd_out.read
return JSON.parse(output)
end
小心尝试解析STDERR输出。它可能不是JSON,并且在解析器抛出时会导致异常。
我们关闭脚本的输出/命令行应用程序的输入,因为许多从STDIN读取输入的命令行工具将挂起,直到它们的STDIN关闭。这就是pip_cmd_in.close
将要做的事情,它会关闭PhantomJS的STDIN,并向它发出信号,告知它应该开始处理。然后,当它输出到STDOUT时,您的脚本应该通过pipe_cmd_out
中提供的流来查看。
而且,不是将STDOUT和STDERR的输出加倍到一个变量,我可能会使用:
@pid = Process.spawn(
"phantomjs",
"myparser.js",
:in => pipe_cmd_in,
:out => pipe_cmd_out,
:err => pipe_cmd_err
)
Timeout.timeout(400) do
pipe_cmd_in.close
Process.wait(@pid)
output = pipe_cmd_out.read
if output.empty?
pipe_cmd_err
else
JSON.parse(output)
end
end
调用上述代码的代码需要检测返回值是String还是Array或Hash。如果是第一次发生错误。如果它是后两个中的一个,那么它是成功的,你可以迭代它们。