自動化厨のプログラミングメモブログ │ CODE:LIFE

Python/ExcelVBA/JavaScript/Raspberry Piなどで色んなことを自動化

GASとSlackとGoogleHomeで毎朝なにを着れば良いか教えてもらう

f:id:maru0014:20181014232943p:plain

最近は季節の変わり目で朝起きて寒いなーと思って上着を着ていくと、意外と昼間は暑くて上着が邪魔になったり。逆に意外と寒かったり。

朝どんな服装にするべきか悩ましいのですが、tenki.jpではどれくらいの服装なら体感温度が適温になるのか「服装指数」というものを表示してくれています。

これを毎朝確認すれば前述のような自体は避けられる!と思ったが自動化厨にとってそれは大変面倒くさいというもの。

今回はGASでお手軽にスクレイピング→Slackへ投稿→GoogleHomeに喋らせるという仕組みを作りました。

※今回の記事では「GASでスクレイピング→Slackへ投稿」までを紹介します。「→GoogleHomeに喋らせる」は以下の過去記事を参照ください。

code-life.hatenablog.com

Googleスプレッドシートでスクレイピング

東京地方(東京)の服装指数 - 日本気象協会 tenki.jp

https://tenki.jp/indexes/dress/3/16/4410/

東京の方はこちらのページですね。お住まいの地域に合わせてページURLをコピーしておきましょう。

このページに欲しい情報は全て載っています。

  1. 服装指数
  2. どんな服装にすべきかのコメント
  3. 天気
  4. 最高/最低気温

これらの情報をスプレッドシート上に取得・表示できるようにするのですが、Googleスプレッドシートには超便利な関数「importxml」があります。

この関数は=importxml("スクレイピング対象URL","XPathクエリ")のように記述するだけで対象要素のテキストをセルに表示することができます!

スプレッドシートの完成イメージ

f:id:maru0014:20181014234349p:plain

だいたいこんなかんじです。ぶっちゃけ3行目以下は不要なんですが、なぜかXpathのインデックス番号を指定しても全部取得されちゃってめんどくさいので諦めてこのまま使いました。

関数入力

各項目を以下のようなXpathで取得します。

※今回スクレイピング対象URLはI1セルに入力してあります。

項目 関数入力
A 日付 =today()
B 指数 =importxml(I1,"//*[@id='main-column']//*[contains(@class, 'indexes-telop-0')]")
C 天気 =importxml(I1,"//*[@id='main-column']//*[@class='weather-telop']")
D 最高気温(℃) =importxml(I1,"//*[@id='main-column']//*[@class='high-temp']")
E 最低気温(℃) =importxml(I1,"//*[@id='main-column']//*[@class='low-temp']")
F 降水確率 =importxml(I1,"//*[@id='main-column']//*[@class='precip']")
G コメント =importxml(I1,"//*[@id='main-column']//*[contains(@class, 'indexes-telop-1')]")
H 文章 =CONCATENATE("本日の天気は",C2,"、最高気温",D2,"、最低気温",E2,"、降水確率",F2,"%、服装指数は",B2,"です。",G2,"。")

H列にはSlackへ投稿させるメッセージ内容です。各項目を文章として結合しています。

以下を参考にしました

review-of-my-life.blogspot.com

qiita.com

GASでスプレッドシートのデータを取得してSlackへ投稿

Slackで事前に着信Webフックを用意しておきます。以下の過去記事を参考に設定してください。

code-life.hatenablog.com

コード.jsに以下のように記述

var postUrl = 'ここにSlackの着信WebフックURL';

function myFunction() {
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); //スプレッドシートを取得
  var sheet = spreadsheet.getActiveSheet(); //シートを取得
  var message = sheet.getRange("H2").getValue(); //投稿用文章が入っているセルの中身を取得
  
  var payload = JSON.stringify(
  {
     "text" : message
  });
  
  var options =
  {
    "method" : "post",
    "contentType" : "application/json",
    "payload" : payload
  };
  
  UrlFetchApp.fetch(postUrl, options); //Slackへ投稿
}

ここまでできたらテスト実行。再生ボタンを押しましょう。

f:id:maru0014:20181015000147p:plain

これでSlackへ無事投稿されればOK。

毎朝自動実行する

再生ボタン左にある時計アイコンをクリック。

以下のように設定して保存すれば毎朝教えてくれます。

f:id:maru0014:20181015000336p:plain

【2020年更新】はてなブログで書いたコードブロックに行番号を表示する方法(CSS+JavaScript)

  • この記事でできるようになること
    • 完成イメージは以下の状態
  • CSSのcontent: counterを使ってみる
  • JavaScriptで行ごとにdivで囲む
    • CSSで付け足したdivごとにカウントアップ
    • [おまけ]コードブロック左上に言語名を表示するCSS

この記事でできるようになること

  1. はてなブログのコードブロックに行番号を表示
  2. はてなブログのコードブロックの偶数行のみ背景色をつける
  3. [おまけ]はてなブログのコードブロックの左上に言語名を表示する

最初は行番号のみで考えていましたがついでに縞々もやってみました。

完成イメージは以下の状態

f:id:maru0014:20180522222246p:plain

要件

  • JavaScriptで追加する方法もあるみたいだけどCSSでやりたい
  • シンタックスハイライトははてな標準で十分
  • highlight.jsなどのライブラリは使いたくない
続きを読む

【脱jQuery?】jQueryみたいなセレクタが使えるdocument.querySelectorが便利

この記事でできるようになること

  • jQueryを使わずに#myID .myClassみたいなセレクタが使えるようになる

ちょっとしたjavascriptを書くだけなのにCSSっぽいセレクタを使って楽したいがためにjQueryライブラリを読ませるのは面倒。

document.getElementById('aaa').getElementsByClassName('bbb')[0]

なんて書くの長すぎ。

jQuery使わずにもっと短い書き方無いんかい!

と思って調べたところありました。

 

document.querySelector

概要

与えられた CSS セレクタにマッチする文書中の最初の要素(※深さ優先の先行順走査によるもの)を返します。

構文

element = document.querySelector( selectors );

引用元:document.querySelector() - Web API インターフェイス | MDN

つまり、jQuery読み込まずにjQueryと同じセレクタが使える。これです。探していたやつ。

 

使用例

See the Pen querySelector by maru (@maru0014) on CodePen.

 

複雑なセレクタじゃないならgetElementsByClassNameの方が早い?

MDNの例を見てみるとクラス名で単一の要素を取得する場合は.getElementsByClassNameの方が処理速度が早いとあります。 document.querySelectorでクラス名指定する場合は複雑なセレクタになるときだけ使うのが良さそうです。2階層以上指定のときとかでしょうか

element = document.getElementsByClassName('aaa')[0].getElementsByClassName('bbb')[0]

element = document.querySelector('.aaa > .bbb');

これくらい差があればquerySelectorの方がいいですね。状況に応じて使い分けるか、速度はそこまで求めないから全部querySelectorでやっちゃうかってとこです。

 

一発で配列化できるdocument.querySelectorAll

document.querySelectorはクラス名で指定した時も最初にマッチした単一の要素を取ってきます。

対してdocument.querySelectorAllはセレクタにマッチした要素を全て配列として格納してくれる。

.getElementsByClassName('bbb')と同じ挙動ですね。

これもおそらく.querySelectorAllよりも.getElementsByClassNameが速度的には早いのかもしれませんが、セレクタが複雑な場合は.querySelectorAllを使うことになるでしょう。

 

使用例

See the Pen querySelectorAll by maru (@maru0014) on CodePen.

上記は.collection > .collection-item > spanというセレクタで配列にしています。 もし.getElementsByClassNameなどを用いて書くなら

//CLASSでセレクトして配列に格納
var list = document.getElementsByClassName('collection')[0].getElementsByClassName('collection-item');

//listの数だけ繰り返し
for(var i = 0; i < list.length; i++) { 
  //連番を付ける
  list[i].getElementsByTagName('span')[0].textContent = [i+1] + '.' + list[i].getElementsByTagName('span')[0].innerText;
}

となります。長いいいいい。 いや、まぁ連番付けるのjavascriptでやんなよって話は置いといて。

とにかく短く、簡単に書ける。 でもjQuery使っていい状況ならjQuery使ったほうが結局楽なんですがね。ちょっとしたプログラムのためにjQuery読ませるの重すぎてやだってときにquerySelectorを使うことにします。

  getElementの使い方はこちら code-life.hatenablog.com

【jQuery】htmlページをパーツごとに分けて読み込む方法

この記事でできるようになること

  • ヘッダー、フッターなどの共通部分のHTMLを別ファイルとして管理
  • 切り出したHTMLファイルをjQueryで動的に読み込む

PHPなしで制作するWEBページは管理が煩雑になりがち

HTMLでホームページを構築する際に気をつけたいのが作ったあとのメンテナンス。何にも考えずにガシガシ書いてるとあとあと更新するのが大変なんです。 例えばヘッダーについてるグローバルメニューをカスタムした時には、グローバルメニューが入ってるページ全てを更新しなければならず大変な手間。

ヘッダー部分だけ全ページ共通で読み込ませることができれば楽だと思いませんか?

インラインフレームはレスポンシブデザインで使いづらい

インラインフレームで読み込む方法も良いかもしれませんが、今回の案件ではレスポンシブデザインということでフレーム読み込みは断念。高さの自動調整を実装するのも面倒ですし。 データベースの扱えるサーバーならばPHPでヘッダーやサイドバーなどを読み込むのがスマートな方法なのですが、ECサイト(楽天やYahoo!ショッピング)などのサーバーサイド言語が扱えないような状況では出来ない芸当。

以下の条件のもと行き着いたのが今回紹介する方法です。

  1. レスポンシブ対応
  2. ヘッダーやフッターなどは全て共通化したい
  3. PHPは使えない

jQueryのloadメソッドでhtmlを読み込む

いろいろ調べてみた結果行き着いたのはjQueryのloadメソッドでした。

楽天のGOLDやYahoo!ショッピングのトリプルは JavaScriptが使用可能なので使える!

この方法の概要としてはindex.html内のパーツを読み込みたい場所に、別途用意した「header.html」などを追記するというもの。

  1. index.htmlを完成させる
  2. 共通パーツを切り分ける
  3. 読み込み用のscriptを記述

の手順で行うのがオススメ。

1. まずは普通にindex.htmlを作ろう

<!DOCTYPE HTML>
<html lang="ja">
<head>
</head>
<body>
<div id="header">
タイトルロゴや画像<br>
グローバルメニューなど
</div>

<div id="side">
サイドバー<br>
検索バーやカテゴリーリストなど
</div>

<div id="main">
メインコンテンツは直接記述
</div>

<div id="footer">
サイトのSNS情報や<br>
コピーライトなど
</div>

</body>
</html>

2. 共通パーツを切り分ける

※htmlとして保存する際にはやなどは不要。そのままコピペするだけでOKです。

 

header.html

   <div id="header">
タイトルロゴや画像<br>
グローバルメニューなど
</div>

<div id="header">の中身をコピーしてheader.htmlとして保存

 

side.html

   <div id="side">
サイドバー<br>
検索バーやカテゴリーリストなど
</div>

<div id="side">の中身をコピーしてside.htmlとして保存。

 

footer.html

   <div id="footer">
サイトのSNS情報や<br>
コピーライトなど
</div>

<div id="footer">の中身をコピーしてfooter.htmlとして保存。

3. index.htmlに各パーツを読み込むscriptを記述

head内にjQueryを読み込む

<script src="//code.jquery.com/jquery-2.2.4.min.js"></script>

loadメソッドで各パーツを読み込む

<script>
//共通パーツ読み込み
 $(function() {
 $("#header").load("header.html");
 $("#side").load("side.html");
 $("#footer").load("footer.html");
});
</script>

これでそれぞれID指定した箇所にパーツとなるhtmlがそのまま読み込まれます。

完成形

<!DOCTYPE HTML>
<html lang="ja">
<head>
<script src="//code.jquery.com/jquery-2.2.4.min.js"></script>
<script>
//共通パーツ読み込み
  $(function() {
       $("#header").load("header.html");
       $("#side").load("side.html");
       $("#footer").load("footer.html");
});
</script>
</head>
<body>
<div id="header">
ここにheader.htmlが読み込まれる
</div>

<div id="side">
ここにside.htmlが読み込まれる
</div>

<div id="main">
メインコンテンツは直接記述
</div>

<div id="footer">
ここにfooter.htmlが読み込まれる
</div>

</body>
</html>

loadメソッドで読み込んだページでjQueryが機能しない問題

JavaScriptでページを読み込んでくる際に注意すべき点があります。それは各パーツに適用するJavaScriptの実行順序。 例えばheaderのグローバルメニューなんかはこだわってプルダウンメニューにしたかったりしますよね。

その時に使うのもやはりjQuery。そこで起きるのがこの問題。 実行順序によってはページが読み込まれる前にグローバルメニューのjQueryが実行されてしまい。 メニューが機能しないなんてことがあります。というか今回ありました。

対策方法

実行順序を考えてページ読み込み完了後に指定して・・・・っていう方法もあるにはあるがめんどくさい。

一番簡単な方法はパーツのhtmlの方にscriptを記述してしまうこと。

こうすればパーツを読み込まれる前に実行されることは無いので確実にパーツ内のhtmlにjQueryを適用できます。

(ここに行き着くのに1時間ほど悩みました (´・ω・`))

jQueryを使わないjavascriptでの要素取得方法とテキストの書き換え

今までなんとなーくで書いて、動くからまぁいいか。でやってきてたjavascriptをきちんと勉強しようと思うので、少しずつメモ書きをためていきます。 今回はhtmlドキュメントツリー内の「id」、「class」、「name」を指定して要素取得します。

IDでテキストを取得

getElementById

var test = document.getElementById('aaa').textContent;

idを指定して単一の要素を取得.textContentで要素内のテキストを抽出しています。

テキストを書き換え

document.getElementById('aaa').textContent = '書き換えしました';

=の左右を入れ替えることで要素内のテキストを書き換えることができます。

 

CLASSでテキストを取得

getElementsByClassName

var test = document.getElementsByClassName('bbb')[0].textContent;

classを指定して要素のコレクションを取得し、[0]で何番目の要素かを指定し、.textContentで要素内のテキストを抽出しています。 getElementsというふうに複数形になっているため、何番目の要素を取ってくるのかを指定する必要があるわけです。 プログラミングにおいては[0]から順にカウントしていくので、[0]で上から1番目の要素となり、[1]なら上から2番目の要素となります。

テキストを書き換え

document.getElementsByClassName('bbb')[0].textContent = '書き換えしました';

 

タグの名前を指定してテキストを取得

getElementsByTagName

var test = document.getElementsByTagName('a')[0].textContent;

タグ名で取得する場合もClassNameと同様にコレクションを取得するので、[0]で何番目かを指定します。

テキストを書き換え

document.getElementById('ccc').getElementsByTagName('a')[0].textContent = '書き換えしました';

 

親要素を指定して子要素のテキストを取得

getElementById.getElementsByTagName

var test = document.getElementById('ccc').getElementsByTagName('a')[0].textContent;

まず親要素を指定、さらにその中に含まれている子要素を取得します。

テキストを書き換え

document.getElementById('ccc').getElementsByTagName('a')[0].textContent = '書き換えしました';

 

nameでテキストを取得

getElementsByName

document.getElementsByName("ddd")[0].textContent;

テキストを書き換え

document.getElementsByName("ddd")[0].textContent = '書き換えしました';
↓この本で勉強中!

確かな力が身につくJavaScript「超」入門 (確かな力が身につく「超」入門シリーズ)

確かな力が身につくJavaScript「超」入門 (確かな力が身につく「超」入門シリーズ)

あくまで自分用のメモ書きのつもりですが、使えそうだったらコピペで使ってやってください。 なにか間違ってたりもっと良い方法があったら教えてくださいな。