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月分申込

ASP.NET Core でFacebook連携

基本的にはここ

Enabling authentication using Facebook, Google and other external providers — ASP.NET documentation

開発時点では環境をDevelopmentモードで起動する

本番ではUserSecretのファイルではなく環境変数を使用することになるのでDevelopmentモードで起動しなくてはいけない。やり方はふた通り

  • dotnet runのオプションとして渡す場合
dotnet run ASPNETCORE_ENVIRONMENT=Development
  • マシンの環境変数にあらかじめ設定しておく
export ASPNETCORE_ENVIRONMENT=Development
dotnet run

ruby2.0とrails4.0をrvmでインストールした

最近は、とてもとても忙しくてなかなかお家で開発作業を進めることができなかったけど、
手がけていたアプリがとりあえずリリースできて少しだけ落ち着いたので早速rubyを導入する。
ということで、ruby2.0、ails4.0(beta1)をインストールしてみた。

opensslのバージョンが古いとダメっぽいのでそこのupdateから始める。
(環境はmaxos10.7.5)
全部で結構時間かかったけどとりあえずできた。

brewのupdate

$ brew update 
$ brew install openssl


rvmのupdateからのruby2.0インストール

$ rvm get stable
$ rvm reload
$ rvm install 2.0.0
$ rvm use 2.0.0 --default

rails4.0 beta1のインストール

$ gem install rails -v 4.0.0.beta1

rails の seeds.rbの使い方について

連休だったので、久しぶりにC#から離れてRailsのお勉強。
もうすぐRuby2.0&Rails4.0がリリースされるみたいなので、もう一度参入するチャンス!
で、今回はRailsのseeds.rbの簡単な使い方をまとめる。

概要

  • マスタデータの投入。つまり初期データの投入に使える。

コマンド

rake db:seed

使い方

seeds.rbの編集

コマンドを呼び出すたびにseeds.rbの中身が追加されてしまうので、一回全部消去してidのautoincrementもクリアして入れなおす。
例えばBlogというモデルがあった場合は、一回delete_allを読んで


Blog.delete_all # 一回全部消して
Blog.connection.execute("delete from sqlite_sequence where name='blogs'") # autoincrementをクリアして
Blog.create(:name => "hoge blog") # データ投入

sqlite3で確認

コマンド呼び出したら実際にデータが投入されているか確認する

sqlite3 db/development.sqlite3

> select * from blogs;

注意点

日本語を扱う場合はファイルの先頭に以下を忘れないようにする。

# encoding: utf-8

依存関係がある場合は親のインスタンスを渡す

blog1 = Blog.create(:name => "hoge blog")
Entry.create(:blog => blog1, context => 'hogehogehoge')

Windowsアプリケーションで矢印キーとEnterでのフォーカス移動

というトンデモな仕様を実装しろとのこと。
明らかにWindows標準APIを無視した設計だったので、
仕様の再検討を!!ってまずお願いしたけど、一応調べてみた。

TextBoxなどでは、文字列にカーソル当たっている状態では左右キーは文字間を移動するのに使われる。
その為、キャレット位置が先頭のときに←を押すと前のコントロールへフォーカス
キャレット位置が最終のときに→を押すと次のコントロールへフォーカスという動きを考えた。
TextBoxには、SelectionStartというプロパティがあって簡単にキャレット位置を判定できたが、
こまったのはそれ以外のコントロールについて。
例えば、ComboBoxなんかも任意文字列が入力できるのでキャレット位置を取得したい。
TextBoxにあったSelectionStartプロパティが使えたらなーっと思ってたらこっちにもあった。

DRYじゃねぇ・・・。

とおもった。
TextBoxBaseクラスを継承してたり、Interfaceとかで提供されてて欲しかった。

一応コード
TextBoxのみ文字間移動を対応。その他については考慮していない
あと、実装したのは共通で利用するForm。(KeyPreviewプロパティをTrueにしておく)
共通で利用するFormを継承したFormで自動的に適用される。

        private void CommonForm_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Left)
            {
                if (this.ActiveControl is TextBox)
                {
                    TextBox textBox = (TextBox)this.ActiveControl;
                    if (textBox.SelectionStart != 0)
                    {
                        return;
                    }   
                }

                this.SelectNextControl(this.ActiveControl, false, true, true, true);
            }

            if (e.KeyCode == Keys.Right)
            {
                if (this.ActiveControl is TextBox)
                {
                    TextBox textBox = (TextBox)this.ActiveControl;
                    if (textBox.SelectionStart != textBox.Text.Length)
                    {
                        return;
                    }
                }

                this.SelectNextControl(this.ActiveControl, true, true, true, true);
            }

            if (e.KeyCode == Keys.Enter)
            {
                bool forward = e.Modifiers != Keys.Shift;
                this.SelectNextControl(this.ActiveControl, forward, true, true, true);
            }
        }

SVNリポジトリの使用方法について

Subversionの各リポジトリ(trunk、tags、branches)の運用方法について記載します。

次期バージョンの開発を行なう場合

次期バージョンの開発(新規機能追加や既存機能改修など)を行なう場合は trunk にて作業を行ないます。
その為、trunkは常に最も新しい状態(開発版)となっています。(不具合がある可能性も高い)

開発を終えたバージョンをリリースしたい

trunkでの開発を終えたバージョンをリリースする場合、 branchesにバージョン番号のディレクトリを作成 し、枝分かれさせます。
リリース作業はbranchesに作成したリポジトリで作業を行ないます。

(例)
sample_appシステムの場合、「sample_app/branches/2.1」といった具合

リリース後、不具合が発生したので対応したい

リリース後の不具合対応はbranchesで管理している該当のバージョンのリポジトリに対して修正を行ないます。
その為、 branchesでの修正内容は状況に応じてtrunkにマージ する必要があります。
(次期バージョンでは前のバージョンの不具合修正を反映させなくても良いなどのケースではマージする必要はないが、原則は常にbranchesの修正内容はマージすること。)

安定版として保存しておきたい

リリース後branchesでの不具合が収束し、安定版として保存しておきたい場合はbranchesからtagsへコピーします。
大幅な改修などメジャーバージョン番号が変わるような改修が入った場合、不測の事態に備えていつでも元に戻れるように前のバージョンを保存しておく必要があります。

(例)
大幅な業務変更のため、メジャーバージョン番号を1.Xから2.Xに変更する。
その際、1.Xの最終安定版のバージョンである1.9をtagsへスナップショットとして保存しておく。
万が一、2.X系リリース後に大きな不具合が発生し、1.X系に差し戻す場合はtagsで保存しておいた1.X系の最終安定版の1.9に置き換える。
sample_appシステムの場合、「sample_app/tags/1.0」といった具合。