ご機嫌よう。IT系Vtuber、御津島なつめです。こちらは、動画「Power AutomateでWebスクレイピングしてみたよ」でご紹介したフローの中の、JavaScriptコードを掲載する記事です。ちょっと長い動画になっちゃったんですけど、記事だけでは何の話なのかよくわからないと思いますので、よろしければ、まず動画の方からご覧いただければと思います。
動画の方は、できるだけわかりやすくなるように作りましたが(私なりに)、こちらの記事は、JavaScriptなど、ある程度プログラミングの知識をお持ちの方向けになってます。基本的には、各関数のポイントとなるところだけ説明してます。Webページ関連なので、HTMLやDOMについての知識もあると、コードをより読みやすいんじゃないかなと思います。
ということで、順番にコードを見ていきたいと思います
function ExecuteScript() {
const before = %InternalDaysBefore%;
let today = new Date();
const now = Date.now();
today.setTime(now - 60 * 60 * 24 * before * 1000);
const area = document.getElementById('%TargetArea%');
const times = area.getElementsByTagName('time');
let ch = false;
let date = new Date();
let tmp = '';
for (let i=0; i<times.length; i++) {
const t = times[i].innerText;
if (t.indexOf('分前') > 0) {
const m = parseInt(t.substr(0, t.indexOf('分前')));
date.setTime(now - 60 * m * 1000);
}
else if (t.indexOf('時間前') > 0) {
const h = parseInt(t.substr(0, t.indexOf('時間前')));
date.setTime(now - 60 * 60 * h * 1000);
}
else if (t.indexOf('年') > 0) {
let str = t.substr(t.indexOf('月') + 1);
const d = parseInt(str.substr(0, str.indexOf('日')));
str = t.substr(t.indexOf('年') + 1);
const m = parseInt(str.substr(0, str.indexOf('月')));
if (m == today.getMonth() + 1 &amp;&amp; d == today.getDate()) {
date.setTime(now - 60 * 60 * 24 * before * 1000);
}
else if (m == today.getMonth() + 1 &amp;&amp; d > today.getDate()) {
continue;
}
else {
return 1;
}
}
if (date.getDate() == today.getDate()) {
ch = true;
}
else if (ch == true) {
return 1;
}
}
return 0;
}
こちらのコードを元に、Power Automate特有のお作法みたいなのをご説明しておきますね。
全体を囲んでいる「ExecuteScript()」という関数は、「WebページでJavaScript関数を実行」というアクションそのものを実現するためのものです。このアクションを作成するときに、自動的に生成されるコードです。動かしたいプログラムは、この関数の中に書きます。
2行目は、beforeという変数を定義して、そこにPower Automate側の変数である「InternalDaysBefore」を代入してます。Power Automateの変数を扱うときは、JavaScriptのコードの中でも、Power Automateの他のアクションの中で変数を指定するときと同じように、変数名を「%」で囲んで書くんですね。これは私の想像ですけど、%~%の辺りは、一種のマクロとして、実行時に展開される感じなんじゃないかと思います。なので、変数の中身によっては、クォーテーションで囲んでおかないと、実行時エラーが出るかもしれません。この行では、数値が来ることがほぼ確定してるので、省略してしまってますけど、基本的には、文字列扱いした方が良さそうです。本当はこの行も、parseInt()した方が安全なんだろうなとは思います。
7行目にもPower Automateの変数が出てきますけど、こっちは、JavaScriptのメソッドに、そのまま文字列の引数として渡してますね。このアクションの関数では、Webページのチェック対象箇所を、HTMLのid属性で指定してるんですけど、そのIDはPower Automateの入出力変数として定義してるので、それを渡してます。このIDは後のアクションでも必要なので、プログラム中に直接書くのではなく、グローバルな定数みたいな感じで定義して、使い回してます。
これは動画でも触れましたけど、returnした値がPower Automateへ渡されます。ここではチェックした結果を数値として、真のときは1、偽のときは0で返してます。もちろん、true / falseでも良いと思います。ただ、その場合、Power Automateへは、多分ただの文字列で返されそうな気がするので、数値で返す方がすっきりするんじゃないかなと、なつめ的には思います。
動画でも触れたように、この関数のチェック対象は、「20分前」とか「2021年12月6日」のような、プレスリリースの掲載日時です。これらは、対象WebページではtimeというHTMLタグで書かれてます。なので、まずgetElementsByTagName('time')して、ページに表示済みのtimeオブジェクトの一覧を配列として取得してます。それから、その配列をforループで回して、各要素のinnerHTMLの記述内容を取得し、動作時点のDateオブジェクトとの比較などで、新旧をチェックする、というのが、処理のメインという感じです。
ちなみに、新着のプレスリリースの表示欄だけをチェックしたいので、documentオブジェクトからではなく、先にgetElementById('%TargetArea%')で取得したDOMオブジェクトのメソッドとして呼んでます。
それはそうと、肝心の日時チェックそのものは、もうちょっとスマートなやり方があるかもしれませんね……。
function ExecuteScript() {
window.scrollBy(0, window.innerHeight);
}
この関数は、シンプルですね。機能も、スクロールするだけの、シンプルなものです。このコードは、「掲載日時チェック」の方に含めてしまっても良いんですけど、こうやって機能ごとに独立させておいた方が、他の箇所にもアクションごとコピーするだけで使えて便利なので、こうしてます。まあ、結局、ここでしか使ってないんですけどね。
function ExecuteScript() {
const before = %InternalDaysBefore%;
let today = new Date();
const now = Date.now();
today.setTime(now - 60 * 60 * 24 * before * 1000);
const zf = new Intl.NumberFormat(
'ja',
{ minimumIntegerDigits : 9, useGrouping : false });
const area = document.getElementById('%TargetArea%');
const details = area.getElementsByClassName('%TargetClass%');
let result = new Array();
let ch = false;
let date = new Date();
for (let i=0; i<details.length; i++) {
const article = details[i];
const t = article.getElementsByTagName('time');
if (t.length < 1) {
continue;
}
const rd = t[0].innerText;
if (rd.indexOf('分前') > 0) {
const m = parseInt(rd.substr(0, rd.indexOf('分前')));
date.setTime(now - 60 * m * 1000);
}
else if (rd.indexOf('時間前') > 0) {
const h = parseInt(rd.substr(0, rd.indexOf('時間前')));
date.setTime(now - 60 * 60 * h * 1000);
}
else if (rd.indexOf('年') > 0) {
let str = rd.substr(rd.indexOf('月') + 1);
const d = parseInt(str.substr(0, str.indexOf('日')));
str = rd.substr(rd.indexOf('年') + 1);
const m = parseInt(str.substr(0, str.indexOf('月')));
if (m == today.getMonth() + 1 &amp;&amp; d == today.getDate()) {
date.setTime(now - 60 * 60 * 24 * before * 1000);
}
else {
continue;
}
}
if (date.getDate() == today.getDate()) {
ch = true;
}
else if (ch == true) {
continue;
}
if (!ch) {
continue;
}
const a = article.getElementsByTagName('a');
if (a.length > 0) {
const href = a[0].href;
const ht = href.split(/&amp;/);
let cid = '';
let rid = '';
for (let j=0; j<ht.length; j++) {
if (ht[j].match(/company_id/)) {
const ct = ht[j].split(/=/);
cid = zf.format(ct[1]);
}
else if (ht[j].match(/release_id/)) {
const rt = ht[j].split(/=/);
rid = zf.format(rt[1]);
}
}
const url = 'https://prtimes.jp/main/html/rd/p/' + rid + '.' + cid + '.html';
result.push('"' + url + '","' + a[0].innerText + '"');
}
}
return result.join("\n");
}
この関数、「掲載日時チェック」のものと、処理が半分近く同じなので、構造も似たような感じになってます。違うのは、こっちでは、新着プレスリリース表示欄からの一覧取得を、getElementsByClassName('%TargetClass%')で行っているところ。掲載日時だけでなく、タイトルなども取得したいので、ブロックごと拾ってるんですね。なので、ループの中で、timeタグのオブジェクトを取得し直してから、日付チェックを入れてます。
URLの取得方法も、なつめ的にはポイントだったりします。動画では、単に「URLを取得して」とお話ししましたけど、実は、記事タイトルのAタグのhrefをそのまま取得してるんじゃなくて、企業IDとリリースIDの方を取得して、それらの情報からURLを構成してます。こうした方が、動画の説明欄にニュースソースとして貼ったり、ALISに番外編として載せるときに、便利なんですね、私にとって。これも、ちょっとした工夫だったりします。
WebスクレイピングといえばPythonが主流ですけど、Windowsだと、ちょっとセットアップが面倒だったりしますよね。逆に、サーバ環境などでのスクレイピングでは、動的ロードに対応するのに、ちょっと苦労したりします。Power Automateでのスクレイピングは、この両方を割と簡単にクリアできるので、結構ありなんじゃないかなって思いました。何より、JavaScriptが書ければOKなので、ハードルは割と低めに感じます。他のページでも、試してみたいですね。
ちなみに、Power AutomateにはPythonスクリプトを実行するアクションもあるので、JavaScriptで拾ってきた内容をPythonで処理する、みたいなことも、できるかもしれません。Power Automate for Desktop、工夫次第で、いろいろと面白いことができそうですね。
それではご機嫌よう。御津島なつめでした!