てけとーぶろぐ。

ソフトウェアの開発と、お絵かきと、雑記と。

Node.jsでJSONを返すHTTPサーバーを作る

最近TypeScriptでWebアプリを作っています。
そのなかで使ったことないけどAPIサーバーにNode.jsという選択はありかもなぁ。
本格的に使わなくともちょっとしたものを作るのに使えたらよさそう。
と思うようになりました。

以下の点でよさそうだと思っています。

  • サーバー側もJavaScriptやらTypeScriptで書ける
  • Web APIで使うJSONと相性がいい
  • 気になっているMongoDBと相性がいい
  • 認証などに使っているFirebaseと相性がいい

ということでちょっと使ってみることに。

本屋でピッタリな書籍を見つけました。これで学びました。

入門Node.jsプログラミング

入門Node.jsプログラミング

  • 作者:Jonathan Wexler
  • 出版社/メーカー: 翔泳社
  • 発売日: 2019/09/25
  • メディア: 単行本(ソフトカバー)

大体わかったので試しに以前Pythonで書いたJSONを返すサーバーをNode.jsで作ってみます。
publicディレクトリーにJSONファイルを置いておいて、例えば「test.json」を置いて
そのファイル名のURL、この例だと「localhost:3000/test」にアクセスすると「test.json」の内容が返ってきます。
Webアプリの開発中にWeb APIのモックなんかに使えるかと思います。

以下手順。

Node.jsのインストール

以下からNode.jsをダウンロードしてインストールします。
Node.js

アプリケーションの初期化

ディレクトリーを作って「npm init」

$ mkdir json_server
$ cd json_server/
$ npm init

各質問にはエンターキーでデフォルトを選んで行けば問題なしですが
「entry point:」に対しては「main.js」を指定します。

ライブラリーのインストール

必要なライブラリーをインストールします。

$ npm install express http-status-codes --save

簡単にWebサーバー、Webアプリが作れるexpressと
HTTPのステータスコードの定義が入ったhttp-status-codesです。

コードの作成

json_server/main.js を以下の内容で作ります。

const express = require("express");
const app = express();

const errorController = require("./controllers/errorController")
const jsonFileController = require("./controllers/jsonFileController")

app.set("port", process.env.PORT || 3000);

app.get(/\/.+/, jsonFileController.showJsonFile);

app.use(errorController.pageNotFoundError); 
app.use(errorController.internalServerError);

app.listen(app.get("port"), () => {
    console.log(`Server is running at http://localhost:${app.get("port")}`);
});

json_server/controllers/jsonFileController.js を以下の内容で作ります。

const fs = require("fs");
const path = require("path");

exports.showJsonFile = (req, res, next) => {
    const dirPath = 'public';
    fs.readdir(dirPath, function(err, files) {
        if (err) {
            next(err);
            return;
        }
        if (err) next(err);
        for (const file of files) {
            if (!fs.statSync(path.join(dirPath, file)).isFile() ||
                path.extname(file) !== ".json") {
                continue;
            }
            if (req.url !== '/' + path.basename(file, path.extname(file))) {
                continue;
            }
            fs.readFile(path.join(dirPath, file), (err, data) => {
                if (err) {
                    next(err);
                    return;
                }
                res.send(JSON.parse(data.toString()));
            });
            return;
        }
        next();
    });
};

json_server/controllers/errorController.js を以下の内容で作ります。
こいつはエラー処理に特に気を使わない場合は不要です。

const httpStatus = require("http-status-codes");

exports.pageNotFoundError = (req, res) => { 
    let errorCode = httpStatus.NOT_FOUND; 
    res.status(errorCode);
    res.send("Page not found");
};

exports.internalServerError = (error, req, res, next) => { 
    let errorCode = httpStatus.INTERNAL_SERVER_ERROR; 
    console.log(`Error occurred: ${error.stack}`);
    res.status(errorCode);
    res.send({ error: error });
};

json_server/public/test.json としてサンプルのjsonファイルを作ります。

{
    "message": "こんにちは"
}
実行

以下のコマンドで実行します。
終了するときは Ctrl+C です。

$ node .\main.js

使い始めのお作法が分かればあとは楽ちんですね。

ヘッドレスChromeCastでYoutube音声をディスプレイ無しで再生

Youtube音声をバックグラウンドでダラダラ流したいなと思いました。

スマホで月額払ってYouTube Premiumでも使えという話ですが
押し入れに眠っているChromeCastを使ってもできそうだと思いました。

普通にChromeCastをディスプレイなりに繋げばいいわけですが
余っているディスプレイもないし、音だけ聞きたいのでディスプレイをわざわざ点けたくない。

ディスプレイ無しでChromeCastでYoutube音声は流せないのか?
いくつかのコネクターの接続でできました。

f:id:kurimayoshida:20190218133345j:plain
完成品(かっこわるい…)

HDMIVGA 変換コネクターをつないで
VGAネクター側に抵抗を差し込むとディスプレイがつながっている扱いになる
という仕組みです。

f:id:kurimayoshida:20190218133321j:plain
VGAネクター側の抵抗

スマホからChromeCastにYoutubeをキャストしてやるとスピーカーからYoutube音声が流れます。
スマホの画面を消しても流れ続けます。

f:id:kurimayoshida:20190218133456j:plain
再生中

以下が材料

Chromecast(第1世代)(他でもいけるはず。リンクは第2世代。)

HDMIとHDMI延長コネクター《メスとメス》

HDMIとHDMI延長コネクター《メスとメス》

f:id:kurimayoshida:20190218133029j:plain
抵抗(75Ω)3本

抵抗を指すと…というところは以下を参考にしました。

qiita.com

試していませんが
HDMIVGA 変換コネクターは以下のほうが小型でよさそうですね…。

また抵抗がむき出しなのは危険なので
55円でダミープラグを作ってVirtualDesktopのデスクトップを広げた話 - Qiita
のようにコネクターにしたほうがいいですね。

出来合いのものもあるようですが抵抗つけただけでできると知ると買う気が起きませんね…。

レーザーカッターでラズパイケースをつくる

以前100均素材でラズパイケースをつくったが
最近はもっぱらMDFボードをレーザーカッターで切ってつくっている。

こういうものには3Dプリンターだろうと思って試したのだが
現状では設計時間もコストも工作時間も精度も
レーザーカッターの方がいいという結論に。
箱状のものだからなのだとは思う。

自分のつくりかたを紹介する。

まずおおまかな設計。
今回はラズパイZeroの公式ケースがそのまま収まるケースを作る。

raspberry pi zero size」などでWeb検索してサイズの入った図面を手に入れ
サイズを把握する。

以下のサイトでベースとなる図面を作成する。

www.makercase.com

2.5mm厚のMDFを使う予定なので「Material Thickness」は2.5mmに。
「Generate Laser Cutter Case Plans」でsvgファイルを出力する。

f:id:kurimayoshida:20190202214854p:plain

このSVGIllustratorなどで編集して
カメラ用の穴やUSB端子用の穴を加えたりする。

正しい位置に穴を加えるために
前述のサイズの入った図面を使ったり
実際に公式ケースのサイズを測ったりする。
サイズを測るのにはデジタルノギスが便利。

できた図面。

f:id:kurimayoshida:20190202214838p:plain

図面ができたら2.5mm厚のMDFボードを買う。
大きなホームセンターに行けば手に入ると思う。
自分は近くのUnidy等で買う。
大きなものを買ってn等分にカットしてもらうと割安に。

f:id:kurimayoshida:20190202214924j:plain

あとはMDFボードをレーザーカッターで図面に従ってカットする。

レーザーカッターを時間貸ししている施設で行う。
自分はFabLabをよく使っている。

f:id:kurimayoshida:20190202214907j:plain

図面と素材を持っていくとレーザーカッターでカットしてくれるという場所もある。
自分はCAINZ工房を一度使ったことがある。

CAINZというホームセンター内にレーザーカッターがあり
そこで買ったMDFならカットしてくれる。
量産するならば割安になると思う。

f:id:kurimayoshida:20190202214903j:plain

カットできたら組み立てる。

f:id:kurimayoshida:20190202214928j:plain

ネジで止めて、お得意の100均の三脚をつける。

f:id:kurimayoshida:20190202214938j:plain

ちなみに
こちらの「ラズパイZero公式ケース用ケース(三脚付き)」を
2/10の同人ハードウェアフェスにて販売予定です。
よろしければお越しください。
mag.switch-science.com

ポケモンGOフレンド。

数ヶ月前のこと。

ポケモンGOのフレンドが
同僚のおっちゃん1人しかいなかったので
なんとなくフレンド募集掲示板に書き込まれていた
フレンドコード何件かにフレンド申請を送ってみた。

程なくして数人から承認が。
めでたくフレンドが増えたと思ったのもつかの間
何回かプレゼント交換が済むと次々と縁切りされてしまった。

そんな中1人残った方が。

1ヶ月間くらいか。

その方は、島根県松江の方らしく
毎日松江のお寺なんかの心休まる風景(ポケストップ画像)が
送られてくる。

僕はというと
気を抜くと家の前の公園で満たされてしまうプレゼントの中から
マシな風景(それでも通勤経路の風景なのだが)を
見繕って送る日々。

そんな日が続いた。

そしてある日、いつものようにプレゼントを開けてみると
そこにはいつもと違う風景が。

「松江空港」

え、空港? …旅行? どこへ…?

こっちに来る? いや、あるはずない、妄想も大概に…

顔も名前も知らない
毎日ポケストップ画像をやり取りするだけの関係。

そして翌日送られてきたのは

「ディズニーリゾート」

なんかこう、わくわく感が伝わってくる。
ポケストップ画像だけでこの表現力。

やるじゃんか…!

…って、そういうゲームじゃねえからこれ!

Chromebook で chromebrew を試す。

最近 Visual Studio Code を使っていて、Chromebook版もあるということを知り試してみた。

Visual Studio Code for Chromebooks and Raspberry Pi

見てみると crouton extension を使うもののよう。起動に少し手間がかかる。
インストールして画面が出た時は感動したが
いざ使えてみると他のツールも使いたくなり
結局フルスペックのPCじゃないとだめじゃないかという結論に…。

いやいや、ChromebookChromebook としていいところがあって
それにあと少しだけ、ローカルで、ちょっとしたテキストを打ったり、ちょっとしたプログラムを組んだり、そういったことができればいいんだと気づく。

そんなところに chromebrew というものを見つけた。
Homebrew のような Chrome OS 用のパッケージマネージャー。

GitHub - skycocker/chromebrew: Package manager for Chrome OS

開発者モードで使えるシェル上で、chroutonを使わずに、Linuxでお馴染みの各種ソフトをインストールしたり、使ったりできるようになる。
インストールは簡単。

  1. Chromebookデベロッパーモードにする
  2. Chrome 上で ctrl+alt+t でcroshシェルのタブを開き
  3. shell と打ってエンターキーでデベロッパーシェルを開く
  4. 以下のコマンドを打ってエンターキーでインストール実行
curl -Ls git.io/vddgY | bash

wgetを使ったインストールは最新の Chrome OS にはwgetコマンドがなく使えず

Windowsな人に優しいキーバインドのターミナルベースのテキストエディター「micro」や「Pytyon3」「openssh」なんかをインストールした。

デベロッパーシェル上で以下を実行すればそれぞれインストールできる。

crew install micro
crew install python3
crew install openssh

なかなかいいじゃないか。

Selenium+Python+PhantomJSでWebサイトのスクレイピング

先回の続き。

操作対象のWebブラウザーChromeからPhantomJSに変える。

PhantomJSはGUIを持たない、いわば透明なブラウザー
これにしてやると、動作中に画面を占領されないし
コマンドラインから起動できてサーバー上でのバッチ実行などにも使えるようになる。

PhantomJSインストール

http://phantomjs.org/download.html からダウンロードしてパスを通しておく。

操作用Pythonスクリプトの作成

先回とほぼ同じ。
webdriverの作成の部分だけ変わる。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import codecs
import time

from selenium import webdriver
from selenium.common.exceptions import TimeoutException

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

WAIT_SECOND = 30

USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36"

if __name__ == '__main__':

    driver = webdriver.PhantomJS(
        service_args=['--ignore-ssl-errors=true', '--ssl-protocol=TLSv1'], 
        desired_capabilities={'phantomjs.page.settings.userAgent': USER_AGENT})
    driver.set_window_size(1280, 1024)

    driver.get('https://google.co.jp/')

    # 検索キーワードとエンターキーを入力
    t=driver.find_element_by_id('lst-ib')
    t.send_keys(u'てけとーぶろぐ\n')

    # 要素の表示待ち
    WebDriverWait(driver, WAIT_SECOND).until(
        EC.visibility_of_element_located((By.CLASS_NAME, '_Rm')))

    # リンクをクリック
    b=driver.find_element_by_xpath('//*[@id="rso"]/div/div/div[1]/div/div/h3/a')
    b.click()

    # 要素の表示待ち
    WebDriverWait(driver, WAIT_SECOND).until(
        EC.visibility_of_element_located((By.CLASS_NAME, 'entry-title-link')))

    # スクリーンショットの保存
    driver.save_screenshot("ss.png")

    # ソースの書き出し
    file_name = 'test.html'
    with codecs.open(file_name, 'a', 'utf_8') as f:
        f.write(driver.page_source)

    driver.close()

ポイント

service_args

CentOSにおいてサイトによってはこの設定をしないと表示できなかったので指定する。

userAgent

今回はChromeと同じユーザーエージェントを設定している。
サイトによってはこの設定をしないとChromeでの表示結果と変わってしまうので指定する。

スクリーンショット

以下のコードでスクリーンショットを画像ファイルとして保存できる。

driver.save_screenshot("ss.png")

見えないブラウザーなので状況把握に便利。

Selenium+PythonでChromeの自動操作

Webアプリのテストのときや、Web上のデータ収集のときなど
Webブラウザーを自作プログラムから操作できると便利なことがある。

というわけでSelenium+PythonChromeを自動操作してみる。

今回は Windows, Python 2.7 という組み合わせで行ったけど
Python 3.x でも同様だと思う。

Pythonのインストール

https://www.python.org/の「Download」から
python-2.7.13.msi」をダウンロードしてインストールする。

PythonSeleniumのインストール

「C:\Python27」「C:\Python27\Scripts」にパスを通した状態で
コマンドプロンプトから以下を実行する。

pip install selenium

Chrome用ドライバーのインストール

https://sites.google.com/a/chromium.org/chromedriver/downloads
の「Latest Release: ChromeDriver 2.29」のリンクから
「chromedriver_win32.zip」をダウンロード、展開して
中の「chromedriver.exe」を適当な場所に置く。

操作用Pythonスクリプトの作成

例えば「main.py」として以下のように作成する。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import codecs

from selenium import webdriver
from selenium.common.exceptions import TimeoutException

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

WAIT_SECOND = 30

if __name__ == '__main__':

    driver = webdriver.Chrome()  
    driver.maximize_window()    
    driver.get('https://google.co.jp')
    
    # 検索キーワードとエンターキーを入力
    t=driver.find_element_by_id('lst-ib')
    t.send_keys(u'てけとーぶろぐ\n')

    # 要素の表示待ち
    WebDriverWait(driver, WAIT_SECOND).until(
        EC.visibility_of_element_located((By.CLASS_NAME, '_Rm')))

    # リンクをクリック
    b=driver.find_element_by_xpath('//*[@id="rso"]/div/div/div[1]/div/div/h3/a')
    b.click()

    # 要素の表示待ち
    WebDriverWait(driver, WAIT_SECOND).until(
        EC.visibility_of_element_located((By.CLASS_NAME, 'entry-title-link')))

    # ソースの書き出し
    file_name = 'test.html'
    with codecs.open(file_name, 'a', 'utf_8') as f:
        f.write(driver.page_source)

    driver.close()

操作用Pythonスクリプトの実行

「chromedriver.exe」にパスを通した状態で
作成した操作用Pythonスクリプトを実行する。

python main.py

ポイント

要素情報の確認方法

スクリプトでWebサイト上の要素を指定するのに
要素のXPathなどが必要だがこの確認には
Webブラウザーの機能を使うのが楽。
ChromeだとF12キーでデベロッパーツールを表示して
Shift+Ctrl+C を押してから要素をマウスクリック。
HTML上でハイライトされた行を右クリックして
Copy → Copy XPath とか。

確実なロード待ち方法

読み込み待ちには上記の例のように表示を待つのが確実。
サイトによっては要素を非表示状態でロードした後
動的に表示状態に切り替えたりするので。

その他の要素指定方法

要素の指定の仕方はXPath以外にも幾つかある。
ドキュメントをどうぞ。
http://selenium-python.readthedocs.io/locating-elements.html