Open2

node.js上のpuppeteerライブラリでHTML表の全行の要素をスクレイピングする

Teru roomTeru room

ターゲット

スクレイピング対象のターゲットHTMLは以下のようなWebページです。あるプロジェクトのメンバー一覧です。

スクリーンショット 2021-01-04 0.08.30.png

結論

HTML表の行数を求めるには、行を表すアイテムセレクター #content > center > table > tbody > tr をブラウザの page オブジェクトの $$() メソッドに指定します。そうしてメソッドの戻り値の length プロパティがHTML表の行数となります。

HTML表の行数を求める
let numOfAccount = 
      ( await page.$$( '#content > center > table > tbody > tr' ) ).length

求めた行数を変数 numOfAccount に格納して利用します。

HTMLの構造

一覧表はcenterタグ内にtableタグがあり、さらにtableタグ内にtbodyタグがあり、さらにtbodyタグ内にtrタグがあります。このtrタグ内の情報をスクレイピングします。

スクレイピング対象のターゲットHTML
<center>
  <table border="1" bgcolor="white" rules="all" class="TableColor">
    <tbody>
       <tr bgcolor="#eeeeff" class="TableHeadingColor">
         <th>アカウント</th>
         <th>氏名</th>
         <th>メールアドレス</th>
         <th>所属</th>
         <th>ロール</th>
         <th>アクセス表示</th>
         <th>表示順</th>
         <th colspan="2">状態変更</th>
       </tr>
       <tr bgcolor="#ffffff">
         <td>
           <a name="9999999"><img src="../images/user/invisible.png"></a>
           <a href="javascript:void(0)" class="userEditAnchor" data-account="9999999" data-role="2">9999999</a>
         </td>
         <td>帆花 風彦</td>
	<td>
          <a href="mailto:hoge_fuuhiko@baaboo.co.jp">hoge_fuuhiko@baaboo.co.jp</a>
        </td>
        <td>馬場時時開発本部</td>
        <td>開発メンバー</td>
        <td class="textCenterTd"><input type="checkbox" class="showaccessmode" name="access_9999999" value="1" checked="">
         </td>
         <td>
           <span id="oarea_9999999">
             <input type="hidden" name="order_9999999" value="1" id="order_9999999">
             <input type="button" class="disporderbtn" name="order_0272054" value="1">
           </span>
           <input type="hidden" name="dborder_9999999" value="1">
	</td>
         <td>
           <input type="button" id="invalid_9999999" name="label" value="無効">
         </td>
	<td>削除不可</td>
      </tr>
  ・・・・・・・・・・以下表の行数分<tr>タグを繰り返し・・・・・・・・・・
    </tbody>
  </table>
</center>

アイテムセレクター

スクレイピング対象のターゲットHTMLの tr タグのアイテムセレクターはタグの入れ子構造から

  • #content > center > table > tbody > tr

となります。

そこで、 TARGET オブジェクトのプロパティ

  • TARGET.project.userList.itemSelector.trTag'#content > center > table > tbody > tr' と定義

して利用することにします。

オブジェクト定義
const TARGET = {
  projectList: []
  ,
  url: {
      home: 'https://target.hoge.jp/'
    , project: {
        urlMask: '%ProjectNo%'
      , alllist: 'https://target.hoge.jp//manage/list.php'
      , userList: 'https://target.hoge.jp/users/list.php?project_id=%ProjectNo%'
    }
  }
  ,
  project : {
    allList: {
        rowNo: { top: 2 }
      , colNo: { id: 2, name: 3, team: 4 }
      , itemSelector: {
          rowNoMask: '%rowNo%', colNoMask: '%colNo%'
          , trTag: '#content > form > center > table > tbody > tr'
          , aTag: ' > a'
          , template: {
              row: '#content > form > center > table > tbody > tr:nth-child(%rowNo%)'
            , col: ' > td:nth-child(%colNo%)'
          }
        }
    }
    ,
    userList: {
        topRowNo: 2
      , colNo: { account: 1, name: 2, mailAddress: 3, belongs: 4 , role: 5 } 
      , itemSelector : {
          rowNoMask: '%rowNo%', colNoMask: '%colNo%', accountMask: '%account%'
        , trTag: '#content > center > table > tbody > tr'
        , aTag: ' > a.userEditAnchor'
        , invalidButton: 'input[id=invalid_%account%]'
        , searchButton: 'searchbtn'
        , template: {
              row: '#content > center > table > tbody > tr:nth-child(%rowNo%)'
            , col: ' > td:nth-child(%colNo%)'
          }
      }
    }
  }
}

HTML表の行数の使い方例

page.$$( TARGET.project.userList.itemSelector.trTag ) ).length により求められたHTML表の行数を格納した変数numOfAccountを最大値にしてforループで繰り返して、HTML表に格納されたデータをスクレイピングします。ここでは、アカウント氏名メールアドレスロールをスクレイピングしています。この処理を以下のgetAccountList()関数で実装します。

getAccountList()
const getAccountList = async function( page, accountList ) {
  console.log( '..... getAccountList() .....' )
  let [ account, name, mailAddress, belongs, role ] = [ '', '', '', '', '' ]
  let accountObj = {}
  try {
    let numOfAccount = 
      ( await page.$$( TARGET.project.userList.itemSelector.trTag ) ).length
    
    console.log( '<------ Project Member List Start ------>' )
    for( let rowNo = TARGET.project.userList.topRowNo; rowNo <= numOfAccount; rowNo++ ) {
      [ account, name, mailAddress, belongs, role ] = await getAccountItem( page, rowNo )

      if( account == '' ) continue

      accountObj = {    アカウント    : account
                      , 氏名       : name
                      , メールアドレス: mailAddress
                      , ロール      : role
                    }
      accountList.push( accountObj )
    }
  } catch( error ) {
    console.log( '------> Project Member List End Over <------' )
  } finally {
    if( Object.values( accountObj ).length == 0 ){
      accountObj = {    アカウント   : ''
                      , 氏名       : ''
                      , メールアドレス: ''
                      , ロール      : ''
                    }
      accountList.push( accountObj )
    }
    return accountList
  }
}
Teru roomTeru room

もう少し全体像がわかる説明を付けてクローズしたいと思います。クローズ後は記事として公開する予定です。現在、Qiita もまだ利用しているので💦、Qiita に先に公開いたしました。