- 投稿日:2020-03-24T21:11:04+09:00
[Node.js] request モジュール がDeprecated になった
- 投稿日:2020-03-24T21:11:04+09:00
[Node.js] request モジュール がDeprecated になっていた
内容
As of Feb 11th 2020, request is fully deprecated. No new changes are expected to land. In fact, none have landed for some time.
- 2020年2月11日をもってDeprecatedになったようです。
- 下記のモジュールについても
request
と依存関係があることから非推奨となりました。- 代替手段についてはこのissueで議論されています。
参考ページ
- 投稿日:2020-03-24T20:11:02+09:00
三井住友銀行口座の残高を自動取得する(Puppeteerで)
やりたいこと・背景
- 三井住友銀行のインターネットバンキング SMBCダイレクトで口座にログインし、現在の口座残高を取得
- 他行では個人向けのAPIが公開されていたりする(個人で動作を試せる銀行系オープンAPIまとめ等参照)が、三井住友銀行では現在法人向けしかないようす
- MoneyForwardやZaim等のFinTechサービスのAPIを使って取得することはできるかもしれませんが、それらのサービスを日常的には利用していないため、調べていません
- 三井住友銀行のLINE公式アカウントに友だち登録してID連携すると、チャット形式で残高が取得できます。この記事ではよりプログラマブルに取得したい場合を想定しています。たとえば「オレオレ家計簿APIみたいなものをつくりたい」や「残高が一定金額以上(以下)になったら通知させたい」といった応用ができます
使用するもの
- Node(v12.16.1)
- npm (v6.13.4)
- Puppeteer (v2.1.1):ヘッドレスブラウザを立ち上げてスクレイピングなどに使えるNodeライブラリ
手順
- すぐに自分自身の口座で試したい場合はソースコードからREADMEに沿って進めればOKです
- https://github.com/embokoir/puppeteer-smbc
- 以下はコマンドで進める場合
$ mkdir puppeteer-smbc $ cd puppeteer-smbc $ npm init -y $ npm install puppeteer dotenv $ touch server.js .env
.env
(000...
を自分のものに書き換える)BRANCH_CODE=000 #支店名3桁 ACCOUNT_NUMBER=0000000 #口座番号7桁 PASSWORD=0000 #第一認証4桁
server.js
(そのまま)const puppeteer = require('puppeteer') const dotenv = require('dotenv') dotenv.config() const BRANCH_CODE = process.env.BRANCH_CODE const ACCOUNT_NUMBER = process.env.ACCOUNT_NUMBER const PASSWORD = process.env.PASSWORD; (async () => { // validate .env if (!BRANCH_CODE || !ACCOUNT_NUMBER || !PASSWORD) { console.log('Invalid Try. Make sure to create a file ".env" and write your BRANCH_CODE / ACCOUNT_NUMBER / PASSWORD') return } // launch browser console.log('launching browser...') const browser = await puppeteer.launch({ headless: false, args: ['--no-sandbox'] }) // go to page const page = await browser.newPage() await page.setViewport({ width: 1440, height: 2000 }) await page.setExtraHTTPHeaders({ 'Accept-Language': 'ja' }) const LOGIN_URL = 'https://direct.smbc.co.jp/aib/aibgsjsw5001.jsp' await page.goto(LOGIN_URL, { waitUntil: 'domcontentloaded' }) // set input-data then submit await page.type('input[name=S_BRANCH_CD]', BRANCH_CODE) await page.type('input[name=S_ACCNT_NO]', ACCOUNT_NUMBER) await page.type('input[name=PASSWORD]', PASSWORD) await Promise.all([ page.waitForNavigation({ waitUntil: 'networkidle0' }), page.click('input[type=submit]') ]).catch(async err => { console.log(err.response) process.exit(1) }) // redirect to balance-page then get balance await page.goto('https://direct3.smbc.co.jp/servlet/com.smbc.SUPRedirectServlet') const balance = await page.$eval('.fRight', elm => elm.textContent.replace(/\s/g, '')) .catch(async err => { console.log(err) process.exit(1) }) console.log(`Your current balance is ${balance}`) // close browser await browser.close() })()実行(
headless: false
なのでブラウザが立ち上がる)$ node server.js
注意
- 正しく
.env
を設定する。第一認証を一定回数以上間違えるとインターネットバンキングのロックがかかるようです- 入力部分のセレクタは2020/3/24現在のものです。サイトのHTML構造が変更したら適宜こちらも書き換える
おわり
- 投稿日:2020-03-24T19:34:09+09:00
Puppeteerなら、Google App Engine Node.jsでスクレイピングしてスプレッドシート(GAS経由)にデータを蓄積するのが無料だよ!
以下の記事の概要です。
- 日経225オプションのデータを入手したい。(理由は、分析して儲けるため)
- 私が契約している証券会社は、Javascriptでデータを取得するタイプのサイトなので、Headless Chromeの利用が必須。
- これまでの経験で、Google App Engine(python)は使えるが、それ以外はハードル高い。
- Cloud Runに手を出そうとしたが、ちょっとハードルが高かった。
- Google App Engineだと、Javascriptでデータ取得するタイプのサイトにスクレイピングするのは、無理。
- と思っていたら、Puppeteerなるツールがあって、それを使えば、Google App Engineで運用できると最近知った。
- てことで、Puppeteerでアプリを作って、Google App Engine Node.js(GAE)で運用しようということになった。
- GAEなら、cronで定期実行可能なので、定期的にデータを取得できる。
- 定期的にデータが取得できるなら、Googleスプレッドシートへ書き込んでおけば、後から分析可能だよね。
- 分析する時は、BigQueryが有るから、楽勝じゃん。
- という思いつきで、だいたい1ヶ月(2月半ばから3月半ばまで)くらいかかって、作ったのが以下のシステムです。(1ヶ月もかかってこれかよ?ってツッコミは無しで。。。。褒めて育つタイプなので。)
- 本業でフロントエンドをjavascriptで書いてる人なんて、こんなツールで、自動テストとかして、爆速で自動化してるんだろうなーーー(遠い目)。
- 2020/1月後半から、コロナウイルス大流行で、日経平均爆下げ状態。(プロはこんな時こそボロ儲けのようです。:私じゃない。涙)
- もう少し、安定してる時が良かったけど、仕方がない。
- データをためて、頑張って分析しよう!!
- もうちょっとこうした方が、ええ感じやで等、ゆるめのツッコミ歓迎です。
- ちなみに、私はエンジニアではなく、ただのサンデープログラマかつ、コピペプログラマです。自分に必要な事しか知らないです。
前提事項
puppeteerをスクレイピング用アプリとして利用します。(言語はNode.js(javascript)です。)
- Google App Engine(GAE) Node.jsでスクレイピングする為にpuppeteerが必要です。
- GAEでは、特殊なメソッドなどを利用することが多いですが、ほぼpuppeteerだけで動きます。
- puppeteerが、ブラウザを動かし、ブラウザ経由でWEBサイトにアクセスするので、GAEの特殊メソッドが不要です。
Google App Engine Node.jsをスクレイピングの基盤として利用します。
- ここチェックしてくださいね。無料で利用できる枠の説明です。Google Cloud Platform の無料枠
- Google App Engine Node.jsは、Cloud Buildが必要で、Cloud Buildを利用するには、課金が必要です。
- Cloud Buildは、コンテナをビルドするためのツール的なものだと思われます。(分かってません。)
- 詳しくは、こちら超入門!GCP のCIツール、Cloud Build でリリースサイクル高速化!!
- Cloud Buildに課金は必要ですが、無料で利用可能です。(ただし、操作が必要です。操作方法は後ほど出てきます。)
- Cloud Buildを利用するってことは、自分ではpuppeteerアプリを書いてるけど、AppEngine上ではコンテナで動いてるはず。
- コンテナの知識不要だから、Cloud Run よりもハードルが低いです。しかも、軽い処理ならだいたい無料で動きます。
Google Apps Scriptを利用します。
- puppeteerでスクレイピングしたデータを受け取る処理をします。
- 受け取ったデータをGoogleスプレッドシートへ書き込みます。
Googleスプレッドシートを利用します。
- puppeteerでスクレイピングしたデータをGoogle Apps Scriptを通じてGoogleスプレッドシートへ蓄積していきます。
- 最終的に、BigQueryで分析して、投資で儲けます。(ホンマか?とつっこむところ!)
- ちなみに、BigQueryは、SQLが分かれば使えます。クレジットカードの登録は必要ですが、趣味程度で金払うことは多分無いです。(自己責任でよろしく)
- Googleスプレッドシートにデータを入れておけば、BigQueryを利用する際のデータの投入が簡単です。(ここでは触れません。)
私のローカル環境は、ChromeBook(HP CromeBook x360)です。
- Visual Studio Code 1.41.1
- linuxは、Debian 9.11
- Node.js v12.14.0
- npm 6.14.3
- これを書いた日:2020/3/24
puppeteer(Node.js)の準備
- とりあえずは、ローカル環境でやってみる。
- うまくいったら、Cloud Shellで実行してみる。
- ホームディレクトリで、手動で以下を実施する。
- (もしかして、以下の手動コマンドが無理なら、更に下のinstall.shを先に実行したらいいかも。)
mkdir ppt cd ppt npm init -y
- 上記コマンドで、package.jsonができる。
- 以下のように、"scripts": { の中に、"start": "node app.js",を追加してpackage.jsonを保存する。
package.json{ "name": "ppt", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "start": "node app.js", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }
現時点で、lsすると、package.jsonだけがあります。
次に、install.shを現在のディレクトリに作成します。
touch install.sh とかで空ファイル作ってもいいし、エディタで作ってもいいですね。
install.shに以下コードを貼り付ける。 echoうるさめです。
install.sh#!/bin/bash echo 'Cloud Shell では、毎回以下のエラーが出る' echo '(node:791) UnhandledPromiseRejectionWarning: Error: Failed to launch the browser process!' echo '/home/あなたのID/ppt/node_modules/puppeteer/.local-chromium/linux-722234/chrome-linux/chrome: error while loading shared libraries: libXss.so.1: cannot open shared object file: No such file or directory' echo '回避するために、以下のsudo apt-get install libxss1 を実行する' echo '面倒なので、いろいろまとめてこのシェルを実行する' echo ----------------- echo ----------------- echo ----------------- echo npm install request npm install request echo ----------------- echo ----------------- echo ----------------- echo npm install date-fns-timezone npm install date-fns-timezone echo ----------------- echo ----------------- echo ----------------- echo npm install -g npm npm install -g npm echo ----------------- echo ----------------- echo ----------------- echo npm install express puppeteer --save npm install express puppeteer --save echo ----------------- echo ----------------- echo ----------------- echo sudo apt-get update sudo apt-get update echo ----------------- echo ----------------- echo ----------------- echo sudo apt-get install libxss1 sudo apt-get install libxss1 echo ----------------- echo ----------------- echo ----------------- echo cd /home/あなたのID/ppt/node_modules/puppeteer/.local-chromium/linux-722234/chrome-linux cd /home/あなたのID/ppt/node_modules/puppeteer/.local-chromium/linux-722234/chrome-linux echo ----------------- echo ----------------- echo ----------------- echo 'ldd chrome | grep not' echo 'libXss.so.1 => not found <= echo '↑↑これがでないように sudo apt-get install libxss1 した' echo 'だから、下の命令の結果は、何も表示されない' ldd chrome | grep not
- 実行は、 ./install.sh
- 実行できない時は、以下のコマンドを試すべし(実行権限追加)
- sudo chmod +x install.sh
- Croud Shellでは起動のたびに実行しないとエラーが発生したので、めんどくさくて作成した。
- シェル実行後にlsすると、install.sh node_modules package.json package-lock.json がある。
- 最後に、app.jsを作成し、以下の「puppeteerのソースコード」を貼り付ける。
- 「puppeteerのソースコード」の、あなたのID、あなたのパスワード、証券会社のURL、GASで発行したURLを自分の物に書き換えれば、動くはず。
- GASで発行したURLは、後ほど出てきます。
- 証券会社名は、ヒントが有るのですぐにわかりますよね。
puppeteerのソースコード
app.js//Node.js //var、let、constの使い方が適当なので、ええ感じに書き換えてね! async function run(){ const user_id = '';//s??証券のあなたのID const user_pass = '';//s??証券のあなたのパスワード const shouken_url = '';//証券会社のURL(s??証券) const spreadsheet_url = '';//GASで発行したURL const pptr = require('puppeteer'); var now1 = new Date(); const outtime = 80000; const waittime = 8000; // ブラウザを起動する const browser = await pptr.launch({ // headless: false,//GAEにデプロイする時、CloudShellではコメントにして、ブラウザ非表示にする。ローカルテストの時はコメント外して、ブラウザの動きを確認してデバッグする。 // 目視確認用に操作遅延(ms)これを入れておくと、間違いが少ない "slowMo" : 10, args: [ // デフォルトでは言語設定が英語なので日本語に変更 '--lang=ja,en-US,en', // Chromeウィンドウのサイズ '--window-size=1200,800', // Chromeウィンドウのポジション '--window-position=10,10', '--no-sandbox', ] }) try { // ページつくる const page = await browser.newPage() page.setDefaultNavigationTimeout('120000') await page.setViewport({ width: 1200, height: 800 }) let navigationPromise = page.waitForNavigation() await page.goto(shouken_url, { timeout: outtime }) console.log('証券会社 WEBサイトサイトへアクセス') await page.waitForSelector('.sb-box-sub-02-content ', {visible: true , timeout: outtime }) await page.waitForSelector('.sb-box-sub-02-content > dl > dd > #user_input > input', {visible: true}) await page.click('.sb-box-sub-02-content > dl > dd > #user_input > input') await page.type('.sb-box-sub-02-content > dl > dd > #user_input > input', user_id) await page.waitForSelector('.sb-box-sub-02-content > dl > dd > #password_input > input') await page.click('.sb-box-sub-02-content > dl > dd > #password_input > input') await page.type('.sb-box-sub-02-content > dl > dd > #password_input > input', user_pass) await page.waitForSelector('.sb-box-sub-02-inner > .sb-box-sub-02-content > .sb-position-c > a > .ov') await page.click('.sb-box-sub-02-inner > .sb-box-sub-02-content > .sb-position-c > a > .ov' , { timeout: outtime }) console.log('クリック:ログイン') await navigationPromise navigationPromise = page.waitForNavigation() await page.waitForSelector('#navi01P', {timeout: outtime}) await page.click('#navi01P > ul > li:nth-child(8) > a > img', { timeout: outtime }) console.log('クリック:先物・オプション') await navigationPromise //参考記事 //Puppeteerで次ページへの遷移を待つ (別タブや別ウインドウの遷移を待つ) //https://qiita.com/hnw/items/a07e6b88d95d1656e02f const newPagePromise = new Promise( resolve => browser.once('targetcreated', target => resolve(target.page()))); console.log('waitFor : ' + waittime) await page.waitFor( waittime ) await page.waitForSelector('.md-t-box-btn-01-inner > .floatR > p > a > img') page.click('.md-t-box-btn-01-inner > .floatR > p > a > img', { timeout: outtime }) console.log('クリック:先物・オプション取引サイト') const newPage = await newPagePromise; await newPage.setViewport({ width: 1200, height: 800 }) //newPage.waitFor(waittime)しないと、frameだけしかない状態で次の処理をしてエラーになるのを防ぐため。 //もっとカッコいい方法があるはず!! console.log('waitFor : ' + waittime) await newPage.waitFor(waittime) await newPage.waitFor('frameset > frame', {timeout: outtime}); //先物・オプション取引サイトは、frameset > frame > frameset > frameの構造 //Frameの位置を正しく認識させる //画面上メニューの「取引」をクリックしたい //トップframeset下の0番目のframeを選し、その下のframeset下の1番目のframeを選択する //具体的にはココ! let frame_menu = await newPage.frames()[0].childFrames()[0].childFrames()[1] //frameが選択できたので、idの#menu20をクリック //idのみでクリックできる場合は、これで良い frame_menu.click('#menu20') console.log('クリック:取引') await newPage.waitFor('frameset > frame', {timeout: outtime}); console.log('waitFor : ' + waittime) await newPage.waitFor(waittime) //取引−オプション新規注文 をクリック await click_submenu(newPage,outtime,waittime); //取引−オプション新規注文画面にいる状態 //取引−オプション新規注文画面のframeに移動した状態 let frame_main = await newPage.frames()[0].childFrames()[2].childFrames()[1] //「20000より上」「17000より下」のボタンが有るか確認する let path_down = '/html/body/div[1]/table/tbody/tr/td[3]/span/span/form/table[2]/tbody/tr[2]/td/table/tbody/tr/td/table[1]/tbody/tr[4]/td[1]/input' let path_up = '/html/body/div[1]/table/tbody/tr/td[3]/span/span/form/table[2]/tbody/tr[2]/td/table/tbody/tr/td/table[1]/tbody/tr[2]/td[2]/input' let link_down = await frame_main.$x(path_down) let link_up = await frame_main.$x(path_up) var ymd = await yymmdd(); if(link_up.length != 0){ //「20000より上」があれば、上ページのデータを取得 await click_next(newPage,path_up,outtime,waittime); //画面に表示されるデータを取得する let textdata = await getTable(newPage,ymd); //取得したデータをスプレッドシートへ書きこむ await postSpreadsheet(textdata,spreadsheet_url); } //取引−オプション新規注文 をクリック //最初に開く画面へ移動 await click_submenu(newPage,outtime,waittime); //画面に表示されるデータを取得する let textdata = await getTable(newPage,ymd); //取得したデータをスプレッドシートへ書きこむ await postSpreadsheet(textdata,spreadsheet_url); if(link_down.length != 0){ await click_next(newPage,path_down,outtime,waittime); let textdata = await getTable(newPage,ymd); await postSpreadsheet(textdata,spreadsheet_url); } var now2 = new Date(); var now11 = now1.getMinutes() * 60 + now1.getSeconds() var now21 = now2.getMinutes() * 60 + now2.getSeconds() console.log('time : ' + (now21 - now11) ); await browser.close() console.log('Exit') console.log('Exit') }catch (e){ //失敗時、すぐに止めたい。GAEのインスタンスを無駄に使わない await browser.close() console.log('ERR') console.log('ERR') console.log(e) logError(e) }//try { }//async function run(){ async function click_submenu(newPage,outtime,waittime){ let frame_menu2 = await newPage.frames()[0].childFrames()[0].childFrames()[1] let frame_submenu = await frame_menu2.$x('/html/body/form/div/table/tbody/tr/td/table[2]/tbody/tr/td[2]/span/a[2]') // ↑await これ忘れがち!! 忘れるとエラーになる。何度も失敗した!! await frame_submenu[0].click(); console.log('クリック:取引 > オプション新規注文') await newPage.waitFor('frameset > frame', {timeout: outtime}); console.log('waitFor : ' + waittime) await newPage.waitFor(waittime) } async function click_next(newPage,path,outtime,waittime){ console.log('mainコンテンツのframe取得_click_next') let frame_main2 = await newPage.frames()[0].childFrames()[2].childFrames()[1] //上へor下へのボタンへのパス frame_submenu = await frame_main2.$x(path) console.log('クリック:次ページ') await frame_submenu[0].click(); await newPage.waitFor('frameset > frame', {timeout: outtime}); console.log('waitFor : ' + waittime) await newPage.waitFor(waittime) } async function getTable(newPage,ymd){ //参考 //puppeteerでの要素の取得方法 //page.$のところ //https://qiita.com/go_sagawa/items/85f97deab7ccfdce53ea //日経平均、日経225先物期近 取得 //日経平均、日経225先物期近、などが並ぶframeへ移動 console.log('frame取得 toolBarコンテンツ') let frame_toolBar = await newPage.frames()[0].childFrames()[1].childFrames()[1] //xpathのフルパスでテーブルタグのtrを指定する(IDで上手く取れない場合は、これが簡単) console.log('テーブルタグ取得 日経平均、日経225先物期近'); let tbla = await frame_toolBar.$x('/html/body/table/tbody/tr/td/table/tbody/tr[2]') console.log('データ取得 日経平均、日経225先物期近'); var txtNikkei = ''; for (let i = 0; i < tbla.length; i++) { let tr = await (await tbla[i].getProperty('innerHTML')).jsonValue(); let tr1 = tr.split('<') let datas = []; //console.log(tr1) for(let i in tr1){ //i=2は、日経平均 i=12は、日経225先物期近 if(i == 2 || i == 12){ let inner = tr1[i].split('>')[1] if(inner != '' && inner != null && inner != '\n ' && inner !='\n '){ //上のconsole.log(tr1)で、発見ーーーーー↑↑↑↑↑↑↑↑↑↑↑↑↑↑ ↑↑↑↑↑↑↑↑↑↑↑↑ if(inner.toString().split(',').length > 0){ let splitdata = inner.toString().split(',') if(splitdata[1] != '' && splitdata[1] != null){ datas.push(splitdata[0] + splitdata[1]); //console.log('i = ' + i + ' ' + splitdata[0] + splitdata[1]); }else{ datas.push(splitdata[0]); //console.log('i = ' + i + ' ' + splitdata[0]); } } }else{ datas.push('-1'); console.log('i = ' + i + ' 日経平均のデータが空'); } } } txtNikkei += datas.toString(); } //SQ日 取得 console.log('frame取得 mainコンテンツ') let frame_main = await newPage.frames()[0].childFrames()[2].childFrames()[1] console.log('テーブルタグ取得 SQ日'); let sq = await frame_main.$x('/html/body/div[1]/table/tbody/tr/td[3]/span/span/form/table[2]/tbody/tr[2]/td/table/tbody/tr/td/table[2]/tbody/tr/td/table/tbody/tr[2]/td[2]/table/tbody/tr/td[2]') console.log('データ取得 SQ日'); var SQ = await (await sq[0].getProperty('innerHTML')).jsonValue(); //日経225オプションのデータ 取得 console.log('テーブルタグ取得 日経225オプション'); let tbl1 = await frame_main.$x('/html/body/div[1]/table/tbody/tr/td[3]/span/span/form/table[2]/tbody/tr[2]/td/table/tbody/tr/td/table[1]/tbody/tr[3]/td/table/tbody/tr[2]/td[2]/table/tbody/tr') console.log('データ取得 日経225オプション'); var textdata = ''; for (let i = 0; i < tbl1.length; i++) { var tr = await (await tbl1[i].getProperty('innerHTML')).jsonValue(); var tr1 = tr.split('<') var datas = []; for(let i in tr1){ let inner = tr1[i].split('>')[1] if(inner != '' && inner != null && inner != '新規買' && inner != '新規売'){ datas.push(inner) ; } } textdata += ymd + ',' + datas.toString() + ',' + txtNikkei + ',' + SQ + ',\n'; } console.log(textdata) return textdata; } async function postSpreadsheet(textdata,spreadsheet_url){ //参考 curlコマンドをPythonやnode.jsのコードに変換する方法 //https://qiita.com/tottu22/items/9112d30588f0339faf9b var request = require('request'); var headers = { 'Content-Type': 'text/csv' }; var dataString = textdata; var options = { url: spreadsheet_url, method: 'POST', headers: headers, body: dataString }; function callback(error, response, body) { if (!error) { console.log(' OK post Google Spreadsheet'); console.log(' OK statusCode : ' + response.statusCode); console.log(' OK result : ' + body); }else{ console.log(' NG post Google Spreadsheet'); console.log(' NG statusCode : ' + response.statusCode); console.log(' NG err : ' + error); } } request(options, callback); /* デプロイするまでは、curlでやっていたが、GAEでファイル書込は不可だったので、上記にした。 GASのテストをする分には、curlが簡単で良かったかも。 const exec = require('child_process').exec; exec('curl -v -H "Content-Type: text/csv" -X POST -d @./textdata.csv ' + spreadsheet_url , (err, stdout, stderr) => { if (err) { console.log(err); }; console.log(stdout); }); */ }; async function yymmdd(param){ //AppEngineは、UTCなのでAsia/Tokyoへタイムゾーンを変更 //node.jsでタイムゾーンの変換処理にdate-fns-timezoneを利用する //https://qiita.com/kazuhiro1982/items/b1235a893ee874d8ff65 const { startOfDay, addDays } = require('date-fns'); const { convertToTimeZone } = require('date-fns-timezone'); // タイムゾーン定義 const timeZone = "Asia/Tokyo"; // 現在時刻(UTC)を取得 const targetDate = new Date(); // TimeZone付きDateに変換 const now = convertToTimeZone(targetDate, { timeZone: timeZone }); var year = now.getYear(); // 年 var month = now.getMonth() + 1;// 月 var day = now.getDate(); // 日 var hour = now.getHours(); // 時 var min = now.getMinutes(); // 分 var sec = now.getSeconds(); // 秒 var dayOfWeek = now.getDay(); //曜日 [ "日", "月", "火", "水", "木", "金", "土" ] if(year < 2000) { year += 1900; } if(month < 10){month = '0' + month} if(day < 10){day = '0' + day} if(hour < 10){hour = '0' + hour} if(min < 10){min = '0' + min} if(sec < 10){sec = '0' + sec} var ymd = year + '/' + month + '/' + day + ' ' + hour + ':' + min + ':' + sec; if(param == 'week' ){ return dayOfWeek ; }else if(param == 'hour' ){ return hour ; }else{ return ymd ; } }; async function run2(){ var week = await yymmdd('week'); var hour = await yymmdd('hour'); if(week >= 2 && week <=5 ){//火〜金[ "日", "月", "火", "水", "木", "金", "土" ] console.log('Start Job week:' + week + ' hour:' + hour); run(); }else if(week == 1 && hour >= 7 ){//月曜かつ7時以上 console.log('Start Job week:' + week + ' hour:' + hour); run(); }else if(week == 6 && hour <= 7 ){//土曜かつ7時以下 console.log('Start Job week:' + week + ' hour:' + hour); run(); }else{ console.log('No Start week:' + week + ' hour:' + hour); } } run2();
puppeteerのソースコードの説明
- 説明1
- 以下の4ヵ所を書き換える
- const user_id = '';//S??証券のあなたのID
- const user_pass = '';//S??証券のあなたのパスワード
- const shouken_url = '';//証券会社のURL(S??証券)
const spreadsheet_url = '';//GASで発行したURL(Google Apps Scriptの公開方法で、説明が出てきます。)
説明2
- // headless: false,//GAEにデプロイする時、CloudShellではコメントにして、ブラウザ非表示にする。
- headless: false を有効にすると、ブラウザが表示されます。ローカルの開発環境で作業中の際は、こちらの方がデバッグしやすいです。
説明3(frameのたどり方が分からなくて一番苦労したところ!)
説明4(XPathが分かりにくくて苦労したところ!)
- XPathを活用すると、ID等が簡単に指定できますが、そのままでは動かない場合や、うまくいかないことが多かった。
- そのため、Chromeの「デベロッパーツール」で、ソースのたどりたいタグを右クリックして、Copy → Copy full XPath で full XPathを使っています。
説明5
- この時点で、app.jsが動くので、以下のコマンドで実行してみる
- npm start または、 node app.js
その他
- 参考にさせてもらったサイトは、ソースのコメントや、最後に記載してます。
Google Apps Scriptのソースコードとコメント
doPost(e)function doPost(e) { var csvText = e.postData.getDataAsString();//テキストファイルにする(CSVになってる) Logger.log("csv : " + csvText); var spreadsheet = SpreadsheetApp.openById('');//書込先のスプレッドシートのID var sheet = spreadsheet.getSheetByName('シート1');//スプレッドシートのシート名 var arr = csvText.split(',');//CSVをカンマでsplitする。 var arrData = sheet.getDataRange().getValues();//現在スプレッドシートに入ってるデータを全部取得して、2次元配列にする。 var len_arrData = arrData.length;//現在スプレッドシートに入ってるデータが、何行あるか確認してる var j = 0; var sheet_cols = 19;//GAEで取得したデータの1行当たりの列数が19なので、19としてる。 /* (arr.length -1) の意味:GAE上で改行コードを入れたが、改行コードをうまく認識できないので、全部で 配列がいくつ有るか数えて、最後に1引く(1引くのは、GAE上最後のデータの後にカンマを入れたから) (arr.length -1)は、19の倍数になってるので、19(sheet_cols)で割って、何行あるかを取得する。 */ var cols_arr = (arr.length -1) / sheet_cols; for(var i = 0 ; i < cols_arr ; i++){ arrData.push([ arr[j],arr[j+1],arr[j+2],arr[j+3],arr[j+4],arr[j+5],arr[j+6],arr[j+7],arr[j+8],arr[j+9],arr[j+10],arr[j+11],arr[j+12],arr[j+13],arr[j+14],arr[j+15],arr[j+16],arr[j+17],arr[j+18]]); j = j + sheet_cols; }; /* もともと入っていた配列データを消して、上のpushで追加した行だけのデータにする。 */ for(var i = 0 ; i < len_arrData ; i++){ arrData.shift();//配列の上からデータを削除 } var rows = arrData.length;//行数の確認 var cols = arrData[0].length;//列数の確認 /* (len_arrData + 1) :今取得したデータを書き込む行番号を決める。元々スプレッドシートに入ってるデータの行数+1で最終行の次の行に書き込む。 1 :1列目(A列)に書く rows :今回追加する行数 cols :今回追加する列数(19) arrData :今回追加するデータの配列 */ sheet.getRange((len_arrData + 1) ,1,rows,cols).setValues(arrData); // 結果を返す var output = ContentService.createTextOutput(); output.setMimeType(ContentService.MimeType.JSON); output.setContent(JSON.stringify({ message: "success!" })); return output; }
Google Apps Scriptの公開方法
- メニューバー => 公開 => ウェブアプリケーションとして導入
- Current web app URL: このURLを、GAE側で利用する。 => const spreadsheet_url = '';//GASで発行したURL
- Project version: 更新の都度、上げていくと、何回デプロイしたか分かって楽しい
- Execute the app as: Meにする
- Who has access to the app: Anyone,even anonymousにする
- 「更新」ボタンをクリック
スプレッドシートの状態
- https://docs.google.com/spreadsheets/d/1I2jCxS6Sddk7A6teBFlhpbIqhEEaaaabbbbccccdddd/edit#gid=0
- スプレッドシートURLの 1I2jCxS6Sddk7A6teBFlhpbIqhEEaaaabbbbccccdddd を
- Google Apps Script の var spreadsheet = SpreadsheetApp.openById('');//書込先のスプレッドシートのIDに設定する。
- 以下のように、データが蓄積されていきます。
GAE用 app.yaml
- app.yamlは、最初に作成したディレクトリ内に作成します。
app.yamlruntime: nodejs10 instance_class: F4_1G #puppeteer利用のため handlers: - url : / script : auto secure : always #このあたりに、adminと書けば、自分しか使えないサイトになったと思うが、エラーになる。
GAE用 cron.yaml
- cron.yamlは、最初に作成したディレクトリ内に作成します。
cron.yamlcron: - description: "PJ1" url: / schedule: every 5 minutes from 08:50 to 12:00 timezone: Asia/Tokyo
GAE用 デプロイ方法
- 最初に作成したディレクトリ内で、実行します。
- あなたのプロジェクトIDが、PJ1の場合。
- --quiet を入れない場合、確認があります。
- シェルで実行する場合は、--quietが便利
- デプロイが失敗しまくる時は、以下2つのファイルを消しに行くべし
- 1. AppEngineのバージョン画面の最新情報以外を全部消す
- 2. Strageの「staging.〜」「us.artifacts.〜」をクリックして出てきたファイルを全部消す。 CloudBuildのファイルが溜まってるから?
gcloud app deploy --quiet --project PJ1 gcloud app deploy cron.yaml --quiet --project PJ1
GAEを無料で使うテクニック(ってほどでもないか?)
Google App Engine は、もともと無料枠がけっこう大きい
- 課金しない状態で、以下のように運用可能です。
- 28CPU時間を毎日利用しても、無料です。
- 28CPU時間を使い切ると、システムが勝手に止まります。
- システムがリセットされて再び動き出すのは、16時。
Google App Engine は、課金してなくても動くが、デプロイができない。
- デプロイには、Cloud Buildが必要で、Cloud Buildに課金が必要
- だから、デプロイするときだけ、Cloud Buildを課金状態にする。 デプロイ終わったら忘れず課金を消す。
- この操作をすれば、無料で運用可能。
- 課金状態で、1日に28CPU時間以上動かしてしまうと、課金されます。
もっとGAEを使いたい場合
- app.yamlの設定で、instance_class: F4_1G と設定したので、CPU時間をかなり消費する。
- 5分に1回の実行を、3時間回すと、12x3=36回程度で、28CPU時間を消費します。
- 9時から12時で終わります。 足りませんよね。
- そこで、プロジェクトIDを複数作成します。例PJ1 PJ2等。そして以下のように設定します。
- cron.yamlのPJ1用には、schedule: every 5 minutes from 08:50 to 12:00
- cron.yamlのPJ2用には、schedule: every 5 minutes from 12:05 to 15:20
- 注意点:cron.yamlをデプロイするだけの場合は、Cloud Buildの課金が不要なので、課金作業無しで、デプロイ可能です。
- しかし、PJ1 PJ2 を課金対象にした上でデプロイした後、課金をやめないと、誤って課金される場合があるので注意要です。
- 投稿日:2020-03-24T19:00:09+09:00
完全にクラウドで完結する無料の Web 開発環境 2020 年春
目的
2020 年春時点での、ぼくのかんがえたさいきょうのうぇぶかいはつかんきょうを作ります。完全にクラウドで完結する Web 開発環境を無料で構築します。ここで言う「完全にクラウドで完結する」とは、環境をセットアップしてコードを書き、テストをして本番環境にデプロイするまでの全てをブラウザだけで完結することを指します。ローカルのコマンドラインツールやローカルで動く IDE などは一切使わないというのがポイントです。
つまり、Windows、Linux、Mac など OS の依存がないだけでなく、ブラウザが動く環境さえあれば良いので、iPad や Android タブレット、果てはネカフェの PC でもそのままに適用することが出来ます。また全ての環境がクラウド側にあるという事は、作業中の状態なども全てクラウド側にあるという事です。これはスタバでドヤるどころではない究極のノマド環境です。ネットがあってブラウザがあれば、いつでもどこでも開発が出来ます。
本当に無料?
開発環境までは無料ですが、本番環境で一部課金が必要になる場合があります。2020 年春時点での限界です。
(2020-03-26 追記)
現状、開発環境が無料で使えているのは、フリートライアルの 22,500 円が使えているからでした。料金ページの試算によれば、フルタイムの開発で約 5,500 円/月なので、週五でフルに使っても 4 ヶ月分という事になりますね。趣味グラマーとしては優に一年以上は持つかなという感触です。継続して利用するかどうかは、クレジットが尽きた後にそれまでの統計を見て再検討することにします。
価格対メリットで言うと、現時点での個人的な判断としては俄然アリです。そのくらい楽です。
(2020-03-26 追々記)
クレジットの有効期限は 30 日しかありませんでした。だいぶ勇み足だったようで申し訳ないです。どちらにせよ上記のようにメリットの方が大きく感じているので、個人的には課金していくと思います。環境の概要と選択理由
この開発環境は 3 つの PaaS に依存しています。GitHub、Visual Studio Online (以下 VS Online)、Heroku で、それぞれの役割分担は下記のようになっています。
- GitHub
- git リポジトリの保持・管理
- 本記事では触れませんが必要に応じてイシュー管理など
- VS Online
- IDE
- 従来のローカル環境に相当する動作確認テスト
- Heroku
- 本番環境
- 独自ドメインのホスト
- 本記事では触れませんが必要に応じて CI/CD
実は Heroku の部分に関しては、Google App Engine や Azure Web Apps でも代替可能なのですが、今回は Node.js をターゲットにしており、また独自ドメインを使いたかったので、Heroku という選択肢になりました。
2020 年春時点で、Google App Engine には Node.js を使用する無料のオプションがなく、Azure Web Apps には独自ドメインを使う無料のオプションがありません。これらは将来的に変わる可能性があるので、現時点での最適解と受け止めてもらえればと思います。例えば Python を使うのであれば Google App Engine が良いかもしれませんし、独自ドメインが不要であれば Azure Web Apps が良いかもしれません。個人的には Azure Web Apps に一番期待をしています。同じ Microsoft の傘の下で、GitHub、VS Online、Azure Web Apps の連携が将来的にもっと良くなる可能性があるからです。
環境構築
以下、環境構築の手順を解説していきます。スクリーンショットが概ね英語になってますが、これは VS Online がそもそもまだ英語しかない、Heroku のアカウント作成にバグがあり (?) 日本語だと失敗した、などの理由によるものです。日本語環境で作業される方も UI は変わらないでしょうから、適宜読み替えて下さい。
新規レポジトリの作成
まず、お手持ちの GitHub アカウントで GitHub に新規レポジトリを作成します。そんなに注意点はありませんが、VS Online 環境の構築が上手くいかないので、リポジトリは公開設定にしておいて下さい。後述する VS Online 上での GitHub の認証を済ませた後ならプライベートに設定しなおしても大丈夫なのですが、初期段階では公開設定にしておいて下さい。
今回は Node.js アプリを作るので、
.gitignore
のデフォルトを Node にし、ライセンスはいつもの MIT にしています。
VS Online 環境の構築
VS Online 環境を構築するにあたって、持っていない場合はあらかじめ MS アカウントを作っておいて下さい。MS アカウントを有効にしてログインしたら、VS Online のログインページ にアクセスします。
ここで VS Online にサインイン "Sign in" すると規約への同意を求められ、その後、新しい環境を作成 "Create environment" 出来るようになります。適当な名前を付けて、"Git Repository" に先ほど作ったリポジトリの "https" の URL を指定して下さい。SSH だと動作しないので注意が必要です。また、前述のようにリポジトリは公開設定である必要があります。
また、フリートライアル分のクレジットの消費をなるべく抑えるためには、"Instance" を "Basic" にし、"Suspend" を "After 5 minutes" にするのが最良です。インスタンスを作成するリージョンについては、距離の問題から "South East Asia" が日本からは最速ですが、最安は "East US" なので、そこにも気をかけるとクレジットの消費を抑えることが出来ます。
この辺、後から変更したくなった場合でも数クリックで完了してしまうあたりはクラウドの真骨頂ですね。VS Online 環境の作成には数十秒の時間がかかり、その後、作成された環境に接続 "Connect" すると、ブラウザ上で VS Code が立ち上がりコードが書ける状態になります。なかなかのインパクトですね。
VS Online からコードをプッシュする
コードを書いたら当然 GitHub にプッシュしなければ始まらないのですが、なにか特別な設定が必要でしょか?否、この時点ですでに必要な設定は完了しているのです。
すると見慣れたプロンプトが立ち上がりますが、この時点で、VS Online 上のインスタンスに SSH した状態になっています。
そうしたらいつものように、
add
してcommit
してpush
するだけです。
なお、初回やトークンの期限が切れた時は GitHub の OAuth 認証を求められるので、VS Online からのアクセスを許可して下さい。またここで OAuth 認証をした後であれば、GitHub 側でリポジトリをプライベートに設定しても問題ありません。
Web アプリを作る
いよいよ VS Online 上で Web アプリを作っていきます。今回は検証用なのでごくごく簡単な Web アプリです。VS Online 上のターミナルを使って、いつものように作成してきます。
最初にやる事は Node.js のバージョンの固定です。現在のデフォルトバージョンは v12 で、そのままでも現状なら問題はないのですが、将来的にデフォルトのバージョンが上がった際、後述の Heroku でのデフォルトバージョンとの間に差異があると面倒なことになりかねません。ですので、VS Online でも Heroku でも v12 を明示的に指定して進める事にします。
といっても何も面倒なことはありません。VS Online 環境には
nvm
もインストール済みなので、いつものようにするだけです。$ nvm install 12 $ nvm use 12続いて Web アプリの初期設定、必要なパッケージのインストールをしていきましょう。これまたいつも通りですね。
$ npm init $ npm install express --saveプロジェクトのルートディレクトリに
app.js
を作りごくごく簡単な Web アプリを作ります。app.jsconst express = require('express'); const app = express(); const port = 3000; app.get('/', (req, res) => res.send('The app is running!')); app.listen(port, () => console.log(`Example app listening on port ${port}!`));
package.json
にサーバを立ち上げるコマンドを追加しましょう。package.json"scripts": { "start": "node app" }あとは走らせるだけ。簡単ですね。
$ npm start
VS Online 環境上の Web アプリを開く
さて前項で立ち上げた Web アプリは VS Online のインスタンス上で 3000 ポートを開いて走っています。ローカル環境であればここで
http://localhost:3000
を開いて動作確認するところですが、同じようには行かないのでポートフォワーディングの設定をします。下記の設定を手動でしなくても自動で同様の設定がされていることがあるようです。条件はよく分かっていないのですが、ポートを開いただけで設定済みの状態になってたらラッキーくらいに思っておけば良いと思います。
左側のメニューの中に、VS Online の設定をするアイコンがあるので開いて、
"ENVIRONMENT DETALIS" > "Forwarded Ports"
の右にあるコンセントのアイコンをクリックすると、フォワードするポートが入力できるようになるので、3000 と入力して Enter します。
するとポートフォワーディングの設定が完了しブラウザから先ほど作った Web アプリが開けるようになります。ここでは設定の名前をデフォルトのまま "localhost:3000" としているので、そのエントリがメニューに追加されます。ここで "localhost:3000" はそのままブラウザで開くリンク、その右のアイコンはクリップボードへの URL のコピー、一番右のアイコンは設定の削除です。
このポートフォワーディングされた先の URL を開き、"The app is running!" と表示されればバッチリです。あとはライブラリを追加するなり、ファイルを増やすなりして下さい。ローカルの VS Code を使って Node.js アプリを作っている時と同じ事が全部クラウド上で実現します。
Web アプリを Heroku 上で動くように修正する
VS Online 上で開発し動作確認も出来た Web アプリを本番環境である Heroku 上で動かすためには、いくらかの変更を加える必要があります。
まず、Heroku 上で走らせる Web アプリに正しく接続できるようにするためには、Heroku 側から指定されているポートでサーバを立ち上げないといけません。環境変数
PORT
にその値が格納されているので、下記のようにapp.js
で指定します。app.jsconst port = process.env.PORT || 3000;次に、VS Online で Node.js のバージョンを固定したのと同様、Heroku でもバージョンを固定するために、
package.js
に記述を追加します。package.json"engines": { "node": "12.x", "npm": "6.x" }これだけで基本的には動くはずですが、
package.json
の "scripts" でより細かな制御を行うことも出来ます。package.json"scripts": { "start": "node app", "build": "webpack", "heroku-postbuild": "node heroku-special-command && webpack" }"start" に関しては変わりません。サーバを立ち上げるためのコマンドです。"build" を指定すると "start" の前に呼ばれます。
webpack
を呼び出したりするのが典型的な使い道になります。"heroku-postbuild" は Heroku 専用の "build" スクリプトです。これが定義されている場合は、"build" の代わりに呼ばれます。VS Online 上でのビルドと、Heroku 上でのビルドで異なる動作が必要な場合に使えますね。通常これだけで十分かと思いますが、より細かな制御についてはオフィシャルドキュメントを参照して下さい。
Web アプリを Heroku にデプロイする
さあ、いよいよ本番環境へのデプロイです。Heroku のアカウントはあらかじめ作成しておいて下さい。Heroku のダッシュボードにログインしたら、新規アプリの作成 "Create a new app" をします。
アプリの名前は、Heroku が自動的に付与するサブドメイン名でも使用されるので、アルファベット、数字、ハイフン、しか使えません。リージョンは現在のところアメリカかヨーロッパしかないので、日本のユーザが大半ならアメリカにしておくのが良いでしょう。パイプラインは、CI/CD をやるのであれば設定が必要ですが、本記事では触れません。
アプリを作成したら "Deploy" タブの "Deployment method" で GitHub を選んで、GitHub に接続 "Connect to GitHub" しましょう。
例によって OAuth 認証が求められるので GitHub へのアクセスを Heroku に許可すると、GitHub 上のリポジトリが一覧から選べるようになるので、最初に作ったリポジトリを選びます。
あとは同じページの一番下、"Manual deploy" で対象のブランチ (デフォルトは master) を選んでブランチをデプロイ "Deploy branch" すると、手動によるデプロイが始まります。
上部のボタンからアプリを開く "Open app" と、
https://[指定した名前].herokuapp.com/
が開くので、そこで "The app is running!" と表示されていればデプロイ完了です。自動デプロイを設定する
手動でのデプロイが確認出来たら次にこれを自動化します。といっても、"Manual deploy" のすぐ上 "Automatic deploys" で対象のブランチを選択し自動デプロイを有効化 "Enable Automatic Deploys" するだけです。すると下記のように、緑のチェックマークが表示されます。
この状態で VS Online を開き例えば
app.js
の一部を編集して変更を git にプッシュすると、程なくして Heroku 上で自動的にデプロイが走ります。その様子は "Activity" タブで確認出来るので、デプロイが終わったら先ほどの URL で変更後の状態が確認出来るはずです。
独自ドメインを設定する
URL は
https://[指定した名前].herokuapp.com/
のままでいいや。どうせ趣味だし。くらいの場合は、これ以降の設定は必要ありません。herouapp.com
だとちょっと格好付かないなぁと思った場合は、お手持ちのドメインを Heroku に設定して運用することが出来ます。独自ドメインの設定自体は簡単にできるのですが、初回のみ Heroku アカウントの認証を行う必要があります。認証というのはクレジットカードの登録で、ユーザメニューの "Account Setting" を開き、"Billing" タブの "Billing Information" から行います。
クラジットカードの登録が終わるとこのように表示されて、アカウントの認証が済んだことになります。クレジットカードを登録しただけで課金されることはないので安心して良いですが、独自ドメイン自体に課金はせずクレジットカードの登録だけ求めるというのは、いずれ課金に誘導したいサービスとしては上手いところを突くなと思いました。
アカウントの認証が済んだら、"Setting" タブの中にある "Domains" セクションからドメインを追加 "Add domain" します。"Domain name" には使用する予定のドメイン名を入力してください。
ここから設定を進めるとそのドメイン専用の "DNS target" が発行されるので、これをメモしておきます。
そしたら、次にお手持ちの DNS サーバの管理画面で "CNAME" レコードに先ほどの "DNS target" を指定してください。ここでは手元で確認出来る、Google と提携している GoDaddy 並びにさくらインターネットの管理画面の例を載せておきますが、どこのサービスもそんなに大きく違わないのではないかと思います。発行された "DNS target" の末尾に
.
を足す必要があるかどうかは DNS 管理サービスによって異なるので注意してください。
以上で設定完了です。簡単ですね!一つ注意点としては、Heroku のサーバは
ping
を返さない (ICMP に応答しない) ので、疎通確認をping
だけでやっていると見誤ります。nslookup
が返ってくるのを待って HTTP で疎通確認するのが良いでしょう。SSL を設定する
これで最後の設定になります。独自ドメインのサイトに対して、Let's Encrypt の SSL 証明書を設定し HTTPS を有効にします。令和の時代になっても HTTP なんて寝ぼけたことは言ってられないですからね。
ただ…ごめんなさい、かく言う筆者も途中まで気づいていなかったのですが、タイトルに「無料」と書きながらここだけは課金が必要になります。Google App Engine は無料で独自ドメインの SSL を提供しているので、どうしても無料で完了させたい場合は Node.js を使わずに Google App Engine を使う方が良いかもしれません(GitHub 連携のやりやすさは試したことがないので分かりません)。
Heroku で独自ドメインの SSL が使えるのは、最低課金の Hobby プランからで $7/月になります。Hobby プランのもう一つの大きなメリットは、Free プランと違ってアイドル時間が長くなってもインスタンスがサスペンドされる事がない点で、アクセスが少ないサイトであってもスピンアップの時間を気にする必要が無くなります。あと、Google App Engine で Node.js を運用するより、少なくとも手元の計算では安いです。
まず "Resources" タブを開き "Change Dyno Type" ボタンから "Hobby" プランを選択し保存 "Save" します。
これだけで SSL が設定可能になるので、"Settings" タブの "SSL Certificates" セクションで SSL を設定 "Configure SSL" します。そこで ACM を選べば設定は完了です。
数十秒も待てば自動設定が終わるはずなので "Settings" タブをリロードし、"ACM Status" の欄が Ok になっていれば完了です。
https://[設定済み独自ドメイン]
にアクセスしてみましょう。Let's Encrypt で暗号化されたサイトが表示されるはずです。
あとがき
以上で、2020 年春時点での、ぼくのかんがえたさいきょうのうぇぶかいはつかんきょうの設定は完了です。あとはもう好きなだけ、いつでもどこでもコードが書けます!
なお前述のように Azure Web Apps には大きな期待をしています。Azure Web Apps が独自ドメイン+SSL を無料でサポートしたあかつきには、最強の座は Azure に渡ることになりますからね。
雑感
こと Web の開発に関しては、GitHub を買って、npm までも (間接的に) 買って、VS Code 作ってオンライン化して、TypeScript を業界標準に押し上げ、そして Azure を擁する Microsoft 最強じゃないですか? Microsoft がこんなに Web に強い会社になるとは 10 年前には考えられなかったです。
付録
作業時にもっと詳細まで取得していたスクリーンショットを GitHub 上で公開しています。どこまで役立つか分かりませんが参考までに。
- 投稿日:2020-03-24T11:47:05+09:00
MacにHomebrew経由でnodenvを導入する(+PHPStorm対策)
以前
ndenv
を使っていたのですが、気づいたらdeprecated
になっていたので転職時に新しい端末になったのを機にnodenv
へ移行しました。anyenv
使って入れるのがステップ数多くてだるいなと思っていたのですが、homebrew
であれば割とサクッと入れられそうだったので今回はこちらで導入してみるので、備忘録として残しておきます。https://github.com/nodenv/nodenv
Homebrewの導入
導入済み前提で進めるので、導入方法については割愛します。
既存のNode.jsまたはNode.jsバージョン管理の削除
各々の環境で異なっている所だと思うので適時環境にあった方法にて対応ください。Macなら大体当該ディレクトリを消せば済むと思います。
nodenvのインストール
brew install nodenv
まぁまぁ時間がかかると思うので、のんびり待ちます。
初期化コマンドをシェルプロファイルに追記
eval "$(nodenv init -)"
を追記します。最近のMacならzshがデフォルトだと思うので~/.zshrc
でしょうか。
nodenv init
を打つとどのファイルに追記すればよいのかを教えてくれるようです。% nodenv init # Load nodenv automatically by appending # the following to ~/.zshrc: eval "$(nodenv init -)"追記したらターミナルを再起動します。
インストール確認
nodenv
にはnodenv-doctor
という診断ツールが用意されています。以下のコマンドを叩くとnodenv
が正常にインストールされているかと、利用状況が出力されます。% curl -fsSL https://github.com/nodenv/nodenv-installer/raw/master/bin/nodenv-doctor | bash Checking for `nodenv' in PATH: /usr/local/bin/nodenv Checking for nodenv shims in PATH: OK Checking `nodenv install' support: /usr/local/bin/nodenv-install (node-build 4.8.0) Counting installed Node versions: none There aren't any Node versions installed under `/Users/ユーザ/.nodenv/versions'. You can install Node versions like so: nodenv install 2.2.4 Auditing installed plugins: OK
いたれりつくせりですね。
nodenv
の利用方法ターミナルに
nodenv
打てば全部丁寧に教えてはくれるのですが、代表的なのを抜粋しておきます。利用出来るNode.jsのバージョン一覧
nodenv install -l
指定したバージョンのNode.jsをインストール
nodenv install {{ バージョン }}
インストール済みのNode.js一覧
nodenv versions
よくlistって打って怒られてます…
現在のNode.jsバージョン
nodenv version
node -v
でもいいんですが、こちらで表示するとNode.jsのバージョン選定理由が併記されます。12.16.0 (set by /Users/ユーザ/.nodenv/version)
グローバルで使用するNode.jsのバージョンを指定
nodenv global {{ バージョン }}
プロジェクトで使用するNode.jsのバージョンを指定
nodenv local {{ バージョン }}
ターミナルでの現在位置に
.node-version
というファイルが作られます。このファイルがあると、当該ディレクトリでNode.js実行時に自動的に指定バージョンのものを使おうとし、指定バージョンが導入されてなければ当該バージョンをインストールするようにエラーが出ます。該当バージョンが見つからない場合nodenv: version `12.16.0' is not installed (set by /Users/ユーザ/プロジェクト/.node-version)
PHPStormなどのJetBrains製IDE利用時に
npm
がないと怒られる場合PHPStormなどのJetBrains製IDEを使っていると、過去に
ndenv
を利用していた時にも同じことを書いていますが、この状態でプロジェクトを開いてNPMスクリプトを実行しようとした際にパッケージマネージャが見つからないと怒られると思います。非常にだるいのでなんとかします。
jetbrains-npm
をインストールhttps://github.com/nodenv/jetbrains-npm
nodenv
のインストール先によって導入方法が異なるようですが、今回はデフォルトパスにインストールしているので推奨されている方法で導入します。mkdir -p "$(nodenv root)/lib/node_modules" git clone https://github.com/nodenv/jetbrains-npm "$(nodenv root)"/lib/node_modules/npm導入が終わるとPHPStormがNPMを見つけてくれるようになり、NPMタブも無事叩き放題になります。
昔と比べると導入するのも楽になった気がします。多大な労力を割くような場所ではないと思うので、楽になるに越したことはないですね。
- 投稿日:2020-03-24T01:28:39+09:00
2020年から始めるAzure Cosmos DB - JavaScript SDK (SQL API)を見てみる (Part.2)
この記事について
本記事は、2020年3月6日 (米国時間) にて、Azure Cosmos DB に新しく Free Tier (無償利用枠) が登場したことに伴い、改めて Azure Cosmos DB を色々と触っていく試みの 4 回目です。
今回も、前回記事 同様、 Microsoft Azure Cosmos JavaScript SDK について見ていきたいと思います。対象読者
- Azure Cosmos DB について学習したい方
- Node.js で Azure Cosmos DB への CRUD 操作を行いたい方
- Microsoft Azure Cosmos JavaScript SDK の動作について理解したい方
- TypeScript のパラメータプロパティ宣言を知らない方
Microsoft Azure Cosmos JavaScript SDK
実際に、Microsoft Docs の内容を元に、JavaScript SDK (SQL API) の中身を見ていきます。
今回はデータベースの読み取り、または削除
を行う Database について確認します。Database
TypeDoc の記載は、以下の通りです。
Operations for reading or deleting an existing database.
既存のデータベースの読み取りまたは削除の操作。const client: CosmosClient = new CosmosClient({ endpoint, key }); const database: Database = client.database(databaseId);実際に使用する際は、CosmosClient クラスを生成した後、CosmosClient クラス内にある
database
メソッドを使用して、Database クラスを生成する手順が必要になります。
CosmosClient クラスのdatabase
メソッドは、どうなっているのか、実際に中身を確認してみます。Azure/azure-sdk-for-js/sdk/cosmosdb/cosmos/src/CosmosClient.tspublic database(id: string): Database { return new Database(this, id, this.clientContext); }はい、Database クラスのコンストラクタが動いています。
まあ Database 型の定数(const)に値を代入しているのだから当たり前ですよね。このコンストラクタ、プログラミング初心者にとってはすごく理解が難しい引数
this
がありますが、一旦置いておいて、実際のコンストラクタの中身を見ていきます。Azure/azure-sdk-for-js/sdk/cosmosdb/cosmos/src/client/Database/Database.tsexport class Database { public readonly containers: Containers; public readonly users: Users; constructor( public readonly client: CosmosClient, public readonly id: string, private clientContext: ClientContext ) { this.containers = new Containers(this, this.clientContext); this.users = new Users(this, this.clientContext); }コンストラクタの第一引数に
CosmosClient
があります。
つまり、先ほどの this とは、このコンストラクタの引数に自分自身への参照(CosmosClient)を渡していた、ということです。
ところで、 (私みたいな) TypeScript 初心者がこのコードをみた際、きっと下記のような疑問を持つのではないかと思います。(実は前回の内容の中にも同じものがあったんですけどね、、)あれ、、先ほど引数で渡してきた CosmosClinet と databaseId、全然使われていないように見えるけど、どうなっているの...!?
安心してください、もちろん
渡した引数には意味があります
。ここで注目するのはコンストラクタの引数に public/private/readonly の修飾子がついているところです。TypeScript には、
パラメータプロパティ宣言
というものがあります。これは、コンストラクタの引数の中でプロパティの作成および初期化が行えるようになるというものです。つまり、上記で記載した Database クラスのコンストラクタの内容は
export class Database { public readonly client: CosmosClient; public readonly id: string; private clientContext: ClientContext; public readonly containers: Containers; public readonly users: Users; constructor( client: CosmosClient, id: string, clientContext: ClientContext ) { this.client = client; this.id = id; this.clientContext = clientContext; this.containers = new Containers(this, this.clientContext); this.users = new Users(this, this.clientContext); }と同じ内容ということになります。
実際にコンパイルされた後の JavaScript ファイルも見てみます。Azure/azure-sdk-for-js/sdk/cosmosdb/cosmos/dist/index.js(5988行目付近)class Database { constructor(client, id, clientContext) { this.client = client; this.id = id; this.clientContext = clientContext; this.containers = new Containers(this, this.clientContext); this.users = new Users(this, this.clientContext); } }内容が同じであることを確認できました。
TypeScript って本当に便利ですね。さて、本題のコンストラクタ処理に戻ります。
Azure/azure-sdk-for-js/sdk/cosmosdb/cosmos/src/client/Database/Database.ts(再掲)export class Database { public readonly containers: Containers; public readonly users: Users; constructor( public readonly client: CosmosClient, public readonly id: string, private clientContext: ClientContext ) { this.containers = new Containers(this, this.clientContext); this.users = new Users(this, this.clientContext); }コンストラクタのパラメータプロパティ宣言を除くと、
Containers
とUsers
のクラスを生成する処理があります。
実際に、この 2 つのコンストラクタの中身をみていきます。Azure/azure-sdk-for-js/sdk/cosmosdb/cosmos/src/client/Container/Containers.tsexport class Containers { constructor(public readonly database: Database, private readonly clientContext: ClientContext) {} }Azure/azure-sdk-for-js/sdk/cosmosdb/cosmos/src/client/User/Users.tsexport class Users { constructor(public readonly database: Database, private readonly clientContext: ClientContext) {}また出たな!パラメータプロパティ! ※決してどこかの企業のフレーズを真似したわけではありません笑
ここでもコンストラクタのパラメータプロパティ宣言を使い、
Containers
クラスとUsers
クラスを生成していますね。このあたりのクラスについては、次回以降、確認していきたいと思います。さいごに
今回は、Azure Cosmos DB に JavaScript/Node.js で接続する際に CosmosClient クラスを使って生成する Database クラスについて、中身を確認してみました。
今回の内容も、以前、実際に CRUD アプリを作成 (Qiita 記事) した時はたったの 1 行で終わってしまった内容です。前回、
普段のアプリ開発では、あまり意識しない世界なのかもしれませんが、実際にコンストラクタの中で何が行われているのかを確認することは、Azure Cosmos DB の仕組みや使用するライブラリへの深い知見を得る ためには必要な事かな、と思いました。
と思っていた自分ですが、Microsoft Azure Cosmos JavaScript SDK の中身を見ていくことは、ライブラリの理解だけでなく、
TypeScript の勉強もできる
という一石二鳥なことではないか、と感じました。他のライブラリについてもこれは同じように言えることだと思います。次回は、
Container/Containers
クラスについて見ていこうと思います。関連リンク
前回記事
参考資料
Microsoft Docs
TypeScript Handbook
Qiita
- 投稿日:2020-03-24T00:37:08+09:00
【Node】dotenvで環境変数を設定する
概要
- Nodeでプログラムを実行する時に環境(dev/stag/prodなど)ごとに値が異なる部分はコードを修正せずに実行するため環境変数として埋め込むことがよくあると思います
- セットする環境変数が1つ2つであればコマンド実行時に設定すればよいですが規模が大きくなってきたらファイルでまとめて定義したくなるでしょう
- そんな時はdotenvを使うと便利です
- dotenvを使うと
.env
ファイルに定義された値を環境変数として使うことができます- また、システムの環境変数として値が設定されていればそちらを優先して使うということもできます
- なので、開発時はローカルで
.env
を配置し、本番ではホスティングサービスの機能で環境変数として設定するといった使い方をすることでリポジトリ内のファイルを変更せずに実行することができますNodeスクリプトの実行時にdotenvを使う場合
- シンプルな形でいうと
index.js
があってnode index.js
で実行するようなケースです- まずはdotenvをinstallします
npm i dotenv
- 次にサンプル用の
index.js
を作成します
require('dotenv').config();
としておくことでdotenvが適用されるようになりますprocess.env
で環境変数を取得できますTEST_VALUE
という環境変数の値を取得しコンソールに出力するサンプルですindex.jsrequire('dotenv').config(); const value = process.env.TEST_VALUE; console.log(value);
- 最後に値を定義する
.env
ファイルを作成します.env# 環境変数を定義 TEST_VALUE=.envで値を設定しています
- 実行してみましょう
node index.js
- ログに
.envで値を設定しています
と出力されているはずです.env
の値をシステム環境変数で上書きできることも確認しておきましょうTEST_VALUE="コマンドで値を設定しています" node index.js
- ログに
コマンドで値を設定しています
と出力されているはずですwebpackでビルド時にdotenvを使う場合
- webpackなどでビルドしてから実行するケースも多くあると思います
- ここではWebpackでdotenvを使う場合の手順を紹介します
- dotenv-webpackというライブラリを使用します
npm i webpack webpack-cli dotenv-webpack
- webpackの設定ファイルを作成します
webpack.config.jsconst Dotenv = require('dotenv-webpack'); module.exports = { plugins: [ // { systemvars: true } を設定するとシステム環境変数も読み込まれるようになる new Dotenv({ systemvars: true }), ], };
index.js
を修正します
- dotenvはビルド時に適用されるため設定が消えてスッキリしました
index.jsconst value = process.env.TEST_VALUE; console.log(value);
- ビルドして実行してみましょう
# ビルド(dist/main.jsが出力される) npx webpack -p index.js # 実行 node dist/main.js
- 実行すると
.envで値を設定しています
とログが出力されるはずです- システムの環境変数で上書く場合ビルド時に環境変数を設定します
# 環境変数を設定してビルド TEST_VALUE="コマンドで値を設定しています" npx webpack -p index.js # 実行 node dist/main.js
- 実行すると
コマンドで値を設定しています
とログが出力されるはずですまとめ
- dotenvを使った環境変数の設定のしかたを紹介しました
- Nodeを使ったアプリでもこういった技術を活用してCI/CD回していきましょう!