我有一个格式非常简单的 XML 文档,我想将它翻译成适合导入 Hive 的 TSV。本文档的格式很简单:
<root>
<row>
<ID>0</ID>
<ParentID>0</ParentID>
<Url></Url>
<Title></Title>
<Text></Text>
<Username></Username>
<Points>0</Points>
<Type>0</Type>
<Timestamp></Timestamp>
<CommentCount>0</CommentCount>
</row>
</root>
我有一个可以正常工作的 Ruby 脚本,可以将上述格式的文档正确地转换为 TSV。就在这里:
require "rubygems"
require "crack"
xml = Crack::XML.parse(File.read("sample.xml"))
xml['root']['row'].each{ |i|
puts "#{i['ID']} #{i['ParentID']} #{i['Url']} #{i['Title']}..."
}
不幸的是,我需要翻译的文件比这个脚本可以处理的要大得多(> 1 GB)。
这就是 Hadoop 的用武之地。最简单的解决方案可能是用 Java 编写 MapReduce 作业,但鉴于我缺乏 Java 技能,这不是一个选择。所以我想用 Python 或 Ruby 编写一个映射器脚本,我远非专家,但至少可以导航。
我当时的计划是做以下事情:
- 使用 StreamXmlRecordReader 逐条解析文件记录
- 使用破解映射反序列化
- 用制表符隔开的元素的简单反刍来减少它
然而,这种方法一直失败。我使用了各种 Ruby/Wukong 脚本,但都没有成功。这是一个基于这里的文章:
#!/usr/bin/env ruby
require 'rubygems'
require 'crack'
xml = nil
STDIN.each_line do |line|
puts |line|
line.strip!
if line.include?("<row")
xml = Crack::XML.parse(line)
xml['root']['row'].each{ |i|
puts "#{i['ID']} #{i['ParentID']} #{i['Url']}..."
else
puts 'no line'
end
if line.include?("</root>")
puts 'EOF'
end
end
此作业和其他作业失败如下:
hadoop jar /usr/lib/hadoop-0.20/contrib/streaming/hadoop-streaming-0.20.2+737.jar -input /hackernews/Datasets/sample.xml -output out -mapper mapper.rb -inputreader "StreamXmlRecordReader,begin=<row,end=</row>"
packageJobJar: [/var/lib/hadoop-0.20/cache/sog/hadoop-unjar1519776523448982201/] [] /tmp/streamjob2858887307771024146.jar tmpDir=null
11/01/14 17:29:17 INFO mapred.FileInputFormat: Total input paths to process : 1
11/01/14 17:29:17 INFO streaming.StreamJob: getLocalDirs(): [/var/lib/hadoop-0.20/cache/sog/mapred/local]
11/01/14 17:29:17 INFO streaming.StreamJob: Running job: job_201101141647_0001
11/01/14 17:29:17 INFO streaming.StreamJob: To kill this job, run:
11/01/14 17:29:17 INFO streaming.StreamJob: /usr/lib/hadoop-0.20/bin/hadoop job -Dmapred.job.tracker=localhost:8021 -kill job_201101141647_0001
11/01/14 17:29:17 INFO streaming.StreamJob: Tracking URL: http://localhost:50030/jobdetails.jsp?jobid=job_201101141647_0001
11/01/14 17:29:18 INFO streaming.StreamJob: map 0% reduce 0%
11/01/14 17:30:05 INFO streaming.StreamJob: map 100% reduce 100%
11/01/14 17:30:05 INFO streaming.StreamJob: To kill this job, run:
11/01/14 17:30:05 INFO streaming.StreamJob: /usr/lib/hadoop-0.20/bin/hadoop job -Dmapred.job.tracker=localhost:8021 -kill job_201101141647_0001
11/01/14 17:30:05 INFO streaming.StreamJob: Tracking URL: http://localhost:50030/jobdetails.jsp?jobid=job_201101141647_0001
11/01/14 17:30:05 ERROR streaming.StreamJob: Job not Successful!
11/01/14 17:30:05 INFO streaming.StreamJob: killJob...
Streaming Command Failed!
第一个问题是我不知道哪里出了问题:我的脚本或 StreamXmlRecordReader。
第二个问题是,一位亲切而乐于助人的专家告诉我,由于 StreamXmlRecordReader 不会产生额外的记录分隔符,这种方法可能行不通,我需要单行阅读,grep对于行,将所有内容堆叠起来,直到获得 /row,然后对其进行解析。
这是最简单的方法吗?如果是,我如何才能最好地做到这一点?
性能不是一个大问题,因为这些文件每隔几周左右就会被批处理一次,以防万一。