使用Dataflow读取CSV标头

时间:2016-12-23 08:21:35

标签: google-cloud-dataflow apache-beam

我有一个CSV文件,我不知道列名提前。我需要在Google Dataflow中进行一些转换后以JSON格式输出数据。

获取标题行并将标签渗透到所有行的最佳方法是什么?

例如:

ct <- ctree(factor(cyl) ~ ., data = mtcars, minsplit = 2)
plot(ct, inner_panel = make_inner_and_barplot(ct), tnex = 0.8)

...变得(大约):

a,b,c
1,2,3
4,5,6

3 个答案:

答案 0 :(得分:8)

您应该实现自定义FileBasedSource(类似于TextIO.TextSource),它将读取第一行并存储标题数据

    @Override
    protected void startReading(final ReadableByteChannel channel)
    throws IOException {
        lineReader = new LineReader(channel);

        if (lineReader.readNextLine()) {
            final String headerLine = lineReader.getCurrent().trim();
            header = headerLine.split(",");
            readingStarted = true;
        }
    }

和后者,读取其他行时会将其添加到当前行数据中:

    @Override
    protected boolean readNextRecord() throws IOException {
        if (!lineReader.readNextLine()) {
            return false;
        }

        final String line = lineReader.getCurrent();
        final String[] data = line.split(",");

        // assumes all lines are valid
        final StringBuilder record = new StringBuilder();
        for (int i = 0; i < header.length; i++) {
            record.append(header[i]).append(":").append(data[i]).append(", ");
        }

        currentRecord = record.toString();
        return true;
    }

我已在github上实施了快速(完整)解决方案。我还添加了一个数据流单元测试来演示阅读:

@Test
public void test_reading() throws Exception {
    final File file =
            new File(getClass().getResource("/sample.csv").toURI());
    assertThat(file.exists()).isTrue();

    final Pipeline pipeline = TestPipeline.create();

    final PCollection<String> output =
            pipeline.apply(Read.from(CsvWithHeaderFileSource.from(file.getAbsolutePath())));

    DataflowAssert
            .that(output)
            .containsInAnyOrder("a:1, b:2, c:3, ", "a:4, b:5, c:6, ");

    pipeline.run();
}

其中sample.csv包含以下内容:

a,b,c
1,2,3
4,5,6

答案 1 :(得分:1)

我已经基于Luka的源代码创建了一个解决方案(参见上一个答案)。 Luka在github中的代码用于dataflow-1.x,并实现了一个FileBasedSource,它提取第一行并对其进行缓存,然后将其预先添加到每一行。这需要在单个节点上处理整个文件(不可拆分)。

我的FileBasedSource变体只返回文件的第一行;如类javadoc中所述,然后可以将该行拆分(根据需要)并用作处理完整文件的逻辑的侧输入(然后可以并行完成)。该代码与Beam 2.x兼容(在Beam 2.4.0上测试)。

请参阅http://moi.vonos.net/cloud/beam-read-header/

答案 2 :(得分:0)

我正在使用Luka的阅读器,它在启动其他链式管道之前正在读取整个csv文件。是否可以定义块大小,例如读取10行,然后写入,然后读取接下来的10行

 PCollection<String> input = pipeline.apply(Read.from(CustomCsvReader.from(options.getInput())));
PCollection<Map<String,String>> mapOutput = input.apply(MapElements.via(new SimpleFunction<String, Map<String,String>>() {
        @Override
        public Map<String,String> apply(String input) {
          String[] entrys = input.split(",");
          return Stream.of(entrys).map(t -> t.split(":",2)).collect(Collectors.toMap(a -> a[0], a -> a.length > 1 ? a[1]: ""));

        }
    }));
PCollection<String> output = mapOutput.apply(ParDo.of(new CSVToXMLConverter()));
 output.apply(TextIO.write().to(options.getOutput()).withFooter(Constants.CCR_FOOTER));
pipeline.run().waitUntilFinish();