SharePointのニュース記事ごとに閲覧数やいいねの数をCSVへ書き出す

## こんな人に読んでほしい
職場でSharePointのニュース記事投稿を使って情報発信をしていて、各ページの閲覧傾向の分析をしたいような方向けの記事です。フローの実行日ごとに日付ファイル名付きのCSVをSharePointフォルダに書き出します。

こうすることで、時系列のデータを作ることができるので、Power BIを使ってCSVファイルを連結して閲覧数の増加傾向などを分析することができます。（Excel のPower Queryを使ってもよいですね。そのあたりもまた記事作ります）

## SPOニュースについて　おさらい
この記事を読んでいる人には説明の必要がないかもしれませんが、ニュース投稿はSharePointサイトの「+新規」から行えます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/9cfe8872-b3b7-b6cd-81b5-4163c9c8a8b1.png)

投稿された記事は「ページ」という項目のフォルダの中に、.aspxの拡張子が付いたファイルとして保存されています。隠されている「いいね」列を表示すると、いちおうここでもニュースページについているイイねの数を知ることができます。ただ、この数は瞬間の数字なので、今回の取り組みはそのスナップショットを取るようなイメージです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/c5956ba6-b8f6-966e-e12d-7179b7d8b7bc.png)

記事の各ページを開くと、下のほうに閲覧数が表示されています。この数字をスナップショットするのが今回の目的です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/b3458fa7-67a6-22f8-9427-60caab424c47.png)

## まずは、サイト内の記事一覧を取得する
全体の動作としてはサイトの中の記事を一覧化して、そのあとに1記事ずつの閲覧数を取得するイメージです。
というわけで、先ほどのスクショのような記事ファイルの一覧を取ってきます。実はこの部分に少しコツがいります。

### フォルダの中身 実はSPOリスト
SharePointの上にあるファイルの管理、実はSharePointリストとして管理されています。先ほどのスクショにあった「ページ」フォルダの中身に表示されている.aspxファイルの一覧もSPOリストなんです。

ということは、リストの項目一覧をPowerAutomateで取得すればよいわけです。
ここで、クラウドフローでSharePointの操作をしたことがある方ならお気づきでしょう。「複数の項目の取得」アクションで、リスト名を選択しようとしても、「ドキュメントライブラリ」や「ページ」をリストとして選べる選択肢は出てこないのです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/a08618c0-589f-4276-4fb5-45552999a93d.png)

というわけで、以前にこの問題の対処法について記事にしていますのでご覧ください。

https://qiita.com/DaddyDaddy/items/1918c4fcd68ae93546e7

リストのIDを取得すればよいのですが、記事の一番最後に書いてある突っ込みどころ満載の解決策を使います。
リスト名の選択項目で、カスタム値の入力をつかって「サイトのページ」と入力してやります。びっくりですよね！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/10fa4350-4937-1d4a-e355-b9ad53e40210.png)

### 必要な項目だけ「選択」でチョイス
テスト実行して、取得できたJSONをVisual Studio Codeのようなテキストエディタに貼り付けて、体裁を整えて見てみます。今回ほしいのは、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/8c53446c-3a6a-e35e-114e-c1346ff594f7.png)

情報がたっぷりでおなか一杯なので、「選択」を使ってつまみ食いします。

    item()?['{name}']
    
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/2f7c3687-3b21-91c6-8dba-6e74b73ada2f.png)

イイ感じに記事ファイルのタイトル部分だけが取得できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/47a94610-6101-03b6-4423-4f8d038691d4.png)

テキストエディタの中身を見ながら、調子にのって、他のほしい項目を「選択」の中に追加していきます。

    item()?['ID']

    item()?['{FilenameWithExtension}']

    item()?['{Link}']

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/1fedc323-4cec-88bf-4d9e-8aa96ab900e4.png)

スッキリしたJSONでページの一覧を取得することができました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/1554e82b-389d-a27b-087b-446be84490eb.png)

## 情報はSPO用のHTTP要求でゲット
いよいよ、１記事ずつ閲覧数やいいね項目を取得していきます。いきなりループを使って回してみたいところですが、まずはテスト。先ほど取得した適当な記事のリンク（記事のURL）を使って、その記事の情報を取得します。

安心してください。プレミアムではない、標準コネクタのアクションです。「SharePointにHTTP要求を送信します」アクションを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/06fd89fd-5c17-7479-07d6-00be72c28e86.png)

各ページ単位の情報を得るには、ページのフルパスと、SelectPropertiesというオプションを使って取得したい情報を指定してやります。

https://learn.microsoft.com/ja-jp/sharepoint/dev/general-development/sharepoint-search-rest-api-overview#selectproperties

とりあえずやってみます。こんな感じに指定します。かなり長いので注意してコピーしてください。特にURL直前のコロンやクォーテーション「:」や、Pathの前、URL直後のクオート「'」を忘れると動きませんので慎重に。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/11113ce1-804b-e94c-3ffc-978e6d3323b0.png)


    _api/search/query?querytext='path:<ページのURL>'&selectproperties='ViewsLifetime,ViewsLifetimeUniqueUsers,LikeCount'

selectpropertiesのなかで、ViewsLifetime,ViewsLifetimeUniqueUsers,LikeCountの３項目を指定しています。ちなみにLikeCount項目は指定しないと出てきません。

クラウドフローを実行してみて、再度取得できたJSONをテキストエディタで分析します。LikeCountの項目を検索すると、JSONのめちゃくちゃ深い階層にあることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/62b6f250-544b-9d65-86d2-b756918c58a4.png)


とりあえず、selectpropertiesで指定した項目は固まって同じ階層に収納されていますので、お得意の「選択」を使ってその階層だけを取り出します。

「選択」はアレイ型しか受け取ってくれないので、SPOへのHTTP要求の結果であるBodyを[]で囲っています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/ce34f35a-f9f8-1dd0-00b7-039b2ecf5baa.png)

```
item()?['d']?['query']?['PrimaryQueryResult']?['RelevantResults']?['Table']?['Rows']?['results'][0]['Cells']?['results']
```


このような強烈に深いJSONの階層を読み解くのは大変です。こういう時に便利な階層分析を提供している [JSON Pretty Linter Ver3](https://lab.syncer.jp/Tool/JSON-Viewer/) というサイトが便利です。

「選択」した結果、selectpropertiesの0番目にViewsLifetime、1番目にViewsLifetimeUniqueUsers、2番目にLikeCountを取得することができました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/6b42f07d-78aa-de37-35f8-d91f143a6049.png)

ここまでできたら、再度「選択」を使って各項目だけのJSONに再加工ができそうです。selectpropertiesの配列の0番目、1番目、2番目のValueの項目にある値を取得したいので、式はこのようになります。

    item()?['selectproperties'][0]?['Value']
    item()?['selectproperties'][1]?['Value']
    item()?['selectproperties'][2]?['Value']

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/9db3f5a7-086f-4728-affd-cb2cab767524.png)


結果はこのとおり。シンプルに選択した数値のみが取得できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/4074e1dd-321a-317a-c16d-8e75f4197cff.png)

## Apply to eachで回す
ページのURLを使ってHTTP要求で欲しい情報を取得できることがわかりましたので、こんどは複数のページについて順番に同じ処理を繰り返します。

先ほど作った処理の上にApply to each を配置して、選択の出力（ここには、ID、拡張子付きのページファイル名、ページファイルのURLが入っています）を選択します。

![スクリーンショット 2024-07-21 16.53.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/e3b6b1e2-b507-b9a0-251b-e32af88130af.png)

下の３つをループの中に入れたいのですが、そのままだと依存関係があってドラッグ&ドロップできなかったので、一旦スコープの中にまとめてやります。
![スクリーンショット 2024-07-21 16.56.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/c847db0e-6dd0-ac8a-e190-a351f13cc902.png)

スコープごとApply to eachの中に放り込みました。
![スクリーンショット 2024-07-21 16.58.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/eb57bad0-4751-313b-bacf-eeeea09b63c6.png)

HTTP要求のページURLの部分をページごとに差し替える式を加えます。URLの前後にあるコロンやクオーテーションを消してしまわないように注意してください。
![スクリーンショット 2024-07-21 17.00.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/daa957c0-7aed-3da2-149b-42c20db210d4.png)


### エラーになるページとならないページがあった
HTTP要求は成功して、先ほどの通り選択で必要な項目の取得に成功したページもあれば・・・
![スクリーンショット 2024-07-21 17.16.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/e7779de2-9f83-cc6f-522d-02bfc4677b5b.png)

選択に失敗しているページがありました。
![スクリーンショット 2024-07-21 17.18.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/3354e015-7cb8-704a-f53d-96fe6888a93e.png)

失敗している項目の中身を確認してみると、　Table>Rows>Results の中身が空っぽでした。
![スクリーンショット 2024-07-21 17.19.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/61e82759-0142-6633-1471-aa1b0e3821b8.png)

Resutsが空っぽっだったページのURLを見てみると、ニュース投稿したページではなくて、もともとシステムが作成している「Home.aspx」でした。　URLを開いてみると、たしかにこのページは記事ではないし、ページ閲覧数やいいねも表示されていませんでした。そういうものなのかな？
![スクリーンショット 2024-07-21 17.22.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/0593431c-6226-2f97-f491-38f314e22a87.png)

エラーが出っぱなしなのも気持ちが悪いので、以下が空っぽの場合は取得処理を飛ばすように条件付けしてみます。

    item()?['d']?['query']?['PrimaryQueryResult']?['RelevantResults']?['Table']?['Rows']?['results']

まずは、結果の中身がわかりやすいように、「作成」を用意して、以下の式を埋めこみます。

    body('SharePoint_に_HTTP_要求を送信します')?['d']?['query']?['PrimaryQueryResult']?['RelevantResults']?['Table']?['Rows']?['results']
![スクリーンショット 2024-07-21 17.31.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/4c4f2f71-9003-9842-a3d8-0f5766561510.png)

失敗しているループについて、作成の中身を見てみると、空っぽの配列である [] になっていることがわかります。これを条件にします。

前準備として、Apply to eachの前に「変数を初期化する」を使ってからのアレイを設定しておきます。

![スクリーンショット 2024-07-21 17.39.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/514784cb-87cd-1426-ae45-b93d50a45c10.png)

これで、アレイが空っぽではない場合にのみ、選択の処理が動作するようになりました。

![スクリーンショット 2024-07-21 17.40.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/c49a9e2f-68b6-6166-f0e4-6444bfec6391.png)

## 複数の項目で取得できていた情報もJSONに合体
HTTP要求ができて喜んでいましたが、もともと「複数の項目の取得」でとれていた項目も盛り込みましょう。選択3に「選択」から存在していた４項目を追加しました。

注意するべきは、選択３の入力からではなく、Apply to eachの外側ループの値をとること。そのために、上の３つとは異なって、item()ではなくitems('Apply_to_each')を使っています。

    items('Apply_to_each')?['{Name}']
    items('Apply_to_each')?['ID']
    items('Apply_to_each')?['{FilenameWithExtension}']
    items('Apply_to_each')?['{Link}']

![スクリーンショット 2024-07-21 18.07.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/04ccde27-0241-7f19-d5f7-4438063d1d04.png)

よしよし。取れてる取れてる。
![スクリーンショット 2024-07-21 18.32.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/e8e8e6a6-39a0-4695-7865-b83dff608c58.png)


## ループで取得した情報をひとつのJSONに
CSVを作るには、先にJSON化していくことが肝要です。今のところ、Apply to eachのループは一回一回が独立していて、ループの後にその結果を吐き出せていません。

ループの後に結果を残すには、アレイ変数を利用して、ループ一回ずつの結果を追加していく必要があります。

まずは、Apply to eachよりも前で、変数を初期化します。
![スクリーンショット 2024-07-21 18.36.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/200a354e-b748-7456-152b-b02572ece645.png)

選択３の結果をアレイに追加してやります。
![スクリーンショット 2024-07-21 18.38.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/526e5df3-873b-054d-75b3-fb2fa3bbfdd1.png)

本当なら、これで配列に追加ができるはずなのですが、なぜかエラーが出てうまくいかず。
仕方がないので奥の手「作成」を使った方法で変数に追加をしてみました。

選択3の後ろに「作成2」を作り、union関数を使って既存の変数の内容と、新しく追加する内容を合体させます。

合体させた結果を変数に追加します。

    union(body('選択_3'),variables('結果の追加アレイ'))

![スクリーンショット 2024-07-21 18.56.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/f4954b43-0ef6-b855-7500-0b890d4547a3.png)

目的の結果をアレイ変数の中にいれられたら、Apply to eachの後ろ側で、変数に登録されたJSONの配列から、CSVテーブルへ変換させます。ファイル名には日付を表示する関数を加えます。

    convertFromUtc(utcNow(),'Tokyo Standard Time','yyyyMMdd')

![スクリーンショット 2024-07-21 19.04.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/70b1f404-7e3f-8750-477b-1d0617ecf985.png)

ファイル名に日付を入れるには、関数をつかうと便利ですが、以前にこの件について記事を書いたことがありますので、参考にしてください。

https://mocabrown.com/blog/archives/6273

SPOのドキュメントライブラリに書き出されました。
![スクリーンショット 2024-07-21 19.10.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/7985e7de-d8eb-2736-7aaf-2317214603c4.png)

CSVを開いてみると・・・・　おっと。文字化けしてました。
![スクリーンショット 2024-07-21 19.11.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/3eb75f5d-0e15-f1cf-be66-0bc87b8abcfe.png)

こちらも以前ブログに取り上げましたので参考にしてください。

https://mocabrown.com/blog/archives/6754

「ファイルコンテンツ」の前に式で以下を追加します。

    decodeUriComponent('%EF%BB%BF')

![スクリーンショット 2024-07-21 19.14.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/30b0a224-053d-31c9-da02-2d981c0bf41c.png)

ふう。なんとかCSVファイルを出力することができました。お疲れ様でした。

![スクリーンショット 2024-07-21 19.17.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285141/b44159e4-cff2-5632-14d6-c9af4c0fc9e2.png)

ちなみに、結果のスクショでカウントが取得できていない「testFolder」というのは、SPOのページに作成したフォルダです。
フォルダの作り方もこちらで紹介していますので、試してみてください。

https://qiita.com/DaddyDaddy/items/56b935bd2130c37823f6

## まとめ
ちょっと苦労しましたが、ページの閲覧総数、ユニークユーザー数、いいね数を取得することができました。クラウドフローを指定した期間で自動実行させれば、決まったフォルダに日付付きファイル名でCSVファイルとして保存できます。

決まったフォルダに決まったフォーマットでCSVファイルが置かれるので、あとはExcelのPower QueryやPower BI　で分析ができます。
このあたりもそのうち記事にしようと思います。よかったら活用してください。