PrismBox > Tips >

 
C# Tips
 
No.0002 HTMLに含まれる画像を取得
この例のコンソールアプリケーションは引数に指定したHTMLファイルへのURLを開いて、 そのHTMLファイルに含まれている画像ファイル(*.jpg; *.gif; *.png)すべてをローカルディスクのカレントディレクトリにダウンロードして保存します。 ただし、2重ダウンロードのチェックは行っていないので、HTML中に同じファイルへの参照があった場合は再度ダウンロードされ、既存のファイルに上書きして保存します。 また、ディレクトリ構造はすべて取り除いてローカルディスクにダウンロードするので、 たとえば「path001/myimage.jpg」,「path002/myimage.jpg」のようにパスが違って名前が同じファイルがあった場合、HTML中で後に現れる「myimage.jpg」で上書きされてしまいます。 (これを解決する簡単な方法としてパスも含めてファイル名にするという方法が考えられます。たとえば「path001_myimage.jpg」「path002_myimage.jpg」といった感じで。)
using System;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;

namespace prismbox.sample.httpclient {
    class Class2 {
        static void Main(string[] args) {
            // 相対アドレス指定で使うベースアドレス
            string baseurl = "";

            // 引数をチェック
            if (args.Length != 1) {
                // 引数の数が不正な場合
                Console.WriteLine("引数にURLを指定してください");
                return;

            } else if (!args[0].ToLower().StartsWith("http://")) {
                // 引数が「http://」で始まっていない場合
                Console.WriteLine("URLは「http://」で始まっている必要があります");
                return;

            } else {
                if ((args[0].ToLower().StartsWith("http://")) && (args[0].Length == 7)) {
                    // 引数が「http://」しかない場合
                    Console.WriteLine("引数にURLを指定してください");
                    return;
                }
            }

            // 相対アドレス指定で使うベースアドレスを作成
            int pos = args[0].LastIndexOf("/");
            if ((args[0].ToLower().StartsWith("http://")) && (pos == 6)) {
                // 「http://」以降に「/」が1つも見つからない場合は最後に「/」を付け足す
                baseurl = args[0] + "/";
            } else {
                // 最後に見つかった「/」まででURLをちぎる
                baseurl = args[0].Substring(0, pos + 1);
            }

            try {
                // WebClient,Stream,StreamReaderを作成
                WebClient wc = new WebClient();
                Stream st = wc.OpenRead(args[0]);
                StreamReader sr = new StreamReader(st);

                // ストリームをすべて読み取る
                String strHtml = sr.ReadToEnd();

                // Stream,StreamReaderを閉じる
                sr.Close();
                st.Close();

                // 「src」を探し出す正規表現を作成
                Regex rSRC = new Regex("src\\s*=\\s*(?:\"(?<1>[^\"]*)\"|(?<1>\\S+))",
                    RegexOptions.IgnoreCase|RegexOptions.Compiled);

                // Matchを作成
                Match mSRC;

                // 「src」を探して、それが .jpg .gif .png で終わる場合はファイルを取得
                for (mSRC = rSRC.Match(strHtml); mSRC.Success; mSRC = mSRC.NextMatch()) {
                    // 「src=""」の「""」の中身を取得
                    string val = mSRC.Groups[1].Value;

                    // 拡張子をチェック
                    if (val.ToLower().EndsWith(".jpg") || val.ToLower().EndsWith(".gif") ||
                        val.ToLower().EndsWith(".png")) {

                        Console.Write("Downloading " + val + " ... ");

                        // ローカルディスクに保存するときの名前を作成
                        string lcfile = val;
                        if (val.IndexOf("/") != -1) {
                            // ファイル名にパスが含まれている場合は取り除く
                            lcfile = val.Substring(val.LastIndexOf("/") + 1);
                        }
                        
                        // リモートファイルが相対アドレス指定の場合はBaseAddressを設定する
                        if (!val.ToLower().StartsWith("http://")) {
                            wc.BaseAddress = baseurl;
                        } else {
                            wc.BaseAddress = "";
                        }

                        // ファイルをローカルのカレントディレクトリにダウンロードする
                        wc.DownloadFile(val, lcfile);

                        Console.WriteLine("OK");
                    }
                }

            } catch (Exception e) {
                // URLのファイルが見つからない等のエラーが発生
                Console.WriteLine("エラーが発生しました\r\n\r\n" + e.ToString());

            }

            return;
        }
    }
}
No.0001のソースコードよりきちんと引数チェックをしてみました。ただ、HTTPには「https://」というSSL越しのものもあるのですが、この例ではそれは無視しています。
最初はHTML中から「<img src=」を探して画像ファイルを見つけようと思いましたが、「img」の直後に「alt」とか「width」等がくることも考えられるので、 「src=」のみを探すようにしています。この「src=」の検索には正規表現を用いています。 Regexのコンストラクタでは「src=""」の「""」の中身だけ取得できるようにグループ化構造体を用いて正規表現を記述しています。 そして拾ったファイル名に拡張子「.jpg」「.gif」「.png」が付いているかチェックします。ここで拡張子を調べている理由は「src」はJavaScript等の外部ファイルを参照するときにも使われるからです。 多少横着ですが「.jpg」「.gif」「.png」に固定することでまず間違いなく「img」の「src」が拾えます。(ただしこうすることでCGIが自動生成する「.cgi」の拡張子が付いた画像ファイルは拾えないということになりますが...)

実行はコマンドプロンプトより次のようにして行います。(例のソースはそのまま「csc」でコンパイルすれば動きます。実行ファイル名は「httpclient.exe」とします)
C:\samples>httpclient.exe http://www.hogehoge.com/foo/
これで「http://www.hogehoge.com/foo/」のデフォルトHTML(index.htmlやDefault.htm等)に含まれる画像ファイルはすべて「c:\samples」にダウンロードされます。
もちろんURLは「http://www.hogehoge.com/foo/sample.html」のようにファイル名を直接指定してもいいです。すると「sample.html」に含まれる画像ファイルがダウンロードされます。 注意する点は「http://www.hogehoge.com/foo/sample」を指定した場合、「sample」というHTMLファイルを開こうとするということです。 「sample」ディレクトリを参照させたい場合は「http://www.hogehoge.com/foo/sample/」と最後に「/」を忘れずに付加する必要があります。