sou's blog

落ち着いた華やかさがあり、上品に明るく陽気なさまを表す。

ASP.NET Coreでスクレイピングしたものを表示(HtmlAgilityPackを使用)

概要

SgmlReaderが.NET Core対応していないので.NET Core対応しているHtmlAgilityPackを使うことにしました。 ASP.NET Coreでスクレイピングしたページを表示する簡単なWebアプリケーションを作ってみたいと思います。 おっさんにyoしてWebアプリケーションの雛形ができたところからスタートとしたいと思います。

github.com

project.jsonへの登録

dependenceisのところにこんな感じで追加。 するとVSCodeが空気を呼んでrestoreするか聞いてくれるのでやっちゃう。

"HtmlAgilityPack.NetCore": "1.5.0.1"

適当なModelを用意する

    public class Availability
    {
        public string Title {get; set;}
        public IEnumerable<DateTime> Dates {get;set;}

        public string ToString(DateTime date)
        {
            return date.ToString("yyyy年MM月dd日(dddd)");
        }
    }
    public class IndexViewModel
    {
        public List<Availability> Availabilities {get; set;}
    }

Controllerに処理を書く

SelectNodesしたときは//を入れるルールらしい。 スクレイピング自体はやっつけなのでサイトの構成変わったら動かんと思います。

        public async Task<IActionResult> Index()
        {
            // service_category
            var rootUrl = new Uri("https://as.its-kenpo.or.jp/service_category/index");
            var serviceCategoryDom = await GetHtmlAsync(rootUrl);
            var serviceGroupUrl = serviceCategoryDom.DocumentNode.SelectNodes("//a")
                .Where(x => x.InnerText == "直営・通年・夏季保養施設(空き照会)")
                .Select(x => x.Attributes["href"].Value)
                .Select(x => $"{rootUrl.Scheme}://{rootUrl.Host}" + x)
                .Select(x => new Uri(x))
                .First();

            // service_group
            var serviceGroupDom = await GetHtmlAsync(serviceGroupUrl);
            var serviceApplyUrls = serviceGroupDom.DocumentNode.SelectNodes("//li")
                .SelectMany(x => x.Descendants("a"))
                .Select(x => x.Attributes["href"].Value)
                .Select(x => $"{rootUrl.Scheme}://{rootUrl.Host}" + x)
                .Select(x => new Uri(x));
            
            // service_apply
            var availabilities = new List<Availability>();
            foreach (var serviceApplyUrl in serviceApplyUrls)
            {
                var serviceApplyDom = await GetHtmlAsync(serviceApplyUrl);
                var applyUrls = serviceApplyDom.DocumentNode.SelectNodes("//li")
                    .SelectMany(x => x.Descendants("a"))
                    .Select(x => x.Attributes["href"].Value)
                    .Select(x => $"{rootUrl.Scheme}://{rootUrl.Host}" + x)
                    .Select(x => new Uri(x));
                    
                // apply
                foreach (var applyUrl in applyUrls)
                {
                    var applyDom = await GetHtmlAsync(applyUrl);
                    var title = applyDom.DocumentNode.SelectNodes("//table")
                        .Where(x => x.Attributes["class"] != null)
                        .Where(x => x.Attributes["class"].Value == "tform_new")
                        .Select(x => x.Descendants("tr").First())
                        .Select(x => x.Descendants("td").Last().InnerText)
                        .First();
                    var dates = applyDom.DocumentNode.SelectNodes("//select")
                        .Where(x => x.Attributes["id"] != null)
                        .Where(x => x.Attributes["id"].Value == "apply_join_time")
                        .SelectMany(x => x.Descendants("option"))
                        .Select(x => x.Attributes["value"].Value)
                        .Where(x => !string.IsNullOrEmpty(x))
                        .Select(x => Convert.ToDateTime(x));

                    var availability = new Availability();
                    availability.Title = title;
                    availability.Dates = dates;
                    availabilities.Add(availability);
                }

            }

            var model = new IndexViewModel();
            model.Availabilities = availabilities;
            return View(model);
        }

Viewにモデルをバインド

@model Kenpo.Models.HomeViewModels.IndexViewModel

<ul>
@foreach(var availability in Model.Availabilities)
{
    <li>
        <span>@availability.Title</span>
        <ul>
            @foreach(var date in availability.Dates)
            {
                <li>@availability.ToString(date)</li>
            }
        </ul>
    </li>
}
</ul>

結果

勘の良い人ならわかるかもしれませんがこんな感じでIT健保の(使いにくい)サイトから空き日を一覧にしてくれるWebアプリケーションが完成します。

直営・通年・夏季保養施設 トスラブ箱根ビオーレ 2016年度9月分申込
2016年09月29日(木曜日)
直営・通年・夏季保養施設 トスラブ箱根ビオーレ 2016年度10月分申込
2016年10月24日(月曜日)
2016年10月26日(水曜日)
2016年10月31日(月曜日)
直営・通年・夏季保養施設 トスラブ箱根和奏林 2016年度9月分申込
2016年09月20日(火曜日)
直営・通年・夏季保養施設 トスラブ箱根和奏林 2016年度10月分申込
2016年10月06日(木曜日)
2016年10月20日(木曜日)
2016年10月24日(月曜日)
直営・通年・夏季保養施設 トスラブ湯沢 2016年度9月分申込
2016年09月19日(月曜日)
2016年09月20日(火曜日)
2016年09月22日(木曜日)
2016年09月26日(月曜日)
2016年09月28日(水曜日)
2016年09月29日(木曜日)
直営・通年・夏季保養施設 トスラブ湯沢 2016年度10月分申込
2016年10月02日(日曜日)
2016年10月03日(月曜日)
2016年10月04日(火曜日)
2016年10月05日(水曜日)
2016年10月06日(木曜日)
2016年10月12日(水曜日)
2016年10月13日(木曜日)
2016年10月17日(月曜日)
2016年10月18日(火曜日)
2016年10月19日(水曜日)
2016年10月20日(木曜日)
2016年10月24日(月曜日)
2016年10月25日(火曜日)
2016年10月26日(水曜日)
2016年10月27日(木曜日)
2016年10月31日(月曜日)
直営・通年・夏季保養施設 トスラブ館山ルアーナ 2016年度9月分申込
2016年09月29日(木曜日)
直営・通年・夏季保養施設 トスラブ館山ルアーナ 2016年度10月分申込
直営・通年・夏季保養施設 中沢ヴィレッジ 2016年度9月分申込
2016年09月29日(木曜日)
直営・通年・夏季保養施設 中沢ヴィレッジ 2016年度10月分申込
2016年10月03日(月曜日)
2016年10月04日(火曜日)
2016年10月05日(水曜日)
2016年10月06日(木曜日)
2016年10月11日(火曜日)
2016年10月12日(水曜日)
2016年10月13日(木曜日)
2016年10月17日(月曜日)
2016年10月18日(火曜日)
2016年10月19日(水曜日)
2016年10月20日(木曜日)
2016年10月24日(月曜日)
2016年10月25日(火曜日)
2016年10月26日(水曜日)
2016年10月27日(木曜日)
2016年10月31日(月曜日)
直営・通年・夏季保養施設 ホテルハーヴェスト那須 2016年度9月分申込
2016年09月27日(火曜日)
直営・通年・夏季保養施設 ホテルハーヴェスト那須 2016年度10月分申込
2016年10月31日(月曜日)
直営・通年・夏季保養施設 ホテルハーヴェスト斑尾 2016年度9月分申込
2016年09月23日(金曜日)
2016年09月26日(月曜日)
2016年09月27日(火曜日)
2016年09月28日(水曜日)
2016年09月29日(木曜日)
2016年09月30日(金曜日)
直営・通年・夏季保養施設 ホテルハーヴェスト斑尾 2016年度10月分申込
2016年10月02日(日曜日)
2016年10月03日(月曜日)
2016年10月04日(火曜日)
2016年10月05日(水曜日)
2016年10月06日(木曜日)
2016年10月07日(金曜日)
2016年10月11日(火曜日)
2016年10月12日(水曜日)
2016年10月13日(木曜日)
2016年10月16日(日曜日)
2016年10月17日(月曜日)
2016年10月18日(火曜日)
2016年10月19日(水曜日)
2016年10月20日(木曜日)
2016年10月21日(金曜日)
2016年10月23日(日曜日)
2016年10月24日(月曜日)
2016年10月25日(火曜日)
2016年10月26日(水曜日)
2016年10月27日(木曜日)
2016年10月30日(日曜日)
2016年10月31日(月曜日)
直営・通年・夏季保養施設 ブルーベリーヒル勝浦 2016年度9月分申込
2016年09月19日(月曜日)
2016年09月20日(火曜日)
2016年09月21日(水曜日)
2016年09月28日(水曜日)
2016年09月29日(木曜日)
直営・通年・夏季保養施設 ブルーベリーヒル勝浦 2016年度10月分申込
2016年10月02日(日曜日)
2016年10月03日(月曜日)
2016年10月04日(火曜日)
2016年10月05日(水曜日)
2016年10月06日(木曜日)
2016年10月11日(火曜日)
2016年10月12日(水曜日)
2016年10月13日(木曜日)
2016年10月16日(日曜日)
2016年10月18日(火曜日)
2016年10月19日(水曜日)
2016年10月20日(木曜日)
2016年10月23日(日曜日)
2016年10月24日(月曜日)
2016年10月25日(火曜日)
2016年10月26日(水曜日)
2016年10月27日(木曜日)
2016年10月30日(日曜日)
2016年10月31日(月曜日)
直営・通年・夏季保養施設 ホテルハーヴェスト伊東 2016年度9月分申込
2016年09月30日(金曜日)
直営・通年・夏季保養施設 ホテルハーヴェスト伊東 2016年度10月分申込
2016年10月03日(月曜日)
2016年10月04日(火曜日)
2016年10月05日(水曜日)
2016年10月06日(木曜日)
2016年10月11日(火曜日)
2016年10月12日(水曜日)
2016年10月13日(木曜日)
2016年10月17日(月曜日)
2016年10月18日(火曜日)
2016年10月19日(水曜日)
2016年10月20日(木曜日)
2016年10月24日(月曜日)
2016年10月25日(火曜日)
2016年10月26日(水曜日)
2016年10月27日(木曜日)
直営・通年・夏季保養施設 ホテルハーヴェスト スキージャム勝山 2016年度9月分申込
2016年09月25日(日曜日)
2016年09月26日(月曜日)
2016年09月27日(火曜日)
2016年09月28日(水曜日)
2016年09月29日(木曜日)
2016年09月30日(金曜日)
直営・通年・夏季保養施設 ホテルハーヴェスト スキージャム勝山 2016年度10月分申込
2016年10月01日(土曜日)
2016年10月02日(日曜日)
2016年10月03日(月曜日)
2016年10月04日(火曜日)
2016年10月05日(水曜日)
2016年10月06日(木曜日)
2016年10月07日(金曜日)
2016年10月10日(月曜日)
2016年10月11日(火曜日)
2016年10月12日(水曜日)
2016年10月13日(木曜日)
2016年10月14日(金曜日)
2016年10月15日(土曜日)
2016年10月16日(日曜日)
2016年10月17日(月曜日)
2016年10月18日(火曜日)
2016年10月19日(水曜日)
2016年10月20日(木曜日)
2016年10月21日(金曜日)
2016年10月23日(日曜日)
2016年10月24日(月曜日)
2016年10月25日(火曜日)
2016年10月26日(水曜日)
2016年10月27日(木曜日)
2016年10月28日(金曜日)
2016年10月30日(日曜日)
2016年10月31日(月曜日)
直営・通年・夏季保養施設 琵琶レイクオーツカ 2016年度9月分申込
2016年09月28日(水曜日)
直営・通年・夏季保養施設 琵琶レイクオーツカ 2016年度10月分申込
2016年10月02日(日曜日)
2016年10月03日(月曜日)
2016年10月04日(火曜日)
2016年10月05日(水曜日)
2016年10月06日(木曜日)
2016年10月07日(金曜日)
2016年10月11日(火曜日)
2016年10月12日(水曜日)
2016年10月13日(木曜日)
2016年10月14日(金曜日)
2016年10月16日(日曜日)
2016年10月17日(月曜日)
2016年10月18日(火曜日)
2016年10月19日(水曜日)
2016年10月20日(木曜日)
2016年10月21日(金曜日)
2016年10月24日(月曜日)
2016年10月25日(火曜日)
2016年10月26日(水曜日)
2016年10月27日(木曜日)
2016年10月31日(月曜日)
直営・通年・夏季保養施設 ホテル日航プリンセス京都 2016年度9月分申込
2016年09月26日(月曜日)
2016年09月27日(火曜日)
直営・通年・夏季保養施設 ホテル日航プリンセス京都 2016年度10月分申込
2016年10月31日(月曜日)
直営・通年・夏季保養施設 ホテルハーヴェスト南紀田辺 2016年度9月分申込
2016年09月23日(金曜日)
2016年09月25日(日曜日)
2016年09月26日(月曜日)
2016年09月27日(火曜日)
2016年09月28日(水曜日)
2016年09月29日(木曜日)
2016年09月30日(金曜日)
直営・通年・夏季保養施設 ホテルハーヴェスト南紀田辺 2016年度10月分申込
2016年10月02日(日曜日)
2016年10月03日(月曜日)
2016年10月04日(火曜日)
2016年10月05日(水曜日)
2016年10月06日(木曜日)
2016年10月07日(金曜日)
2016年10月10日(月曜日)
2016年10月11日(火曜日)
2016年10月12日(水曜日)
2016年10月13日(木曜日)
2016年10月14日(金曜日)
2016年10月16日(日曜日)
2016年10月17日(月曜日)
2016年10月18日(火曜日)
2016年10月19日(水曜日)
2016年10月20日(木曜日)
2016年10月21日(金曜日)
2016年10月23日(日曜日)
2016年10月24日(月曜日)
2016年10月25日(火曜日)
2016年10月26日(水曜日)
2016年10月27日(木曜日)
2016年10月28日(金曜日)
2016年10月30日(日曜日)
2016年10月31日(月曜日)
直営・通年・夏季保養施設 角間温泉 岩屋館 2016年度9月分申込
2016年09月26日(月曜日)
2016年09月27日(火曜日)
2016年09月30日(金曜日)
直営・通年・夏季保養施設 角間温泉 岩屋館 2016年度10月分申込
2016年10月03日(月曜日)
2016年10月04日(火曜日)
2016年10月05日(水曜日)
2016年10月06日(木曜日)
2016年10月11日(火曜日)
2016年10月12日(水曜日)
2016年10月13日(木曜日)
2016年10月17日(月曜日)
2016年10月18日(火曜日)
2016年10月19日(水曜日)
2016年10月20日(木曜日)
2016年10月25日(火曜日)
2016年10月26日(水曜日)
2016年10月27日(木曜日)
直営・通年・夏季保養施設 ホテルハーヴェスト旧軽井沢 2016年度9月分申込
直営・通年・夏季保養施設 リゾートホテル蓼科 2016年度9月分申込
2016年09月28日(水曜日)
2016年09月29日(木曜日)
直営・通年・夏季保養施設 鎌倉パークホテル 2016年度9月分申込