Np-Urのデータ分析教室

オーブンソースデータなどWeb上から入手できるデータを用いて、RとPython両方使って分析した結果を書いていきます

esa API と R Shiny + Pandoc でWordやPDFファイルを自動生成するツールを作ってみました

私の周りでは、情報管理にesaを使っている方が非常に多いです。

esaは私も使っていますが、以下のようなメリットがあり、とても便利です。

  • WIPの状態でも公開できる
  • 情報の検索がしやすい
  • 更新の履歴が追える

顧客との打ち合わせの議事録もesaにまとめるチーム・会社も多いそうです。
齟齬をなくすため顧客にも議事録内容を共有する際に、わざわざesaの内容をWordファイルに編集し直したり、それをPDFに変換している、という声をある方から聞きました。
それが面倒で、最初からWordファイルに書くようになり、esaなどで共有されなくなってしまった、という状態になっているそうです。

それはかなり無駄だと思うので、自動でWordとPDFファイルを自動生成ツールを作ってみました。

構成概要

社内の特定のPCにWebサーバを立てて、皆がそこにアクセスしてツールを使えるようにしたいなと思いました。
特にこだわりはないのですが、慣れているのでRとShinyを使ってWebサーバを立てるようにしました。

また、Pandocというツールを使うことで、markdown形式のファイルをWordやPDFにすることができます。
ただし、環境構築がちょっと面倒なので、Dockerを使うことにしました。

完成イメージ

f:id:Np-Ur:20200505145053p:plain

まず、esaの記事idを数値入力し、「Markdownを取得」ボタンを押すことで、記事内容の取得を行います。
またその際に、markdownからWordファイルやPDFファイルをPandocで生成します。

その後、「Download Word」や「Download PDF」をクリックすることで、生成されたファイルをダウンロードできます。

実際に作成

コードは以下に置きました。

github.com

Dockerfile

すでにShiny環境が入った、rocker/shinyというDockerイメージがあるので、それをベースに作成します。

以下のようにDockerfileを作成します。

FROM rocker/shiny:3.6.3

RUN apt update && apt install -y libcurl4-openssl-dev libssl-dev
RUN apt-get install -y texlive-latex-base texlive-fonts-recommended texlive-latex-recommended texlive-lang-cjk texlive-luatex texlive-xetex
RUN R -e "install.packages(c('httr', 'dplyr'), dependencies=T)"    


rocker/shiny:3.6.3 をベースにして、イカを追加しています。

  • 4行目では、pandoc用のツール群をインストール
  • 5行目では、Rのライブラリである以下インストール
    • httr(esaのAPIを使用するための、httpアクセス用ライブラリ)
    • dplyr(パイプで処理を書く用のライブラリ)
  • 3行目では、httrライブラリを使うための、ツール群をインストール


esaのAPI KEY

esaのAPIを使うためには、アクセストークンとesaのチーム名が必要があります。
詳しくは公式サイトをご覧ください。

RからesaのAPIを叩く方法については、以下記事を参考にしました。

uribo.hatenablog.com

esaのAPI用のアクセストークンは、環境変数で持っていた方が扱いやすそうだが、通常のRコンソールでは使用できてもShiny上に渡せないことが分かったので、ちょっと辛いですが、esa_info.csv というファイルで管理することにしました。

以下のように、esa_info.csv というファイルを作って、その中に、アクセストークンとチーム名を書いておいてください。
(XXXXXXXX という部分を取得したトークンやチーム名に置換する)

ESA_API_KEY,ESA_TEAM_NAME
XXXXXXXXXXXXXXXXX,XXXXXXXX


ui.Rとserver.R

app.Rという1つのファイルで書いても良かったのですが、一応ui.Rとserver.Rに分けています。

ui.R

library(shiny)

shinyUI(fluidPage(
  
  titlePanel("esa記事をWord or PDFで出力"),
  
  sidebarLayout(
    sidebarPanel(
      numericInput("numericInput_data",
                   "esaの記事IDを入力",
                   min = 1,
                   value = 1),
      actionButton("render_md", "Markdownを取得"),
      br(),
      uiOutput("download_files")
    ),
    mainPanel(
      verbatimTextOutput("text")
    )
  )
))


server.R

library(shiny)
library(httr)
library(dplyr)

esa_info <- read.csv("esa_info.csv")
headers <- httr::add_headers(`Authorization` = paste("Bearer", esa_info[1, 1]),
                           `Content-Type`  = "application/json")
team_name <- esa_info[1, 2]

shinyServer(function(input, output) {  
  output$text <- renderText({    
    paste(markdown_text())
  })

  markdown_text <- reactive({
    input$render_md
    input_id <- isolate(input$numericInput_data)

    api_result <- GET("http://api.esa.io",
           path = paste("v1/teams", team_name, "posts", input_id, sep = "/"),
           config = headers)
    
    if (api_result %>% status_code() != 200){
        return(paste("API Error:", api_result %>% status_code()))
    }

    contents <- api_result %>% content()
    write(contents$body_md, paste("www/sample_", input_id, ".md", sep=""))
    system(paste("pandoc -f markdown -t docx www/sample_", input_id, ".md -o www/sample_", input_id, ".docx", sep=""))
    system(paste("pandoc -t latex www/sample_", input_id, ".docx -o www/sample_", input_id, ".pdf  --pdf-engine=xelatex -V documentclass=bxjsarticle -V classoption=pandoc", sep=""))
    
    return(contents$body_md)
  })

  output$download_files <- renderUI({
    input_id <- input$numericInput_data
    tagList(
      h3(tags$a(href=paste("sample_", input_id, ".docx", sep=""), target="blank", "Download Word", download=paste("sample_", input_id, ".docx", sep=""))),
      h3(tags$a(href=paste("sample_", input_id, ".pdf", sep=""), target="blank", "Download PDF", download=paste("sample_", input_id, ".pdf", sep=""))),
    )
  })
})

server.Rでは、まずesa_info.csv からトークン情報やチーム名を取得しています。
その後、ui.Rから渡ってきた記事IDを元にesa APIにアクセスして、記事情報を取得・WordとPDFファイル作成を行なっています。

ui.Rやserver.R の中身自体は特に難しくないと思うので解説はこのぐらいにしておきます。

RとShinyで作るWebアプリケーション

RとShinyで作るWebアプリケーション

docker-compose.yaml

以下のようにdocker-compose用のファイルを作成します。

version: '3'
services: 
  esa-shiny:
    container_name: esa-shiny
    image: esa-shiny:1.0
    ports:
      - 3838:3838
    # environment:
    #   - ESA_API_KEY=XXXXXXXXXXXXXXXX
    #   - ESA_TEAM_NAME=XXXXXX
    volumes: 
      - $PWD/shiny-src:/srv/shiny-server/esa
  • イメージは先ほど作成した、「esa-shiny:1.0」
  • 3838でポートフォワーディング
  • ShinyのソースコードをDockerコンテナ内の /srv/shiny-server/esa にマウント


Dockerコンテナを起動

以下のようなディレクトリ 構成で先ほどのファイルを用意してください。

- shiny-src/
  - esa_info.csv
  - ui.R
  - server.R
  - www/
- docker-compose.yaml
- Dockerfile


その後、以下を実行してDockerイメージを作成します。

$ docker build -t esa-shiny:1.0 .

次に、以下を実行してDockerコンテナを立ち上げます。

$ docker-compose up -d

これでShinyアプリケーションが立ち上がりました。

ブラウザで、【localhost:3838/esa】、もしくは 【今起動しているパソコンのIPアドレス:3838/esa】にアクセス(例 192.168.1.100:3838/esa)してください。

適当なesa記事IDを入力して、WordやPDFファイルを生成できるようになっています。


まとめ

今回は、esa APIとPandocを使ってWordファイルやPDFファイルの作成に挑戦しました。

この状態では、簡素なWordファイルが作成されていますが、テンプレートを用いることで見た目をカスタマイズできます。
次回は、その辺りをやっていきます。