テクノロジー

Power Automateでスクレイピングするために書いたJavaScriptコード

御津島なつめ's icon'
  • 御津島なつめ
  • 2021/12/07 12:57

ご機嫌よう。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 && d == today.getDate()) {
            date.setTime(now - 60 * 60 * 24 * before * 1000);
        }
        else if (m == today.getMonth() + 1 && 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関数を実行」というアクションそのものを実現するためのものです。このアクションを作成するときに、自動的に生成されるコードです。動かしたいプログラムは、この関数の中に書きます。

Power AutomateからJavaScriptへ変数を渡す

2行目は、beforeという変数を定義して、そこにPower Automate側の変数である「InternalDaysBefore」を代入してます。Power Automateの変数を扱うときは、JavaScriptのコードの中でも、Power Automateの他のアクションの中で変数を指定するときと同じように、変数名を「%」で囲んで書くんですね。これは私の想像ですけど、%~%の辺りは、一種のマクロとして、実行時に展開される感じなんじゃないかと思います。なので、変数の中身によっては、クォーテーションで囲んでおかないと、実行時エラーが出るかもしれません。この行では、数値が来ることがほぼ確定してるので、省略してしまってますけど、基本的には、文字列扱いした方が良さそうです。本当はこの行も、parseInt()した方が安全なんだろうなとは思います。

7行目にもPower Automateの変数が出てきますけど、こっちは、JavaScriptのメソッドに、そのまま文字列の引数として渡してますね。このアクションの関数では、Webページのチェック対象箇所を、HTMLのid属性で指定してるんですけど、そのIDはPower Automateの入出力変数として定義してるので、それを渡してます。このIDは後のアクションでも必要なので、プログラム中に直接書くのではなく、グローバルな定数みたいな感じで定義して、使い回してます。

JavaScriptからPower Automateへ変数を渡す

これは動画でも触れましたけど、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オブジェクトのメソッドとして呼んでます。

それはそうと、肝心の日時チェックそのものは、もうちょっとスマートなやり方があるかもしれませんね……。

 

「Webページをスクロール」のアクション

function ExecuteScript() {
window.scrollBy(0, window.innerHeight);
}

この関数は、シンプルですね。機能も、スクロールするだけの、シンプルなものです。このコードは、「掲載日時チェック」の方に含めてしまっても良いんですけど、こうやって機能ごとに独立させておいた方が、他の箇所にもアクションごとコピーするだけで使えて便利なので、こうしてます。まあ、結局、ここでしか使ってないんですけどね。

 

「記事の一覧をCSVとして取得する」アクション

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 && 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(/&/);
        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、工夫次第で、いろいろと面白いことができそうですね。

それではご機嫌よう。御津島なつめでした!

Content image

Power AutomateでWebスクレイピングしてみたよ

Twitter(@MinatonekoJinja)

Supporter profile icon
Article tip 1人がサポートしています
獲得ALIS: Article like 86.47 ALIS Article tip 10.00 ALIS
御津島なつめ's icon'
  • 御津島なつめ
  • @NatsumeMidzushima
バーチャルYouTuberの御津島なつめです。IT関連の話題を中心に、浅く広く緩い動画を作ったりしています。

投稿者の人気記事
コメントする
コメントする
こちらもおすすめ!
Eye catch
クリプト

「ハッシュ」とは何なのか、必ず理解させます

Like token Tip token
0.10 ALIS
Eye catch
ゲーム

ドラクエで学ぶオーバフロー

Like token Tip token
30.10 ALIS
Eye catch
テクノロジー

オープンソースプロジェクトに参加して自己肯定感を高める

Like token Tip token
85.05 ALIS
Eye catch
テクノロジー

彼女でも分かるように解説:ディープフェイク

Like token Tip token
32.10 ALIS
Eye catch
クリプト

NFT解体新書・デジタルデータをNFTで販売するときのすべて【実証実験・共有レポート】

Like token Tip token
120.79 ALIS
Eye catch
クリプト

17万円のPCでTwitterやってるのはもったいないのでETHマイニングを始めた話

Like token Tip token
46.60 ALIS
Eye catch
他カテゴリ

機械学習を体験してみよう!(難易度低)

Like token Tip token
69.82 ALIS
Eye catch
テクノロジー

iOS15 配信開始!!

Like token Tip token
7.20 ALIS
Eye catch
他カテゴリ

ALISのシステム概観

Like token Tip token
5.00 ALIS
Eye catch
テクノロジー

なぜ、素人エンジニアの私が60日間でブロックチェーンゲームを制作できたのか、について語ってみた

Like token Tip token
270.93 ALIS
Eye catch
クリプト

Bitcoinの価値の源泉は、PoWによる電気代ではなくて"競争原理"だった。

Like token Tip token
159.32 ALIS
Eye catch
クリプト

ブロックチェーンの51%攻撃ってなに

Like token Tip token
0.00 ALIS