常常因為資料分析的需求,會有需要爬網頁資料的時候,而以往爬網頁不外乎將Html拉回來後,依據Tag去拆解資訊。 但現今的網站很大部分都是前端透過API拉版面,以Instagram來說,如果直接透過網址將Html拉回來,會只得到空空的外殼而已,什麼都找不到。 這時候就需要模擬瀏覽器行為來讓Javascript運作,甚至操作瀏覽器去點擊特定按鈕。
[![](https://2.bp.blogspot.com/-6yR04AI5phg/Wmf7S3BE3TI/AAAAAAAAITs/gELt5or7tQkL60jmDRYwb1-YGuNU8cKggCLcBGAs/s400/1.png)](https://2.bp.blogspot.com/-6yR04AI5phg/Wmf7S3BE3TI/AAAAAAAAITs/gELt5or7tQkL60jmDRYwb1-YGuNU8cKggCLcBGAs/s1600/1.png) Instagram拉回來的網頁就只有一個空殼而已....
透過Selenium.WebDriver,以及Seleium.WebDriver.ChromeDriver套件,可以寫程式操作Chrome的操作行為
[![](https://1.bp.blogspot.com/-DkheSsyDReI/Wmf8FTKy0VI/AAAAAAAAIT0/raZdEEh40xgGJQbTFwhZ2A3aQbOOh4VWACLcBGAs/s640/1.png)](https://1.bp.blogspot.com/-DkheSsyDReI/Wmf8FTKy0VI/AAAAAAAAIT0/raZdEEh40xgGJQbTFwhZ2A3aQbOOh4VWACLcBGAs/s1600/1.png)
[![](https://3.bp.blogspot.com/-vnkv6jsDImY/Wmf8OnewkhI/AAAAAAAAIT4/FnlCPUMVsH05noZ1RVbhxFbWqMIYKZJ5gCLcBGAs/s640/1.png)](https://3.bp.blogspot.com/-vnkv6jsDImY/Wmf8OnewkhI/AAAAAAAAIT4/FnlCPUMVsH05noZ1RVbhxFbWqMIYKZJ5gCLcBGAs/s1600/1.png)
接著來一步一步分析如何透過它來爬網頁
開啟Chrome瀏覽器,並且連到想爬的網頁 : https://www.instagram.com/mercci22/
1 2 3 4 5 6 using (IWebDriver driver = new ChromeDriver()) { driver.Navigate().GoToUrl("https://www.instagram.com/mercci22/" ); }
接著分析目標網頁,會發現所有PO文資料都放在一個Div且Class為_cmdpi裡面
[![](https://1.bp.blogspot.com/-sbreT5cUils/Wmf99JegYUI/AAAAAAAAIUI/oIXZxzMGX7c9LU4uVQ9H0BSJUI67UgdGwCLcBGAs/s640/1.png)](https://1.bp.blogspot.com/-sbreT5cUils/Wmf99JegYUI/AAAAAAAAIUI/oIXZxzMGX7c9LU4uVQ9H0BSJUI67UgdGwCLcBGAs/s1600/1.png)
往下找出每一行、每一格,在div[class=’_cmdpi’]底下會有div[class=’_70iju’]每一行
[![](https://4.bp.blogspot.com/-yluY12_h7T0/Wmf_JJsdiBI/AAAAAAAAIUQ/zLIRDslBbZQEYGRvu0qXp4JoSdObu-lPwCLcBGAs/s640/1.png)](https://4.bp.blogspot.com/-yluY12_h7T0/Wmf_JJsdiBI/AAAAAAAAIUQ/zLIRDslBbZQEYGRvu0qXp4JoSdObu-lPwCLcBGAs/s1600/1.png)
而每一行裡面又有三個Div代表每一格
[![](https://4.bp.blogspot.com/-jnEHEexjkeM/Wmf_roHtA4I/AAAAAAAAIUY/4t6RR_UwEP8-1I7EipD7guWU4Ddyk3d5QCLcBGAs/s640/1.png)](https://4.bp.blogspot.com/-jnEHEexjkeM/Wmf_roHtA4I/AAAAAAAAIUY/4t6RR_UwEP8-1I7EipD7guWU4Ddyk3d5QCLcBGAs/s1600/1.png)
所以就來透過套件的API找出每一行,並點擊每一格吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 using (IWebDriver driver = new ChromeDriver()) { driver.Navigate().GoToUrl("https://www.instagram.com/mercci22/" ); var PostContainerElement = driver.FindElement(By.ClassName("_cmdpi" )); var Rows = PostContainerElement.FindElements(By.ClassName("_70iju" )); foreach (var row in Rows) { var Boxs = row.FindElements(By.XPath("div" )); foreach (var box in Boxs) { box.Click(); } } }
這時候如果你執行程式,應該會看到它開啟Chrome並且連到網址然後點擊每一格打開視窗
[![](https://3.bp.blogspot.com/-UIjpEynpLcA/WmgBKT9R10I/AAAAAAAAIUk/WA64zbd-g-YoQdf33ZVlrfVbXDLsYJkhACLcBGAs/s640/1.png)](https://3.bp.blogspot.com/-UIjpEynpLcA/WmgBKT9R10I/AAAAAAAAIUk/WA64zbd-g-YoQdf33ZVlrfVbXDLsYJkhACLcBGAs/s1600/1.png)
接著來分析彈跳出來的視窗,會發現當視窗開啟時,網頁會出現以下Div[role=’dialog’]這個元素,關閉後就會移除
[![](https://4.bp.blogspot.com/-clgYAUdg4y0/WmgBoMCshoI/AAAAAAAAIUo/E3NwCF5n5i8Cpilg7m-OChlEQEgGHOGAwCLcBGAs/s640/1.png)](https://4.bp.blogspot.com/-clgYAUdg4y0/WmgBoMCshoI/AAAAAAAAIUo/E3NwCF5n5i8Cpilg7m-OChlEQEgGHOGAwCLcBGAs/s1600/1.png)
所以我們要想辦法拿到這個Div Dialog,才有辦法擷取Po文的文案、日期、圖片,找到Dialog後,後面就重複上述步驟分析Tag,會發現
圖片 : 放在Div[class=’_4rbun’]底下的Img Tag文案 : 放在Img Tag的Alt裡面時間 : 放在Article > div > div > a > time這個Tag裡面
所以目前程式如下
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 (IWebDriver driver = new ChromeDriver()) { driver.Navigate().GoToUrl("https://www.instagram.com/mercci22/" ); var PostContainerElement = driver.FindElement(By.ClassName("_cmdpi" )); var Rows = PostContainerElement.FindElements(By.ClassName("_70iju" )); foreach (var row in Rows) { var Boxs = row.FindElements(By.XPath("div" )); foreach (var box in Boxs) { box.Click(); var article = driver.FindElement(By.XPath("//div[@role='dialog']/div/div/article" )); if (article.FindElements(By.ClassName("_4rbun" )).Count == 0 ) { continue ; } var ImgContainer = article.FindElement(By.ClassName("_4rbun" )); var Img = ImgContainer.FindElement(By.TagName("img" )); var Date = article.FindElement(By.XPath("div/div/a/time" )); } } }
這時候執行的時候可能會發生Exception,原因嘗試取得Dialog底下的Artilce,但Dialog點擊後產生會有時間差導致
[![](https://4.bp.blogspot.com/-TUSU5eY99xU/WmgWjs_9uBI/AAAAAAAAIVE/QnOtvHvoW38zNLWZQ7m6NpICJluVxyc2QCLcBGAs/s640/1.png)](https://4.bp.blogspot.com/-TUSU5eY99xU/WmgWjs_9uBI/AAAAAAAAIVE/QnOtvHvoW38zNLWZQ7m6NpICJluVxyc2QCLcBGAs/s1600/1.png)
優化這段程式,加上Wait的限制,而Selenium提供兩種Wait的方式
我們這邊加上第一種預設等待
1 2 driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(2 );
讀取每個Element時,如果暫時不存在兩秒後TimeOut,之後再執行看看,會發現跑到第二次box.Click()的時候跳Exception。
[![](https://4.bp.blogspot.com/-y0clUXejLl4/WmgZwH5ZmeI/AAAAAAAAIVQ/bDTSCWKn9jkNDem1AxJ-TAQPwPQpW-7hACLcBGAs/s640/1.png)](https://4.bp.blogspot.com/-y0clUXejLl4/WmgZwH5ZmeI/AAAAAAAAIVQ/bDTSCWKn9jkNDem1AxJ-TAQPwPQpW-7hACLcBGAs/s1600/1.png)
原因是當我們打開Dialog時,如果爬完不點擊關閉視窗,會點不到第二隔的元素
[![](https://1.bp.blogspot.com/-gPgFjISg5XI/WmgaJ1qWtUI/AAAAAAAAIVU/ii1yaeglfDgqvpH05tnhPa8Soxe3OszjACLcBGAs/s640/1.png)](https://1.bp.blogspot.com/-gPgFjISg5XI/WmgaJ1qWtUI/AAAAAAAAIVU/ii1yaeglfDgqvpH05tnhPa8Soxe3OszjACLcBGAs/s1600/1.png) 蓋住了第二格元素,所以要執行關閉視窗按鈕
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 using (IWebDriver driver = new ChromeDriver()) { driver.Navigate().GoToUrl("https://www.instagram.com/mercci22/" ); driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(2 ); var PostContainerElement = driver.FindElement(By.ClassName("_cmdpi" )); var Rows = PostContainerElement.FindElements(By.ClassName("_70iju" )); foreach (var row in Rows) { var Boxs = row.FindElements(By.XPath("div" )); foreach (var box in Boxs) { box.Click(); var article = driver.FindElement(By.XPath("//div[@role='dialog']/div/div/article" )); if (article.FindElements(By.ClassName("_4rbun" )).Count == 0 ) { driver.FindElement(By.ClassName("_dcj9f" )).Click(); continue ; } var ImgContainer = article.FindElement(By.ClassName("_4rbun" )); var Img = ImgContainer.FindElement(By.TagName("img" )); var Date = article.FindElement(By.XPath("div/div/a/time" )); driver.FindElement(By.ClassName("_dcj9f" )).Click(); } } }
這樣就可以順利地走完每一格,並且把圖片、文案、時間資料都讀出來了
** **
多圖片的情境 加下來是應用的第二部分,Instagram是可以分享多圖片的,而多張圖片是在點擊向右按鈕後,才會動態透過JS撈出來
[![](https://4.bp.blogspot.com/-S8AFeQpjcHk/WmgbnCX9SgI/AAAAAAAAIVk/2jcAH-RsJ4cg7IN6X2_UwCWVmbNX1BJeACLcBGAs/s400/1.png)](https://4.bp.blogspot.com/-S8AFeQpjcHk/WmgbnCX9SgI/AAAAAAAAIVk/2jcAH-RsJ4cg7IN6X2_UwCWVmbNX1BJeACLcBGAs/s1600/1.png)
所以必須寫程式判斷是否有這個按鈕,如果有,表示有多張圖片,要求Driver去點擊那個按鈕,並且撈取Img的Src路徑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var ImgContainer = article.FindElement(By.ClassName("_4rbun" )); var Img = ImgContainer.FindElement(By.TagName("img" )); List<string > ImgUrls = new List<string > { Img.GetAttribute("src" ) }; while (article.FindElements(By.CssSelector("a[class='_8kphn _by8kl coreSpriteRightChevron']" )).Count > 0 ) { var nextBtn = article.FindElement(By.CssSelector("a[class='_8kphn _by8kl coreSpriteRightChevron']" )); nextBtn.Click(); Thread.Sleep(500 ); Img = ImgContainer.FindElement(By.TagName("img" )); ImgUrls.Add(Img.GetAttribute("src" )); }
這樣就能順利拿到多張圖的路徑了
讀取第二頁的情境 Instagram是滑鼠移到最下方才會動態載入第二頁,所以需要能控制視窗移到最下方來觸發它
```csharp
IJavaScriptExecutor js = (IJavaScriptExecutor)driver;
//如果爬完第一頁還沒爬完,則執行JS讓視窗滾到最下方,觸發讀取第二頁
js.ExecuteScript("window.scrollTo(0,1000000)");
```
目前綜合以上所提的應用,應該已經能完全將Instagram的網站資料爬回來,只能說Selenium真的是一個強大的東西阿!!
參考文章:XML XPath的選擇節點語法 Selenium Documentation