前回、【ネタ】 齋藤飛鳥の画像をスクレイピングを作成しましたが、これに改良を加えます。
問題点として突然Google Chromeが起動する、ユーザーがこれを閉じてしまうと処理が実行できません。またChromeを表示させないこともできますが、処理に時間がかかるためユーザーはアプリがフリーズしてしまったように錯覚してしまうかもしれません。そこでプログレスバーを表示して処理は実行されているのだということがわかるようにします。それから他のキーワードでも検索できるようにします。
デザイナでこんな感じにつくります。一番上のプログレスバーは指定した数に対して取得数はどうなっているか、二番目はスリープしているときにスリープ時間を表示するためのものです。これが動いていればフリーズしたとは思われないはずです。三番目は画像のダウンロード進行状況を示すものです。
ダウンロードが終わったらダウンロードした画像が保存されているフォルダを開きます。これで処理が終了したこともわかります。またダウンロードをときどき失敗することがあるので、ログも生成できるようにしました。
NumericUpDownコントロールで取得できる件数を選択できるようにします。1000件まで、最小は1件、デフォルトは100件です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
using OpenQA.Selenium; using OpenQA.Selenium.Chrome; public partial class Form1 : Form { public Form1() { InitializeComponent(); numericUpDown1.Minimum = 1; numericUpDown1.Maximum = 1000; numericUpDown1.Value = 100; } } |
画像のurlを取得するメソッドです。プログレスバーで取得した数とスリープしている待ち時間がわかるようにしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
public partial class Form1 : Form { List<string> GetImageUrls(ChromeDriver driver, int max) { progressBar1.Value = 0; progressBar1.Maximum = max; List<string> urls = new List<string>(); List<string> vs = new List<string>(); while (true) { string source = driver.PageSource; List<string> urls2 = new List<string>(); // imgタグを取得 var elms = driver.FindElementsByTagName("img"); StringBuilder sb = new StringBuilder(); foreach (var elm in elms) { // 同じimgタグであれば取得しない string id = elm.ToString(); if (vs.Any(x => x == id)) continue; vs.Add(id); urls2.Add(elm.GetAttribute("src")); } urls.AddRange(urls2); // 新しいsrc属性が取得できないなら終了 if (urls2.Count == 0) { progressBar1.Value = max; return urls; } // 不適切なデータや重複は排除 urls = urls.Where(x => x != null && x != "").ToList(); urls = urls.Distinct().ToList(); // 最大取得数に達しているなら終了 if (urls.Count >= max) { progressBar1.Value = max; return urls.Take(max).ToList(); } // プログレスバーで途中経過を表示 progressBar1.Value = urls.Count; // スクロールさせて新しいデータを読み込ませる int y = elms.Last().Location.Y; string script = string.Format("window.scrollBy(0, {0})", y); driver.ExecuteScript(script); // 新しいデータが読み込まれるまで待つ progressBar2.Maximum = 30; for (int cnt = 0; cnt < 30; cnt++) { // プログレスバーで待ち時間を表示 progressBar2.Value = cnt + 1; System.Threading.Thread.Sleep(100); } // 「もっと見る」ボタンがクリックできるならクリックする try { IWebElement showMore = driver.FindElementByClassName("sw-MoreButton__button"); if (showMore != null) { showMore.Click(); } } catch { } } } } |
「取得」ボタンが押されたら画像を保存するフォルダを選択させて、上記のGetImageUrlsメソッドで画像urlを取得します。そして後述のGetJpgFileメソッドで画像をダウンロードします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
public partial class Form1 : Form { private void button1_Click(object sender, EventArgs e) { // %エンコードをするため System.Web.dllを参照設定に追加 string searchWord = textBox1.Text; if (searchWord == "") { MessageBox.Show("検索ワードを入力してください"); return; } // 画像を保存するフォルダを選択させる FolderBrowserDialog dialog = new FolderBrowserDialog(); dialog.ShowDialog(); string path = dialog.SelectedPath; dialog.Dispose(); progressBar1.Value = 0; progressBar2.Value = 0; progressBar3.Value = 0; // Chromeを起動させるが、ウィンドウは表示されないようにする ChromeDriverService driverService = ChromeDriverService.CreateDefaultService(); driverService.HideCommandPromptWindow = true; ChromeOptions options = new ChromeOptions(); options.AddArgument("--headless"); ChromeDriver driver = new ChromeDriver(driverService, options); driver.Url = "https://search.yahoo.co.jp/image/search?p=" + System.Web.HttpUtility.UrlEncode(searchWord); // ページが読み込まれるまでしばらく待つ progressBar2.Maximum = 30; for (int cnt = 0; cnt < 30; cnt++) { progressBar2.Value = cnt + 1; System.Threading.Thread.Sleep(100); } // 画像のurlを取得 List<string> urls = GetImageUrls(driver, (int)numericUpDown1.Value); // ログファイルを作成する string logFilePath = String.Format("{0}\\log.txt", path); System.IO.StreamWriter sw = new System.IO.StreamWriter(logFilePath); // 画像をダウンロードする。保存できたかどうかログに残す int i = 0; progressBar3.Value = 0; progressBar3.Maximum = urls.Count; foreach (string url in urls) { i++; progressBar3.Value = i; string filePath = String.Format("{0}\\{1}.jpg", path, i.ToString()); GetJpgFile(filePath, url, sw); if(i%10 == 0) System.Threading.Thread.Sleep(1000); } sw.Close(); progressBar1.Value = progressBar1.Maximum; progressBar2.Value = progressBar2.Maximum; progressBar3.Value = progressBar3.Maximum; // driverをDisposeする driver.Quit(); driver.Dispose(); // 処理が完了したら画像をダウンロードしたフォルダを開く System.Diagnostics.Process.Start(path); } } |
取得した画像urlから画像をダウンロードするメソッドです。ログを保存する処理が追加されていますが、前回とほとんど同じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
public partial class Form1 : Form { // 指定したファイルパスに画像ファイルを保存する public static void GetJpgFile(string filePath, string url, System.IO.StreamWriter sw) { var ms = new System.IO.MemoryStream(); try { var req = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(url); var res = (System.Net.HttpWebResponse)req.GetResponse(); var st = res.GetResponseStream(); Byte[] buf = new Byte[1000]; while (true) { int read = st.Read(buf, 0, buf.Length); if (read > 0) ms.Write(buf, 0, read); else break; } System.IO.File.WriteAllBytes(filePath, ms.ToArray()); // 成功したらその旨ログに書き込む sw.WriteLine("成功:" + url); } catch { // 失敗したらその旨ログに書き込む sw.WriteLine("失敗:" + url); } } } |