Help us understand the problem. What is going on with this article?

Node.jsからPythonのコードを呼び出すPython-shellを使ってみた

Node.jsからPythonの処理を実行したくて・・・

ちょっとばかし、簡易的なデモアプリを作っていて、Node.jsでアプリを書き始めていました。しかし、急遽Python使って機械学習モデルを使った予測結果をNode.js側で表示する必要が出てきました。Flaskとか使ってRESTの口を用意することはすぐに思いついたのですが、同じPC内で動かすならそこまで頑張る必要もないと思い、もっと簡易的な方法が無いのかなと探したところ、Python-shellというNode.jsのライブラリがあるではないですか!
ということで、それを使ってみた記録になります。

Python-shellとは

Node.jsのライブラリで、Node.js上でPythonスクリプトを実行するができます。
マニュアルは以下になります。
https://www.npmjs.com/package/python-shell

他に深い説明はないです。というかできないです。ホントこれだけ。

Python-shellってどう使うの?

まずインストールする

Node.jsおよびNPMはインストールされていることは前提になります。以下のコマンドでインストールです。

npm install python-shell

はい、簡単ですね。インストールもすぐに終わります。

Node.js上に書いてみる。

以下のようにpython-shellを呼び出します。
変数名に{}を付けることがポイントのようです。

test.js
var {PythonShell} = require('python-shell');

呼び出し方は3種類あって、それぞれ説明します。

書き方その①:コードべた書きパターン

まずはNode.jsのコードに中で、Pythonのコードをべた書きする方法です。
その時はrunStringを使います。
左から、第1引数がPythonのコード、第2引数が実行時のオプション(後述)、第3引数がNode.jsのコールバック関数になります。

test.js
var {PythonShell} = require('python-shell');
PythonShell.runString('x=1+1;print(x)', null, function (err,data) {
  if (err) throw err;
  console.log(data)
  console.log('finished');
});

怠かったんで、マニュアルからそのままパクってきました。
こうすると、計算結果の2が返ってきます。
ポイントはPython内でprintした標準出力の結果が、Node.js側にレスポンスとして返されるようです。

書き方その②:スクリプト読み込みパターン

Pythonのコードが長くなったら、べた書きはありえません。なので、スクリプトを別に分けてコードを書くことが一般的でしょう。my_script.pyというPythonコードを用意したとして、以下のように呼び出します。

test.js
var {PythonShell} = require('python-shell');
PythonShell.run('my_script.py', null, function (err) {
  if (err) throw err;
  console.log('finished');
});

これも、怠かったんで、マニュアルからそのままパクってきました。

書き方その③:インスタンス化して実行するパターン

newを使ってインスタンスを作ります。その後、sendで文字列を用意して、'message'を使ってPython側に送り込んで、コールバック内の変数messageにPython側でのレスポンスが返ってきます。

test.js
var pyshell = new PythonShell('my_script.py');

// sends a message to the Python script via stdin
pyshell.send('hello');

pyshell.on('message', function (message) {
  // received a message sent from the Python script (a simple "print" statement)
  console.log(message);
);

オプションはどう指定するのさ?

JSON形式で用意します。代表的なものは以下かなと。
特に重要なものを少し説明します。
pythonPathは明示的に指定しましょう。Python2と3が共存している環境の場合に、ちゃんと3を実行してくれるようにパスを指定することを強く推奨します。Python2の状態で実行実行しようとしたら動かなかったので、ここでは絶対に欠かさないように!

test.js
var {PythonShell} = require('python-shell');
var options = {
  mode: 'text', // testもしくはjson
  pythonPath: 'path/to/python', // Python3のパスを指定しないと動かないので注意
  pythonOptions: ['-u'], 
};

PythonShell.run('my_script.py', options, function (err) { // 第2引数にオプションを入れる
  if (err) throw err;
  console.log('finished');
});

具体的にやってみたサンプルコード

Python側も具体的にやってみたサンプルを載せます。
やりたいことは、Node.js側からjson形式のデータをPython側に送りつけて、それをpandasに入れて予測モデルを実行し、その結果を返すまでのコードです。

Python側のコード

①Node.jsから受け取ったJSONデータをPandasに入れる
②LightGBMで書かれたモデルをpickleで読み込む
③スコアリングをする

test.py
import sys,json,pickle,lightgbm
import pandas as pd
jsonData =sys.stdin.readline() #  ①データはこうやって読み込むらしいがテキスト形式になっているので注意!
valid_x = pd.io.json.json_normalize(json.loads(jsonData) ) # json形式に変換してから、Pandasに読み込ませる

with open('./model.pickle', mode='rb') as fp: # ②モデルの読み込み
   fit = pickle.load(fp)

y_pred = fit.predict(valid_x, num_iteration=fit.best_iteration)
print(y_pred) # ③スコアリングの実施。この標準出力結果がNode.jsに返される

Node.js側のコード

json形式のデータを受け取って、それをPythonに送って実行させて、その結果を表示する。
pythonに送るときに、データ形式をテキストに直さないと動かないのがポイントでした。(もしかしたら、適切な方法があるかもですが、オプションとか変更しても違いがわかりませんでした)

test.js
var testData = require('./test.json'); // JSON形式のデータを変数に格納
var {PythonShell} = require('python-shell');

var options = {
    pythonPath: 'C:/Users/XXXXXXXXX/Anaconda3/python' ,
    pythonOptions: ['-u'], 
    scriptPath: './'
};

var pyshell = new PythonShell('./test.py',options);
pyshell.send(JSON.stringify(testData)) ; // 文字列に直してからじゃないと送れないみたい・・・。JSONのままだと怒られる。modeをjsonで指定しても変化はなかった。
pyshell.on('message',function(message){
     console.log("予測結果:"+JSON.parse(message) ); // JSONにパースする必要はないけども、これが割と求めている表示になってくれたので、こうしている
});
予測結果:0.1

とまあ、こんな感じでした。
他にもポツポツ落とし穴がありそうな雰囲気ですが、とりあえず自分がやりたいことはこれでできたので、大丈夫と思います。

Ayumu_walker
IT万屋だったけど、最近はデータサイエンティストになりつつあります。IBM認定の量子コンピューターエンジニアQiskit Advocateです。 元はDb2のインフラ系エンジニア。アプリ開発から、機械学習、深層学習、数理最適化、最新技術のつまみ食い中。 元理論物理学徒(量子情報理論、一般相対性理論、宇宙論、熱力学)。 あくまで個人の意見で記事を書いています。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away