[Power BI] Power QueryでCSSセレクタを使ってWebページからデータを取得する

※元ネタ
- ["例を使用してテーブルを抽出する とは"](https://qiita.com/PowerBIxyz/items/ffee73e7336600ad54d6), Takeshi Kagata, 2018-05-25, Qiita, https://qiita.com/PowerBIxyz/items/ffee73e7336600ad54d6
- ["Using Html.Table() To Extract URLs From A Web Page In Power BI/Power Query M"](https://blog.crossjoin.co.uk/2018/08/30/power-bi-extract-urls-web-page/), Chris Webb, 2018-08-30, CHRIS WEBB'S BI BLOG, https://blog.crossjoin.co.uk/2018/08/30/power-bi-extract-urls-web-page/
- ["Web Scraping with Html.Table in Power Query"](https://ssbipolar.com/2018/10/27/web-scraping-with-html-table-in-power-query/), Matthew Roche, 2018-10-28, BI Polar, https://ssbipolar.com/2018/10/27/web-scraping-with-html-table-in-power-query/

# 関連記事

- [Mから始めよう #5 〜Power BIでWebコンテンツの利用と個人用ゲートウェイ〜](https://qiita.com/spumoni/items/f66bb43f0c3f13308d79)
- [[Power BI] Power Query の Web.Contents 関数でREST APIを使う](https://qiita.com/spumoni/items/419ffd1f07947a07cad0)
- [(Power BI) Mから始めよう #8 〜クエリ・パラメータの各種使い方](https://qiita.com/spumoni/items/4badf29d0b3e170fa15b#b%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E9%96%A2%E6%95%B0%E3%81%A8url%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%E3%81%A7web%E3%82%B9%E3%82%AF%E3%83%AC%E3%82%A4%E3%83%94%E3%83%B3%E3%82%B0)
- [ページ分割されているWebでページ数がわからないデータを取得する](https://qiita.com/spumoni/items/2832b63fa627b20a5de2#4-%E3%83%9A%E3%83%BC%E3%82%B8%E5%88%86%E5%89%B2%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8Bweb%E3%81%A7%E3%83%9A%E3%83%BC%E3%82%B8%E6%95%B0%E3%81%8C%E3%82%8F%E3%81%8B%E3%82%89%E3%81%AA%E3%81%84%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B)
- [(Power BI) Mから始めよう #10 エラー処理とHTTPステータスコード](https://qiita.com/spumoni/items/cd5843e0104fed9b4874)
- [[Power BI] Power QueryでCSSセレクタを使ってWebページからデータを取得する](https://qiita.com/spumoni/items/cad005c1deaa522531d8)


# 0.Html.Tableの構文

```text:Html.Table構文
Html.Table(
　　　　html as any,                          // (1)
　　　　columnNameSelectorPairs as list,      // (2)
　　　　optional options as nullable record   // (3)
) as table
```

(1)は、htmlのコードで、取得したWebページの内容が入ります。
(2)は、列名と、CSSセレクターで選択された項目の対が入ります。
(3)は、データ行をCSSセレクターで指定するRowSelectorが入ります。

# 1.ナビゲーターによるtableデータ取得
　<ins>[以下のようなページ](https://fukuyori.github.io/RPALT20220117/)</ins>からテーブル項目のデータを抽出してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/9c08af74-1c3f-85e8-7967-33fea73f281c.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/84e1dacc-d02e-0a2f-c780-fdb03ce27aa4.png)

　上記操作で作成されたM式は、以下のようになります。

```text:Table-1
let
    ソース = Web.BrowserContents("https://fukuyori.github.io/RPALT20220117/"),
    HTMLから抽出されたテーブル = 
        Html.Table(
            ソース, 
            {
                {"Column1", "TABLE[id='Table-1'] > * > TR > :nth-child(1)"}, 
                {"Column2", "TABLE[id='Table-1'] > * > TR > :nth-child(2)"}, 
                {"Column3", "TABLE[id='Table-1'] > * > TR > :nth-child(3)"}, 
                {"Column4", "TABLE[id='Table-1'] > * > TR > :nth-child(4)"}
            }, 
            [RowSelector="TABLE[id='Table-1'] > * > TR"]
        ),
    昇格されたヘッダー数 = 
        Table.PromoteHeaders(
            HTMLから抽出されたテーブル, 
            [PromoteAllScalars=true]
        )
in
    昇格されたヘッダー数
```

# 2.CSSセレクター

※参照

https://qiita.com/spumoni/items/fcc71034ce162fcf3db7

　Html.Tableの最初の引数「ソース」は、 https://fukuyori.github.io/RPALT20220117/ から取得されたWebページの内容が入ります。対象となるデータの行は第3引数の **[RowSelector="TABLE[id='Table-1'] > * > TR"]** ですので、idが"Table-1"の属性を持つtableタグから１つ飛ばしてtrタグで並んでいる部分が１レコードの対象になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/621ec88f-2083-1c2d-1601-16eeba7acd89.png)
　第２引数でColumn1からColumn4までの中身を指定しています。このCSSセレクタでは第３引数で指定されている部分も含めて記述されていますが、取得できる範囲は第３引数で示されているので、以下のような書き方でも同様のデータを取得できます。

```text:Table-1 type 2
        Html.Table(
            ソース, 
            {
                {"Column1", "th:first-child"}, 
                {"Column2", "th:nth-child(2),td:nth-child(2)"}, 
                {"Column3", "th:nth-child(3),td:nth-child(3)"}, 
                {"Column4", "th:nth-child(4),td:nth-child(3)"}
            }, 
            [RowSelector="TABLE[id='Table-1'] > * > TR"]
        ),
```
　CSSセレクターの使い方については、「<ins>[CSSセレクターを使いこなす : Power Automate for desktopがなんとなく使える講座 #12](https://qiita.com/spumoni/items/fcc71034ce162fcf3db7)</ins>」でも書きましたので参照してください。ただし、jQueryのフィルター **eq()** 等は使えず、jQueryの子要素フィルター **first-child, nth-child, nth-of-type** 等は使えます。

　また、いきなりタグ名なしで **{"Column1", ":first-child"}** のような書き方はできず、 **{"Column1", "TR > :first-child"},** という書き方は可能です。

## CSSセレクターを使いこなすには
　GUIに頼らないで使いこなすには、調べたい場所を右クリックして「開発者ツールで調査する」を選びます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/0f5bf11f-820e-2d4b-4911-26a9437192f0.png)
　右側に表示されたhtmlで「selectorをコピー」を選択してメモ帳に張り付けて調べたり、htmlタグを手繰って調べたりします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/ff36d8c8-b403-a133-7552-0bc4592a3d7d.png)

# 3.リスト項目の取得

　次にlistタグのデータはtableタグのデータのようにはいきません。「例を使用してテーブルを追加」を使って項目を選択していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/3a920218-29b6-9169-b307-770902ce6b5f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/065b5d25-5cb7-47f4-78c4-d88eca5baa19.png)

　作成されたM式は以下のようになりました。

```text:ListItem
let
    ソース = Web.BrowserContents("https://fukuyori.github.io/RPALT20220117/"),
    HTMLから抽出されたテーブル = 
        Html.Table(
            ソース, 
            {
                {"列1", "[id*=""ListItem""]"}, 
                {"列2", "A", each [Attributes][href]?}
            }, 
            [RowSelector="[id*=""ListItem""]"]
        ),
    変更された型 = 
        Table.TransformColumnTypes(
            HTMLから抽出されたテーブル,
            {
                {"列1", type text}, 
                {"列2", type text}
            }
        )
in
    変更された型
```

　最初の表記に合わせれば、以下のように書き換えることもできます。

```text:ListItem type 2
        Html.Table(
            ソース, 
            {
                {"列1", "LI"}, 
                {"列2", "A", each [Attributes][href]?}
            }, 
            [RowSelector="UL#List-1 > LI"]
        ),
```
　２列目は、属性hrefの値を取得するため、 **each [Attributes][href]?** とeachを使った３つ目の記述が付きます。最後についてる **?** ですが、属性 **href** が存在しないとき、通常は戻り値がエラーになってしまうのですが、?があるとnullが返ってきます。

https://docs.microsoft.com/ja-jp/powerquery-m/m-spec-operators?WT.mc_id=DP-MVP-5004418

# 4.コードがすっきり
　自動で作成すると、コードがすごいことになったりします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/7bf360ba-ae21-0846-31b2-064cfa76db6c.png)

　これを手作業で書くと、以下のようにスッキリさせることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/7b958144-b9b9-7c51-5ee4-c6b2a8cc87bd.png)

　コードをスッキリ書きたくなりませんか？