プログラミング素人のはてなブログ

プログラミングも電気回路も専門外の技術屋の末端が勉強したことや作品をアウトプットするブログ。コードに間違いなど見つけられたら、気軽にコメントください。 C#、Python3、ラズパイなど。

C#でウェブスクレイピング

今回はウェブスクレイピングをやってみます。
↓の続きになります。
s51517765.hatenadiary.jp

ウェブスクレイピングとして、Webの情報を取得する基本的な構文は↓を参考にしました。
www.casleyconsulting.co.jp

Visual Studio上でクラスファイルを追加して、ウェブスクレイピングのコードを作成します。
f:id:s51517765:20170505154304j:plain

これは、URLを指定するとそのページのHTMLをstring型の文字列として取得します。
この内容は、あまり深く考えずにこういう一つのメソッドとして理解しておけばとりあえずいいと思います。

        public string Gethtml(string url)
        {
            // 指定されたURLに対してのRequestを作成します。
            var req = (HttpWebRequest)WebRequest.Create(url);     //using System.Net;

            // html取得文字列
            string html;

            // 指定したURLに対してReqestを投げてResponseを取得します。
            using (var res = (HttpWebResponse)req.GetResponse())
            using (var resSt = res.GetResponseStream())
            // 取得した文字列をUTF8でエンコードします。
            using (var sr = new StreamReader(resSt, Encoding.UTF8))     //using System.IO;
            {
                // HTMLを取得する。
                html = sr.ReadToEnd();
            }

            return html;
        }

このとき、System.NetとSystem.IOを使いますので、以下をファイルの先頭に追加しておきます。

using System.Net;
using System.IO;

つぎに、同じクラスファイル上にキーワードを抽出するプログラムを作成します。
引数としては先ほどのメソッドで取得したHTMLとキーワードを指定します。
考え方としては、Keywordを含む文を<P></P>で囲まれる部分で区切り取得します。
f:id:s51517765:20170506121935j:plain

①まずHTML全体の中からKeywordをさがす。html.IndexOf(Keyword)構文でhtmlのなかでKeywordの位置を探します。
Substring(start,Length)構文で、start =0(最初)から前記Keyword位置までを取得。
②前記Keywordよりまえの部分の中から後ろからさがして<p>を探す。
ここでは、Keywordから前へひとつづつ文字を追加して<p>をさがし、見つかったらこれがほしいKeywordより前の部分になります。

for (int n = 1; n <= buf_buf_mae.Length; n++)
            {
                buf_mae = buf_buf_mae.Substring((buf_buf_mae.Length) - n, n);

③~⑤後ろは同様に</p>を探します。
これを足し合わせるとKeywordを含む文が取得できます。

string getWord = buf_mae + buf_ushiro;

これをまとめると、Keywordの取得メソッド

        public string GetKeyword(string html, string Keyword)
        {
        header = "<p"
        footer = "</p"

            // html文字列内からKeywordをさがしその位置を返す
            int keywordStart = html.IndexOf(Keyword);

            string buf_buf_mae = html.Substring(0, keywordStart);
            string buf_mae = "";
            for (int n = 1; n <= buf_buf_mae.Length; n++)
            {
                buf_mae = buf_buf_mae.Substring((buf_buf_mae.Length) - n, n);
                if (buf_mae.IndexOf(header) > 0)
                {  //後ろからさがして見つかったら
                    break;
                }
                if (n > 5000)
                {
                    MessageBox.Show("Header get Error.");
                    break;
                }
            }

            string buf_ushiro = html.Substring(keywordStart); //indexOfから後ろを取得第2引数省略
            int nagasa_u = buf_ushiro.IndexOf(footer);
            buf_ushiro = html.Substring(keywordStart, nagasa_u + 4); //start位置,対象文字列長さ

            string getWord = buf_mae + buf_ushiro;
            return getWord;

        }

次にForm1にHTMLのボタンクリックイベントを作成します。

     private void buttonGethtml_Click(object sender, EventArgs e)
        {
            ViewBoxBackColorSet();

            if (checkBoxWriteOut.Checked == false && checkBoxPreview.Checked == false)
            {
                MessageBox.Show("Select output");
            }
            else
            {
                DateTime dt = DateTime.Now;
                string date = dt.ToString("yyyy_MM_dd_HH_mm_ss");
                try
                {
                    textBoxPreview.AppendText(dt.ToString("HH:mm:ss" + Environment.NewLine));

                    var scr = new Scraping();  //クラスScrapingを使うためにインスタンス作成
                    string url = textBoxURL.Text;
                    string html = scr.Gethtml(url);

                      Keyword = comboBoxKeyword.Text;
                    
                    string getWord;
                    getWord = scr.GetKeyword(html, Keyword);
                    if (checkBoxPreview.Checked == true)
                    {
                        textBoxPreview.AppendText(getWord + Environment.NewLine);
                    }
                    if (checkBoxWriteOut.Checked == true)
                    {
                        using (StreamWriter writer = new StreamWriter("H_" + date + ".txt", true))
                        {
                            writer.Write(getWord);
                        }
                    }

                }
                catch
                {
                    MessageBox.Show("Check URL");
                }
                ViewBoxBackColorReset();
            }
        }

このへんがオブジェクト指向(クラス設計)っぽいところ。
クラスを使うためにインスタンスの宣言をして、入力(ここではURL)と出力(ここではHTML)(および、HTMLとKeywordとWord)だけが見えている状態になります。

 var scr = new Scraping();  //クラスScrapingを使うためにインスタンス作成
 string url = textBoxURL.Text;
 string html = scr.Gethtml(url);

 string getWord;
 getWord = scr.GetKeyword(html, Keyword);

これで、必要部分は取得できましたが、HTMLのタグが不要です。
f:id:s51517765:20170506123724j:plain

  public string deleteTag(string getWord)
        {
            int buf_int = -1;
            int buf_int2;
            string tukau1, tukau2;
            string buf_1;
            string deleteTag_getword;

            for (int n = 1; n < 80; n++)
            {
                buf_int = getWord.IndexOf("<");
                if (buf_int < 0)
                {
                    break;
                }

                tukau1 = getWord.Substring(0, buf_int);
                buf_1 = getWord.Substring(buf_int + 1);
                buf_int2 = buf_1.IndexOf(">");
                tukau2 = buf_1.Substring(buf_int2 + 1);

                getWord = tukau1 + tukau2;
              
            }
            deleteTag_getword = getWord;
                 return deleteTag_getword;
        }

このように<>で囲まれた部分をHTML Tagとみなして削除するメソッドを入れると、すっきりします。
タグのスタート<を探し、これ以前の文字列を取得し、<の次の文字から>を探しこれをタグとみなして捨てる。>の次の文字から同じようにタグを探し、文字列を取得し最初に取得したタグの外とつなげて…という風にしていきます。
forの構文の繰り返し数は適当にいれていますが、タグが見つからなくなったら、IndexOf = -1となるので、こうなったらBreak;で終了。

bufはbufferの略であまり綺麗とは言えないがよく使われる一時的な変数のことです。

f:id:s51517765:20170506125852j:plain

※HTMLのタグは「半角<>」を使いますが、ブログ上で半角<>を使うタグとみなされて表示されないので、全角<>を使っています。