- PR -

WebClient で得た HTML を HtmlDocument で解析したいが WebBrowser がないと使えない?

1
投稿者投稿内容
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2008-05-31 12:37
私は以前から WebBrowser コントロールを使って、Webの自動巡回ツールのようなアプリケーションを開発しています。
たとえば、
http://www.atmarkit.co.jp/fdotnet/dotnettips/687nondispbrowser/nondispbrowser.html
のような感じで WebBrowser と HtmlDocument を使って HTML を解析できています。

今回、軽さを求めて WebClient クラスも使いたいと思って、
http://www.atmarkit.co.jp/fdotnet/dotnettips/302wcget/wcget.html
などを参考にして HTML を取得することはできたのですが、取得した HTML を解析する方法が分かりません。文字列を IndexOf や Regex で解析することはできるのですが、できれば WebBrowser で HtmlDocument が使えたように、WebClient でも HtmlDocument を使いたいのですが、どうすればできるでしょうか?

ちなみに、試行錯誤して、つぎのようなやりかたはできたのですが、WebBrowser を使っているので、根本的な解決にはなっていません。

コード:
using System;
using System.IO;
using System.Net;
using System.Windows.Forms;
using System.Text;

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

        private void button1_Click(object sender, EventArgs e)
        {
            WebClient wc = new WebClient();
            Stream st = wc.OpenRead("http://www.example.com/");
            Encoding enc = Encoding.GetEncoding("Shift_JIS");
            StreamReader sr = new StreamReader(st, enc);
            string html = sr.ReadToEnd();
            sr.Close();
            st.Close();

            WebBrowser wb = new WebBrowser();
            wb.Navigate("about:blank"); // 良く分からん。
            HtmlDocument hd = wb.Document;
            hd.OpenNew(false); // 良く分からん。
            hd.Write(html);

            // たとえばリンク(a href)を抽出する。
            foreach (HtmlElement link in hd.Links)
            {
                Console.WriteLine(link.GetAttribute("href"));
            }
        }
    }
}

ちゃっぴ
ぬし
会議室デビュー日: 2004/12/10
投稿数: 873
投稿日時: 2008-05-31 13:16
HtmlDocument って確か COM MSHTML の wrapper じゃなかったかな?

だとすると、body porperty に放り込んでごにょごにょすればいけるんじゃないでしょうか?

たしかに DOM 使って parse できたら便利だと思うんですが、MSHTML って ActiveX とか JavaScript とかが browser の設定にしたがって利用されちゃうのがいまいちなんですよね。個別に on, off できる property あればいいのに。。。
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2008-05-31 20:06
引用:

ちゃっぴさんの書き込み (2008-05-31 13:16) より:
HtmlDocument って確か COM MSHTML の wrapper じゃなかったかな?

だとすると、body porperty に放り込んでごにょごにょすればいけるんじゃないでしょうか?


ありがとうございます。
たしかにそんな感じでごにょごにょしたいのですが、たとえば、MSHTML.TLB を参照設定して、
コード:
private void button1_Click(object sender, EventArgs e)
{
    WebBrowser wb = new WebBrowser();
    HtmlDocument hd = wb.Document;
    hd.Body = null; // とりあえず null を set してみたいが、ここでコンパイルエラーになる。
    mshtml.HTMLDocument mshtmlHd = (mshtml.HTMLDocument)hd.DomDocument;
    mshtmlHd.body = null; // とりあえず null を set してみたいが、ここでコンパイルエラーになる。
}


のようなことをしようとしても、System.Windows.Forms.HtmlDocument クラスの Body プロパティーも、mshtml.HTMLDocument クラスの body プロパティーも、読み取り専用なので、この時点でもう行き詰ってしまっています。ほかにもなにか面白そうなプロパティーがあっても「読み取り専用」ということが多く、どうも外からいじることが難しそうに感じます。
しかし、System.Windows.Forms.HtmlDocument クラスは new できませんが、mshtml.HTMLDocument クラスは new できますね。
Hongliang
ぬし
会議室デビュー日: 2004/12/25
投稿数: 576
投稿日時: 2008-05-31 22:01
HTMLDocument(ちなみにこれ new できるけど interface)のインスタンスを作って、write を呼び出す(IHTMLDocument2 にキャストしないと駄目だったかな)ことで任意の html を読み込ませることが可能です。
HtmlDocument の方が欲しいなら internal なコンストラクタをリフレクションで呼び出すことになります(第一引数は null で良い模様)。internal なんで動作の保証はできませんが。
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2008-06-03 20:50
引用:

Hongliangさんの書き込み (2008-05-31 22:01) より:
HTMLDocument(ちなみにこれ new できるけど interface)のインスタンスを作って、write を呼び出す(IHTMLDocument2 にキャストしないと駄目だったかな)ことで任意の html を読み込ませることが可能です。


ありがとうございます。
遅くなりましたが、教えていただいたやりかたそのままでできました。
以下、リンク(a href)を抽出する例のコードです。
「参照の追加」で COM の MSHTML.TLB を追加しています。
なお、インスタンスの解放などが要るのかもしれませんが、今のところ省いています。

コード:
using System;
using System.Windows.Forms;

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

        private void button1_Click(object sender, EventArgs e)
        {
            string html = "<html><body><a href='http://www.example.com/'>example</a></body></html>";
            mshtml.HTMLDocument hd = new mshtml.HTMLDocument();
            mshtml.IHTMLDocument2 ihd2 = (mshtml.IHTMLDocument2)hd;
            // hd.write(new object[] { html }); // これだと COMException (-2147352571(== 0x80020005)) が起こる。
            ihd2.write(new object[] { html });
            foreach (mshtml.IHTMLElement link in hd.links)
            {
                Console.WriteLine((string)link.getAttribute("href", 0));
            }
        }
    }
}



引用:

Hongliangさんの書き込み (2008-05-31 22:01) より:
HtmlDocument の方が欲しいなら internal なコンストラクタをリフレクションで呼び出すことになります(第一引数は null で良い模様)。internal なんで動作の保証はできませんが。


できれば WebBrowser コントロールと WebClient 等を併用したいので、こっちのやりかた(小文字の HtmlDocument のやりかた)にしたいのですが、難しそうで分かりません。
(私としては、HTMLDocument(mshtml の大文字のほう)で、今のところやりたいことはできそうなので、ここで完了を報告させていただきます。)
burton999
ぬし
会議室デビュー日: 2003/10/06
投稿数: 898
お住まい・勤務地: 東京
投稿日時: 2008-06-04 11:11
私も以前、WebClientで取得したhtmlをDOMで解析したくて悩んだ結果
以下のライブラリを使用しました。参考までに。

Html Agility Pack - Home
http://www.codeplex.com/htmlagilitypack
1

スキルアップ/キャリアアップ(JOB@IT)