前回はTwitterのツイートをスクレイピングで取得しましたが、今回はもっと本格的にやってみます。
Twitterはスクロールすると過去のツイートをみることができます。そのためスクレイピングで過去のツイートを取得するためにはスクロールさせる必要があります。
Seleniumなら以下の方法でスクロールさせることができます。
1 2 3 4 5 |
using OpenQA.Selenium.Chrome; ChromeDriver driver = new ChromeDriver(); driver.Url = "http://twitter.com/XXXX"; driver.ExecuteScript("window.scrollBy(0, 1000)"); |
ではどこまでスクロールさせるかどうやって調べればいいのでしょうか? HTMLを調べてみると各ツイートはarticleタグで囲われています。そこで最後のarticleタグがある座標を調べればそこまでスクロールさせればよいことがわかります。
1 2 3 4 5 6 7 8 9 |
using OpenQA.Selenium; using System.Collections.ObjectModel; ReadOnlyCollection<OpenQA.Selenium.IWebElement> articles = driver.FindElementsByTagName("article"); IWebElement lastElm = articles.Last(); int y = lastElm.Location.Y; string script = string.Format("window.scrollBy(0, {0})", y); driver.ExecuteScript(script); |
スクロールさせても以前取得した過去のツイートをもう一度拾ってしまうことがあります。Seleniumだと以下の方法でIDのようなものを取得できます(HTMLの中にはない。Selenium独自のものか?)。そこでこれを保存しておけばすでに取得したものかどうかを判別することができます。
1 2 3 4 5 6 7 8 9 10 11 12 |
List<string> vs1 = new List<string>(); // フィールド変数 ReadOnlyCollection<OpenQA.Selenium.IWebElement> articles = driver.FindElementsByTagName("article"); foreach (IWebElement article in articles) { string id = article.ToString(); if (vs1.Any(x => x == id)) continue; vs1.Add(id); // 処理をする } |
それからツイートだけでなく、日時、ツイート数、いいねの数も取得します。
時刻はHTMLを調べてみると
1 |
<time datetime="2021-03-05T14:11:50.000Z">3月5日</time> |
このような形になっているので
1 2 3 |
string time = "<time datetime="2021-03-05T14:11:50.000Z">3月5日</time>"; string[] vs = time.Split(new string[] { "\"", "T", "." }, StringSplitOptions.RemoveEmptyEntries); time = String.Format("{0} {1}", vs[1], vs[2]); |
これで time = “2021-03-05 14:11:50″が取得できます。
ではさっそくやってみましょう。
ボタン1をクリックするとSeleniumが起動してブラウザが立ち上がります。
1 2 3 4 5 6 7 8 9 10 11 12 |
public partial class Form1 : Form { ChromeDriver driver = null; private void button1_Click(object sender, EventArgs e) { driver = new ChromeDriver(); driver.Url = "http://twitter.com/XXXX"; System.Threading.Thread.Sleep(5000); } } |
ボタン2をクリックするとツイートと日時、リツイート数、いいね数を取得して、スクロールします。取得したデータはリストのなかに保存します。
リツイート数、いいね数はarticleタグのなかにある div class = “css-1dbjc4n r-xoduu5 r-1udh08x”タグのうち、2番目と3番めのものを調べればわかります。
ボタン1を押したあとボタン2を押すたびに古いツイートがリストのなかに取得されていきます。
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 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
using AngleSharp.Dom; using AngleSharp.Html.Parser; using AngleSharp.Html.Dom; public class Data { public string tweet = ""; public string time = ""; public string retweet = ""; public string good = ""; } public partial class Form1 : Form { List<string> ids = new List<string>(); List<Data> Datas = new List<Data>(); private void button2_Click(object sender, EventArgs e) { Datas.AddRange(GetTweet()); } private List<Data> GetTweet() { List<Data> datas = new List<Data>(); if (driver == null) return datas; string body = driver.PageSource; HtmlParser parser = new HtmlParser(); var doc = parser.ParseDocument(body); IHtmlCollection<IElement> elms = doc.GetElementsByTagName("article"); ReadOnlyCollection<IWebElement> articles = driver.FindElementsByTagName("article"); for (int i = 0; i < elms.Count(); i++) { // すでに取得したツイートは無視 string id = articles[i].ToString(); if (ids.Any(x => x == id)) continue; ids.Add(id); // ツイートと日時、リツイート数、いいね数を取得 string Html = elms[i].OuterHtml; IHtmlDocument doc2 = parser.ParseDocument(Html); // ツイートを取得 string tweet = ""; IHtmlCollection<IElement> innerEelms = doc2.GetElementsByClassName("css-901oao r-18jsvk2 r-1tl8opc r-a023e6 r-16dba41 r-ad9z0x r-bcqeeo r-bnwqim r-qvutc0"); if (innerEelms.Count() > 0) tweet = innerEelms[0].TextContent; // ツイート日時を取得 string time = ""; innerEelms = doc2.GetElementsByTagName("time"); if (innerEelms.Count() > 0) { time = innerEelms[0].OuterHtml; string[] vs = time.Split(new string[] { "\"", "T", "." }, StringSplitOptions.RemoveEmptyEntries); time = String.Format("{0} {1}", vs[1], vs[2]); } // リツイート数を取得 string retweet = ""; string good = ""; innerEelms = doc2.GetElementsByClassName("css-1dbjc4n r-xoduu5 r-1udh08x"); if (innerEelms.Count() >= 2) retweet = innerEelms[1].TextContent; // いいね数を取得 if (innerEelms.Count() >= 3) good = innerEelms[2].TextContent; // 取得したデータをリストに格納 datas.Add(new Data() { good = good, retweet = retweet, tweet = tweet, time = time, }); } // 新しくデータを取得するためにスクロールさせる IWebElement lastElm = articles.Last(); int y = lastElm.Location.Y; string script = string.Format("window.scrollBy(0, {0})", y); driver.ExecuteScript(script); return datas; } } |
ツイートが取得できたらこれを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 37 38 39 40 41 |
using ClosedXML.Excel; public partial class Form1 : Form { private void button3_Click(object sender, EventArgs e) { SaveFileDialog dialog = new SaveFileDialog(); dialog.Filter = "Excelファイル(*.xlsx)|*.xlsx"; if (dialog.ShowDialog() == DialogResult.OK) { string ExcelFilePath = dialog.FileName; // Excelファイルを作る using (var workbook = new XLWorkbook()) { // ワークシートを追加する var worksheet = workbook.Worksheets.Add("シート1"); // セルに値や数式をセット worksheet.Cell(1, "A").Value = "日時"; worksheet.Cell(1, "B").Value = "Tweet"; worksheet.Cell(1, "C").Value = "リツイート"; worksheet.Cell(1, "D").Value = "いいね"; int i = 2; foreach (Data data in Datas) { worksheet.Cell(i, "A").Value = data.time; worksheet.Cell(i, "B").Value = data.tweet; worksheet.Cell(i, "C").Value = data.retweet; worksheet.Cell(i, "D").Value = data.good; i++; } // ワークブックを保存する workbook.SaveAs(ExcelFilePath); } } dialog.Dispose(); } } |