20200913のNode.jsに関する記事は13件です。

New Relic APMをNode.jsのアプリケーションに導入してパフォーマンス解析をする

背景

導入方法

前提として、NewRelicのAPMライセンス適用済みのアカウントが作成されていること

  • NewRelicポータル > APM > Add More で Node js を選択
  • 画面の案内に沿って導入する
    1. アプリケーション名を決めて入力する
    2. Node.jsアプリケーションのディレクトリでnewrelicのNPMパッケージを導入
      $ npm install newrelic --save
    3. NewRelicの設定ファイル(newrelic.js)をダウンロードし、Node.jsアプリケーションのディレクトリ直下に配置
      (NewRelicのライセンスキーと1.で入力したアプリケーション名が記載されている)
    4. Node.jsのアプリケーション本体(app.jsなど)にrequireを追加する require('newrelic');
    5. Node.jsのアプリケーションを再起動する。($npm startなど) image.png

どんな情報が見えるのか?

ISUCONの中でどのようなパフォーマンス解析に使っていたかのメモです。
(アプリケーションはコンテスト中で改修した後のもの)

Summary

  • アプリケーションのトランザクション処理時間のグラフが確認できる。
  • トランザクション全体の時間の中でMySQLの処理時間の割合がわかる
  • スループット、エラーレート、Apdexスコア(レスポンスタイムベースのユーザ満足度)
  • 下部にはアプリケーションを処理しているサーバ別のレスポンス・スループット・エラーレート・CPU使用率・メモリ使用率の平均値が表示される。 image.png

Distributed Tracing

  • アプリケーションのトランザクションのサンプリング一覧を表示する。
    全体の処理時間・バックエンドの処理時間でソートし遅いトランザクションを特定することができる。
  • 更にドリルダウンすることでトランザクションの中でのバックエンドの呼び出し時間の割合(MySQLならテーブル単位)を確認することができる。キャプチャの例ではトランザクション中でMySQLのchairテーブルに2回selectを発行し、Redisに1回setしていることがわかる。

image.png
image.png

Service Map

  • アプリケーションが依存している外部サービスがマップ上に表示される。
  • キャプチャの例ではアプリケーションから2ノードのRedisと1ノードのMySQLにアクセスしていることがわかる
  • データベースをクリックするとクエリの処理時間のグラフも表示される image.png

Dependencies

  • Service Mapに表示されていた外部サービスが一覧表示される

image.png

Transactions

  • 初期表示の「Sort by Most time consuming」でアプリケーションのトランザクションの中で「処理時間 × リクエスト数」が多い順に表示される。(キャプチャ1枚目)
  • 特定機能にフォーカスすると処理時間の内訳がグラフ表示される。キャプチャ2枚目の例ではMySQLのchairテーブルのselectが支配的なことがわかる。
  • パフォーマンス改善の効果が高い機能を特定しチューニングの方向性を決めるために利用できる。

image.png
image.png

Databases

  • 初期表示の「Sort by Most time consuming」でアプリケーションが接続しているデータベースのクエリの「処理時間 × リクエスト数」が多い順に表示される。
  • テーブル単位のselect/insertの処理時間の外観をつかむことができるのでDBチューニングの方向性を決めるために利用できる。 image.png

感想

  • NewRelicの導入簡単すぎ。(Node.jsの場合)
  • アプリケーションの機能単位での処理時間、データベースのテーブル単位のselect/insertの処理時間が確認できるため、パフォーマンス低下の原因を速やかに特定できる。とても便利。
  • ISUCONのスコアベースでは100-200点程度の影響しかなかったので、New Relic APM Agentによるパフォーマンス影響よりも可観測性のメリットの方が大きいと思われる。(競技の最終盤では外しましたが)本番運用しているアプリケーションではNewRelicでモニタリングし続けたほうがいいと思いました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GitHub Actionsを使ってWebサイト(EC2)の更新を自動化する

GitHub Actionsを使ってWebサイトの更新を自動化する手法を備忘録がてらにご紹介します。
この記事を読むことで、下記の作業を自動化できます。

  1. Node.jsを用いた静的アセット(html,css,js)のビルド
  2. scpを用いたビルドアーティファクトのAWS EC2インスタンスへのデプロイ
  3. slackへの通知

技術・ツール:hammer_pick:

前提となる技術とツールをご紹介します。
Node.js、EC2インスタンス、slackは既に用意されているものとして説明します。

名前
GitHub Actions
AWS EC2
Node.js(v12.18.3)
slack

GitHub Actions を始める前に

slackへの通知やEC2インスタンスへのssh接続が必要となるため、GitHubのsecretsを使用して秘匿情報を隠蔽します。
[ Settings > Secrets > New secret ]より、下記のsecretsを追加してください。

85b5fbbf85f9f4aa46660dc1aae31e25.png

名前 用途
EC2_HOST EC2インスタンスへ接続するためのホスト名です。
EC2_KEY EC2インスタンスへ接続するための秘密鍵です。
EC2_OUTPUT_DIR 実際に公開されるサイトのドキュメントルートです(/var/www/htmlが一般的ですが、各種Webサーバの設定に依存します)。
EC2_USER EC2インスタンスへ接続するためのユーザー名です。
SLACK_WEBHOOK_URL slackのAppディレクトリで確認できるIncoming WebhookのURLです。詳しくはこちらをご確認ください。

GitHub Actions の用意

詳細な説明が不要な場合は、下記のymlファイルをコピペすれば問題なく動くと思います(たぶん)。

GitHub Actionsを使うには、リポジトリのルートディレクトリに .github/workflows/file-name.yml というファイルを追加する必要があります。このymlファイルの構文は公式のリファレンスを参考にするのが良いと思います。
今回は、ビルド・デプロイ・slack通知を自動化したいので、下記のようなymlファイルを書きます。

build-deploy-notify.yml
name: Build and Deploy to EC2

on:
  push:
    branches:
      - master

env:
  SSH_PRIVATE_KEY: ${{ secrets.EC2_KEY }}
  REMOTE_HOST: ${{ secrets.EC2_HOST }}
  REMOTE_USER: ${{ secrets.EC2_USER }}
  TARGET: ${{ secrets.EC2_OUTPUT_DIR }}
  SOURCE: 'public'
  SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
  SLACK_CHANNEL: ci

jobs:
  build-and-deploy:
    name: Build and Deploy
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Use Node.js v12.18.3
        uses: actions/setup-node@v1
        with:
          node-version: '12.18.3'

      - name: Build
        run: |
          npm install
          npm run build

      - name: Deploy to EC2
        uses: easingthemes/ssh-deploy@v2.1.1

      - name: Slack Notification on Success
        if: success()
        uses: rtCamp/action-slack-notify@v2.0.2
        env:
          SLACK_TITLE: Deploy Success
          SLACK_COLOR: good

      - name: Slack Notification on Failure
        if: failure()
        uses: rtCamp/action-slack-notify@v2.0.2
        env:
          SLACK_TITLE: Deploy Failure
          SLACK_COLOR: danger

yml ファイルの詳解

name

Workflowの名前となり、実際にGitHub上のActionsページにて表示される名前です。処理に直接的な影響は及ぼしません。

on

Workflowが実行される条件の部分です。
今回はmasterブランチにpushされたイベントを検知して、Workflowが実行されるようになっています。
push以外にも様々なイベントをトリガーとして設定可能で、ブランチも複数選択可能です。

build-deploy-notify.yml
# workflowの名前です。
name: Build and Deploy to EC2

# masterブランチにpushしたときのイベントをトリガーにしています。
on:
  push:
    branches:
      - master

env

このWorkflowにおける環境変数です。
${{ secrets.SOME_SECRET }} で [ Settings > Secrets ]で設定した値を使用することができます。
後述しますが、今回いくつかの外部Actionを使用しており、ここで設定した環境変数は外部のActionを使用する際に必須のパラメータとなります。GitHub ActionsはMarketplaceに公開されている外部のアクションを使用することができます。

build-deploy-notify.yml
env:
  SSH_PRIVATE_KEY: ${{ secrets.EC2_KEY }}
  REMOTE_HOST: ${{ secrets.EC2_HOST }}
  REMOTE_USER: ${{ secrets.EC2_USER }}
  TARGET: ${{ secrets.EC2_OUTPUT_DIR }}
  # publicはビルドアーティファクトが出力されるディレクトリです。
  # このプロジェクトではWebpackを使って、静的アセットをpublicディレクトリに出力しています。
  SOURCE: 'public'
  SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
  SLACK_CHANNEL: ci

jobs

実際の処理群を記述する場所です。

stepsにビルド・デプロイ・通知などの処理をそれぞれ記述しています。
Use Node.js v12.18.3で、Node.jsとnpmのセットアップ、Buildnpm scriptsを実行、 Deploy EC2でEC2インスタンスへのデプロイ、Slack Notification on Success(or Failure)でslackへの通知作業を行っています。ここまでの記述でわかるとおり、実際の処理はほとんど書いていません。これはusesに記述している外部のアクションを用いているからです。
easingthemes/ssh-deploy@v2.1.1では、scpを用いた静的アセットの輸送を、rtCamp/action-slack-notify@v2.0.2では、Incoming Webhookを用いたSlackへの通知をそれぞれ担っています。また、環境変数envは各ステップごとにも設定することが可能で、slackへの通知では成功・失敗でそれぞれメッセージや文字色を分けるよう設定しています。

いずれかのstep中にエラーが発生すると当該のWorkflowは失敗となります。
ビルドやデプロイなど、失敗する可能性が0%ではない処理を含んでいる場合は、成功・失敗の条件分岐をさせることも可能で、今回はSlackの通知をそれぞれ成功・失敗で出し分けられるようにしています。
if:failure()もしくはif:success()で条件分岐させています。

build-deploy-notify.yml
jobs:
  build-and-deploy:
    name: Build and Deploy
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Use Node.js v12.18.3
        uses: actions/setup-node@v1
        with:
          node-version: '12.18.3'

      - name: Build
        run: |
          npm install
          npm run build

      - name: Deploy to EC2
        uses: easingthemes/ssh-deploy@v2.1.1

      - name: Slack Notification on Success
        if: success()
        uses: rtCamp/action-slack-notify@v2.0.2
        env:
          SLACK_TITLE: Deploy Success
          SLACK_COLOR: good

      - name: Slack Notification on Failure
        if: failure()
        uses: rtCamp/action-slack-notify@v2.0.2
        env:
          SLACK_TITLE: Deploy Failure
          SLACK_COLOR: danger

まとめ

EC2にデプロイする場合は、AWSのCodePipeLine(CodeBuild,CodeDeploy)を使うことが一般的かと思いますが、今回はあえてGitHub Actionsを使ってみました。それぞれメリット・デメリットがあるので、プロジェクトにあった技術を選定するのが良いと思います。GitHub Actionsを使えば、デプロイ先をEC2以外にすることもできますし、通知先をSlack以外にすることも可能かと思いますので、可用性や汎用性といった部分が非常に高いように感じました。価格も無料ですし、コードのテストを追加したいといった場合にCI的に扱えることも魅力の一つかなと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.jsでナイーブベイズ分類器を使った分類を行う

ナイーブベイズ分類器のBayesモジュールを使う

ナイーブベイズ分類器は、次のようなことができます。

  • スパムメールの判定
  • ニュース記事やブログ記事のカテゴリー判定

ごく簡単にいうと、学習に必要なのはカテゴリーに関連する単語をたくさん登録するだけです。カテゴリーのわかっている文章を単語に分解して登録します。判定するときには、カテゴリーに関わる単語の出現率で判定されます。

もちろん、もっと正しい理解をしたほうがいいですが、bayesモジュールを使うならこの程度のイメージを持っておくだけで使えて、なかなか有益な結果を得られます。詳しく知りたい方は末尾のリンク先を参照してください。1

使い方(イメージ)

// 学習
classifier.learn('カテゴリーAに関する長文・・・・・', 'カテゴリーA')
classifier.learn('カテゴリーBに関する長文・・・・・', 'カテゴリーB')
classifier.learn('カテゴリーCに関する長文・・・・・', 'カテゴリーC')

// 判定
const category = classifier.categorize('カテゴリーを判定したい文章')

準備(予備知識)

分かち書き

先に示したように、bayesモジュールのlearnメソッドを使ってカテゴリーのわかっている文章を学習させます。英語であれば自動で単語に分割して登録されるのですが、日本語の場合は単語への分割がうまくいきません。そこで、bayesが日本語の文章の単語への分割=分かち書きができるようにその機能を持ったメソッドを渡してあげます。分かち書きの機能を提供するtiny-segmenterモジュールを使って次のようにします。(イメージを掴むために、こちらのサイトでtiny-segmenterの動作を見ておくとよいです。)

const segmenter = new TinySegmenter()

var classifier = bayes({
  tokenizer: function (text) { 
    return segmenter.segment(text);
  }
});

async/await

また、最初の使い方イメージでは省略しましたが、bayesのlearnメソッドとcategorizeメソッドはasyncで提供されているため、簡単に使用するにはawaitをつけて呼び出す必要があります。awaitはaysncメソッド内でしか使えません。そのため下記のサンプルコードではasyncが使われています。

データ準備

学習に使う文章はWikipediaからもってきましょう。以下のURLでアクセスするとXMLデータが得られます。2

https://ja.wikipedia.org/wiki/特別:データ書き出し/キーワード

得られるデータにはちょっと無駄な情報が多いですが簡単に済ますために今回はこれをこのまま使いましょう。ブラウザで以下の3つのURLにアクセスして、それぞれyoritomo.txt、takauji.txt、ieyasu.txtとして保存してください。

yoritomo.txtとして保存
https://ja.wikipedia.org/wiki/特別:データ書き出し/源頼朝
takauji.txtとして保存
https://ja.wikipedia.org/wiki/特別:データ書き出し/足利尊氏
ieyasu.txtとして保存
https://ja.wikipedia.org/wiki/特別:データ書き出し/徳川家康

これで準備は完了です。

インストール

bayesのインストール
npm install bayes
tiny-segmenterのインストール
npm install tiny-segmenter

デモ・コード

実行結果は次の通りです。

実行結果
$ node bayes-demo.js 
判定=[源頼朝] -- 日本で最初に幕府を開いた人物で、妻は尼将軍としても有名な北条政子である。
判定=[源頼朝] -- 後鳥羽天皇によって征夷大将軍に任ぜられた。
判定=[源頼朝] -- 奥州を平定した。
判定=[足利尊氏] -- 室町幕府を開いた。
判定=[足利尊氏] -- 鎌倉幕府の滅亡後、鎮守府将軍・左兵衛督に任ぜられた。
判定=[足利尊氏] -- 歌人としても知られる。
判定=[徳川家康] -- 幼少時代を人質として過ごした。
判定=[徳川家康] -- 室町幕府最後の将軍足利義昭が信長包囲網を企てたとき、協力要請を受けたがこれを無視した。

これがデモ・コードです。ぐだぐだ説明する必要もないと思います。シンプル。

bayes-demo.js
var bayes = require('bayes');
const TinySegmenter = require('tiny-segmenter')
const fs = require('fs')

// 分かち書きの機能を使うため
const segmenter = new TinySegmenter()


// 学習用文章の読み込み
var txt_yoritomo = fs.readFileSync('yoritomo.txt', 'utf-8')
var txt_takauji = fs.readFileSync('takauji.txt', 'utf-8')
var txt_ieyasu = fs.readFileSync('ieyasu.txt', 'utf-8')

// 分かち書き機能の設定
var classifier = bayes({
  tokenizer: function (text) { 
    return segmenter.segment(text);
  }
});

async function demo() {
  // 学習
  await classifier.learn(txt_yoritomo, '源頼朝');
  await classifier.learn(txt_takauji, '足利尊氏');
  await classifier.learn(txt_ieyasu, '徳川家康');

  // 判定して結果を表示
  async function categorize(text) {
    // 判定
    var r = await classifier.categorize(text);
    console.log("判定=[" + r + "] -- " + text);
  }

  // 文章のカテゴリーを判定する(分類する)
  categorize('日本で最初に幕府を開いた人物で、妻は尼将軍としても有名な北条政子である。');
  categorize('後鳥羽天皇によって征夷大将軍に任ぜられた。');
  categorize('奥州を平定した。');
  categorize('室町幕府を開いた。');
  categorize('鎌倉幕府の滅亡後、鎮守府将軍・左兵衛督に任ぜられた。');
  categorize('歌人としても知られる。');
  categorize('幼少時代を人質として過ごした。');
  categorize('室町幕府最後の将軍足利義昭が信長包囲網を企てたとき、協力要請を受けたがこれを無視した。');
}

demo()

bayesモジュールのコードを読む(わずか271行!)

bayesモジュールのソースコードを見てみると、わずか271行しかない比較的簡単な内容となっている。読んで理解するにはさすがに少しナイーブベイズ分類器について理解を深めておいたほうがよい。ナイーブベイズ分類器の良い解説記事はたくさんあるので探して読んでください。1

leanメソッドが学習部分です。

naive_bayes.js抜粋
/**
 * textがどのcategoryに対応しているか学習することで、ナイーブベイズ分類器を訓練する
 *
 * @param  {String} text
 * @param  {Promise<String>} class
 */
Naivebayes.prototype.learn = async function (text, category) {
  var self = this

  //はじめてのカテゴリの場合は、カテゴリのデータ構造を初期化する
  self.initializeCategory(category)

  //カテゴリにマップされたドキュメント数をカウントする
  self.docCount[category]++

  //学習したドキュメントの総数をカウント
  self.totalDocuments++

  //テキストを単語に分割して配列にする
  var tokens = await self.tokenizer(text)

learnメソッドの後半では、各単語の出現回数をカウントしています。カウントしているのは、カテゴリ中の単語出現回数(wordFreqencyCount)と、カテゴリーの総単語数(wordCount)です。

naive_bayes.js抜粋
  //テキスト内の各トークンの頻度カウントを取得します。
  //get a frequency count for each token in the text
  var frequencyTable = self.frequencyTable(tokens)

  /*
      このカテゴリの語彙数と単語数を更新します。
      Update our vocabulary and our word frequency count for this category
   */

  Object
  .keys(frequencyTable)
  .forEach(function (token) {
    //この単語がない場合は、私たちの語彙に追加します。
    //add this word to our vocabulary if not already existing
    if (!self.vocabulary[token]) {
      self.vocabulary[token] = true
      self.vocabularySize++
    }

    var frequencyInText = frequencyTable[token]

    //このカテゴリのこの単語の頻度情報を更新する
    //update the frequency information for this word in this category
    if (!self.wordFrequencyCount[category][token])
      self.wordFrequencyCount[category][token] = frequencyInText
    else
      self.wordFrequencyCount[category][token] += frequencyInText

    //このカテゴリにマップされたすべての単語のカウントを更新します。
    //update the count of all words we have seen mapped to this category
    self.wordCount[category] += frequencyInText
  })

  return self
}

categorizeメソッドは与えられたテキストのカテゴリーを判定します。すべてのカテゴリーごとに可能性を調べて、最も高い可能性のカテゴリーを選択します。可能性の算出は、テキスト中の各単語について、各単語の確率を加算するという方法です。非常にシンプルですね。

naive_bayes.js抜粋
/**
 * テキストがどのカテゴリに属するかを決定する
 *
 * @param  {String} text
 * @return {Promise<string>} category
 */
Naivebayes.prototype.categorize = async function (text) {
  var self = this
    , maxProbability = -Infinity
    , chosenCategory = null

  var tokens = await self.tokenizer(text)
  var frequencyTable = self.frequencyTable(tokens)

  //カテゴリを反復処理して、最も確率の高いカテゴリを求める
  Object
  .keys(self.categories)
  .forEach(function (category) {

    // このカテゴリの全体的な確率を計算することから始める
    // => 学習したすべての文書のうち、このカテゴリのものはどれくらいあったか
    var categoryProbability = self.docCount[category] / self.totalDocuments

    //アンダーフロー対策に対数(log)を取る
    var logProbability = Math.log(categoryProbability)

    //テキスト中の各単語 `w` について P( w | c ) を決定する
    Object
    .keys(frequencyTable)
    .forEach(function (token) {
      var frequencyInText = frequencyTable[token]
      var tokenProbability = self.tokenProbability(token, category)

      // console.log('token: %s category: `%s` tokenProbability: %d', token, category, tokenProbability)

      //この単語のP( w | c )の対数(log)を求める
      logProbability += frequencyInText * Math.log(tokenProbability)
    })

    if (logProbability > maxProbability) {
      maxProbability = logProbability
      chosenCategory = category
    }
  })

  return chosenCategory
}

以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

第6回:Node.jsの環境構築

目次:Webアプリ開発ロードマップ

第6回:Node.jsの環境構築

今回からNode.jsを利用しバックエンドを作成していきます。

Node.jsパッケージを作成する

下記のコマンドでプロジェクトのディレクトリを生成し、Node.jsのプロジェクト生成コマンドを実行します。

$ mkdir back
$ cd back
$ npm init

パッケージの設定を問われますが、後で変更可能なので全て省略で構いません。

This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (back) 
version: (1.0.0) 
description: 
entry point: (index.js) 
test command: 
git repository: 
keywords: 
author: 
license: (ISC) 
About to write to /Users/radiance/git/web-learning-loadmap/back/package.json:

{
  "name": "back",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

Is this OK? (yes) yes

ディレクトリ内にpackage.jsonだけ生成されます。
これがNode.jsパッケージの設定ファイルになります。

Node.jsでJavaScriptを実行する

1) back/index.jsファイルを作成し、下記の内容を記載します。

console.log('Hello World !');

2) 作成したJavaScriptを実行する
Node.jsでJavaScriptを実行するには下記のコマンドを実行します。

$ node index.js

JavaScriptが実行され、標準出力に文字が出力されます。

Hello World !

パッケージのスクリプト機能を利用して実行してみる

Node.jsパッケージにはコマンドラインをスクリプトとして登録し実行を簡単にする機能があります。
1) package.jsonを開き、scriptsを下記のように修正します。

  "scripts": {
    "start": "node index.js"
  },

2) スクリプトを実行してみる
スクリプトとして登録した"start"のキーワードを呼び出してみます。

$ npm run start

node index.jsが実行され、JavaScriptが動作します。

> node index.js

Hello World !

キーワード"start"は特別で、runを省略して実行することができます。$ npm start

index.jsはもう利用しませんので削除してください。

TypeScriptを利用する

より綺麗で再利用可能なプログラムを書くために、TypeScriptを利用します。
TypeSciptはNode.jsのモジュールで、独自の記法のTypeSciptファイルをJavaScriptファイルにビルドする機能を提供します。
1) Node.jsのtypescriptモジュールをパッケージにインストールします。

$ npm install typescript

package.jsonにモジュールが追加されました。

  "dependencies": {
    "typescript": "^4.0.2"
  }

node_modulesディレクトリ配下にインストールされたモジュールの本体がダウンロードされています。
image.png

2) TypeScriptの設定ファイルを作成する。
pacakge.jsonと同じ階層にtsconfig.jsonを作成します。
内容は下記のように記載します。

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es2017",
    "sourceMap": true,
    "outDir": "./build",
    "types": ["node"]
  },
  "exclude": [
    "node_modules"
  ]
}

3) 最初に起動するTypeScriptファイルを作成する
app/main.tsを作成し、下記の内容を記載します。

console.log('Hello World !');

TypeScriptはクラスを利用した構文で書きますが、一番最初に起動されるファイルはJavaSciprtと同じ記載方法です。

4) ビルドする
下記のコマンドを実行し、TypeScriptで記載したコードをビルドします。

$ node_modules/typescript/bin/tsc

下記のエラーが発生するので、@types/nodeモジュールをインストールします。

error TS2688: Cannot find type definition file for 'node'.
$ npm install @types/node

再度ビルドするとエラーは解消します。
ビルド結果はbuildディレクトリ配下に出力されます。
image.png

5) ビルドしてできたJavaScriptファイルを実行する

$ node build/main.js
Hello World !

6) ビルド、実行を行うスクリプトを追加する

package.jsonにビルド、実行を行うスクリプトを追加します。

  "scripts": {
    "build": "tsc",
    "start": "node build/main.js"
  },

package.jsonのscriptsではnode_modules/{パッケージ名}/bin/の下にある実行ファイルはパスを省略して実行することができます。

ビルド

$ npm run build
> tsc

実行

$ npm start
> node build/main.js

Hello World !

ビルドと実行を一度のコマンドで動かすには、scriptを下記のように&&で連結して記載します。

  "scripts": {
    "build": "tsc",
    "start": "tsc && node build/main.js"
  },

最後に

今回はバックエンドの環境を構築しました。次回はニュースフィード一覧を取得するREST APIを作成します。
今回開発したソースコードはGitHubに入っています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

npm install --save について

はじめに

本投稿は npm install コマンドの --save オプションについてですが、すでに

npmでパッケージをインストールする際、ネットを検索すると --save というオプションをよく見かける。
ex)axios をインストールする場合

$ npm install axios --save

-g でグローバルにインストールする際には見ないのだが、何者なのか調べてみた。

npm install --save オプションについて

package.jsondependencies に登録してくれるようだ。

何がうれしいのか

git にコミットする際、パッケージをインストールしているフォルダ node_modules.gitignore によって除外されます。
違う開発環境を git からクローンして構築する場合、package.json を元に復元します。
よって、同じパッケージ環境を簡単に構築することができます。

結論: --save オプションは必要か

現在の環境では --save オプションは不要です。
2017-05-30にリリースされた npm5.0.0 以降ではデフォルトで --save がつくようになりました。
よって、現在では古い環境を除いて基本的に不要です。

参考

https://yosuke-furukawa.hatenablog.com/entry/2017/05/30/090602
https://blog.npmjs.org/post/161081169345/v500
https://nodejs.org/ja/download/releases/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

実行できて見栄えも良いMarkdown手順書で快適な運用ライフを送ろう!

実行できる手順書(LC4RI)ってのご存じでしょうか?

ドキュメントとコマンドが混じって書いてあって、コマンド部分は実行できて、
その実行結果が載っています。
なので、手順書として読めて、かつ実行した内容とその時の出力がエビデンスとしてとれるもので
運用作業者にとってはExcel手順書の置き換えにぴったし
なんですね

そういう書き方が出来るツールとしてjupyter notebookが良く使われているんですけど、
これが運用してみるとドキュメントの並び替えとか、編集が意外と面倒だったりする。

もっと普通のエディタで書けたら・・そう思ってました。したらヒラめいた!

VSCodeのmarkdown編集機能を実行できるようにすれば良いんだ!!

つくってみたわ

lc4ri2.gif

VScodeのマーケットプレイスからインストールするならこちら

こっちGit。

つかいかた

基本は普通のmarkdownと同じ書きっぷりです。リスト水平線番号リストで変わります。

VSCodeのカーソル位置から下にあるコマンドを実行していきます
なので、実行したい部分までカーソルを持ってってこの拡張機能を動かしてください。
ショートカットキーに拡張機能の実行を割り当てると超便利ですよ!

リスト

1.png

実行したいコマンドを書きます。
タブでインデントをずらすとAND条件になります。
つまり、

  • - ls existsfile.txt
    • - rm existsfile.txt

でexistsfile.txtが無ければ削除しない、みたいな書き方ができます。

水平線

2.png

3.png

コマンドの実行範囲を区切ります。区切らないとリスト表記のを上から順に実行しちゃいます。

番号リスト

4.png

変数をもてます1.~9.の範囲で実行結果の出力を変数として格納します
重いコマンドとか何度も実行したくないものは結果を格納して使いまわせます。

あとがき

VSCodeのエコシステムが使えるので文章校正とか他の拡張機能の恩恵も受けれますね!
これで運用作業ドキュメントも見栄え良く、使いやすくなるものと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ウソ穴 Ver 6 の作り方

はじめに

個人開発ウソ穴の作り方を紹介します。

ウソ穴とは

ウソ穴は、ライブ映像 or 動画とARを組み合わせて、壁に穴が空いた錯覚を作り出します。Webサイトなので、ユーザーはアプリのインストール無くウソ穴を使用できます。

ウソ穴 Ver 6

今回は、Android端末でも動作実績のある Ver 6 を紹介します。

デモ映像

ウソ穴 Ver 6 Type B で顔に穴を開けてみました。

ウソ穴 Ver 6 Type A で壁に穴を開け、外のベランダの様子を見ました。

ウソ穴 Ver 6 Type A,B,C タイプ別の特徴

ウソ穴 Ver 6 は、TypeA,B,C の3種類あります。

  • ウソ穴 Type A / GStreamer
    • ライブ配信と組み合わせたウソ穴
    • ライブ配信はGStreamerを使用
    • 映像遅延 : ライブ配信の遅延に依存
  • ウソ穴 Type B / 時雨堂 sora
    • ライブ配信と組み合わせたウソ穴
    • ライブ配信は時雨堂さんの sora を使用
    • 映像遅延 : ほぼゼロ
  • ウソ穴 Type C / MP4動画
    • 動画と組み合わせたウソ穴
    • 映像遅延 : 動画ファイル読み込みの時間が必要

構成図

それぞれの構成図を紹介します。

ウソ穴 Ver 6 Type A / GStreamer

image.png

ウソ穴 Ver 6 Type B / 時雨堂 sora

image.png

ウソ穴 Ver 6 Type C / MP4動画

image.png

ソース

ソースは、githubに置きました。

作り方

作り方は、ウソ穴 Ver 5 と同じです。それぞれのリンクは以下です。

ウソ穴 Ver 6 Type A / GStreamer

作り方 -> ウソ穴 Ver 5 Type A / GStreamer

ウソ穴 Ver 6 Type B / 時雨堂 sora

作り方 -> ウソ穴 Ver 5 Type B / 時雨堂 Sora

ウソ穴 Ver 6 Type C / MP4動画

作り方 -> ウソ穴 Ver 5 Type C / MP4動画

line.png

ウソ穴の開発はつづく

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[メモ] TypeScript で hello-world!

<LABEL>-<MESSAGE> <LABEL>-<MESSAGE> <LABEL>-<MESSAGE>

はじめに

TypeScriptを仕事で使うことになったので最近勉強を始めました。
そのときのメモです。

TypeScriptをインストールする

公式サイトにインストールの手順が書いてあります。
自分の環境では以下のコマンドを使いました。
--save-devでTypeScriptコンパイラをローカル(プロジェクトフォルダ内)にインストールしています。

npm init -y
npm install typescript --save-dev

TypeScriptプロジェクトの設定 (tsconfig.json)

npx tsc --initを実行してtsconfig.jsonを作ります。
tsconfig.jsonにはコンパイラオプションなどの設定を書きます。
詳しくはこちらを参照。

tsconfig.json
{
    "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        "sourceMap": true
    }
}

インテリセンスが効いているのでCtr+Spaceで入力候補を表示してその中から選ぶと良さそうです。
image.png

ビルドタスクを構成する

ターミナル(T) -> 既定のビルドタスクの構成... から tsc: ビルド tsconfig.json を選択します。
ビルドタスクの設定(tasks.json)は.vscodeフォルダ中にあります。

image.png

image.png

.vscode>tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "type": "typescript",
            "tsconfig": "tsconfig.json",
            "problemMatcher": [
                "$tsc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "label": "tsc: ビルド - tsconfig.json"
        }
    ]
}

TypeScriptのコード

見ての通りです。
TypeScriptのことはまだよく分かっていないのでこれだけにしてきます。

main.ts
class Message {
    constructor(public message: string) {}

    greet() {
        return this.message;
    }
}

console.log(new Message("hello-world!").greet());

ビルド

Ctr+Shift+Bでビルドタスクを実行します。
プロジェクトフォルダ内のTypeScriptのコード(.ts)がJavaScriptのコードに変換され、デバッグ用のmapファイルが出力されます。

デバッグ

デバッグビュー > launch.jsonファイルを作成します。(リンク)からNode.jsを選ぶだけです。
他の言語の場合と大体同じ手順です。
デバッグ構成の詳細はこちらを参照。

image.png

.vscode>launch.json
{
    // IntelliSense を使用して利用可能な属性を学べます。
    // 既存の属性の説明をホバーして表示します。
    // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "プログラムの起動",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "${workspaceFolder}\\main.js"
        }
    ]
}

hello-worldを出力するファイルの名前をmain.tsにしたので"program"のファイル名をデフォルトの"index.js"から"main.js"に変更しました。

これでプログラムの起動、デバッグができます。
image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

気象庁アメダス観測データのAPI「JJWD」をきちんと作り直した

長らく停止しておりましたが2020年9月、再始動しました。
https://jjwd.info/
image.png

JJWD とは / 作り直した動機

JSONized Japanse Weather Dataの略で、気象庁が公開しているCSV形式のアメダス観測値データを使いやすいJSON形式に変換して提供するAPIサービスです。

当初は2017年のアドベントカレンダーに間に合わせるために構築したAPIでした。

停止したにもかかわらず、記事にコンスタントにLGTMがついておりました。
需要はあるものを放置したくはないですし、データを必要としている人も多いと思ったのできちんと使える形で整備した次第です。

image.png

使い方

使用に際して

気象業務法に抵触しない範囲で使用してください。
観測された値を基に何らかの処理を行って独自の予測を行う場合、資格ならびに気象庁の許可が必要です(観測されたままの値や、発表された気象予報を第三者に伝達することは問題ありません)。

また、気象庁以外が「警報」「注意報」などを発表することは許可されていません。

観測所検索

GET https://jjwd.info/api/v2/stations/search?

URL に以下のパラメータを付与して検索できます。

  • stn_name_ja: 観測所名 日本語(部分一致)
  • stn_name_en: 観測所名 英語(部分一致)
  • pref_ja: 都府県振興局 日本語(部分一致)
  • pref_en: 都府県振興局 英語(部分一致)
  • address: 住所 日本語(部分一致)

たとえば
https://jjwd.info/api/v2/stations/search?pref_ja=東京&address=世田谷
では、都府県振興局に 東京 を含み、なおかつ住所に 世田谷 を含む観測所が取得できます。

北海道は振興局単位なのでご注意ください。

/api/v2/stations/search?pref_ja=東京&address=世田谷
{
    "about": "JJWD - JSONized Japanese Weather Data:  https://jjwd.info/",
    "datasource": "https://www.data.jma.go.jp/obd/stats/data/mdrr/",
    "author": "Original data are from Japan Meteorological Agency. API data are modified by jjwd.info .",
    "version": "v2.0",
    "stations": [
        {
            "stn_num": 44126,
            "pref_ja": "東京",
            "pref_en": "Tokyo",
            "stn_type": "RobotRain",
            "stn_name_ja": "世田谷",
            "stn_name_ja_kana": "セタガヤ",
            "stn_name_en": "SETAGAYA",
            "stn_multipoint": false,
            "target_main_ja": null,
            "target_sub_ja": null,
            "target_main_en": null,
            "target_sub_en": null,
            "stn_temp": false,
            "stn_daylight": true,
            "address": "世田谷区岡本",
            "address_sub": null,
            "lat": 35.6267,
            "lng": 139.62,
            "lat_sub": null,
            "lng_sub": null,
            "elevation": 35,
            "alt_anemometer": null,
            "alt_thermometer": null,
            "elevation_sub": null,
            "alt_anemometer_sub": null,
            "alt_thermometer_sub": null,
            "start_date_rain": "1974-11-01",
            "start_date_multi": null,
            "snow_stn_num": null,
            "updatedAt": "2020-09-11T19:00:32.933Z",
            "preall": {
                "year": 2020,
                "month": 9,
                "day": 12,
                "hour": 19,
                "minute": 50,
                "precip_1h_new_record_month": null,
                "precip_1h_new_record_in_decade": null,
                "precip_3h_new_record_month": null,
                "precip_3h_new_record_in_decade": null,
                "precip_6h_new_record_month": null,
                "precip_6h_new_record_in_decade": null,
                "precip_12h_new_record_month": null,
                "precip_12h_new_record_in_decade": null,
                "precip_24h_new_record_month": null,
                "precip_24h_new_record_in_decade": null,
                "precip_48h_new_record_month": null,
                "precip_48h_new_record_in_decade": null,
                "precip_72h_new_record_month": null,
                "precip_72h_new_record_in_decade": null,
                "precip_daily_new_record_month": null,
                "precip_daily_new_record_in_decade": null,
                "precip_1h": 0.5,
                "precip_1h_q": 8,
                "precip_1h_daily_max": 13,
                "precip_1h_daily_max_q": 5,
                "precip_3h": 0.5,
                "precip_3h_q": 8,
                "precip_3h_daily_max": 24,
                "precip_3h_daily_max_q": 4,
                "precip_6h": 1,
                "precip_6h_q": 8,
                "precip_6h_daily_max": 24,
                "precip_6h_daily_max_q": 4,
                "precip_12h": 5,
                "precip_12h_q": 8,
                "precip_12h_daily_max": 26.5,
                "precip_12h_daily_max_q": 4,
                "precip_24h": 29,
                "precip_24h_q": 8,
                "precip_24h_daily_max": 29,
                "precip_24h_daily_max_q": 4,
                "precip_48h": 29,
                "precip_48h_q": 8,
                "precip_48h_daily_max": 29,
                "precip_48h_daily_max_q": 4,
                "precip_72h": 29.5,
                "precip_72h_q": 8,
                "precip_72h_daily_max": 29.5,
                "precip_72h_daily_max_q": 4,
                "precip_daily": 28.5,
                "precip_daily_q": 4,
                "updatedAt": "2020-09-12T11:13:41.878Z"
            },
            "max_wind": null,
            "max_gust": null,
            "max_temp": null,
            "min_temp": null
        }
    ]
}

観測所番号を指定して取得

GET https://jjwd.info/api/v2/station/{stn_num}

観測所番号がわかっている場合はこちらを使用することができます。

/api/v2/station/46046
{
    "about": "JJWD - JSONized Japanese Weather Data:  https://jjwd.info/",
    "datasource": "https://www.data.jma.go.jp/obd/stats/data/mdrr/",
    "author": "Original data are from Japan Meteorological Agency. API data are modified by jjwd.info .",
    "version": "v2.0",
    "station": {
        "stn_num": 46046,
        "pref_ja": "神奈川",
        "pref_en": "Kanagawa",
        "stn_type": "RobotRain",
        "stn_name_ja": "相模原中央",
        "stn_name_ja_kana": "サガミハラチュウオウ",
        "stn_name_en": "SAGAMIHARACHUO",
        "stn_multipoint": false,
        "target_main_ja": null,
        "target_sub_ja": null,
        "target_main_en": null,
        "target_sub_en": null,
        "stn_temp": false,
        "stn_daylight": true,
        "address": "相模原市中央区中央",
        "address_sub": null,
        "lat": 35.5717,
        "lng": 139.37,
        "lat_sub": null,
        "lng_sub": null,
        "elevation": 149,
        "alt_anemometer": null,
        "alt_thermometer": null,
        "elevation_sub": null,
        "alt_anemometer_sub": null,
        "alt_thermometer_sub": null,
        "start_date_rain": "1975-05-16",
        "start_date_multi": "1975-05-16",
        "snow_stn_num": null,
        "createdAt": "2020-08-29T15:44:50.463Z",
        "updatedAt": "2020-09-11T19:00:32.937Z",
        "preall": {
            "year": 2020,
            "month": 9,
            "day": 12,
            "hour": 19,
            "minute": 50,
            "precip_1h_new_record_month": null,
            "precip_1h_new_record_in_decade": null,
            "precip_3h_new_record_month": null,
            "precip_3h_new_record_in_decade": null,
            "precip_6h_new_record_month": null,
            "precip_6h_new_record_in_decade": null,
            "precip_12h_new_record_month": null,
            "precip_12h_new_record_in_decade": null,
            "precip_24h_new_record_month": null,
            "precip_24h_new_record_in_decade": null,
            "precip_48h_new_record_month": null,
            "precip_48h_new_record_in_decade": null,
            "precip_72h_new_record_month": null,
            "precip_72h_new_record_in_decade": null,
            "precip_daily_new_record_month": null,
            "precip_daily_new_record_in_decade": null,
            "precip_1h": 0,
            "precip_1h_q": 8,
            "precip_1h_daily_max": 10,
            "precip_1h_daily_max_q": 5,
            "precip_3h": 0.5,
            "precip_3h_q": 8,
            "precip_3h_daily_max": 10.5,
            "precip_3h_daily_max_q": 4,
            "precip_6h": 2,
            "precip_6h_q": 8,
            "precip_6h_daily_max": 13.5,
            "precip_6h_daily_max_q": 4,
            "precip_12h": 5,
            "precip_12h_q": 8,
            "precip_12h_daily_max": 16,
            "precip_12h_daily_max_q": 4,
            "precip_24h": 18.5,
            "precip_24h_q": 8,
            "precip_24h_daily_max": 18.5,
            "precip_24h_daily_max_q": 4,
            "precip_48h": 18.5,
            "precip_48h_q": 8,
            "precip_48h_daily_max": 18.5,
            "precip_48h_daily_max_q": 4,
            "precip_72h": 19,
            "precip_72h_q": 8,
            "precip_72h_daily_max": 19,
            "precip_72h_daily_max_q": 4,
            "precip_daily": 17.5,
            "precip_daily_q": 4,
            "updatedAt": "2020-09-12T11:13:41.884Z"
        },
        "max_wind": null,
        "max_gust": null,
        "max_temp": null,
        "min_temp": null
    }
}

各パラメータの意味など、詳細はドキュメントをご参照ください
https://jjwd.info/doc-ja.html

データソース

基地局データ

気象庁の地域気象観測所一覧 [ZIP圧縮形式]から取得しています。
ZIP形式の中にCSVが格納されているのですが、開発中に提供されているファイルの名前が変わってしまい、取得できなくなるエラーが発生しました(気象庁さん、配布するファイルの名称は固定してほしい……)。

観測値データ

「最新の気象データ」CSVダウンロードから取得しています。データのつらさは相変わらずです。その辺は前回と同じようになんとかしました。

実装

Node.js + Express.js + Sequelize の無難な構成です。

ざっくりとしたディレクトリ構成
/
| - /schedule
| - | - fetch_amedas_stations.js
| - | - fetch_csv_files.js
| - /models
| - /public
| - | - /css
| - | - /js
| - app.js
| - package.json

データベース

Heroku Postgresを使用しています。件数的にはHobby devに収まるので無料の枠内で運用しています。

アクセス数が増えたら Connection Limit などを緩和するために Standard 0 などにアップグレードする必要がありそうです(Hobby Basicは行数の制限緩和のみなので)。

データの更新

Heroku Scheduler でNodeのスクリプトを走らせてUpsertしています。
Heroku Scheduler を使うと、Herokuにデプロイした repository 上にあるスクリプトを定時に走らせる事が可能です。DBのモデルやnpmパッケージも本体と共用できるので便利です。

観測値データの更新は 10 分おきに取得しています。
基地局データの更新は 1 日おきに取得しています。ただし、生の CSV データが配布されている観測値と異なり、基地局データは元データが zip で圧縮されているので /tmp にデータをダウンロードした上で展開する処理を挟んでいます(Heroku では /tmp/log 以外にファイルを書き込めないので要注意です)。

静的ページ(ドキュメント)

シンプルに app.use(express.static(__dirname + '/public')); で配信しています。

デザインには Tailwind.css を使用しています。
便利なCSSフレームワークですが、何もしないとファイルサイズが巨大なので、デプロイ前にpurgeやminifyしましょう。

また、軽くCSSアニメーションを入れたかったのでanimistaを使用しています。

デプロイ

Heroku を使用しています。
独自ドメインを使用したかったので、いまのところ月額7ドルの Hobby Dyno を使用しています。

独自ドメイン

公式ドキュメントを参考に、バリュードメインで以下のように CNAME で設定しています。

ドメイン設定
cname @ ****************.herokudns.com.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Jestとpuppeteerで複数ページへの同じテストをすっきりまとめたサンプル

Jestとpuppeteerでe2eテストを書いています。大量のページに対してページのtitleをチェックしています。配列に対象ページのURLとtitleをまとめると、すっきり書けたのでメモしておきます。

配列

検査したい要素、titleとurlをまとめて指定しています。

const pages = [
  {
    'title': 'はじめに - Bootstrap 4.5 - 日本語リファレンス',
    'url': 'https://getbootstrap.jp/docs/4.5/getting-started/introduction/',
  },
  {
    'title': 'ダウンロード - Bootstrap 4.5 - 日本語リファレンス',
    'url': 'https://getbootstrap.jp/docs/4.5/getting-started/download/',
  },
  {
    'title': 'ファイル構成 - Bootstrap 4.5 - 日本語リファレンス',
    'url': 'https://getbootstrap.jp/docs/4.5/getting-started/contents/',
  },
];

テスト部分

testを配列のループで囲んでいます。ページを取得してtitleを照合しています。

  for (const i in pages) {
    const title = pages[i].title;
    const url = pages[i].url;
    it('should be titled "' + title + '"', async () => {
      await page.goto(url);
      await expect(page.title()).resolves.toMatch(title);
    });
  }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Jestとpuppetterで繰り返しテストをarrayにまとめたサンプル

Jestとpuppeteerでe2eテストを書いています。大量のページに対してページのtitleをチェックしています。配列に対象ページのURLとtitleをまとめると、すっきり書けたのでメモしておきます。

配列

検査したい要素、titleとurlをまとめて指定しています。

const pages = [
  {
    'title': 'はじめに - Bootstrap 4.5 - 日本語リファレンス',
    'url': 'https://getbootstrap.jp/docs/4.5/getting-started/introduction/',
  },
  {
    'title': 'ダウンロード - Bootstrap 4.5 - 日本語リファレンス',
    'url': 'https://getbootstrap.jp/docs/4.5/getting-started/download/',
  },
  {
    'title': 'ファイル構成 - Bootstrap 4.5 - 日本語リファレンス',
    'url': 'https://getbootstrap.jp/docs/4.5/getting-started/contents/',
  },
];

テスト

testを配列のループで囲んでいます。ページを取得してtitleを照合しています。

  for (const i in pages) {
    const title = pages[i].title;
    const url = pages[i].url;
    it('should be titled "' + title + '"', async () => {
      await page.goto(url);
      await expect(page.title()).resolves.toMatch(title);
    });
  }

コード全体

loop.test.js
const pages = [
  {
    'title': 'はじめに - Bootstrap 4.5 - 日本語リファレンス',
    'url': 'https://getbootstrap.jp/docs/4.5/getting-started/introduction/',
  },
  {
    'title': 'ダウンロード - Bootstrap 4.5 - 日本語リファレンス',
    'url': 'https://getbootstrap.jp/docs/4.5/getting-started/download/',
  },
  {
    'title': 'ファイル構成 - Bootstrap 4.5 - 日本語リファレンス',
    'url': 'https://getbootstrap.jp/docs/4.5/getting-started/contents/',
  },
];

describe('LOOP', () => {
  beforeAll(async () => {
    await page.setDefaultNavigationTimeout(0);
  });
  for (const i in pages) {
    const title = pages[i].title;
    const url = pages[i].url;
    it('should be titled "' + title + '"', async () => {
      await page.goto(url);
      await expect(page.title()).resolves.toMatch(title);
    });
  }
});

実行結果

% jest loop.test.js
 PASS  ./loop.test.js
  LOOP
    ✓ should be titled "はじめに - Bootstrap 4.5 - 日本語リファレンス" (696 ms)
    ✓ should be titled "ダウンロード - Bootstrap 4.5 - 日本語リファレンス" (331 ms)
    ✓ should be titled "ファイル構成 - Bootstrap 4.5 - 日本語リファレンス" (515 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        2.251 s, estimated 9 s
Ran all test suites matching /loop.test.js/i.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Jestとpuppetterで同じテストをarrayにまとめたサンプル

Jestとpuppeteerでe2eテストを書いています。大量のページに対してページのtitleをチェックしています。配列に対象ページのURLとtitleをまとめると、すっきり書けたのでメモしておきます。

配列

検査したい要素、titleとurlをまとめて指定しています。

const pages = [
  {
    'title': 'はじめに - Bootstrap 4.5 - 日本語リファレンス',
    'url': 'https://getbootstrap.jp/docs/4.5/getting-started/introduction/',
  },
  {
    'title': 'ダウンロード - Bootstrap 4.5 - 日本語リファレンス',
    'url': 'https://getbootstrap.jp/docs/4.5/getting-started/download/',
  },
  {
    'title': 'ファイル構成 - Bootstrap 4.5 - 日本語リファレンス',
    'url': 'https://getbootstrap.jp/docs/4.5/getting-started/contents/',
  },
];

テスト部分

testを配列のループで囲んでいます。ページを取得してtitleを照合しています。

  for (const i in pages) {
    const title = pages[i].title;
    const url = pages[i].url;
    it('should be titled "' + title + '"', async () => {
      await page.goto(url);
      await expect(page.title()).resolves.toMatch(title);
    });
  }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

かんたん Appleのヘルスデータをエクスポート、解析、csvに変換する方法

はじめに

みなさんは iOS アプリ Healthをつかってますか?
そんなみなさんは、体重や睡眠時間、歩数などの健康データはApple Healthのアプリ上に記録されていると思います。
このアプリは、健康状態の情報を記録し、アプリ上ではみえますが、実際には自分でデータ分析を行うことはできません。
データ分析とデータの可視化にはいろんな形式があると思いますが。今回はExcelやGoogle Sheetsのようなスプレッドシートアプリケーションを使って、解析するためにcsv 出力したいです。
今回、そんなみなさんにぴったりのCLIを作ったのでご紹介させていただきます.

Apple health dataの抽出方法

まずはこの動画を開いてみてください

  • iPhoneでHealthを開きます。
  • 上隅にあるプロフィールのアイコンをタップすしてください。
  • ヘルスのプロフィールの一番下までスクロールして、"Export All Health Data"をタップします。
  • 更に"Export"をタップすると、データをエクスポートすることを確認してエクスポート処理を開始します(完了するまでに少し時間がかかる場合があります)
  • 上記で抽出したファイルをローカルやGoogle Driveに保存してください

抽出したファイル

image.png
抽出したなら、その中の export.xml を使います

Nodejs環境設定

本当の初心者のためのNode.js超入門 ~環境構築編~
などを参考にローカルにNodejsの環境を構築してください

コマンドインストール方法

下記のコマンドを叩いてahcdというコマンドをインストールしてください

$ npm i -g ahcd

使い方

$ ahcd                                                                                                                                                                                                
================================================================================
Apple Health Care Data convert xml to csv

Author     : Fumikazu Fujiwara 
Homepage   : https://github.com/freddiefujiwara/ahcd#readme
LICENSE    : MIT
Report bugs: https://github.com/freddiefujiwara/ahcd/issues
================================================================================

Usage: ahcd [-h] <file> [-t <type>] [-d <dir>]
  • 引数の<file>は必ずexport.xmlを指定してください
  • -t は特定のcsvだけ出力します (例えば -t BodyMass など)
  • -d は出力先のディレクトリを指定します (例えば -d /path/to など)

実際に使ってみると

$ ahcd -d . export.xml                                                                                                                                                                                
Read export.xml
Analyze export.xml
Wrote ./Height.csv (1 records)
Wrote ./HeartRate.csv (87 records)
Wrote ./BodyMassIndex.csv (50 records)
Wrote ./BloodPressureDiastolic.csv (165 records)
Wrote ./BodyMass.csv (51 records)
Wrote ./BodyFatPercentage.csv (50 records)
Wrote ./FlightsClimbed.csv (1045 records)
Wrote ./BloodPressureSystolic.csv (165 records)
Wrote ./SleepAnalysis.csv (1193 records)
Wrote ./StepCount.csv (12032 records)
Wrote ./DistanceWalkingRunning.csv (13631 records)

最後に

さぁ ちょっと長かったですが、
これで、Excelなどのシートで解析できますね
またahcdpull request大歓迎です
よろしくおねがいします

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む