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


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

![スクリーンショット 2021-01-04 0.08.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124810/1e9ed814-4b0d-6799-be9c-7f10188138ce.png)


## 結論

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

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

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

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

```HTML:スクレイピング対象のターゲット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'**

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

```JavaScript:オブジェクト定義
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()関数で実装します。

```JavaScript: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
  }
}
```