YouTubeで再生回数の多い動画のurlを取得するではSelenium.Chrome.WebDriverを使ってデータを取得しましたが、今回はPuppeteerSharpをつかってデータを取得します。
それから前回の方法ではだいたいの再生回数と大体のアップロード日しか取得できませんでしたが、今回は動画ページにもアクセスして正確な再生数とアップロード日を取得します。またチャンネルページにもアクセスしてチャンネル開設日とチャンネル総再生数も取得します。
NuGetでPuppeteerSharpとClosedXMLをインストールします。そしてデザイナでこんな感じのものを作ります。
まず取得したデータを格納するクラスを作成します。
動画urlと動画に関する情報、チャンネル情報を格納します。
動画に関する情報は動画タイトル、動画url、アップロード日、再生数です。これをVideoInfoクラスに格納します。またチャンネルに関する情報はチャンネル名、チャンネルurl、チャンネル登録日、チャンネル総再生数です。これはChannelInfoクラスに格納します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class Data { public string VideoUrl = ""; public VideoInfo VideoInfo; public ChannelInfo ChannelInfo; } public class VideoInfo { public string VideoTitle = ""; public string VideoUrl = ""; public string UploadDate = ""; public long VideoViews = 0; } public class ChannelInfo { public string ChannelName = ""; public string ChannelUrl = ""; public string ChannelRegistDate = ""; public long ChannelViews = 0; } |
コンストラクタ内でBrowserオブジェクトとPageオブジェクトを生成します。準備ができるまでbutton1.Enabled = false;にして、処理のためのボタンを押すことができないようにします。
page1は検索結果の取得用、page2は動画ページの解析用、page3はチャンネルページ解析用です。
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 |
using PuppeteerSharp; public partial class Form1 : Form { public Form1() { InitializeComponent(); // 全部で何件取得するか numericUpDown1.Minimum = 1; numericUpDown1.Maximum = 1000; numericUpDown1.Value = 10; StartBrowsers(); } Page page1 = null; Page page2 = null; Page page3 = null; async void StartBrowsers() { button1.Enabled = false; await new BrowserFetcher().DownloadAsync(BrowserFetcher.DefaultRevision); Browser browser = await Puppeteer.LaunchAsync(new LaunchOptions { Headless = true, }); page1 = await browser.NewPageAsync(); page2 = await browser.NewPageAsync(); page3 = await browser.NewPageAsync(); button1.Enabled = true; } } |
処理のためのボタンが押されたときの処理を示します。
自作メソッド GetDatasScrollを呼び出して動画情報とチャンネル情報を取得します。表示されているページの情報が取得できたらスクロールして下に書かれている情報も取得します。データが取得できたらExcelファイルとして保存します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public partial class Form1 : Form { private void button1_Click(object sender, EventArgs e) { // 取得データをexcelファイルとして保存する。 // ファイル名を決める string filePath = ""; SaveFileDialog dialog = new SaveFileDialog(); dialog.Filter = "Excelファイル(*.xlsx)|*.xlsx"; if (dialog.ShowDialog() == DialogResult.OK) filePath = dialog.FileName; dialog.Dispose(); if (filePath == "") return; Task.Run(()=> { List<Data> datas = GetDatasScroll(keyword, (int)numericUpDown1.Value).Result; SaveExcel(filePath, datas); MessageBox.Show("完了しました"); }); } } |
検索結果が表示されているページの情報を取得し、スクロールして下に書かれている情報も取得する処理を示します。HTMLを解析すればdiv.text-wrapper.style-scope.ytd-video-rendererを調べれば動画のタイトルやurlを取得できることがわかります。全部取得できたらスクロールさせて最大取得数に達するか全件取得できるまで繰り返します。
処理の進行状況を示すためにプログレスバーを表示させます。ひとつめは何件取得できたかを示すためのもので、もうひとつは1件の取得がどこまで完了したか(スリープしているあいだもプログレスバーを動かしてフリーズしてしないことを知らせる)です。
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 |
public partial class Form1 : Form { private async Task<List<Data>> GetDatasScroll(string keyword, int max) { string str = System.Web.HttpUtility.UrlEncode(keyword); string searchUrl = String.Format("https://www.youtube.com/results?search_query={0}&sp=CAMSAhAB", str); await page1.GoToAsync("searchUrl"); List<Data> datas = new List<Data>(); // 何件取得できたかを示す Invoke((Action)(() => { progressBar1.Maximum = max; progressBar1.Value = 0; })); while (true) { Invoke((Action)(() => { progressBar2.Maximum = 10; progressBar2.Value = 0; })); // スリープしているあいだもプログレスバーを動かしてフリーズしてしないことを知らせる for (int i = 0; i < 10; i++) { System.Threading.Thread.Sleep(500); // 5秒待つ Invoke((Action)(() => { progressBar2.Value++; })); } ElementHandle[] elms = page1.QuerySelectorAllAsync("div.text-wrapper.style-scope.ytd-video-renderer").Result; bool isMore = await GetDatas(elms, datas, max); if (!isMore) break; var lastElm = elms.Last(); await lastElm.HoverAsync(); } return datas; } } |
動画情報を取得する処理を示します。
検索結果のHTMLのdiv.text-wrapper.style-scope.ytd-video-rendererのなかにある
yt-formatted-string.style-scope.ytd-video-renderer
a#video-title.yt-simple-endpoint.style-scope.ytd-video-renderer
yt-formatted-string#text.style-scope.ytd-channel-name.complex-string
a.yt-simple-endpoint.style-scope.yt-formatted-string
を調べれば、動画タイトル、動画url、チャンネル名、チャンネル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 |
public partial class Form1 : Form { private async Task<bool> GetDatas(ElementHandle[] elms, List<Data> datas, int max) { List<string> videoUrls = new List<string>(); foreach (ElementHandle elm in elms) { Invoke((Action)(() => { progressBar2.Maximum = 21; progressBar2.Value = 0; })); string videoTitle = ""; var elm1 = elm.QuerySelectorAsync("yt-formatted-string.style-scope.ytd-video-renderer").Result; if (elm1 != null) videoTitle = elm1.GetPropertyAsync("textContent").Result.ToString().Substring(9); string videoUrl = ""; var elm2 = elm.QuerySelectorAsync("a#video-title.yt-simple-endpoint.style-scope.ytd-video-renderer").Result; if (elm2 != null) videoUrl = elm2.GetPropertyAsync("href").Result.ToString().Substring(9); string channelTitle = ""; var elm3 = elm.QuerySelectorAsync("yt-formatted-string#text.style-scope.ytd-channel-name.complex-string").Result; if (elm3 != null) channelTitle = elm3.GetPropertyAsync("textContent").Result.ToString().Substring(9); string channelurl = ""; var elm4 = elm.QuerySelectorAsync("a.yt-simple-endpoint.style-scope.yt-formatted-string").Result; if (elm4 != null) channelurl = elm4.GetPropertyAsync("href").Result.ToString().Substring(9); Invoke((Action)(() => { progressBar2.Value = 1; })); if (!datas.Any(x => x.VideoUrl == videoUrl)) { videoUrls.Add(videoUrl); VideoInfo videoInfo = await GetVideoInfo(videoTitle, videoUrl); ChannelInfo channelInfo = await GetChannelInfo(channelTitle, channelurl); Data data = new Data() { VideoUrl = videoUrl, VideoInfo = videoInfo, ChannelInfo = channelInfo, }; datas.Add(data); Invoke((Action)(() => { progressBar1.Value++; })); if (datas.Count >= max) return false; } } if (videoUrls.Count == 0) return false; return true; } } |
実際に動画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 |
public partial class Form1 : Form { async Task<VideoInfo> GetVideoInfo(string videoTitle, string videoUrl) { await page2.GoToAsync(videoUrl); for (int i = 0; i < 10; i++) { System.Threading.Thread.Sleep(500); // 5秒待つ Invoke((Action)(() => { progressBar2.Value++; })); } var elms1 = page2.QuerySelectorAsync("div#info-text.style-scope.ytd-video-primary-info-renderer").Result; if (elms1 != null) { var elms3 = elms1.QuerySelectorAsync("span.view-count.style-scope.ytd-video-view-count-renderer").Result; var elms4 = elms1.QuerySelectorAsync("yt-formatted-string.style-scope.ytd-video-primary-info-renderer").Result; string videoViews1 = elms3.GetPropertyAsync("textContent").Result.ToString().Substring(9); string uploadDate = elms4.GetPropertyAsync("textContent").Result.ToString().Substring(9); // 余分な文字列を取り除く videoViews1 = videoViews1.Replace(" 回視聴", ""); videoViews1 = videoViews1.Replace(",", ""); long videoViews = 0; try { videoViews = long.Parse(videoViews1); } catch { } return new VideoInfo() { VideoTitle = videoTitle, VideoUrl = videoUrl, UploadDate = uploadDate, VideoViews = videoViews, }; } return null; } } |
実際にチャンネルurlにアクセスして登録日や総再生数を取得する処理を示します。チャンネルurlの後ろに/aboutをつければ概要ページにアクセスできます。その要素を調べれば必要な情報を取得できます(説明手を抜きすぎ)。
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 |
public partial class Form1 : Form { async Task<ChannelInfo> GetChannelInfo(string channelName, string channelUrl) { await page3.GoToAsync(channelUrl + "/about"); for (int i = 0; i < 10; i++) { System.Threading.Thread.Sleep(500); // 5秒待つ Invoke((Action)(() => { progressBar2.Value++; })); } var elm = page3.QuerySelectorAsync("div#right-column.style-scope.ytd-channel-about-metadata-renderer").Result; if (elm != null) { var elm2 = elm.QuerySelectorAsync("span.style-scope.yt-formatted-string").Result; string date = elm2.GetPropertyAsync("textContent").Result.ToString().Substring(9); var elm3s = elm.QuerySelectorAllAsync("yt-formatted-string.style-scope.ytd-channel-about-metadata-renderer").Result; string views = elm3s[2].GetPropertyAsync("textContent").Result.ToString().Substring(9); views = views.Replace(" 回視聴", ""); views = views.Replace(",", ""); long channelViews = 0; try { channelViews = long.Parse(views); } catch { } return new ChannelInfo() { ChannelName = channelName, ChannelUrl = channelUrl, ChannelRegistDate = date, ChannelViews = channelViews, }; } else return null; } } |
取得したデータをExcelファイルとして保存するメソッドを示します。
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 |
using ClosedXML.Excel; public partial class Form1 : Form { void SaveExcel(string excelPath, List<Data> datas) { using (var workbook = new XLWorkbook()) { var worksheet = workbook.Worksheets.Add("シート1"); worksheet.Cell(1, "A").Value = "動画タイトル"; worksheet.Cell(1, "B").Value = "動画Url"; worksheet.Cell(1, "C").Value = "アップロード時"; worksheet.Cell(1, "D").Value = "再生数"; worksheet.Cell(1, "E").Value = "チャンネル名"; worksheet.Cell(1, "F").Value = "チャンネルUrl"; worksheet.Cell(1, "G").Value = "チャンネル登録日"; worksheet.Cell(1, "H").Value = "チャンネル総再生数"; int i = 2; foreach (Data data in datas) { worksheet.Cell(i, "A").Value = data.VideoInfo.VideoTitle; worksheet.Cell(i, "B").Value = data.VideoInfo.VideoUrl; worksheet.Cell(i, "C").Value = data.VideoInfo.UploadDate; worksheet.Cell(i, "D").Value = data.VideoInfo.VideoViews; worksheet.Cell(i, "E").Value = data.ChannelInfo.ChannelName; worksheet.Cell(i, "F").Value = data.ChannelInfo.ChannelUrl; worksheet.Cell(i, "G").Value = data.ChannelInfo.ChannelRegistDate; worksheet.Cell(i, "H").Value = data.ChannelInfo.ChannelViews; i++; } workbook.SaveAs(excelPath); } } } |
以下は「プログラミング」で検索した結果を取得したものです。Kan & Aki’s CHANNELかんあきチャンネルの総再生数は78.5億回。ヤバすぎます。