TensorFlowによる精度計算の流れを追う
accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.argmax(Y, 1), tf.argmax(Y_, 1)), tf.float32))
Yをニューラルネットワークの出力層、Y_をその正解ラベルとした場合、学習モデルの精度を計算するとき、 TensorFlowでは上記ようにAPIを組合せて書くことがある。 式が長いので、各APIの処理の流れを、1つずつ出力しながら追ってみる。 分かれば非常にシンプルな処理の流れである。
計算の流れ
NumpyとTensorFlowをimportしておく。
>>> import tensorflow as tf >>> import numpy as np
YとY_に[3, 4]のサンプルデータをセットする。 1行目を不正解としている。
>>> Y = np.array([ ... [0.1, 0.2, 0.3, 0.4], ... [0.0, 0.8, 0.2, 0.0], ... [0.0, 0.4, 0.5, 0.1] ... ]) >>> print Y [[ 0.1 0.2 0.3 0.4] [ 0. 0.8 0.2 0. ] [ 0. 0.4 0.5 0.1]] >>> Y_ = np.array([ ... [0.0, 0.0, 1.0, 0.0], ... [0.0, 1.0, 0.0, 0.0], ... [0.0, 0.0, 1.0, 0.0] ... ]) >>> print Y_ [[ 0. 0. 1. 0.] [ 0. 1. 0. 0.] [ 0. 0. 1. 0.]]
TensorFlowのSessionを開始。 Sessionと言えば、昨日、映画のSessionを見ましたが非常に良い映画でした。
>>> sess = tf.Session()
ここから精度計算におけるTensorFlowのAPIの説明。 まずはtf.argmax()から。 第2パラメーターに1をセットすると、行ごとに最大となる列を返す。 Yの場合、1行目が4列目の0.4、2行目が2列目の0.8、3行目が3列目の0.5が最大となる。 1行目は0からカウントされるので、以下のようになる。 Y_についても同様。 ちなみに、第2パラメーターに0をセットすると、列ごとに最大となる行を返す。
>>> print sess.run(tf.argmax(Y, 1)) [3 1 2] >>> print sess.run(tf.argmax(Y_, 1)) [2 1 2]
続いてtf.equal()。 渡された2つのベクトルが一致しているか否かを見る。 今回は[3 1 2]と[2 1 2]を比較しているので次のようになる。
>>> eq = tf.equal(tf.argmax(Y, 1), tf.argmax(Y_, 1)) >>> print sess.run(eq) [False True True]
tf.cast()では第1パラメーターを第2パラメーターのデータ・タイプに変換する。 [False True True]もfloat32に変換すると次の通り。
>>> print sess.run(tf.cast(eq, tf.float32)) [ 0. 1. 1.]
最後はtf.reduce_mean()。 np.mean()と同じで平均を計算する。 [ 0. 1. 1.]の平均なので2/3となる。
>>> print sess.run(tf.reduce_mean(tf.cast(eq, tf.float32))) 0.666667
最後に全部繋げれば、一気に計算できる。
>>> accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.argmax(Y, 1), tf.argmax(Y_, 1)), tf.float32)) >>> print sess.run(accuracy) 0.666667
jsmでYahooファイナンスのデータを片っ端から取得してMongoDBに保存する
はじめに
jsm(Japanese Stock Market)という Yahooファイナンスをクロールして株関連データを取得できるライブラリーがある。 Brand、Finance、Priceデータが取得できるので、東証一部に絞ってデータを片っ端から取得するコードを書いた。
インストール
pipを更新してからjsmをインストールする。 スクレイピングはBeautifulSoup4で行っているので(再)インストール
$ sudo pip install --upgrade pip $ sudo pip install jsm $ sudo pip install beautifulsoup4 -U
MongoDBのラッパー
save, read, deleteを用意したMongoDBのラッパーファイル(db.py)を作成する。 DB名とCollection名を初期化で指定する。 saveはdictのlistをdataとして渡す。
# -*- coding: utf-8 -*- import sys import pymongo reload(sys) sys.setdefaultencoding('utf-8') class DB: def __init__(self, db_name, coll_name): self.db_name = db_name self.coll_name = coll_name def save(self, data): client = pymongo.MongoClient('localhost', 27017) db = client[self.db_name] coll = db[self.coll_name] coll.insert(data) def read(self): client = pymongo.MongoClient('localhost', 27017) db = client[self.db_name] coll = db[self.coll_name] data = [d for d in coll.find()] return data def delete(self): client = pymongo.MongoClient('localhost', 27017) db = client[self.db_name] coll = db[self.coll_name] coll.drop()
前準備
クローラーファイル(clawer.py)を作成し、必要なライブラリーと初期設定を行う。 上記のdb.pyもインポートしておく。 stockデータベースに、Brand、Finance、Priceコレクションを作成して取得したデータを保存する。
# -*- coding: utf-8 -*- import sys import jsm from progressbar import ProgressBar import pandas as pd import datetime from db import DB reload(sys) sys.setdefaultencoding('utf-8') DB_STOCK = 'stock' # Stock DB COLL_BRAND = 'brand' # Brand Collection COLL_FINANCE = 'finance' # Finace Collection COLL_PRICE = 'price' # Price Collection START_DATE = datetime.date(2014, 1, 1) # 株価の取得開始日
Brandデータを取得
get_brand()で全銘柄が取得できるが、取得状況の進捗を見たいのでカテゴリーごとに取得する。
def get_brands(self): db = DB(DB_STOCK, COLL_BRAND) #db.delete() categories = [ '0050', # 農林・水産業 '1050', # 鉱業 '2050', # 建設業 '3050', # 食料品 '3100', # 繊維製品 '3150', # パルプ・紙 '3200', # 化学 '3250', # 医薬品 '3300', # 石油・石炭製品 '3350', # ゴム製品 '3400', # ガラス・土石製品 '3450', # 鉄鋼 '3500', # 非鉄金属 '3550', # 金属製品 '3600', # 機械 '3650', # 電気機器 '3700', # 輸送機器 '3750', # 精密機器 '3800', # その他製品 '4050', # 電気・ガス業 '5050', # 陸運業 '5100', # 海運業 '5150', # 空運業 '5200', # 倉庫・運輸関連業 '5250', # 情報・通信 '6050', # 卸売業 '6100', # 小売業 '7050', # 銀行業 '7100', # 証券業 '7150', # 保険業 '7200', # その他金融業 '8050', # 不動産業 '9050' # サービス業 ] q = jsm.Quotes() pb = ProgressBar(maxval=len(categories)).start() for i in range(len(categories)): lis = [] try: brands = q.get_brand(categories[i]) except: pass for b in brands: dic = { 'category': categories[i], 'ccode': b.ccode, 'market': b.market, 'name': b.name, 'info': b.info } lis.append(dic) db.save(lis) pb.update(i+1)
Finaceデータを取得
Brandデータから東証一部の証券コード(ccode)のみを取得する。
def get_target_ccodes(self): data = DB(DB_STOCK, COLL_BRAND).read() df_brand = pd.DataFrame(data) df_brand = df_brand[df_brand['market']=='東証1部'] ccodes = df_brand['ccode'].tolist() return ccodes
取得した東証一部の証券コードを引数にしてFianceデータを取得する。
def get_finances(self, ccodes): db = DB(DB_STOCK, COLL_FINANCE) #db.delete() q = jsm.Quotes() pb = ProgressBar(maxval=len(ccodes)).start() lis = [] for i in range(len(ccodes)): try: f = q.get_finance(ccodes[i]) except: pass dic = { 'ccode': ccodes[i], 'market_cap': f.market_cap, 'shares_issued': f.shares_issued, 'dividend_yield': f.dividend_yield, 'dividend_one': f.dividend_one, 'per': f.per, 'pbr': f.pbr, 'eps': f.eps, 'bps': f.bps, 'price_min': f.price_min, 'round_lot': f.round_lot, 'years_high': f.years_high, 'years_low': f.years_low } lis.append(dic) pb.update(i+1) db.save(lis)
Priceデータを取得
Financeデータと同様、東証一部の証券コードを引数にしてPriceデータを取得する。
def get_prices(self, ccodes): start_date = START_DATE end_date = datetime.date.today() db = DB(DB_STOCK, COLL_PRICE) #db.delete() q = jsm.Quotes() pb = ProgressBar(maxval=len(ccodes)).start() for i in range(len(ccodes)): lis = [] try: prices = q.get_historical_prices(ccodes[i], jsm.DAILY, start_date, end_date) except: pass for p in prices: dic = { 'ccode': ccodes[i], 'date': p.date, 'open': p.open, 'high': p.high, 'low': p.low, 'close': p.close, 'volume': p.volume } lis.append(dic) db.save(lis) pb.update(i+1)
crawler.pyの全ソース
db.pyと同じ階層にファイルを置いて$ python crawler.py
を実行すればクロールを開始する。
# -*- coding: utf-8 -*- import sys import jsm from progressbar import ProgressBar import pandas as pd import datetime from db import DB reload(sys) sys.setdefaultencoding('utf-8') DB_STOCK = 'stock' COLL_BRAND = 'brand' COLL_PRICE = 'price' COLL_FINANCE = 'finance' START_DATE = datetime.date(2014, 1, 1) class Crawler: """ Refer to https://pypi.python.org/pypi/jsm/0.19 """ def __init__(self): pass def main(self): print 'getting brands...' self.get_brands() print 'getting finances...' ccodes = self.get_target_ccodes() self.get_finances(ccodes) print 'getting prices...' self.get_prices(ccodes) def get_brands(self): db = DB(DB_STOCK, COLL_BRAND) #db.delete() categories = [ '0050', # 農林・水産業 '1050', # 鉱業 '2050', # 建設業 '3050', # 食料品 '3100', # 繊維製品 '3150', # パルプ・紙 '3200', # 化学 '3250', # 医薬品 '3300', # 石油・石炭製品 '3350', # ゴム製品 '3400', # ガラス・土石製品 '3450', # 鉄鋼 '3500', # 非鉄金属 '3550', # 金属製品 '3600', # 機械 '3650', # 電気機器 '3700', # 輸送機器 '3750', # 精密機器 '3800', # その他製品 '4050', # 電気・ガス業 '5050', # 陸運業 '5100', # 海運業 '5150', # 空運業 '5200', # 倉庫・運輸関連業 '5250', # 情報・通信 '6050', # 卸売業 '6100', # 小売業 '7050', # 銀行業 '7100', # 証券業 '7150', # 保険業 '7200', # その他金融業 '8050', # 不動産業 '9050' # サービス業 ] q = jsm.Quotes() pb = ProgressBar(maxval=len(categories)).start() for i in range(len(categories)): lis = [] try: brands = q.get_brand(categories[i]) except: pass for b in brands: dic = { 'category': categories[i], 'ccode': b.ccode, 'market': b.market, 'name': b.name, 'info': b.info } lis.append(dic) db.save(lis) pb.update(i+1) def get_finances(self, ccodes): db = DB(DB_STOCK, COLL_FINANCE) #db.delete() q = jsm.Quotes() pb = ProgressBar(maxval=len(ccodes)).start() lis = [] for i in range(len(ccodes)): try: f = q.get_finance(ccodes[i]) except: pass dic = { 'ccode': ccodes[i], 'market_cap': f.market_cap, 'shares_issued': f.shares_issued, 'dividend_yield': f.dividend_yield, 'dividend_one': f.dividend_one, 'per': f.per, 'pbr': f.pbr, 'eps': f.eps, 'bps': f.bps, 'price_min': f.price_min, 'round_lot': f.round_lot, 'years_high': f.years_high, 'years_low': f.years_low } lis.append(dic) pb.update(i+1) db.save(lis) def get_prices(self, ccodes): start_date = START_DATE end_date = datetime.date.today() db = DB(DB_STOCK, COLL_PRICE) #db.delete() q = jsm.Quotes() pb = ProgressBar(maxval=len(ccodes)).start() for i in range(len(ccodes)): lis = [] try: prices = q.get_historical_prices(ccodes[i], jsm.DAILY, start_date, end_date) except: pass for p in prices: dic = { 'ccode': ccodes[i], 'date': p.date, 'open': p.open, 'high': p.high, 'low': p.low, 'close': p.close, 'volume': p.volume } lis.append(dic) db.save(lis) pb.update(i+1) def get_target_ccodes(self): data = DB(DB_STOCK, COLL_BRAND).read() df_brand = pd.DataFrame(data) df_brand = df_brand[df_brand['market']=='東証1部'] ccodes = df_brand['ccode'].tolist() return ccodes if __name__ == '__main__': Crawler().main()
所感
Priceデータを取得するのに15時間ぐらいかかったので、マルチスレッドにした方がいい。 上記のコードで継続的に最新のデータを収集し続けるには、もう少し改良が必要だけど、 とりあえず、サクッとデータを収集して分析したい人は使えると思う。 jsm自体は、 更新が2015年で止まっていて、GitHubからは削除されているが、ソース自体はPyPIに上がってるので、 これまで自作してた人は動かなくなっても改修できると思う。
ReactのListとKey
ReactでList(ObjectのArray)を描画する際、mapを利用して各Objectの項目をセットする。
var data = [ {id: 1, author: "Pete Hunt", text: "This is one comment"}, {id: 2, author: "Jordan Walke", text: "This is *another* comment"} ]; var CommentList = React.createClass({ render: function() { var commentNodes = this.props.data.map(function (comment) { return ( <div author={comment.author}> {comment.text} </div> ); }); return ( <div className="commentList"> {commentNodes} </div> ); } });
もし描画する際に、各ObjectにユニークなKeyを持たせていない場合は警告が出る。
react.js:20541 Warning: Each child in an array or iterator should have a unique "key" prop.
以下のように、keyに何かしらのユニークな値を持たせると、以下の警告は消える。
<div author={comment.author} key={comment.id}>
参考文献
React + Flask + Python + MongoDBで作るRSSリーダー
これまで2回に渡ってReactについて学んできた。
testpy.hatenablog.com testpy.hatenablog.com
僕は普段、Pythonを使って機械学習やデータ解析のコードを実装してるのだが、 Webアプリ化したいな、できればReactで実現できたらいいな、と思うことが度々あった。 そこでPythonistaのために、Reactを使った簡単なWebアプリ作成記事があればと、 今(2016/10/31現在)は亡きReactチュートリアル日本語版 をベースに、MongoDBに格納したRSSをPythonで読み込み、Flask経由でクライアントに送り、 Reactで描画してみたので、コードを載せておく。 ただし説明はほとんどないので、バリバリのReact初心者の方は、上の記事でベースを固めてから読んでみて下さい。 ちなみに、Reactチュートリアルのソースはまだあります。
見た目
こんな感じにする。
ファイル構造
react-tutorial ├── gigazine_rss.py // GIGAZINEのRSSをMongoDBに格納 ├── node_modules ├── package.json ├── public │ ├── css │ │ └── base.css // RSSを見やすいように加工 │ ├── index.html │ └── scripts │ └── example.js // シンプルなDOMをReactで作成 └── server.py // Flaskを用いてクライアントとサーバーを連携
データフロー
- index.htmlにアクセスする
- index.htmlがexample.jsを呼び出す
- example.jsがserver.jsを呼び出す
- server.jsがgigazine_rss.pyを呼び出す
- gigazine_rss.pyがGIGAZINE RSSを取得してMongoDBに格納する
- server.jsがgigazine_rss.py経由でMongoDBに格納したRSSを読み込む
- server.jsがexample.jsにRSSを渡す
- example.jsがRSSのDOMを作成する
- DOMがindex.htmlに描画される
index.html | example.js | server.py | gigazine_rss.py <-> GIGAZINE RSS | MongoDB
index.html
Reactはここではなく、exmaple.jsに記述する。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>React Tutorial</title> <!-- Not present in the tutorial. Just for basic styling. --> <link rel="stylesheet" href="css/base.css" /> <script src="https://unpkg.com/react@15.3.0/dist/react.js"></script> <script src="https://unpkg.com/react-dom@15.3.0/dist/react-dom.js"></script> <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script> <script src="https://unpkg.com/jquery@3.1.0/dist/jquery.min.js"></script> <script src="https://unpkg.com/remarkable@1.7.1/dist/remarkable.min.js"></script> </head> <body> <div id="content"></div> <script type="text/babel" src="scripts/example.js"></script> </body> </html>
example.js
チュートリアルでは、CommentBox、CommentList、Commetがあったが、 ここではCommentをRssに置き換えている。 またBoxとListで十分表現可能で、公式のDocs にも、そのように書けと書いてあるので、RssBoxとRssListのみとした。
var RssBox = React.createClass({ loadRssFromServer: function() { $.ajax({ url: this.props.url, dataType: 'json', cache: false, success: function(data) { this.setState({data: data}); }.bind(this), error: function(xhr, status, err) { console.error(this.props.url, status, err.toString()); }.bind(this) }); }, getInitialState: function() { return {data: []}; }, componentDidMount: function() { this.loadRssFromServer(); setInterval(this.loadRssFromServer, this.props.pollInterval); }, render: function() { return ( <div className="rssBox"> <h1 className="siteTitle">GIGAZINE RSS</h1> <RssList data={this.state.data} /> </div> ); } }); var RssList = React.createClass({ render: function() { var rssNodes = this.props.data.map(function (rss) { return ( <div className="rss" key={rss.id}> <h3 className="title"> <a href={rss.link}> {rss.title} </a> </h3> <p className="updated">{rss.updated}</p> <p className="summary">{rss.summary}</p> </div> ); }); return ( <div className="rssList"> {rssNodes} </div> ); } }); ReactDOM.render( <RssBox url="/api/rss" pollInterval={2000000} />, document.getElementById('content') );
server.py
gigazine_rss.pyを呼び出して、GIAZINE RSSの保存と読み込みを行っている。
import json import os import time from flask import Flask, Response, request from gigazine_rss import Gigazine_RSS app = Flask(__name__, static_url_path='', static_folder='public') app.add_url_rule('/', 'root', lambda: app.send_static_file('index.html')) # client and server side with MongoDB @app.route('/api/rss', methods=['GET', 'POST']) def comments_handler(): Gigazine_RSS().save() rss = Gigazine_RSS().read() return Response( json.dumps(rss), mimetype='application/json', headers={ 'Cache-Control': 'no-cache', 'Access-Control-Allow-Origin': '*' } ) if __name__ == '__main__': app.run(port=int(os.environ.get("PORT", 3000)), debug=True)
gigazine_rss.py
この記事を1ファイルで実行できるようにした。
# -*- coding: utf-8 -*- import sys import json import nltk import numpy import feedparser import urllib2 from bs4 import BeautifulSoup import re import pymongo reload(sys) sys.setdefaultencoding('utf-8') DATABASE_NAME = 'gigazine' COLLECTION_NAME = 'rss' class Gigazine_RSS: def __init__(self): pass def save(self): rss = self.__get_rss() self.__save_rss(rss) return rss def __get_rss(self): rss_url = 'http://feed.rssad.jp/rss/gigazine/rss_2.0' articles = feedparser.parse(rss_url) rss = [] for e in articles.entries: dic = { 'id': e.id, 'updated': e.updated, 'title': e.title, 'link': e.link, 'summary': self.__getTextOnly(BeautifulSoup(e.summary)) } rss.append(dic) return rss # Extract the text from an HTML page (no tags) def __getTextOnly(self, soup): v = soup.string # Split by tags and check whether nested tags if v == None: # If tags are nested c = soup.contents # Eliminate outmost tags resulttext = '' for t in c: subtext = self.__getTextOnly(t) # If the subtext is null(u''), don't append it if len(subtext) > 0: resulttext += subtext + '\n' return resulttext else: return v.strip() # Eliminate '\n' def __save_rss(self, data): client = pymongo.MongoClient('localhost', 27017) db = client[DATABASE_NAME] co = db[COLLECTION_NAME] co.drop() co.insert(data) def read(self): client = pymongo.MongoClient('localhost', 27017) db = client[DATABASE_NAME] co = db[COLLECTION_NAME] data = [d for d in co.find()] rss = [] for c in co.find(): c.pop('_id', None) rss.append(c) return rss if __name__ == '__main__': Gigazine_RSS().save() #Gigazine_RSS().read()
base.css
見やすいようにセンタリングやコントラストなどの調整をした。
body { background: #fff; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 15px; line-height: 1.7; margin: 0; padding: 30px; } a { color: #4183c4; text-decoration: none; } a:hover { text-decoration: underline; } code { background-color: #f8f8f8; border: 1px solid #ddd; border-radius: 3px; font-family: "Bitstream Vera Sans Mono", Consolas, Courier, monospace; font-size: 12px; margin: 0 2px; padding: 0 5px; } h1, h2, h3, h4 { font-weight: bold; margin: 0 0 15px; padding: 0; } h1 { font-size: 2.5em; } h2 { border-bottom: 1px solid #eee; font-size: 2em; } h3 { font-size: 1.5em; } h4 { font-size: 1.2em; } p, ul { margin: 15px 0; } ul { padding-left: 30px; } .rssBox { width: 600px; margin: auto; } .updated { color: #999; } .siteTitle { text-align: center; margin: 20px 0 40px; } .summary { margin: 0 0 40px; }
Reactチュートリアル入門: Pythonサーバーサイド連携編
チュートリアル | React
がなくなったようで。。良いチュートリアルだったのに。。
まあとりあえず、前回は
クライアントサイドのみでWebサイトを表示したので、
今回はサーバーサイドで取得したデータをクライアントサイドに渡して表示させる。
とは言っても、JSONファイルを表示するだけなので、非常に簡単。
ただし、サーバーサイドはNode.jsではなく、Pythonを使う。
その前にまず、前回のReactコードを少しカスタマイズする。
クライアントサイドのカスタマイズ
CommentBox
前回、シンプルだったCommentBoxに色々追記する。
loadCommentsFromServer: function() { $.ajax({ url: this.props.url, dataType: 'json', cache: false, success: function(data) { this.setState({data: data}); }.bind(this), error: function(xhr, status, err) { console.error(this.props.url, status, err.toString()); }.bind(this) }); },
Ajax通信を行う。urlは、後ほどReactDOM.renderでCommentBoxに渡される。 これがサーバーサイドへのパスになる。dataはサーバーサイドから返ってきたデータ。
getInitialState: function() { return {data: []}; },
クライアントサイドのdataの初期化。dataはObjectのArrayなので[]となる。
componentDidMount: function() { this.loadCommentsFromServer(); setInterval(this.loadCommentsFromServer, this.props.pollInterval); },
DOMに関わる初期化。 loadCommentsFromServerで設定したAjaxリクエストや、リフレッシュ頻度を指定するsetIntervalの登録など、 server-side rendering時には必要ない初期化処理についてはこの中で行う。
以上より、今回のCommentBoxは次のようになる。
var CommentBox = React.createClass({ loadCommentsFromServer: function() { $.ajax({ url: this.props.url, dataType: 'json', cache: false, success: function(data) { this.setState({data: data}); }.bind(this), error: function(xhr, status, err) { console.error(this.props.url, status, err.toString()); }.bind(this) }); }, getInitialState: function() { return {data: []}; }, componentDidMount: function() { this.loadCommentsFromServer(); setInterval(this.loadCommentsFromServer, this.props.pollInterval); }, render: function() { return ( <div className="commentBox"> <h1>Comments</h1> <CommentList comments={this.state.data} /> </div> ); } });
ReactDOM.render
設定したCommentBoxに、ReactDOM.renderでパラメーターを渡す。 サーバーサイドのパスとなるurlと、リフレッシュ頻度となるpollIntervalを指定する。 前回はurlキーではなく、dataキーにクライアントサイドで定義したdataを渡していた。 pollIntervalは公式では2000(2秒)だが、短すぎるので2000秒としておく。
ReactDOM.render( <CommentBox url="/api/comments" pollInterval={2000000} />, document.getElementById('content') );
Pythonサーバーサイド
Pythonのサーバーサイドは、Flaskを使っている。 Flaskは僕も知らないので、ググって下さい。 でもFlaskはまったく悩むことなく簡単に使えるようで、 クライアントサイドで指定したurl='/api/comments' [check1!] のすぐ下のcomments_handler() の中でデータを取得して返している。 今回はGETのみなので、単純にserver.pyと同じディレクトリーに置かれたcommnet.jsonを読み込んで [check2!] 、 json形式にPythonで適切に変形して、クライアントサイドに返している [check3!] ことが分かる。
import json import os import time from flask import Flask, Response, request app = Flask(__name__, static_url_path='', static_folder='public') app.add_url_rule('/', 'root', lambda: app.send_static_file('index.html')) # client and server side with json file @app.route('/api/comments', methods=['GET', 'POST']) # Check1! def comments_handler(): with open('comments.json', 'r') as f: comments = json.loads(f.read()) # Check2! if request.method == 'POST': new_comment = request.form.to_dict() new_comment['id'] = int(time.time() * 1000) comments.append(new_comment) with open('comments.json', 'w') as f: f.write(json.dumps(comments, indent=4, separators=(',', ': '))) return Response( json.dumps(comments), # Check3! mimetype='application/json', headers={ 'Cache-Control': 'no-cache', 'Access-Control-Allow-Origin': '*' } ) if __name__ == '__main__': app.run(port=int(os.environ.get("PORT", 3000)), debug=True)
実行
$ python server.py
上記のコマンドを実行すると、以下のように表示されるはず。
Reactチュートリアル入門:クライアントサイド編
チュートリアル | React(何故か2016-10-23時点ではNot Found) を理解しながら写経すると、Reactで簡単なWebアプリが作れるようになる。 しかし、僕自身もそうだが、テンプレートを使ったWebアプリしか作ったことしかなく、 Reactを理解したいという人向けに、今回はクライアントサイドだけをいじってWebアプリを作成する。 そもそもReactとは何か、Reactを使うと何が便利なのかを知りたい人は Reactを使うとなぜjQueryが要らなくなるのか が分りやすい。
Reactチュートリアルの始め方
Reactチュートリアルのプロジェクトをクローンして、パッケージをインストールする。
$ git clone https://github.com/reactjs/react-tutorial.git
$ cd react-tutorial
$ npm install
初期設定
プロジェクトの中身を見ると、色々ファイルが入っているので、必要なものだけに絞って残りは削除する。
react-tutorial ├── LICENSE ├── README.md ├── app.json ├── comments.json ├── db.py ├── db.pyc ├── node_modules │ ├── body-parser │ └── express ├── npm-debug.log ├── package.json ├── public │ ├── css │ ├── index.html │ └── scripts ├── requirements.txt ├── server.go ├── server.js ├── server.php ├── server.pl ├── server.py └── server.rb
以下が削除した結果。
react-tutorial ├── comments.json // 今回は使わない ├── node_modules // npm installで生成されたフォルダー │ ├── body-parser │ └── express ├── package.json // メタ情報ファイル ├── public │ ├── css │ ├── index.html // 今回編集する唯一のファイル │ └── scripts ├── requirements.txt // 後で使う ├── server.js // 後で実行する └── server.py // 後で実行する
- comments.json
- コメントが書かれたJSONファイル。今回は使わない。
- node_modules
- "npm install"を実行した時に生成されたフォルダー。Reactなど今回使うnode.js関連のパッケージが入っている。基本触らない。
- package.json
- ライセンスや著者、バージョンなど記述されたメタ情報ファイル。"npm install"の後でパッケージの依存関係の記述も自動的に追加されている。
- public
- HTML・CSS・JavaScript・画像など、クライアントサイドのファイルを置く場所。
- index.html
- 今回は編集する唯一のファイル。
- server.jsとserver.py
- アプリケーション・サーバーを起動するNode.jsとPythonのコード。他の言語を使いたい人は、それらのファイルを残す。
- requirements.txt
- Pythonのサーバーサイドを実行する際に必要なライブラリーをインストールするための設定ファイル。
index.htmlを開くと以下のようになるが、今回は下の方のscriptダグで囲まれたエリアにReactコードを追記していく。 そのため、"script/example.js"をインポートしているscriptタグをコメントアウトしておく。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>React Tutorial</title> <!-- Not present in the tutorial. Just for basic styling. --> <link rel="stylesheet" href="css/base.css" /> <script src="https://unpkg.com/react@15.3.0/dist/react.js"></script> <script src="https://unpkg.com/react-dom@15.3.0/dist/react-dom.js"></script> <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script> <script src="https://unpkg.com/jquery@3.1.0/dist/jquery.min.js"></script> <script src="https://unpkg.com/remarkable@1.7.1/dist/remarkable.min.js"></script> </head> <body> <div id="content"></div> <!-- <script type="text/babel" src="scripts/example.js"></script> --> <script type="text/babel"> // To get started with this tutorial running your own code, simply remove // the script tag loading scripts/example.js and start writing code here. </script> </body> </html>
Reactの実装
最終的なWebアプリの見た目はこんな感じ。
まずは、Webアプリで表示するためのJSONを記述する。
var data = [ {id: 1, author: "Pete Hunt", text: "This is one comment"}, {id: 2, author: "Jordan Walke", text: "This is *another* comment"} ];
ここからReact。
上記ので記述したdataを、CommentBox
に入れた結果を、index.htmlの<div id="content"></div>
内にレンダリングする、
という意味のコードを記述する。
レンダリングするDOMの構造を、このCommentBox
から始まるReactによって作成することが、今回の主な内容であり、Reactのコアな部分になる。
ReactDOM.render( <CommentBox data={data} />, document.getElementById('content') );
上記のdata
キーは、CommentBox
では{this.props.data}
として表される。
逆に言えば、親のデータは{this.props.xxx}
で取得できる。
また{this.props.data}
は、子であるCommentList
のcomments
キーにそのまま渡される(オリジナルはdataキー)。
ここより下は、コメントのリストが表示されるので、h1タグでCommentsと記述しておく。
var CommentBox = React.createClass({ render: function() { return ( <div className="commentBox"> <h1>Comments</h1> <CommentList comments={this.props.data} /> </div> ); } });
上記のcomments
キーは、CommentList
では{this.props.comments}
となる。
これはJSONのobjectのarrayなので、個々のobjectを子であるComment
に渡すために、
map関数を使っている。またComment
へはobjectの値をセットしている。
var CommentList = React.createClass({ render: function() { var commentNodes = this.props.comments.map(function (comment) { return ( <Comment author={comment.author} key={comment.id}> {comment.text} </Comment> ); }); return ( <div className="commentList"> {commentNodes} </div> ); } });
上記のauthor
キーは{this.props.author}
になる。
また上記の{comment.text}
は{this.props.children}
になる。
var Comment = React.createClass({ render: function() { return ( <div className="comment"> <h2> {this.props.author} </h2> {this.props.children} </div> ); } });
このようにReactによって、CommetBox
以下でDOMの構造が構築された後、最終的にレンダリングされる。
index.htmlに追記したコードをまとめると以下のようになる。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>React Tutorial</title> <!-- Not present in the tutorial. Just for basic styling. --> <link rel="stylesheet" href="css/base.css" /> <script src="https://unpkg.com/react@15.3.0/dist/react.js"></script> <script src="https://unpkg.com/react-dom@15.3.0/dist/react-dom.js"></script> <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script> <script src="https://unpkg.com/jquery@3.1.0/dist/jquery.min.js"></script> <script src="https://unpkg.com/remarkable@1.7.1/dist/remarkable.min.js"></script> </head> <body> <div id="content"></div> <!-- <script type="text/babel" src="scripts/example.js"></script> --> <script type="text/babel"> // To get started with this tutorial running your own code, simply remove // the script tag loading scripts/example.js and start writing code here. var data = [ {id: 1, author: "Pete Hunt", text: "This is one comment"}, {id: 2, author: "Jordan Walke", text: "This is *another* comment"} ]; var CommentBox = React.createClass({ render: function() { return ( <div className="commentBox"> <h1>Comments</h1> <CommentList comments={this.props.data} /> </div> ); } }); var CommentList = React.createClass({ render: function() { var commentNodes = this.comments.data.map(function (comment) { return ( <Comment author={comment.author} key={comment.id}> {comment.text} </Comment> ); }); return ( <div className="commentList"> {commentNodes} </div> ); } }); var Comment = React.createClass({ render: function() { return ( <div className="comment"> <h2> {this.props.author} </h2> {this.props.children} </div> ); } }); ReactDOM.render( <CommentBox data={data} />, document.getElementById('content') ); </script> </body> </html>
アプリケーション・サーバーの実行
以下のいずれかを実行し、http://localhost:3000/にアクセスすれば、 2つのコメントがレンダリングされた画面が表示されるはず。
- Node.js
node server.js
pip install -r requirements.txt
python server.py
pipの実行は初回だけでよい。
参考文献
Atomがフリーズしたときの対処法
macOS Sierraにアップデートしたら、"Editor is not responding"となったので、その対処法をメモに残しておく。
手順
- Finderのメニューバーから、[移動] > [フォルダーへ移動...]と選択
- テキストボックスに<~/.atom/storage>を入力して[移動]ボタンをクリック
- フォルダー内のファイルを削除
なお、Atom関連の設定が色々と飛ぶようなので、自己責任で。 僕の場合、ファイルは"application.json"しかなかった。 削除したら、"Editor is not responding"というメッセージが表示されることもなくなったし、 インストールしたパッケージやテーマも飛ばなかった。