- 投稿日:2020-09-08T23:22:24+09:00
nodejsでスクレイピング入門[阿部寛]
はじめに
卒研でいろいろあって、nodejsでスクレイピングやりそうになってきましたのでそれのメモです、
入門ということで阿部寛のHPから阿部寛の画像をとっていきたいと思います、スクレイピングとは?
そもそもスクレイピングってなんだろってなるから調べました。
意味はHPから情報を抽出するということらしいでした、
使い道としては、サイトに載ってる情報が欲しいとか、APIがないけどもHPならあるとかだったときはスクレイピングで情報分捕る感じかと、阿部寛ってだーれ?
日本の俳優、テルマエ・ロマエとかに出ているお方、上田次郎、日本科学技術大学理工学部教授、何故ベストを尽くさないのか?
よくHPが情報系の実験ネタ(?)に扱われるほどすごい公式サイトをもっているお方できること
nodejsから阿部寛のサイトにある阿部寛の写真と取得できる
つかったもの
- nodejs
- yarn
できたもの
最初はHTMLデータとろう!!
- axios使って阿部寛のHTMLぶんどります
- なのでaxiosとasyncをyarn addで入れていって、以下のコードを作成します
// ajax関係 const axios = require("axios"); // 阿部寛のサイト const url = "http://abehiroshi.la.coocan.jp/"; const main = async () => { const data = await axios.get(url); console.log(data.data); } main();
- これでできますめ!!!!
- 結果がこんな感じです!!
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS"> <meta name="GENERATOR" content="JustSystems Homepage Builder Version 20.0.6.0 for Windows"> <meta http-equiv="Content-Style-Type" content="text/css"> <title>�������̃z�[���y�[�W</title> </head> <frameset cols=18,82> <frame src="menu.htm" marginheight="0" marginwidth="0" scrolling="auto" name="left"> <frame src="top.htm" marginheight="0" marginwidth="0" scrolling="auto" name="right"> <noframes> <body></body> </noframes> </frameset> </html>
- うおおおやったぜ!!
必要な情報とろう!!
- 今度はFast HTML Parserを使って必要な情報取り出します、
- のでyarn addでぶちこみます
- 卒研では画像はURLでほしいので、文字列データ分捕れそうなのでいきます、許して
- とりあえず動かせるようにしたものがこれです
// ajax関係 const axios = require("axios"); // 阿部寛のサイト const url = "http://abehiroshi.la.coocan.jp/"; // パーサ const HTMLparser = require('fast-html-parser'); const main = async () => { const data = await axios.get(url); console.log(data.data); const root = HTMLparser.parse(data.data); console.log(root.firstChild.structure); } main();
- 結果がこれです
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS"> <meta name="GENERATOR" content="JustSystems Homepage Builder Version 20.0.6.0 for Windows"> <meta http-equiv="Content-Style-Type" content="text/css"> <title>�������̃z�[���y�[�W</title> </head> <frameset cols=18,82> <frame src="menu.htm" marginheight="0" marginwidth="0" scrolling="auto" name="left"> <frame src="top.htm" marginheight="0" marginwidth="0" scrolling="auto" name="right"> <noframes> <body></body> </noframes> </frameset> </html> html head meta meta meta title #text frameset frame frame noframes body
- たぶんできた!!!
- 阿部寛のサイトはサイトを分割してそれをframesetで表示してる感じですね、
- menuはたぶん右側のメニューだと思われます、
- 阿部寛の顔面はtopのほうにあると予想されるので、そっちをみましょ、
- なのでtopの方をaxiosで表示してみます!!
- コードは以下です
// ajax関係 const axios = require("axios"); // 阿部寛のサイト const url = "http://abehiroshi.la.coocan.jp/top.htm"; // パーサ const HTMLparser = require('fast-html-parser'); const main = async () => { const data = await axios.get(url); console.log(data.data); } main();
- 結果はこんな感じです!!
<!-- saved from url=(0022)http://internet.e-mail --> <!-- saved from url=(0022)http://internet.e-mail --> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS"> <meta http-equiv="Content-Style-Type" content="text/css"> <meta name="GENERATOR" content="JustSystems Homepage Builder Version 20.0.6.0 for Windows"> <title>���� ���̃z�[���y�[�W</title> </head> <body background="image/abehiroshi.jpg"> <br> <h1 align="center">���� ���̃z�[���y�[�W</h1> <table align="center"> <tr> <td rowspan="2"><img src="abe-top-20190328-2.jpg" width="350" height="414" border="0"><br> <br> <table width="256"> <tr> <td width="14"> </td> <td width="230">���� ���i���� �Ђ낵�j<br></td> </tr> <tr> <td> </td> <td>���N���� 1964�N6��22��<br></td> </tr> <tr> <td> </td> <td>���t�^ A�^<br></td> </tr> <tr> <td> </td> <td><a href="prof/prof.htm">�v���t�B�[��</a></td> </tr> <tr> <td> </td> <td> </td> </tr> <tr> <td colspan="2"><br> If you have any enquiries regarding my TV drama or film, or would like to make an enquiry concerning future projects, please do not hesitate to contact me through the following email address.<br> <br> mail:<a href="mailto:shigeta@navy.plala.or.jp">shigeta@navy.plala.or.jp</a></td> </tr> </table> <br> ����<strong>:</strong><br> �Γc�I�t�B�X<br> 107-0052<br> �����s�`��ԍ�9-5-29 <br> �ԍ�C�����}���V����303<br> TEL : +81-3-5410-8585<br> FAX : +81-3-5410-0588 </td> <td> </td> <td><div align="center">�������@�ŐV���@������</div></td> </tr> <tr> <td></td> <td> <table width="100%" border="0" cellspacing="0" cellpadding="0"> <tr> <td align="left" valign="bottom"><hr width="100%" size="1"></td> </tr> </table> <!-- start�@--> <table width="100%" border="0" cellspacing="0" cellpadding="0"> <tr> <td align="left" valign="top"><strong>�E�h���}</strong></td> </tr> </table> <table border="0" cellspacing="5" cellpadding="0"> <tr> <td width="70" align="left" valign="top"> </td> <td> <strong> <a href="https://www.tbs.co.jp/dragonzakura/" target="_blank">�u�h���S����2�v</a>(��)<font color="#ff0000"></font><br> <font color="#ff0000">2020�N�Ă�\�肵�Ă���܂������A�V�^�R���i�E�C���X�����g��̉e���ɂ��������������肢�����܂����B</font></strong></td> </tr> </table> <!-- end�@--> <!-- start�@--> <table width="100%" border="0" cellspacing="0" cellpadding="0"> <tr> <td align="left" valign="bottom"><hr width="100%" size="1"></td> </tr> </table> <table width="100%" border="0" cellspacing="0" cellpadding="0"> <tr> <td align="left" valign="top"><strong>�E�f��</strong></td> </tr> </table> <table border="0" cellspacing="5" cellpadding="0"> <tr> <td width="70" align="left" valign="top"> </td> <td> <strong>�u����Ȃ������҂����ցv <br> 2021�N���J�\��<br> </strong></td> </tr> </table> <table border="0" cellspacing="5" cellpadding="0"> <tr> <td width="70" align="left" valign="top"> </td> <td> <strong><br> <a href="http://www.hokusai2020.com/" target="_blank">�uHOKUSAI�v</a> <br> 2021�N ���J�\��<br> <font color="#ff0000">�����J�������ɂȂ�܂����B</font><br> </strong></td> </tr> </table> <table border="0" cellspacing="5" cellpadding="0"> <tr> <td width="70" align="left" valign="top"></td> <td></td> </tr> </table> <!-- end�@--> <table width="100%" border="0" cellspacing="0" cellpadding="0"> <tr> <td align="left" valign="bottom"><hr width="100%" size="1"></td> </tr> </table> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> </td> </tr> </table> </body> </html>
- 見た感じ阿部寛の顔面画像はある感じがしますね!!ビンゴ!!!!
- そこから画像のURLとりましょ!!
- とりあえずコードはこんな感じです!!
// ajax関係 const axios = require("axios"); // 阿部寛のサイト const url = "http://abehiroshi.la.coocan.jp/top.htm"; // パーサ const HTMLparser = require('fast-html-parser'); const main = async () => { const data = await axios.get(url); const root = HTMLparser.parse(data.data); console.log(root.querySelectorAll('img')); } main();
- 結果はこんな感じです!!
[ HTMLElement { tagName: 'img', rawAttrs: 'src="abe-top-20190328-2.jpg" width="350" height="414" border="0"', childNodes: [], classNames: [] } ]
- rawAttrsに顔面画像らしきURLがありますね!!
- あとはそれをとって気合いでとっていきます!!
- コードはこんな感じです!!
// ajax関係 const axios = require("axios"); // 阿部寛のサイト const url = "http://abehiroshi.la.coocan.jp/top.htm"; // パーサ const HTMLparser = require('fast-html-parser'); const main = async () => { const data = await axios.get(url); const root = HTMLparser.parse(data.data); const raw_data = root.querySelectorAll('img')[0].rawAttrs; const img_url = raw_data.split("\"")[1]; console.log(img_url); } main();
- 結果はこんな感じです!!
abe-top-20190328-2.jpg
- ぶんどれたやった!!
画像を保存する
- あとは画像を保存するだけですね、、
- nodejsで画像保存はjimpが良さそうなのでyarn addでjimpをいれます
- あとは以下のコードで行きます!!
// jimp関係 const jimp = require("jimp"); const main = () => { jimp.read("http://abehiroshi.la.coocan.jp/abe-top-20190328-2.jpg", (err, abehiroshi) => { if(err) return; abehiroshi.write("abehiroshi.jpg"); }); } main();
- これであなたのPCに阿部寛が住み着きましたね!!
おわり
- 酔ってます
- あとたっくーさんの発狂している動画やUber eartsにぶちぎれる動画をループで見ていながら書いてます、ごめんなさい
- ボスに見られなければいいやって思ってます
おまけ
- このコードで阿部寛がいっぱい!!
// jimp関係 const jimp = require("jimp"); const size = 100000000000000000000000000000000; const main = () => { jimp.read("http://abehiroshi.la.coocan.jp/abe-top-20190328-2.jpg", (err, abehiroshi) => { if(err) return; for(let i = 0;i < size;i++) { abehiroshi.write("abehiroshi_"+i+".jpg"); } }); } main();参考
- 投稿日:2020-09-08T21:37:45+09:00
YOLP の WebAPI で1時間以内の降水予想と雨雲レーダー地図画像を取得する
概要
- YOLP (Yahoo! Open Local Platform) の Yahoo!ジオコーダAPI + 気象情報API + Yahoo!スタティックマップAPI を使用して1時間以内の降水予想と雨雲レーダー地図画像を取得する
- 動作確認環境: Node.js v14.9.0 + node-fetch 2.6.1
node-fetch のインストール
$ npm i node-fetchサンプルコード
'use strict' const fs = require('fs') const qs = require('querystring') const fetch = require('node-fetch') // window.fetch 互換 Fetch API // アプリケーションID const APPID = 'YOUR_APPLICATION_ID' // テキストにマッチした住所情報を取得する async function getAddressLocation(text) { // URLを組み立てる const params = qs.stringify({ appid: APPID, // アプリケーションID query: text, // 検索文字列 al: 2, // 市区町村レベルの住所を検索 exclude_seireishi: false, // 検索対象から政令指定都市レコードを除外するか results: 1, // 検索結果を1件以内に設定 output: 'json' // レスポンスをJSONにする }) const url = 'https://map.yahooapis.jp/geocode/V1/geoCoder?' + params // Yahoo!ジオコーダAPIをコールする const res = await fetch(url) if (!res.ok) { throw res } const json = await res.json() console.log(JSON.stringify(json, null, ' ')) // 住所情報を取得する if (json.Feature && json.Feature.length != 0) { // ヒットした1つめの住所を使う var name = json.Feature[0].Name var ll = json.Feature[0].Geometry.Coordinates.split(',') var bbox = json.Feature[0].Geometry.BoundingBox return { address: name, lat: ll[1], lon: ll[0], bbox: bbox } } else { throw '住所にヒットしなかった' } } // 住所に紐ついた緯度経度とバウンディングボックスの4点の緯度経度をつなげた文字列を返す function getCoordinatesFromBoundingBox(location) { var p = location.bbox.split(' ') var ll0 = p[0].split(',') var ll1 = p[1].split(',') var c = location.lon + ',' + location.lat + ' ' + ll0[0] + ',' + ll0[1] + ' ' + ll1[0] + ',' + ll0[1] + ' ' + ll0[0] + ',' + ll1[1] + ' ' + ll1[0] + ',' + ll1[1] return c } // 降水情報を取得する async function getWeatherInfo(location) { // 雨の強さを取得したい緯度経度(10点まで可)を指定 // フォーマット: 経度,緯度 経度,緯度 経度,緯度 経度,緯度 ... // 経度・緯度の順番でコンマ区切り // 経度・緯度毎に半角スペース区切り const coordinates = getCoordinatesFromBoundingBox(location) // URLを組み立てる var params = qs.stringify({ coordinates: coordinates, appid: APPID, // アプリケーションID output: 'json' // レスポンスをJSONにする }) const url = 'https://map.yahooapis.jp/weather/V1/place?' + params // 気象情報APIをコールする const res = await fetch(url) if (!res.ok) { throw res } const json = await res.json() console.log(JSON.stringify(json, null, ' ')) return json } // 降水情報をテキストに変換する function getWeatherText(weather, location) { var ame = false // 雨が降るか否か // 1箇所でも雨が降る場所があるか for (var feature of weather.Feature) { for (var w of feature.Property.WeatherList.Weather) { if (w.Rainfall > 0) { // 降水強度が0より大きいか ame = true break } } } if (ame) { return location.address + "では、1時間以内に雨が降りそうです。" } else { return location.address + "では、1時間以内には雨が降らないようです。" } } // 地図画像を取得する async function getMapImage(location) { // URLを組み立てる var params = qs.stringify({ width: 800, height: 600, lat: location.lat, lon: location.lon, z: 12, // ズームレベル overlay: 'type:rainfall', // 現在時刻の雨雲レーダーを表示 style: 'base:monotone', // モノトーンスタイル appid: APPID, // アプリケーションID }) const url = 'https://map.yahooapis.jp/map/V1/static?' + params // Yahoo!スタティックマップAPIをコールする const res = await fetch(url) if (!res.ok) { throw res } return Buffer.from(await res.arrayBuffer()) } (async () => { try { // コマンドライン引数を取得 const text = process.argv[2] console.log('Input: ' + text) // 住所情報を取得 const location = await getAddressLocation(text) // 降水情報を取得 const weather = await getWeatherInfo(location) // 降水情報をテキストに変換 const weatherText = getWeatherText(weather, location) console.log('Message: ' + weatherText) // 地図画像を取得 const mapImage = await getMapImage(location) // 地図画像データをファイルに出力 await fs.promises.writeFile('map.png', mapImage) } catch (err) { console.error(err) } })()実行結果
$ node app.js 福島市 Input: 福島市 { "ResultInfo": { "Count": 1, "Total": 1, "Start": 1, "Status": 200, "Description": "", "Copyright": "", "Latency": 0.016 }, "Feature": [ { "Id": "07201", "Gid": "", "Name": "福島県福島市", "Geometry": { "Type": "point", "Coordinates": "140.47469440,37.76089730", "BoundingBox": "140.22998100,37.62433900 140.57092800,37.97664700" }, "Category": [], "Description": "", "Style": [], "Property": { "Uid": "09564f93998c22a98d1921dbd866d75b73f7bd6a", "CassetteId": "b22fee69b0dcaf2c2fe2d6a27906dafc", "Yomi": "フクシマケンフクシマシ", "Country": { "Code": "JP", "Name": "日本" }, "Address": "福島県福島市", "GovernmentCode": "07201", "AddressMatchingLevel": "2", "AddressType": "市" } } ] } { "ResultInfo": { "Count": 5, "Total": 5, "Start": 1, "Status": 200, "Latency": 0.049625, "Description": "", "Copyright": "(C) Yahoo Japan Corporation." }, "Feature": [ { "Id": "202009082115_140.47469_37.760897", "Name": "地点(140.47469,37.760897)の2020年09月08日 21時15分から60分間の天気情報", "Geometry": { "Type": "point", "Coordinates": "140.47469,37.760897" }, "Property": { "WeatherAreaCode": 3610, "WeatherList": { "Weather": [ { "Type": "observation", "Date": "202009082115", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082125", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082135", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082145", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082155", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082205", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082215", "Rainfall": 0 } ] } } }, { "Id": "202009082115_140.22998_37.624339", "Name": "地点(140.22998,37.624339)の2020年09月08日 21時15分から60分間の天気情報", "Geometry": { "Type": "point", "Coordinates": "140.22998,37.624339" }, "Property": { "WeatherAreaCode": 3630, "WeatherList": { "Weather": [ { "Type": "observation", "Date": "202009082115", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082125", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082135", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082145", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082155", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082205", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082215", "Rainfall": 0 } ] } } }, { "Id": "202009082115_140.57093_37.624339", "Name": "地点(140.57093,37.624339)の2020年09月08日 21時15分から60分間の天気情報", "Geometry": { "Type": "point", "Coordinates": "140.57093,37.624339" }, "Property": { "WeatherAreaCode": 3610, "WeatherList": { "Weather": [ { "Type": "observation", "Date": "202009082115", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082125", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082135", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082145", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082155", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082205", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082215", "Rainfall": 0 } ] } } }, { "Id": "202009082115_140.22998_37.976647", "Name": "地点(140.22998,37.976647)の2020年09月08日 21時15分から60分間の天気情報", "Geometry": { "Type": "point", "Coordinates": "140.22998,37.976647" }, "Property": { "WeatherAreaCode": 3520, "WeatherList": { "Weather": [ { "Type": "observation", "Date": "202009082115", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082125", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082135", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082145", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082155", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082205", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082215", "Rainfall": 0 } ] } } }, { "Id": "202009082115_140.57093_37.976647", "Name": "地点(140.57093,37.976647)の2020年09月08日 21時15分から60分間の天気情報", "Geometry": { "Type": "point", "Coordinates": "140.57093,37.976647" }, "Property": { "WeatherAreaCode": 3420, "WeatherList": { "Weather": [ { "Type": "observation", "Date": "202009082115", "Rainfall": 1.35 }, { "Type": "forecast", "Date": "202009082125", "Rainfall": 0.75 }, { "Type": "forecast", "Date": "202009082135", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082145", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082155", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082205", "Rainfall": 0 }, { "Type": "forecast", "Date": "202009082215", "Rainfall": 0 } ] } } } ] } Message: 福島県福島市では、1時間以内に雨が降りそうです。参考資料
- 投稿日:2020-09-08T21:30:16+09:00
簡単レシート印刷 receiptline で行間隔を調整してみた
前回は 80 ミリカメラと 80 ミリフィルムで動画を撮影していました。
「役に立たない機械」感を醸し出していたかもしれないですね。連続で印刷するため、変換ライブラリを少し変更して、自動用紙カットを解除しました。
今回もこの変換ライブラリlib/receiptline.js
に手を入れてみようと思います。用紙節約か、読みやすさか
receiptline に添付されているサンプルデータを印刷してみました。
左は TM-T88V で、右は mC-Print3 です。行間隔が狭いので、文字が詰まって見えます。
今どきのレシートプリンターには用紙節約機能があるので、そういう時代だと思いますが。行間隔を空ける
行間隔を空けて読みやすくしてみようと思います。
レシートプリンターのコマンドを調べて、一部のコマンドを置き換えてみます。ESC/POS コマンドリファレンス
https://reference.epson-biz.com/pos/reference_ja/
- 変更前
- 行間隔なしコマンド ESC 3 n
- 変更後
- 行間隔ありコマンド ESC 2
StarPRNTモード コマンド仕様書
http://sp-support.star-m.jp/SDKDocumentation.aspx
- 変更前
- 行間隔なしコマンド ESC 0
- 変更後
- 行間隔ありコマンド ESC z n
lib/receiptline.js// // ESC/POS // const _escpos = { // start printing: ESC @ GS a n ESC M n FS ( A pL pH fn m ESC SP n FS S n1 n2 ESC 3 n ESC { n // open: printer => '\x1b@\x1da\x00\x1bM0\x1c(A' + $(2, 0, 48, 0) + '\x1b \x00\x1cS\x00\x00\x1b3\x00\x1b{' + $(printer.upsideDown), open: printer => '\x1b@\x1da\x00\x1bM0\x1c(A' + $(2, 0, 48, 0) + '\x1b \x00\x1cS\x00\x00\x1b2\x1b{' + $(printer.upsideDown), ... }; // // StarPRNT MBCS // const _starmbcs = { // start printing: ESC @ ESC RS a n ESC RS F n ESC SP n ESC s n1 n2 ESC 0 (SI) (DC2) // open: printer => '\x1b@\x1b\x1ea0\x1b\x1eF\x00\x1b 0\x1bs00\x1b0' + (printer.upsideDown ? '\x0f' : '\x12'), open: printer => '\x1b@\x1b\x1ea0\x1b\x1eF\x00\x1b 0\x1bs00\x1bz1' + (printer.upsideDown ? '\x0f' : '\x12'), ... };Node を再起動して再印刷。
行間隔は広がりましたが、縦罫線が途切れてしまっています。縦罫線を接続する
変換ライブラリをさらに変更。
縦罫線を引く領域では、行間隔を空けないようにします。ESC/POS
- 縦罫線の始まり
- 行間隔なしコマンドを追加 ESC 3 n
- 縦罫線の終わり
- 行間隔ありコマンドを追加 ESC 2
StarPRNT
- 縦罫線の始まり
- 行間隔なしコマンドを追加 ESC 0
- 縦罫線の終わり
- 行間隔ありコマンドを追加 ESC z n
lib/receiptline.js// // ESC/POS // const _escpos = { ... // start rules: FS C n ESC t n ... LF // vrstart: (widths, left, right) => '\x1cC0\x1bt\x01' + widths.reduce((a, w) => a + '\x95'.repeat(w) + '\x91', left ? '\x9c' : '\x98').slice(0, -1) + (right ? '\x9d' : '\x99'), vrstart: (widths, left, right) => '\x1b3\x00\x1cC0\x1bt\x01' + widths.reduce((a, w) => a + '\x95'.repeat(w) + '\x91', left ? '\x9c' : '\x98').slice(0, -1) + (right ? '\x9d' : '\x99'), // stop rules: FS C n ESC t n ... LF // vrstop: (widths, left, right) => '\x1cC0\x1bt\x01' + widths.reduce((a, w) => a + '\x95'.repeat(w) + '\x90', left ? '\x9e' : '\x9a').slice(0, -1) + (right ? '\x9f' : '\x9b'), vrstop: (widths, left, right) => '\x1b2\x1cC0\x1bt\x01' + widths.reduce((a, w) => a + '\x95'.repeat(w) + '\x90', left ? '\x9e' : '\x9a').slice(0, -1) + (right ? '\x9f' : '\x9b'), ... }; // // StarPRNT MBCS // const _starmbcs = { ... // start rules: ESC $ n ... LF // vrstart: (widths, left, right) => '\x1b$0' + widths.reduce((a, w) => a + '\x95'.repeat(w) + '\x91', left ? '\x9c' : '\x98').slice(0, -1) + (right ? '\x9d' : '\x99'), vrstart: (widths, left, right) => '\x1b0\x1b$0' + widths.reduce((a, w) => a + '\x95'.repeat(w) + '\x91', left ? '\x9c' : '\x98').slice(0, -1) + (right ? '\x9d' : '\x99'), // stop rules: ESC $ n ... LF // vrstop: (widths, left, right) => '\x1b$0' + widths.reduce((a, w) => a + '\x95'.repeat(w) + '\x90', left ? '\x9e' : '\x9a').slice(0, -1) + (right ? '\x9f' : '\x9b'), vrstop: (widths, left, right) => '\x1bz1\x1b$0' + widths.reduce((a, w) => a + '\x95'.repeat(w) + '\x90', left ? '\x9e' : '\x9a').slice(0, -1) + (right ? '\x9f' : '\x9b'), ... };もう一度 Node を再起動して再印刷。
縦罫線がくっつきました!動作条件
実は、これが動作するのは
printers.json
の upsideDown がfalse
の場合です。
この値がtrue
で、印刷の向きを上下反転している場合はうまく動きません。printers.json{ "tm_t88v": { "host": "192.168.1.2", "port": 9100, "cpl": 42, "encoding": "cp932", "gamma": 1.8, "upsideDown": false, "command": "escpos" }, "mc_print3": { "host": "192.168.1.3", "port": 9100, "cpl": 48, "encoding": "cp932", "gamma": 1.8, "upsideDown": false, "command": "starmbcs" } }奥が深いです。
また何か作ったら投稿します。ではまた!
- 投稿日:2020-09-08T06:47:57+09:00
無料でSSR・ホスティング・API鯖を立てれるVercel。TypeScript・ExpressでAPI鯖を立てる。
Vercel
https://vercel.com無料で有名なFaaS(Function as a Service)の
Firebase FunctionsやNetlify Functionsより使い勝手がいいと思う。GitHubの捨てアカ作ってログインしようとしたら弾かれた。
たぶんアカを作ってからある程度時間が経たないと弾かれるっぽい。ソースコード
package.json{ "scripts": { "ts-build": "webpack --mode production" }, "devDependencies": { "@types/express": "^4.17.8", "ts-loader": "^8.0.3", "typescript": "^4.0.2", "webpack": "^4.44.1", "webpack-cli": "^3.3.12", "webpack-node-externals": "^2.5.2" }, "dependencies": { "express": "^4.17.1", "vercel": "^20.1.0" } }src/index.tsimport * as Express from 'express'; const app = Express(); // postリクエスト使えるようにする app.use(Express.json()); app.use(Express.urlencoded({ extended: true })); app.get('/get/:name', (req: Express.Request, res: Express.Response) => { try { res.send({ name: req.params.name }); } catch (error) { res.sendStatus(500); } }); app.post('/post', (req: Express.Request, res: Express.Response) => { try { res.send({ name: req.body.name }); } catch (error) { res.sendStatus(500); } }); if (!process.env.NOW_REGION) { app.listen(process.env.PORT || 3000); } export default app;tsconfig.json{ "compilerOptions": { "baseUrl": "./", "paths": { "src/*": ["src/*"] }, "strict": true } }vercel.json{ "version": 2, "builds": [ { "src": "index.js", "use": "@now/node-server" } ], "routes": [ { "src": "/.*", "dest": "/index.js" } ] }webpack.config.jsconst path = require('path'); const nodeExternals = require('webpack-node-externals'); module.exports = { // モード値を production に設定すると最適化された状態で、 // development に設定するとソースマップ有効でJSファイルが出力される mode: 'development', entry: './src/index.ts', // ファイルの出力設定 output: { path: `${__dirname}/`, filename: 'index.js', libraryTarget: 'this', }, module: { rules: [ { test: /\.ts$/, exclude: /node_modules/, loader: 'ts-loader', options: { configFile: path.resolve(__dirname, 'tsconfig.json'), }, }, ], }, // import 文で .ts ファイルを解決するため // これを定義しないと import 文で拡張子を書く必要が生まれる。 resolve: { // 拡張子を配列で指定 extensions: ['.ts', '.js'], alias: { src: path.resolve(__dirname, 'src'), }, }, externals: [nodeExternals()], };.gitignore.vercel node_modules index.jsコマンド
TypeScriptをビルド
$ npm run ts-build
ローカルで実行 ポート番号3000で鯖が立ち上がる
$ npx vercel dev
デプロイ
$ npx vercel --prodワイの成果物
https://qiita.com/yuzuru2/items/b5a34ad07d38ab1e7378
①コード共有サイト(SPA) React
https://code.itsumen.com②画像共有サイト(SPA) React
https://gazou.itsumen.com③チャット(SSR) Nuxt.js
https://nuxtchat.itsumen.com④チャット(SPA) React
https://chat4.itsumen.com⑤掲示板(SSR) Next.js
https://board.itsumen.com⑥掲示板(SPA) Vue
https://board.itsumen.com⑦レジの店員を呼ぶスマホアプリ(Android)
https://play.google.com/store/apps/details?id=com.itsumen.regi&hl=ja⑧ブログ(静的サイトジェネレータ) Hugo
https://yuzuru.itsumen.com
- 投稿日:2020-09-08T01:17:46+09:00
AWS S3 アクセスログの集約化
前提
AWSのS3のアクセスログは1回のアクセスにつき、指定したバケットに1個のアクセスログファイルを作成するとします。
感覚的には1回につき1個のファイルではなく、同時間に発行されたログを1つのファイルにまとめて吐いているようです。細かいことはAmazon S3 サーバーアクセスのログ記録を見て下さい。
つまり「1時間毎に1回、1日で計24回のアクセスがあったとすると作成されるログファイルの数は24個」としてこの記事を読んで下さい。何がしたいか
前提で述べたように1回のアクセスで1個のログファイルが作成されるので、1000回アクセスされると1000個のログファイルが作成されてしまいます。「1日に発行されたログを全てまとめて見たい」という時に全てのログファイルをローカルにダウンロードするなんて事はしたくありません。S3の仕様上、それなりにコストがかかります。よってこれらのログファイルを1日毎にまとめてしまいます。1日毎ではなく1週間毎や1ヶ月毎にまとめてしまってもいいかもしれません。ただこの後に使用するAWSのLambdaの仕様上ファイルが大きくなりすぎると無理かもしれないので1日毎にしました。
Direction
上の図のような流れで処理を実行します。
1. 左から「User」が「Bucket A」にアクセスする(2020-09-07-14-30)。
2. 「Bucket A」はアクセスされるとログを「Bucket B」に書き込む。
3. 「Bucket B」はログを書き込まれたら「Bucket C」においてある、「2020_09_07.txt」にそのログを追記する。これらの処理の内、1と2の処理はAmazon S3 サーバーアクセスのログ記録の設定で自動で行われるので割愛します。
前準備
3の処理をするためにLambdaより関数を作成します。今回私はNode.jsのデプロイパッケージを作成することで関数を作成しました。
Node.js の Lambda デプロイパッケージを作成するには、どうすればよいですか?作成する際には、アクセス権限に「S3FullAccess」をもつロールを与えておいて下さい。
作成した関数の名前を「sum_logs」とします。sum_logsの設定、「トリガーを追加」より、「Bucket B」に新しいオブジェクトが作成されたらsum_logsが実行されるようにしておきます。
また「Bucket C」に「2020_09_07.txt」というファイルを準備しておいて下さい。
「Bucket B」や「Bucket C」と書いていますが実際のBucketの名前は"B"や"C"として以下のコードを扱って下さい。
index.js
var AWS = require("aws-sdk"); const moment = require("moment"); const s3 = new AWS.S3({ region: "ap-northeast-1", }); exports.handler = async (event, context, callback) => { try { //this means Bucket "C" const dest_bucket = "C"; var uploaded_params = { // event.Records[0].s3.bucket.name means Bucket "B" Bucket: event.Records[0].s3.bucket.name, Key: event.Records[0].s3.object.key, }; var uploaded_obj = await s3.getObject(uploaded_params).promise(); var uploaded_body = uploaded_obj.Body.toString(); var dest_params = { Bucket: dest_bucket, Key: moment().format("YYYY-MM-DD") + ".txt", }; var dest_obj = await s3.getObject(dest_params).promise(); var dest_body = dest_obj.Body.toString(); dest_body += uploaded_body; var new_params = { Bucket: dest_bucket, Key: moment().format("YYYY-MM-DD") + ".txt", Body: dest_body, }; var put_obj = await s3.putObject(new_params).promise(); // delete uploaded logs in Bucket "B" var deleted = await s3.deleteObject(uploaded_params).promise(); return; } catch (e) { console.log(e); return; } };解説
内容は大体読めば分かるでしょう。
今回デプロイパッケージにしたのはMoment.jsを使用したかったからです。個人的には扱いやすいのでおすすめします。注意点とまとめ
今回、日付毎にファイルをまとめていますが、そのベースとなるファイル(今回は2020_09_07.txt)は前もって特定のバケット(今回はBucket "C")に作成しておく必要があります。
このベースになるファイルも手動で作成するなんて馬鹿らしいことはしないので、無論自動で行わせるわけですが疲れたので気が向けば書きます。処理は同様にLambdaを使用しますが、トリガーはEventBridgeを使用します。とっても簡単です。コストについて
「Logが吐かれる度にS3にファイルをPutしてたら、データ転送料は相当かかるんじゃないの?」とか思われるかもしれませんが、僕はAWS Lambda 料金に書かれている
同じ AWS リージョン内における Amazon S3、Amazon Glacier、Amazon DynamoDB、Amazon SES、Amazon SQS、Amazon Kinesis、Amazon ECR、Amazon SNS、Amazon EFS、または Amazon SimpleDB と AWS Lambda 関数の間でのデータ転送は無料です。
という文章を信じています。参考
S3
Amazon S3 サーバーアクセスのログ記録
Lambda
EventBridge
AWS Lambda 料金
mermaid.js
- 投稿日:2020-09-08T00:51:58+09:00
Microsoft Teamsにメッセージと画像を投稿(Node.js)
Teamsのチャンネルに投稿用アカウント(Incoming Webhook)を追加して、
チャンネルにメッセージや画像を投稿する1.投稿したいチャンネルにIncoming Webhookを追加
- Teamsの投稿したいチャンネルで右クリックして [コネクタ] を選択
- [incoming Webhook] を検索して [追加] をクリック、次画面で再度 [追加] をクリック
- incoming Webhookの名前とアイコン画像を設定して [作成] をクリック
- Webhook用のURLが表示されるのでコピーして [完了] をクリック
2.base64にデコードした画像をメッセージに埋め込んでpostで送信
- node.jsの環境つくり、"fs"と"request"をインストール
- index.jsと同じ階層に画像を準備
- 以下のindex.jsを実行
index.jsconst fs = require('fs'); const request = require('request'); fs.readFile('画像名.png', 'base64', (date) => { let base64date = date; let message = "魔法陣ぐるぐる" //画像の上に書きたいメッセージ let options = { uri: "teamsからコピーしたURL", headers: { "Content-type": "application/json", }, json: { "text": message + "<br>" + "![]" + "(" + base64date + ")" } }; request.post(options); // postリクエスト送信 });
注意
- プライベートチャンネルにはこの方法で送信できない
- 画像サイズが大きすぎると、base64にエンコードしたときに文字列が長過ぎて送れない
- 投稿日:2020-09-08T00:51:58+09:00
Microsoft Teamsにメッセージと画像を送る(Node.js)
Teamsのチャンネルに投稿用アカウント(Incoming Webhook)を追加して、
チャンネルにメッセージや画像を投稿する1.投稿したいチャンネルにIncoming Webhookを追加
- Teamsの投稿したいチャンネルで右クリックして [コネクタ] を選択
- [incoming Webhook] を検索して [追加] をクリック、次画面で再度 [追加] をクリック
- incoming Webhookの名前とアイコン画像を設定して [作成] をクリック
- Webhook用のURLが表示されるのでコピーして [完了] をクリック
2.base64にエンコードした画像をメッセージに埋め込んでpostで送信
- node.jsの環境つくり、"fs"と"request"をインストール
- index.jsと同じ階層に画像を準備
- 以下のindex.jsを実行
index.jsconst fs = require('fs'); const request = require('request'); fs.readFile('画像名.png', 'base64', (date) => { let base64date = date; let message = "魔法陣ぐるぐる" //画像の上に書きたいメッセージ let options = { uri: "teamsからコピーしたURL", headers: { "Content-type": "application/json", }, json: { "text": message + "<br>" + "![]" + "(" + base64date + ")" } }; request.post(options); // postリクエスト送信 });
注意
- プライベートチャンネルにはこの方法で送信できない
- 画像サイズが大きすぎると、base64にエンコードしたときに文字列が長過ぎて送れない