AngleSharpで上州屋の釣果情報をぶっこ抜く

釣り情報は釣具店に集まるのが定番である。

釣具店は数多くあれど、釣り業界のIT化の遅れはスポーツ・レジャー界において屈指であり、店舗に赴くことなく情報を得られるのは上州屋・ポイント・キャスティングの3大釣具チェーンのみであると言ってよい。

3大釣具チェーンの中で最もコンテンツが自分にマッチしてるのは上州屋である。

ポイント

  • 情報が釣果持ち込みに偏っており、子供が釣ったとんでもない小型魚など、不要コンテンツが多い
  • どう釣ったかの情報が不足している
  • 関東の情報が少ない

キャスティング

  • 更新頻度が低い
  • 釣れていない

しかし、上州屋の釣果情報にも以下の問題がある。

  • 会社でも見たいので、いかにも釣り情報見てる、というような状態を作りたくない

  • 情報の絞り方が都道府県(しかも釣り場ではなく店舗の)→魚種という順序なので、例えば東陽町店の情報で東伊豆の良質コンテンツがあってもたどり着きにくい

よって、スクレイピングのモチベーションができた。自分の見たい情報を店舗によらず的確に収集したいし、その閲覧はあたかもデータの集約された表を眺めているようにしたい。

htmlを文字列解析して作れるかなと思ったけど、

  • 文字列の解析をするのって嫌だ(人間が読むときにいちいち解析してるわけじゃないのに)

  • 何かXMLって嫌だな

  • C#使いなのでLINQサポートOK

ということで、AngleSharpというものを使ってみることにした。

使い方

qiita.com
使い方も簡単でいいね。

超お試し版の作成

上州屋の釣果情報の「1釣果情報」あたりのhtmlは以下のようになっている。

<!--コンテンツ-->
<div class="info__body">
<div class="info__bodyin">

		<div class="info__head">
			<div class="info__date">
				<p class="info__date_txt"><span>‘19 </span>08月05日</p>
			</div>
			<div class="info__area">
				<span class="info__tag">愛知県</span>豊川店
				<span class="info__link"><a href="../shop/top.php?s=136">店舗情報</a></span>
				<span class="info__link"><a href="../shop/choka.php?s=136">店舗の釣り情報</a></span>
			</div>
			<p class="info__note">登録日:2019年08月06日<br>画像をクリックすると、拡大画像が表示されます</p>
		</div>
		
<div class="info__photo_wrap">
<div class="slider info__photo">
	<div class="slider-for" id="slider-for3307238">

		<div class="sp-slide"><a href="../choka_img/3307238_1.jpg" class="boxer" data-gallery="gallery" title=""><img src="../choka_img/3307238_1.jpg" alt=""></a></div>

		<div class="sp-slide"><a href="../choka_img/3307238_2.jpg" class="boxer" data-gallery="gallery" title=""><img src="../choka_img/3307238_2.jpg" alt=""></a></div>

		<div class="sp-slide"><a href="../choka_img/3307238_3.jpg" class="boxer" data-gallery="gallery" title=""><img src="../choka_img/3307238_3.jpg" alt=""></a></div>

	</div>     
	<div class="slider-nav-wrap">
		<div class="slider-nav" id="slider-nav3307238">

				<div class="sp-thumbnail"><span><img src="../choka_img/3307238_1.jpg" alt=""></span></div>

				<div class="sp-thumbnail"><span><img src="../choka_img/3307238_2.jpg" alt=""></span></div>

				<div class="sp-thumbnail"><span><img src="../choka_img/3307238_3.jpg" alt=""></span></div>

		</div>
		<div class="slick-nav-arrows">
				<div class="slick-next"></div>
				<div class="slick-prev"></div>
		</div>
</div>
</div>
</div>	
		

			
		<div class="info__box">
		<div class="info__boxin">
			<div class="info__boxin02">
				<div class="clearfix">
					<p class="info__sttl">豊川店(愛知県):2019年08月05日の釣果</p>
					<p class="info__weather" style="display:">晴れ</p>
				</div>
					<table class="info__table">
				
						<tbody>

						</tbody>
					</table>
					<div class="info__detail">
						<table style="display:">
							<tr>
								<th>釣り場</th>
								<td>豊川河口周辺</td>
							</tr>
						</table>
						<table style="float:right; display:">
							<tr>
								<th>釣り人</th>
								<td>スタッフ 林</td>
							</tr>
						</table>
					</div>
				</div>					
				<div class="info__comment">
					<p>豊川河口周辺のハゼの様子を見てきました。<br />
現場到着時は下げ潮。<br />
ルアータックルで少し遠投したところにハゼが固まっていました。<br />
のべ竿の方たちは苦戦気味でした。<br />
日によって群れが溜まる場所が変わります。<br />
リール竿も用意していきましょう。<br />
詳細は御来店の上スタッフにお問い合わせ下さい。</p>
					<p class="choka_item">

					</p>
				</div>			
		</div>
		</div>
		
	
</div>
</div>
<!--コンテンツ-->

このように、info_detailクラスの中に釣り人と釣り場、info_commentクラスの中に詳細が記述されているのがわかる。本当はinfo_detailの中にコメントも入れてほしい所だが、そうなってないのが初学者たる私にはちょっと厄介であった。パイロット版を作ったら早速ダサい実装になってしまった。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using AngleSharp.Html.Dom;
using AngleSharp.Html.Parser;

namespace Chokascraper
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private async void button1_ClickAsync(object sender, EventArgs e)
        {
            var urlstring = textBox1.Text;
            dataGridView1.ColumnCount = 3;
            // 指定したサイトのHTMLをストリームで取得する
            var doc = default(IHtmlDocument);
            using (var client = new HttpClient())
            using (var stream = await client.GetStreamAsync(new Uri(urlstring)))
            {
                // AngleSharp.Html.Parser.HtmlParserオブジェクトにHTMLをパースさせる
                var parser = new HtmlParser();
                doc = await parser.ParseDocumentAsync(stream);
            }

            // HTMLからtitleタグの値(サイトのタイトルとして表示される部分)を取得する
            var title = doc.Title;

            var info_details = doc.GetElementsByClassName("info__detail");
            foreach (var element in info_details)
            {
                string point = string.Empty;
                string angler = string.Empty;
                foreach (var children in element.GetElementsByTagName("tr"))
                {
                    string label = string.Empty;
                    foreach (var th in children.GetElementsByTagName("th"))
                    {
                        label += th.TextContent.ToString();
                    }
                    foreach (var td in children.GetElementsByTagName("td"))
                    {
                        if (label == "釣り場")
                            point += td.TextContent.ToString();
                        else if (label == "釣り人")
                            angler += td.TextContent.ToString();
                    }
                }
                dataGridView1.Rows.Add(point, angler, "");
            }

            var info_comments = doc.GetElementsByClassName("info__comment");
            int i = 0;
            foreach (var element in info_comments)
            {
                string comments = string.Empty;
                foreach (var p in element.GetElementsByTagName("p"))
                {
                    comments += p.TextContent.ToString();
                }
                dataGridView1.Rows[i].Cells[2].Value = comments;
                i++;
            }
        }
    }
}

結果

f:id:satoshi_cs12:20190807171419p:plain
見た目はカスだが、とりあえず先頭10件の釣り人・釣り場・コメントの情報を得ることは出来ているみたい。

次バージョン以降

すぐやりたいこと

  • 11件目以降のデータも取得する
  • dataGridのサイズを動的に変える
  • 魚の名前を抽出する
  • 縮小した釣果画像を出す

システム的な伸びしろ

  • fimoからも引っ張る
  • 自動化してデイリー情報を自分のスマホにプッシュする
  • 年単位でのリピート釣行をしやすい上州屋店員の特性を利用して、統計的に次シーズンのターゲットの出来を予想し、釣行予算を的確に立てて無駄釣具・不足釣具の発生を防止する