【Apple Script】Javascriptでスクロールしながら要素を探す方法

以前、HTML内の特定のエレメント(要素)を見つけるために、ブラウザの下スクロールを試していました。


一番下までスクロールされれば、ページ全部の要素が見つかるだろうと思っていましたが、

表示されていないと見つからない要素もあった。

なので、該当の部分を飛ばして、一番下まで一気にスクロールしてしまうと、結局見つからないという自体に。

ということで、何回かに分けてスクロールさせてから、見つかったらスクロールを終わるというプログラムをApple Script と Javascriptで作ってみました。


on getXPathBySelector(args)

    set {sel_:aSel, text_:aText, xpath_:aXPath, scroll_flg_:aScrollFlg} to ¬
        args & {sel_:"", text_:"", xpath_:"", scroll_flg_:false}

    tell application "Safari"

        set cnt_max to 20
        set cnt_current to 0

        if aScrollFlg then
            do JavaScript "window.scroll(0, 0);" in document 1
        end if

        repeat cnt_max times

            set cnt_current to cnt_current + 1

            if aScrollFlg then

                do JavaScript "

            var element = document.documentElement;
            var bottom = element.scrollHeight - element.clientHeight;
            var scrollY = 0;

            scrollY = (bottom / " & cnt_max & ") * " & cnt_current & ";

            window.scroll(0, scrollY);
            " in document 1

            end if

            if aScrollFlg then
                delay 0.1
            end if

            set val to do JavaScript "
        var val = '';

    if aXPath is "" then
        set top_element to "var ele = document;"
    else
        set top_element to "
            var xpaths = document.evaluate('" & aXPath & "', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
            var ele = xpaths.snapshotItem(0);"
    end if

        var lists = ele.querySelectorAll(':scope " & aSel & "');

        for ( var i = 0; i < lists.length; i++ ) {
            var ele = lists[i];

            if('" & aText & "' == ''){
                val = getXpath(ele);
                break;
            }

            if(ele.innerHTML.indexOf( '" & aText & "') > -1){
                val = getXpath(ele);
                break;
            }
        }

function getXpath(element) {
  if(element && element.parentNode) {
    var xpath = getXpath(element.parentNode) + '/' + element.tagName;
    var s = [];

    for(var i = 0; i < element.parentNode.childNodes.length; i++) {
      var e = element.parentNode.childNodes[i];
      if(e.tagName == element.tagName) {
        s.push(e);
      }
    }

    if(1 < s.length) {
      for(var i = 0; i < s.length; i++) {
        if(s[i] === element) {
          xpath += '[' + (i+1) + ']';
          break;
        }
      }
    }

    return xpath.toLowerCase();
  } else {
    return '';
  }
}

        res = val;
        " in document 1

            if val is not "" then
                return val
            end if

            delay 0.1
        end repeat
        return ""
    end tell
end getXPathBySelector

使い方の例:

set xpath_parent to my getXPathBySelector({sel_:"div[data-component=\"entry\"]", text_:"", scroll_flg_:true})
set xpath_grandchild to my getXPathBySelector({sel_:"a", text_:"次へ", xpath_:xpath_parent})

この場合は、全部の縦スクロール量に対して、20分割してスクロールさせている。

見つかったら、要素のXPath を返すようにしている。

探す要素のセレクタを引数にしていて、innerHTMLのテキストでも探せるようにしている。

スクロールして探すか、しないで探すかを設定できる。

親のXPathで範囲を指定できる。

ちなみに、querySelectorAll で、基準となる要素の子孫を取得する時は、「:scope」を付けるとよいらしい。