C# HTML構造解析　AngleSharpの使い方(1)

##はじめに
目的のURLからソースコードを取得できたあと、これを簡単に取り出す方法はないものかと調べたところAngleSharpが現代的な方法と分かり説明していきます。

今回はAngleSharpのメソッドの説明よりはAngleSharpを使うとこんな処理ができる！ということを説明する回になりますので詳しい使い方を知りたい方は『C# HTML構造解析 AngleSharpの使い方(2)』（執筆中）をご参照ください。

では実際に抜き出したHTMLを見てみましょう。

##実際のHTML文字列##

```sample.html

<!------------------------------------------------------ 途中抜粋--------------------------------------->
<div class="history_container_base">
    <div class="history_container">
        <div class="head">
            <span>プレイ履歴</span>
            <a href="/playdata_view/searchHistory?hbs=1" class="search">
                <span class="search_icon"></span>検索
            </a>
        </div>
        <div class="main">
            <div class="lock_count">
                <span class="lock_icon"></span> ロック 0/2            
            </div>
            
            <!--articles-->
            <div class="articles">

                <!-- プレイ履歴を表示するループ -->
                    <a  class="article normal" href="/playdata_view/~~~">

                        <div class="date">
                            2021-05-30 00:11:16                        
                        </div>
                        
                        <div class="mode">
                            プライベート戦三麻                        
                        </div>
                        
                        <div class="replay">
                                <span class="replay_icon"></span>リプレイ
                        </div>
                        
                        <div class="body">
                              1位<span class="">『dartslive』</span> (+49.9)
                              2位<span class="">『麻雀野郎』</span> (-9.6)
                              3位<span class="user">『めっつ』</span> (-40.3)
                        
                        </div>
                    </a>



                    <a  class="article normal" href="/playdata_~~>


                        <div class="date">
                            2021-05-29 23:55:24                        
                        </div>
                        <div class="mode">
                            プライベート戦三麻                        
                        </div>
                        <div class="replay">
                                <span class="replay_icon"></span>リプレイ
                        </div>
                        <div class="body">
                                 1位<span class="user">『めっつ』</span> (+45.3)
                                 2位<span class="">『ぱりぴ』</span> (+4.6)
                                 3位<span class="">『dartslive』</span> (-49.9)
                        </div>
                    </a>


<!---</div>がいくつも足りてないですが省略しています。----->
<!------------------------------------------------------ 以下略--------------------------------------->

```

こちらはMJサイトのユーザープレイ履歴画面です。余談ですが、MJではユーザーのプレイ履歴は確認できるのですが得点の集計は有料なのです。以前はブラウザの画面から１つずつコピペかページを見ながら手入力していました（笑）


続いてC#の処理内容です。ここからは具体的な処理の内容を説明します。

・HTML文字列(string)を分解するためHtmlDocumentコレクションに分解。

・HtmlDocumentコレクションからGetElementsByXXXメソッドを使って必要な要素（Node）を抜出す。
　※今回はさらにGetElementsByXXXメソッドを使ってさらに絞り込みを行っています。

・List<string>にNodeのTextContentをループで抜き出し。
　※今はラムダ式を使ってもっとスマートな書き方あるかもしれません。

・RegexやTrimを使って目的の箇所のみ取り出す。
　※今回はさらに名前と得点をセットとして一番左にuserが入り、
　　あとは名前順になるよう並び替えもおこなっています。

Regex（正規表現）は内容的にすべてググってコピペで事足りるので説明割愛。


最後にreturnで返される文字列が次のようになります。

*2021-05-30 00:11:16,めっつ,-40.3,dartslive,49.9,麻雀野郎,-9.6*
*2021-05-29 23:55:24,めっつ,45.3,ぱりぴ,4.6,dartslive,-49.9*



##C#コンソールアプリの実装例（メソッド部全体）##

```analizemethod.cs
using AngleSharp.Html.Dom;
using AngleSharp.Html.Parser;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text.RegularExpressions;



 private static string AnalizeHtml(int DataCount,string HTMLtext)
        {
　　　　　　　//インスタンス作成
            HtmlParser parser = new HtmlParser();
　　　　　　　//HTMLの文字列を分解します。
            IHtmlDocument doc = parser.ParseDocument(HTMLtext);
　　　　　　 //HTML内の<div class="articles">抜き出したいところ</div>
　　　　　　 //divは入れ子になっており同名のタグが大量にありますがインデントを目安にすると
            //抜き取りたいデータが上記タグ内にすべて収まっていることが分かります。
            var Nodes = doc.GetElementsByClassName("articles");

            //ここから分岐してdateNodes,bodyNodesにはそれぞれ違う絞り込みをかけます。
　　　　　　 //今回のHTMLにarticleクラスは1つしか存在しないためNodesの先頭Nodes[0]を参照します。
            //(取り出した要素が1つでもコレクションになるため必ず[index]が必要です)
            var dateNodes = Nodes[0].GetElementsByClassName("date");
            var bodyNodes = Nodes[0].GetElementsByClassName("body");

　　　　　　 //これはのちのuser（ログインしているユーザー名）を探しています。
            string username = Nodes[0].GetElementsByClassName("user")[0].TextContent.Trim('『').Trim('』');

            //dateNodesの中身抜出し（TextContentはstring型の戻り値なのでTrimが使える）
            var datelist = new List<string>();
            foreach (var d in dateNodes)
                datelist.Add(d.TextContent.Trim());

　　　　　　 //bodyNodesの中身抜出し＆正規表現で『』や()の間の文字列を取り出す。
            int Counter = 0;
            var bodylist = new List<string>();
            foreach (var b in bodyNodes)
            {
                var Points = new Regex(@"\((.+?)\)").Matches(b.TextContent);
                var Names = new Regex(@"『(.+?)』").Matches(b.TextContent);
                var mylist = new List<string>();

                //ソートディクショナリを宣言して名前順（Key順）にソート
                var dic = new SortedDictionary<string, string>();
                for (int p = 0; p < Names.Count; p++)
                {
                    var Name = Names[p].Value.Trim('『').Trim('』');
                    var Point = Points[p].Value.Trim('(').Trim(')');
                    Point = Point.Trim('+');
                    dic.Add(Name, Point);
                }

                //先頭にユーザーネームとデータを挿入。
                mylist.Add(username + "," + dic[username]);
                //ユーザー以外は名前順で並び替えて挿入。
                foreach (var t in dic)
                {
                    if (t.Key != username)
                        mylist.Add(t.Key + "," + t.Value);
                }

                //並び替え後のリスト作成
                bodylist.Add(datelist[Counter] + "," + string.Join(",", mylist));

                Counter++;

                //Counterが指定のデータ数に到達したらループを抜ける
                if (DataCount < Counter)
                    break;
            }

            return string.Join("," + "\n", bodylist);
        }
```
引数のint DataCountはプレイ履歴のうち、上のような成績（一行）を何行とりだすかを決める数でメソッドを呼び出すときに任意で決めます。例えばint DataCount = 50 としておけば上のような成績が50行取り出せます。


細かい説明は次回以降で見ていきたいと思います。
つづく

#開発環境#
言語：C# 
使用ソフト：VisualStudio2019v16.6.0
プロジェクト：コンソールアプリ(.NET Framework)
.NET Framework Version 4.8.04084
NuGetパッケージ
 @AngleSharp v0.15.0 


#参照URL#
[SEGA NET麻雀MJ　ログイン](https://pl.sega-mj.com/players/login "SEGA NET麻雀MJ　ログイン")
[SEGA NET麻雀MJ　プレイ履歴](https://pl.sega-mj.com/playdata_view/showHistory "SEGA NET麻雀MJ　プレイ履歴")