以下の記事の概要です。
- 日経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/bashecho'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 --saveecho-----------------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の使い方が適当なので、ええ感じに書き換えてね!asyncfunctionrun(){constuser_id='';//s??証券のあなたのIDconstuser_pass='';//s??証券のあなたのパスワードconstshouken_url='';//証券会社のURL(s??証券)constspreadsheet_url='';//GASで発行したURLconstpptr=require('puppeteer');varnow1=newDate();constouttime=80000;constwaittime=8000;// ブラウザを起動するconstbrowser=awaitpptr.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{// ページつくるconstpage=awaitbrowser.newPage()page.setDefaultNavigationTimeout('120000')awaitpage.setViewport({width:1200,height:800})letnavigationPromise=page.waitForNavigation()awaitpage.goto(shouken_url,{timeout:outtime})console.log('証券会社 WEBサイトサイトへアクセス')awaitpage.waitForSelector('.sb-box-sub-02-content ',{visible:true,timeout:outtime})awaitpage.waitForSelector('.sb-box-sub-02-content > dl > dd > #user_input > input',{visible:true})awaitpage.click('.sb-box-sub-02-content > dl > dd > #user_input > input')awaitpage.type('.sb-box-sub-02-content > dl > dd > #user_input > input',user_id)awaitpage.waitForSelector('.sb-box-sub-02-content > dl > dd > #password_input > input')awaitpage.click('.sb-box-sub-02-content > dl > dd > #password_input > input')awaitpage.type('.sb-box-sub-02-content > dl > dd > #password_input > input',user_pass)awaitpage.waitForSelector('.sb-box-sub-02-inner > .sb-box-sub-02-content > .sb-position-c > a > .ov')awaitpage.click('.sb-box-sub-02-inner > .sb-box-sub-02-content > .sb-position-c > a > .ov',{timeout:outtime})console.log('クリック:ログイン')awaitnavigationPromisenavigationPromise=page.waitForNavigation()awaitpage.waitForSelector('#navi01P',{timeout:outtime})awaitpage.click('#navi01P > ul > li:nth-child(8) > a > img',{timeout:outtime})console.log('クリック:先物・オプション')awaitnavigationPromise//参考記事//Puppeteerで次ページへの遷移を待つ (別タブや別ウインドウの遷移を待つ)//https://qiita.com/hnw/items/a07e6b88d95d1656e02fconstnewPagePromise=newPromise(resolve=>browser.once('targetcreated',target=>resolve(target.page())));console.log('waitFor : '+waittime)awaitpage.waitFor(waittime)awaitpage.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('クリック:先物・オプション取引サイト')constnewPage=awaitnewPagePromise;awaitnewPage.setViewport({width:1200,height:800})//newPage.waitFor(waittime)しないと、frameだけしかない状態で次の処理をしてエラーになるのを防ぐため。//もっとカッコいい方法があるはず!!console.log('waitFor : '+waittime)awaitnewPage.waitFor(waittime)awaitnewPage.waitFor('frameset > frame',{timeout:outtime});//先物・オプション取引サイトは、frameset > frame > frameset > frameの構造//Frameの位置を正しく認識させる//画面上メニューの「取引」をクリックしたい//トップframeset下の0番目のframeを選し、その下のframeset下の1番目のframeを選択する//具体的にはココ!letframe_menu=awaitnewPage.frames()[0].childFrames()[0].childFrames()[1]//frameが選択できたので、idの#menu20をクリック//idのみでクリックできる場合は、これで良いframe_menu.click('#menu20')console.log('クリック:取引')awaitnewPage.waitFor('frameset > frame',{timeout:outtime});console.log('waitFor : '+waittime)awaitnewPage.waitFor(waittime)//取引−オプション新規注文 をクリックawaitclick_submenu(newPage,outtime,waittime);//取引−オプション新規注文画面にいる状態//取引−オプション新規注文画面のframeに移動した状態letframe_main=awaitnewPage.frames()[0].childFrames()[2].childFrames()[1]//「20000より上」「17000より下」のボタンが有るか確認するletpath_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'letpath_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'letlink_down=awaitframe_main.$x(path_down)letlink_up=awaitframe_main.$x(path_up)varymd=awaityymmdd();if(link_up.length!=0){//「20000より上」があれば、上ページのデータを取得awaitclick_next(newPage,path_up,outtime,waittime);//画面に表示されるデータを取得するlettextdata=awaitgetTable(newPage,ymd);//取得したデータをスプレッドシートへ書きこむawaitpostSpreadsheet(textdata,spreadsheet_url);}//取引−オプション新規注文 をクリック//最初に開く画面へ移動awaitclick_submenu(newPage,outtime,waittime);//画面に表示されるデータを取得するlettextdata=awaitgetTable(newPage,ymd);//取得したデータをスプレッドシートへ書きこむawaitpostSpreadsheet(textdata,spreadsheet_url);if(link_down.length!=0){awaitclick_next(newPage,path_down,outtime,waittime);lettextdata=awaitgetTable(newPage,ymd);awaitpostSpreadsheet(textdata,spreadsheet_url);}varnow2=newDate();varnow11=now1.getMinutes()*60+now1.getSeconds()varnow21=now2.getMinutes()*60+now2.getSeconds()console.log('time : '+(now21-now11));awaitbrowser.close()console.log('Exit')console.log('Exit')}catch(e){//失敗時、すぐに止めたい。GAEのインスタンスを無駄に使わないawaitbrowser.close()console.log('ERR')console.log('ERR')console.log(e)logError(e)}//try {}//async function run(){asyncfunctionclick_submenu(newPage,outtime,waittime){letframe_menu2=awaitnewPage.frames()[0].childFrames()[0].childFrames()[1]letframe_submenu=awaitframe_menu2.$x('/html/body/form/div/table/tbody/tr/td/table[2]/tbody/tr/td[2]/span/a[2]')// ↑await これ忘れがち!! 忘れるとエラーになる。何度も失敗した!!awaitframe_submenu[0].click();console.log('クリック:取引 > オプション新規注文')awaitnewPage.waitFor('frameset > frame',{timeout:outtime});console.log('waitFor : '+waittime)awaitnewPage.waitFor(waittime)}asyncfunctionclick_next(newPage,path,outtime,waittime){console.log('mainコンテンツのframe取得_click_next')letframe_main2=awaitnewPage.frames()[0].childFrames()[2].childFrames()[1]//上へor下へのボタンへのパスframe_submenu=awaitframe_main2.$x(path)console.log('クリック:次ページ')awaitframe_submenu[0].click();awaitnewPage.waitFor('frameset > frame',{timeout:outtime});console.log('waitFor : '+waittime)awaitnewPage.waitFor(waittime)}asyncfunctiongetTable(newPage,ymd){//参考//puppeteerでの要素の取得方法//page.$のところ//https://qiita.com/go_sagawa/items/85f97deab7ccfdce53ea//日経平均、日経225先物期近 取得//日経平均、日経225先物期近、などが並ぶframeへ移動console.log('frame取得 toolBarコンテンツ')letframe_toolBar=awaitnewPage.frames()[0].childFrames()[1].childFrames()[1]//xpathのフルパスでテーブルタグのtrを指定する(IDで上手く取れない場合は、これが簡単)console.log('テーブルタグ取得 日経平均、日経225先物期近');lettbla=awaitframe_toolBar.$x('/html/body/table/tbody/tr/td/table/tbody/tr[2]')console.log('データ取得 日経平均、日経225先物期近');vartxtNikkei='';for(leti=0;i<tbla.length;i++){lettr=await(awaittbla[i].getProperty('innerHTML')).jsonValue();lettr1=tr.split('<')letdatas=[];//console.log(tr1) for(letiintr1){//i=2は、日経平均 i=12は、日経225先物期近if(i==2||i==12){letinner=tr1[i].split('>')[1]if(inner!=''&&inner!=null&&inner!='\n'&&inner!='\n'){//上のconsole.log(tr1)で、発見ーーーーー↑↑↑↑↑↑↑↑↑↑↑↑↑↑ ↑↑↑↑↑↑↑↑↑↑↑↑ if(inner.toString().split(',').length>0){letsplitdata=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コンテンツ')letframe_main=awaitnewPage.frames()[0].childFrames()[2].childFrames()[1]console.log('テーブルタグ取得 SQ日');letsq=awaitframe_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日');varSQ=await(awaitsq[0].getProperty('innerHTML')).jsonValue();//日経225オプションのデータ 取得 console.log('テーブルタグ取得 日経225オプション');lettbl1=awaitframe_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オプション');vartextdata='';for(leti=0;i<tbl1.length;i++){vartr=await(awaittbl1[i].getProperty('innerHTML')).jsonValue();vartr1=tr.split('<')vardatas=[];for(letiintr1){letinner=tr1[i].split('>')[1]if(inner!=''&&inner!=null&&inner!='新規買'&&inner!='新規売'){datas.push(inner);}}textdata+=ymd+','+datas.toString()+','+txtNikkei+','+SQ+',\n';}console.log(textdata)returntextdata;}asyncfunctionpostSpreadsheet(textdata,spreadsheet_url){//参考 curlコマンドをPythonやnode.jsのコードに変換する方法//https://qiita.com/tottu22/items/9112d30588f0339faf9bvarrequest=require('request');varheaders={'Content-Type':'text/csv'};vardataString=textdata;varoptions={url:spreadsheet_url,method:'POST',headers:headers,body:dataString};functioncallback(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);
});
*/};asyncfunctionyymmdd(param){//AppEngineは、UTCなのでAsia/Tokyoへタイムゾーンを変更//node.jsでタイムゾーンの変換処理にdate-fns-timezoneを利用する//https://qiita.com/kazuhiro1982/items/b1235a893ee874d8ff65const{startOfDay,addDays}=require('date-fns');const{convertToTimeZone}=require('date-fns-timezone');// タイムゾーン定義consttimeZone="Asia/Tokyo";// 現在時刻(UTC)を取得consttargetDate=newDate();// TimeZone付きDateに変換constnow=convertToTimeZone(targetDate,{timeZone:timeZone});varyear=now.getYear();// 年varmonth=now.getMonth()+1;// 月varday=now.getDate();// 日varhour=now.getHours();// 時varmin=now.getMinutes();// 分varsec=now.getSeconds();// 秒vardayOfWeek=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}varymd=year+'/'+month+'/'+day+''+hour+':'+min+':'+sec;if(param=='week'){returndayOfWeek;}elseif(param=='hour'){returnhour;}else{returnymd;}};asyncfunctionrun2(){varweek=awaityymmdd('week');varhour=awaityymmdd('hour');if(week>=2&&week<=5){//火〜金[ "日", "月", "火", "水", "木", "金", "土" ]console.log('Start Job week:'+week+' hour:'+hour);run();}elseif(week==1&&hour>=7){//月曜かつ7時以上console.log('Start Job week:'+week+' hour:'+hour);run();}elseif(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)functiondoPost(e){varcsvText=e.postData.getDataAsString();//テキストファイルにする(CSVになってる)Logger.log("csv : "+csvText);varspreadsheet=SpreadsheetApp.openById('');//書込先のスプレッドシートのIDvarsheet=spreadsheet.getSheetByName('シート1');//スプレッドシートのシート名vararr=csvText.split(',');//CSVをカンマでsplitする。vararrData=sheet.getDataRange().getValues();//現在スプレッドシートに入ってるデータを全部取得して、2次元配列にする。varlen_arrData=arrData.length;//現在スプレッドシートに入ってるデータが、何行あるか確認してるvarj=0;varsheet_cols=19;//GAEで取得したデータの1行当たりの列数が19なので、19としてる。/*
(arr.length -1) の意味:GAE上で改行コードを入れたが、改行コードをうまく認識できないので、全部で
配列がいくつ有るか数えて、最後に1引く(1引くのは、GAE上最後のデータの後にカンマを入れたから)
(arr.length -1)は、19の倍数になってるので、19(sheet_cols)で割って、何行あるかを取得する。
*/varcols_arr=(arr.length-1)/sheet_cols;for(vari=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(vari=0;i<len_arrData;i++){arrData.shift();//配列の上からデータを削除}varrows=arrData.length;//行数の確認varcols=arrData[0].length;//列数の確認/*
(len_arrData + 1) :今取得したデータを書き込む行番号を決める。元々スプレッドシートに入ってるデータの行数+1で最終行の次の行に書き込む。
1 :1列目(A列)に書く
rows :今回追加する行数
cols :今回追加する列数(19)
arrData :今回追加するデータの配列
*/sheet.getRange((len_arrData+1),1,rows,cols).setValues(arrData);// 結果を返す varoutput=ContentService.createTextOutput();output.setMimeType(ContentService.MimeType.JSON);output.setContent(JSON.stringify({message:"success!"}));returnoutput;}
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に設定する。
- 以下のように、データが蓄積されていきます。
![スプレッドシートの状態.JPG]()
GAE用 app.yaml
- app.yamlは、最初に作成したディレクトリ内に作成します。
app.yamlruntime:nodejs10instance_class:F4_1G#puppeteer利用のためhandlers:-url :/script :autosecure :always#このあたりに、adminと書けば、自分しか使えないサイトになったと思うが、エラーになる。
GAE用 cron.yaml
- cron.yamlは、最初に作成したディレクトリ内に作成します。
cron.yamlcron:-description:"PJ1"url:/schedule:every 5 minutes from 08:50 to 12:00timezone: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 を課金対象にした上でデプロイした後、課金をやめないと、誤って課金される場合があるので注意要です。
前提事項
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/bashecho'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 --saveecho-----------------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の使い方が適当なので、ええ感じに書き換えてね!asyncfunctionrun(){constuser_id='';//s??証券のあなたのIDconstuser_pass='';//s??証券のあなたのパスワードconstshouken_url='';//証券会社のURL(s??証券)constspreadsheet_url='';//GASで発行したURLconstpptr=require('puppeteer');varnow1=newDate();constouttime=80000;constwaittime=8000;// ブラウザを起動するconstbrowser=awaitpptr.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{// ページつくるconstpage=awaitbrowser.newPage()page.setDefaultNavigationTimeout('120000')awaitpage.setViewport({width:1200,height:800})letnavigationPromise=page.waitForNavigation()awaitpage.goto(shouken_url,{timeout:outtime})console.log('証券会社 WEBサイトサイトへアクセス')awaitpage.waitForSelector('.sb-box-sub-02-content ',{visible:true,timeout:outtime})awaitpage.waitForSelector('.sb-box-sub-02-content > dl > dd > #user_input > input',{visible:true})awaitpage.click('.sb-box-sub-02-content > dl > dd > #user_input > input')awaitpage.type('.sb-box-sub-02-content > dl > dd > #user_input > input',user_id)awaitpage.waitForSelector('.sb-box-sub-02-content > dl > dd > #password_input > input')awaitpage.click('.sb-box-sub-02-content > dl > dd > #password_input > input')awaitpage.type('.sb-box-sub-02-content > dl > dd > #password_input > input',user_pass)awaitpage.waitForSelector('.sb-box-sub-02-inner > .sb-box-sub-02-content > .sb-position-c > a > .ov')awaitpage.click('.sb-box-sub-02-inner > .sb-box-sub-02-content > .sb-position-c > a > .ov',{timeout:outtime})console.log('クリック:ログイン')awaitnavigationPromisenavigationPromise=page.waitForNavigation()awaitpage.waitForSelector('#navi01P',{timeout:outtime})awaitpage.click('#navi01P > ul > li:nth-child(8) > a > img',{timeout:outtime})console.log('クリック:先物・オプション')awaitnavigationPromise//参考記事//Puppeteerで次ページへの遷移を待つ (別タブや別ウインドウの遷移を待つ)//https://qiita.com/hnw/items/a07e6b88d95d1656e02fconstnewPagePromise=newPromise(resolve=>browser.once('targetcreated',target=>resolve(target.page())));console.log('waitFor : '+waittime)awaitpage.waitFor(waittime)awaitpage.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('クリック:先物・オプション取引サイト')constnewPage=awaitnewPagePromise;awaitnewPage.setViewport({width:1200,height:800})//newPage.waitFor(waittime)しないと、frameだけしかない状態で次の処理をしてエラーになるのを防ぐため。//もっとカッコいい方法があるはず!!console.log('waitFor : '+waittime)awaitnewPage.waitFor(waittime)awaitnewPage.waitFor('frameset > frame',{timeout:outtime});//先物・オプション取引サイトは、frameset > frame > frameset > frameの構造//Frameの位置を正しく認識させる//画面上メニューの「取引」をクリックしたい//トップframeset下の0番目のframeを選し、その下のframeset下の1番目のframeを選択する//具体的にはココ!letframe_menu=awaitnewPage.frames()[0].childFrames()[0].childFrames()[1]//frameが選択できたので、idの#menu20をクリック//idのみでクリックできる場合は、これで良いframe_menu.click('#menu20')console.log('クリック:取引')awaitnewPage.waitFor('frameset > frame',{timeout:outtime});console.log('waitFor : '+waittime)awaitnewPage.waitFor(waittime)//取引−オプション新規注文 をクリックawaitclick_submenu(newPage,outtime,waittime);//取引−オプション新規注文画面にいる状態//取引−オプション新規注文画面のframeに移動した状態letframe_main=awaitnewPage.frames()[0].childFrames()[2].childFrames()[1]//「20000より上」「17000より下」のボタンが有るか確認するletpath_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'letpath_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'letlink_down=awaitframe_main.$x(path_down)letlink_up=awaitframe_main.$x(path_up)varymd=awaityymmdd();if(link_up.length!=0){//「20000より上」があれば、上ページのデータを取得awaitclick_next(newPage,path_up,outtime,waittime);//画面に表示されるデータを取得するlettextdata=awaitgetTable(newPage,ymd);//取得したデータをスプレッドシートへ書きこむawaitpostSpreadsheet(textdata,spreadsheet_url);}//取引−オプション新規注文 をクリック//最初に開く画面へ移動awaitclick_submenu(newPage,outtime,waittime);//画面に表示されるデータを取得するlettextdata=awaitgetTable(newPage,ymd);//取得したデータをスプレッドシートへ書きこむawaitpostSpreadsheet(textdata,spreadsheet_url);if(link_down.length!=0){awaitclick_next(newPage,path_down,outtime,waittime);lettextdata=awaitgetTable(newPage,ymd);awaitpostSpreadsheet(textdata,spreadsheet_url);}varnow2=newDate();varnow11=now1.getMinutes()*60+now1.getSeconds()varnow21=now2.getMinutes()*60+now2.getSeconds()console.log('time : '+(now21-now11));awaitbrowser.close()console.log('Exit')console.log('Exit')}catch(e){//失敗時、すぐに止めたい。GAEのインスタンスを無駄に使わないawaitbrowser.close()console.log('ERR')console.log('ERR')console.log(e)logError(e)}//try {}//async function run(){asyncfunctionclick_submenu(newPage,outtime,waittime){letframe_menu2=awaitnewPage.frames()[0].childFrames()[0].childFrames()[1]letframe_submenu=awaitframe_menu2.$x('/html/body/form/div/table/tbody/tr/td/table[2]/tbody/tr/td[2]/span/a[2]')// ↑await これ忘れがち!! 忘れるとエラーになる。何度も失敗した!!awaitframe_submenu[0].click();console.log('クリック:取引 > オプション新規注文')awaitnewPage.waitFor('frameset > frame',{timeout:outtime});console.log('waitFor : '+waittime)awaitnewPage.waitFor(waittime)}asyncfunctionclick_next(newPage,path,outtime,waittime){console.log('mainコンテンツのframe取得_click_next')letframe_main2=awaitnewPage.frames()[0].childFrames()[2].childFrames()[1]//上へor下へのボタンへのパスframe_submenu=awaitframe_main2.$x(path)console.log('クリック:次ページ')awaitframe_submenu[0].click();awaitnewPage.waitFor('frameset > frame',{timeout:outtime});console.log('waitFor : '+waittime)awaitnewPage.waitFor(waittime)}asyncfunctiongetTable(newPage,ymd){//参考//puppeteerでの要素の取得方法//page.$のところ//https://qiita.com/go_sagawa/items/85f97deab7ccfdce53ea//日経平均、日経225先物期近 取得//日経平均、日経225先物期近、などが並ぶframeへ移動console.log('frame取得 toolBarコンテンツ')letframe_toolBar=awaitnewPage.frames()[0].childFrames()[1].childFrames()[1]//xpathのフルパスでテーブルタグのtrを指定する(IDで上手く取れない場合は、これが簡単)console.log('テーブルタグ取得 日経平均、日経225先物期近');lettbla=awaitframe_toolBar.$x('/html/body/table/tbody/tr/td/table/tbody/tr[2]')console.log('データ取得 日経平均、日経225先物期近');vartxtNikkei='';for(leti=0;i<tbla.length;i++){lettr=await(awaittbla[i].getProperty('innerHTML')).jsonValue();lettr1=tr.split('<')letdatas=[];//console.log(tr1) for(letiintr1){//i=2は、日経平均 i=12は、日経225先物期近if(i==2||i==12){letinner=tr1[i].split('>')[1]if(inner!=''&&inner!=null&&inner!='\n'&&inner!='\n'){//上のconsole.log(tr1)で、発見ーーーーー↑↑↑↑↑↑↑↑↑↑↑↑↑↑ ↑↑↑↑↑↑↑↑↑↑↑↑ if(inner.toString().split(',').length>0){letsplitdata=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コンテンツ')letframe_main=awaitnewPage.frames()[0].childFrames()[2].childFrames()[1]console.log('テーブルタグ取得 SQ日');letsq=awaitframe_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日');varSQ=await(awaitsq[0].getProperty('innerHTML')).jsonValue();//日経225オプションのデータ 取得 console.log('テーブルタグ取得 日経225オプション');lettbl1=awaitframe_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オプション');vartextdata='';for(leti=0;i<tbl1.length;i++){vartr=await(awaittbl1[i].getProperty('innerHTML')).jsonValue();vartr1=tr.split('<')vardatas=[];for(letiintr1){letinner=tr1[i].split('>')[1]if(inner!=''&&inner!=null&&inner!='新規買'&&inner!='新規売'){datas.push(inner);}}textdata+=ymd+','+datas.toString()+','+txtNikkei+','+SQ+',\n';}console.log(textdata)returntextdata;}asyncfunctionpostSpreadsheet(textdata,spreadsheet_url){//参考 curlコマンドをPythonやnode.jsのコードに変換する方法//https://qiita.com/tottu22/items/9112d30588f0339faf9bvarrequest=require('request');varheaders={'Content-Type':'text/csv'};vardataString=textdata;varoptions={url:spreadsheet_url,method:'POST',headers:headers,body:dataString};functioncallback(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);
});
*/};asyncfunctionyymmdd(param){//AppEngineは、UTCなのでAsia/Tokyoへタイムゾーンを変更//node.jsでタイムゾーンの変換処理にdate-fns-timezoneを利用する//https://qiita.com/kazuhiro1982/items/b1235a893ee874d8ff65const{startOfDay,addDays}=require('date-fns');const{convertToTimeZone}=require('date-fns-timezone');// タイムゾーン定義consttimeZone="Asia/Tokyo";// 現在時刻(UTC)を取得consttargetDate=newDate();// TimeZone付きDateに変換constnow=convertToTimeZone(targetDate,{timeZone:timeZone});varyear=now.getYear();// 年varmonth=now.getMonth()+1;// 月varday=now.getDate();// 日varhour=now.getHours();// 時varmin=now.getMinutes();// 分varsec=now.getSeconds();// 秒vardayOfWeek=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}varymd=year+'/'+month+'/'+day+''+hour+':'+min+':'+sec;if(param=='week'){returndayOfWeek;}elseif(param=='hour'){returnhour;}else{returnymd;}};asyncfunctionrun2(){varweek=awaityymmdd('week');varhour=awaityymmdd('hour');if(week>=2&&week<=5){//火〜金[ "日", "月", "火", "水", "木", "金", "土" ]console.log('Start Job week:'+week+' hour:'+hour);run();}elseif(week==1&&hour>=7){//月曜かつ7時以上console.log('Start Job week:'+week+' hour:'+hour);run();}elseif(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)functiondoPost(e){varcsvText=e.postData.getDataAsString();//テキストファイルにする(CSVになってる)Logger.log("csv : "+csvText);varspreadsheet=SpreadsheetApp.openById('');//書込先のスプレッドシートのIDvarsheet=spreadsheet.getSheetByName('シート1');//スプレッドシートのシート名vararr=csvText.split(',');//CSVをカンマでsplitする。vararrData=sheet.getDataRange().getValues();//現在スプレッドシートに入ってるデータを全部取得して、2次元配列にする。varlen_arrData=arrData.length;//現在スプレッドシートに入ってるデータが、何行あるか確認してるvarj=0;varsheet_cols=19;//GAEで取得したデータの1行当たりの列数が19なので、19としてる。/*
(arr.length -1) の意味:GAE上で改行コードを入れたが、改行コードをうまく認識できないので、全部で
配列がいくつ有るか数えて、最後に1引く(1引くのは、GAE上最後のデータの後にカンマを入れたから)
(arr.length -1)は、19の倍数になってるので、19(sheet_cols)で割って、何行あるかを取得する。
*/varcols_arr=(arr.length-1)/sheet_cols;for(vari=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(vari=0;i<len_arrData;i++){arrData.shift();//配列の上からデータを削除}varrows=arrData.length;//行数の確認varcols=arrData[0].length;//列数の確認/*
(len_arrData + 1) :今取得したデータを書き込む行番号を決める。元々スプレッドシートに入ってるデータの行数+1で最終行の次の行に書き込む。
1 :1列目(A列)に書く
rows :今回追加する行数
cols :今回追加する列数(19)
arrData :今回追加するデータの配列
*/sheet.getRange((len_arrData+1),1,rows,cols).setValues(arrData);// 結果を返す varoutput=ContentService.createTextOutput();output.setMimeType(ContentService.MimeType.JSON);output.setContent(JSON.stringify({message:"success!"}));returnoutput;}
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に設定する。
- 以下のように、データが蓄積されていきます。
![スプレッドシートの状態.JPG]()
GAE用 app.yaml
- app.yamlは、最初に作成したディレクトリ内に作成します。
app.yamlruntime:nodejs10instance_class:F4_1G#puppeteer利用のためhandlers:-url :/script :autosecure :always#このあたりに、adminと書けば、自分しか使えないサイトになったと思うが、エラーになる。
GAE用 cron.yaml
- cron.yamlは、最初に作成したディレクトリ内に作成します。
cron.yamlcron:-description:"PJ1"url:/schedule:every 5 minutes from 08:50 to 12:00timezone: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 を課金対象にした上でデプロイした後、課金をやめないと、誤って課金される場合があるので注意要です。
puppeteerのソースコード
app.js
//Node.js//var、let、constの使い方が適当なので、ええ感じに書き換えてね!asyncfunctionrun(){constuser_id='';//s??証券のあなたのIDconstuser_pass='';//s??証券のあなたのパスワードconstshouken_url='';//証券会社のURL(s??証券)constspreadsheet_url='';//GASで発行したURLconstpptr=require('puppeteer');varnow1=newDate();constouttime=80000;constwaittime=8000;// ブラウザを起動するconstbrowser=awaitpptr.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{// ページつくるconstpage=awaitbrowser.newPage()page.setDefaultNavigationTimeout('120000')awaitpage.setViewport({width:1200,height:800})letnavigationPromise=page.waitForNavigation()awaitpage.goto(shouken_url,{timeout:outtime})console.log('証券会社 WEBサイトサイトへアクセス')awaitpage.waitForSelector('.sb-box-sub-02-content ',{visible:true,timeout:outtime})awaitpage.waitForSelector('.sb-box-sub-02-content > dl > dd > #user_input > input',{visible:true})awaitpage.click('.sb-box-sub-02-content > dl > dd > #user_input > input')awaitpage.type('.sb-box-sub-02-content > dl > dd > #user_input > input',user_id)awaitpage.waitForSelector('.sb-box-sub-02-content > dl > dd > #password_input > input')awaitpage.click('.sb-box-sub-02-content > dl > dd > #password_input > input')awaitpage.type('.sb-box-sub-02-content > dl > dd > #password_input > input',user_pass)awaitpage.waitForSelector('.sb-box-sub-02-inner > .sb-box-sub-02-content > .sb-position-c > a > .ov')awaitpage.click('.sb-box-sub-02-inner > .sb-box-sub-02-content > .sb-position-c > a > .ov',{timeout:outtime})console.log('クリック:ログイン')awaitnavigationPromisenavigationPromise=page.waitForNavigation()awaitpage.waitForSelector('#navi01P',{timeout:outtime})awaitpage.click('#navi01P > ul > li:nth-child(8) > a > img',{timeout:outtime})console.log('クリック:先物・オプション')awaitnavigationPromise//参考記事//Puppeteerで次ページへの遷移を待つ (別タブや別ウインドウの遷移を待つ)//https://qiita.com/hnw/items/a07e6b88d95d1656e02fconstnewPagePromise=newPromise(resolve=>browser.once('targetcreated',target=>resolve(target.page())));console.log('waitFor : '+waittime)awaitpage.waitFor(waittime)awaitpage.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('クリック:先物・オプション取引サイト')constnewPage=awaitnewPagePromise;awaitnewPage.setViewport({width:1200,height:800})//newPage.waitFor(waittime)しないと、frameだけしかない状態で次の処理をしてエラーになるのを防ぐため。//もっとカッコいい方法があるはず!!console.log('waitFor : '+waittime)awaitnewPage.waitFor(waittime)awaitnewPage.waitFor('frameset > frame',{timeout:outtime});//先物・オプション取引サイトは、frameset > frame > frameset > frameの構造//Frameの位置を正しく認識させる//画面上メニューの「取引」をクリックしたい//トップframeset下の0番目のframeを選し、その下のframeset下の1番目のframeを選択する//具体的にはココ!letframe_menu=awaitnewPage.frames()[0].childFrames()[0].childFrames()[1]//frameが選択できたので、idの#menu20をクリック//idのみでクリックできる場合は、これで良いframe_menu.click('#menu20')console.log('クリック:取引')awaitnewPage.waitFor('frameset > frame',{timeout:outtime});console.log('waitFor : '+waittime)awaitnewPage.waitFor(waittime)//取引−オプション新規注文 をクリックawaitclick_submenu(newPage,outtime,waittime);//取引−オプション新規注文画面にいる状態//取引−オプション新規注文画面のframeに移動した状態letframe_main=awaitnewPage.frames()[0].childFrames()[2].childFrames()[1]//「20000より上」「17000より下」のボタンが有るか確認するletpath_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'letpath_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'letlink_down=awaitframe_main.$x(path_down)letlink_up=awaitframe_main.$x(path_up)varymd=awaityymmdd();if(link_up.length!=0){//「20000より上」があれば、上ページのデータを取得awaitclick_next(newPage,path_up,outtime,waittime);//画面に表示されるデータを取得するlettextdata=awaitgetTable(newPage,ymd);//取得したデータをスプレッドシートへ書きこむawaitpostSpreadsheet(textdata,spreadsheet_url);}//取引−オプション新規注文 をクリック//最初に開く画面へ移動awaitclick_submenu(newPage,outtime,waittime);//画面に表示されるデータを取得するlettextdata=awaitgetTable(newPage,ymd);//取得したデータをスプレッドシートへ書きこむawaitpostSpreadsheet(textdata,spreadsheet_url);if(link_down.length!=0){awaitclick_next(newPage,path_down,outtime,waittime);lettextdata=awaitgetTable(newPage,ymd);awaitpostSpreadsheet(textdata,spreadsheet_url);}varnow2=newDate();varnow11=now1.getMinutes()*60+now1.getSeconds()varnow21=now2.getMinutes()*60+now2.getSeconds()console.log('time : '+(now21-now11));awaitbrowser.close()console.log('Exit')console.log('Exit')}catch(e){//失敗時、すぐに止めたい。GAEのインスタンスを無駄に使わないawaitbrowser.close()console.log('ERR')console.log('ERR')console.log(e)logError(e)}//try {}//async function run(){asyncfunctionclick_submenu(newPage,outtime,waittime){letframe_menu2=awaitnewPage.frames()[0].childFrames()[0].childFrames()[1]letframe_submenu=awaitframe_menu2.$x('/html/body/form/div/table/tbody/tr/td/table[2]/tbody/tr/td[2]/span/a[2]')// ↑await これ忘れがち!! 忘れるとエラーになる。何度も失敗した!!awaitframe_submenu[0].click();console.log('クリック:取引 > オプション新規注文')awaitnewPage.waitFor('frameset > frame',{timeout:outtime});console.log('waitFor : '+waittime)awaitnewPage.waitFor(waittime)}asyncfunctionclick_next(newPage,path,outtime,waittime){console.log('mainコンテンツのframe取得_click_next')letframe_main2=awaitnewPage.frames()[0].childFrames()[2].childFrames()[1]//上へor下へのボタンへのパスframe_submenu=awaitframe_main2.$x(path)console.log('クリック:次ページ')awaitframe_submenu[0].click();awaitnewPage.waitFor('frameset > frame',{timeout:outtime});console.log('waitFor : '+waittime)awaitnewPage.waitFor(waittime)}asyncfunctiongetTable(newPage,ymd){//参考//puppeteerでの要素の取得方法//page.$のところ//https://qiita.com/go_sagawa/items/85f97deab7ccfdce53ea//日経平均、日経225先物期近 取得//日経平均、日経225先物期近、などが並ぶframeへ移動console.log('frame取得 toolBarコンテンツ')letframe_toolBar=awaitnewPage.frames()[0].childFrames()[1].childFrames()[1]//xpathのフルパスでテーブルタグのtrを指定する(IDで上手く取れない場合は、これが簡単)console.log('テーブルタグ取得 日経平均、日経225先物期近');lettbla=awaitframe_toolBar.$x('/html/body/table/tbody/tr/td/table/tbody/tr[2]')console.log('データ取得 日経平均、日経225先物期近');vartxtNikkei='';for(leti=0;i<tbla.length;i++){lettr=await(awaittbla[i].getProperty('innerHTML')).jsonValue();lettr1=tr.split('<')letdatas=[];//console.log(tr1) for(letiintr1){//i=2は、日経平均 i=12は、日経225先物期近if(i==2||i==12){letinner=tr1[i].split('>')[1]if(inner!=''&&inner!=null&&inner!='\n'&&inner!='\n'){//上のconsole.log(tr1)で、発見ーーーーー↑↑↑↑↑↑↑↑↑↑↑↑↑↑ ↑↑↑↑↑↑↑↑↑↑↑↑ if(inner.toString().split(',').length>0){letsplitdata=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コンテンツ')letframe_main=awaitnewPage.frames()[0].childFrames()[2].childFrames()[1]console.log('テーブルタグ取得 SQ日');letsq=awaitframe_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日');varSQ=await(awaitsq[0].getProperty('innerHTML')).jsonValue();//日経225オプションのデータ 取得 console.log('テーブルタグ取得 日経225オプション');lettbl1=awaitframe_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オプション');vartextdata='';for(leti=0;i<tbl1.length;i++){vartr=await(awaittbl1[i].getProperty('innerHTML')).jsonValue();vartr1=tr.split('<')vardatas=[];for(letiintr1){letinner=tr1[i].split('>')[1]if(inner!=''&&inner!=null&&inner!='新規買'&&inner!='新規売'){datas.push(inner);}}textdata+=ymd+','+datas.toString()+','+txtNikkei+','+SQ+',\n';}console.log(textdata)returntextdata;}asyncfunctionpostSpreadsheet(textdata,spreadsheet_url){//参考 curlコマンドをPythonやnode.jsのコードに変換する方法//https://qiita.com/tottu22/items/9112d30588f0339faf9bvarrequest=require('request');varheaders={'Content-Type':'text/csv'};vardataString=textdata;varoptions={url:spreadsheet_url,method:'POST',headers:headers,body:dataString};functioncallback(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);
});
*/};asyncfunctionyymmdd(param){//AppEngineは、UTCなのでAsia/Tokyoへタイムゾーンを変更//node.jsでタイムゾーンの変換処理にdate-fns-timezoneを利用する//https://qiita.com/kazuhiro1982/items/b1235a893ee874d8ff65const{startOfDay,addDays}=require('date-fns');const{convertToTimeZone}=require('date-fns-timezone');// タイムゾーン定義consttimeZone="Asia/Tokyo";// 現在時刻(UTC)を取得consttargetDate=newDate();// TimeZone付きDateに変換constnow=convertToTimeZone(targetDate,{timeZone:timeZone});varyear=now.getYear();// 年varmonth=now.getMonth()+1;// 月varday=now.getDate();// 日varhour=now.getHours();// 時varmin=now.getMinutes();// 分varsec=now.getSeconds();// 秒vardayOfWeek=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}varymd=year+'/'+month+'/'+day+''+hour+':'+min+':'+sec;if(param=='week'){returndayOfWeek;}elseif(param=='hour'){returnhour;}else{returnymd;}};asyncfunctionrun2(){varweek=awaityymmdd('week');varhour=awaityymmdd('hour');if(week>=2&&week<=5){//火〜金[ "日", "月", "火", "水", "木", "金", "土" ]console.log('Start Job week:'+week+' hour:'+hour);run();}elseif(week==1&&hour>=7){//月曜かつ7時以上console.log('Start Job week:'+week+' hour:'+hour);run();}elseif(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)functiondoPost(e){varcsvText=e.postData.getDataAsString();//テキストファイルにする(CSVになってる)Logger.log("csv : "+csvText);varspreadsheet=SpreadsheetApp.openById('');//書込先のスプレッドシートのIDvarsheet=spreadsheet.getSheetByName('シート1');//スプレッドシートのシート名vararr=csvText.split(',');//CSVをカンマでsplitする。vararrData=sheet.getDataRange().getValues();//現在スプレッドシートに入ってるデータを全部取得して、2次元配列にする。varlen_arrData=arrData.length;//現在スプレッドシートに入ってるデータが、何行あるか確認してるvarj=0;varsheet_cols=19;//GAEで取得したデータの1行当たりの列数が19なので、19としてる。/*
(arr.length -1) の意味:GAE上で改行コードを入れたが、改行コードをうまく認識できないので、全部で
配列がいくつ有るか数えて、最後に1引く(1引くのは、GAE上最後のデータの後にカンマを入れたから)
(arr.length -1)は、19の倍数になってるので、19(sheet_cols)で割って、何行あるかを取得する。
*/varcols_arr=(arr.length-1)/sheet_cols;for(vari=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(vari=0;i<len_arrData;i++){arrData.shift();//配列の上からデータを削除}varrows=arrData.length;//行数の確認varcols=arrData[0].length;//列数の確認/*
(len_arrData + 1) :今取得したデータを書き込む行番号を決める。元々スプレッドシートに入ってるデータの行数+1で最終行の次の行に書き込む。
1 :1列目(A列)に書く
rows :今回追加する行数
cols :今回追加する列数(19)
arrData :今回追加するデータの配列
*/sheet.getRange((len_arrData+1),1,rows,cols).setValues(arrData);// 結果を返す varoutput=ContentService.createTextOutput();output.setMimeType(ContentService.MimeType.JSON);output.setContent(JSON.stringify({message:"success!"}));returnoutput;}
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に設定する。
- 以下のように、データが蓄積されていきます。
![スプレッドシートの状態.JPG]()
GAE用 app.yaml
- app.yamlは、最初に作成したディレクトリ内に作成します。
app.yamlruntime:nodejs10instance_class:F4_1G#puppeteer利用のためhandlers:-url :/script :autosecure :always#このあたりに、adminと書けば、自分しか使えないサイトになったと思うが、エラーになる。
GAE用 cron.yaml
- cron.yamlは、最初に作成したディレクトリ内に作成します。
cron.yamlcron:-description:"PJ1"url:/schedule:every 5 minutes from 08:50 to 12:00timezone: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 を課金対象にした上でデプロイした後、課金をやめないと、誤って課金される場合があるので注意要です。
Google Apps Scriptのソースコードとコメント
doPost(e)
functiondoPost(e){varcsvText=e.postData.getDataAsString();//テキストファイルにする(CSVになってる)Logger.log("csv : "+csvText);varspreadsheet=SpreadsheetApp.openById('');//書込先のスプレッドシートのIDvarsheet=spreadsheet.getSheetByName('シート1');//スプレッドシートのシート名vararr=csvText.split(',');//CSVをカンマでsplitする。vararrData=sheet.getDataRange().getValues();//現在スプレッドシートに入ってるデータを全部取得して、2次元配列にする。varlen_arrData=arrData.length;//現在スプレッドシートに入ってるデータが、何行あるか確認してるvarj=0;varsheet_cols=19;//GAEで取得したデータの1行当たりの列数が19なので、19としてる。/*
(arr.length -1) の意味:GAE上で改行コードを入れたが、改行コードをうまく認識できないので、全部で
配列がいくつ有るか数えて、最後に1引く(1引くのは、GAE上最後のデータの後にカンマを入れたから)
(arr.length -1)は、19の倍数になってるので、19(sheet_cols)で割って、何行あるかを取得する。
*/varcols_arr=(arr.length-1)/sheet_cols;for(vari=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(vari=0;i<len_arrData;i++){arrData.shift();//配列の上からデータを削除}varrows=arrData.length;//行数の確認varcols=arrData[0].length;//列数の確認/*
(len_arrData + 1) :今取得したデータを書き込む行番号を決める。元々スプレッドシートに入ってるデータの行数+1で最終行の次の行に書き込む。
1 :1列目(A列)に書く
rows :今回追加する行数
cols :今回追加する列数(19)
arrData :今回追加するデータの配列
*/sheet.getRange((len_arrData+1),1,rows,cols).setValues(arrData);// 結果を返す varoutput=ContentService.createTextOutput();output.setMimeType(ContentService.MimeType.JSON);output.setContent(JSON.stringify({message:"success!"}));returnoutput;}
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に設定する。
- 以下のように、データが蓄積されていきます。
![スプレッドシートの状態.JPG]()
GAE用 app.yaml
- app.yamlは、最初に作成したディレクトリ内に作成します。
app.yamlruntime:nodejs10instance_class:F4_1G#puppeteer利用のためhandlers:-url :/script :autosecure :always#このあたりに、adminと書けば、自分しか使えないサイトになったと思うが、エラーになる。
GAE用 cron.yaml
- cron.yamlは、最初に作成したディレクトリ内に作成します。
cron.yamlcron:-description:"PJ1"url:/schedule:every 5 minutes from 08:50 to 12:00timezone: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 を課金対象にした上でデプロイした後、課金をやめないと、誤って課金される場合があるので注意要です。
スプレッドシートの状態
- 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.yaml
runtime:nodejs10instance_class:F4_1G#puppeteer利用のためhandlers:-url :/script :autosecure :always#このあたりに、adminと書けば、自分しか使えないサイトになったと思うが、エラーになる。
GAE用 cron.yaml
- cron.yamlは、最初に作成したディレクトリ内に作成します。
cron.yamlcron:-description:"PJ1"url:/schedule:every 5 minutes from 08:50 to 12:00timezone: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 を課金対象にした上でデプロイした後、課金をやめないと、誤って課金される場合があるので注意要です。
GAE用 cron.yaml
- cron.yamlは、最初に作成したディレクトリ内に作成します。
cron.yaml
cron:-description:"PJ1"url:/schedule:every 5 minutes from 08:50 to 12:00timezone: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 を課金対象にした上でデプロイした後、課金をやめないと、誤って課金される場合があるので注意要です。
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 を課金対象にした上でデプロイした後、課金をやめないと、誤って課金される場合があるので注意要です。