Selenium在前面的一篇文章中说过是一种浏览器自动化测试的工具,可以利用浏览器的驱动去控制浏览器访问网站,从
而模拟浏览行为抓取数据,这种方式可以抓到更多的数据,但是效率不是很高,而且浏览器的页面必须一直开着,比较
吃资源。最近看到了一个无页面的浏览器PhantomJS,访问网站效率高,速度快,无页面全后台抓取数据,而且可以和
Selenium结合使用个性化定制网站的数据抓取,下面会详细讲一下Selenium与PhantomJS在vs2013中是如何抓取数据
的,以携程网的酒店数据为例。
首先下载Selenium的dll文件和PhantomJS资源,在我的资源中都已经上传了地址在这里~
http://download.csdn.net/detail/u013407099/9687589
然后引用Selenium中的4个dll文件,将PhantomJS中bin目录下的exe文件放到工程目录下就好了
第一步我们先初始化PhantomJS类型的Selenium中的driver来控制浏览器
var driver = new OpenQA.Selenium.PhantomJS.PhantomJSDriver("../../Phantomjs");
第二步就让这个drivier去访问我们想要访问的地址
driver.Navigate().GoToUrl("http://hotels.ctrip.com/citylist");
第三步先在浏览器中访问这个网址,观察网页的DOM结构的规律,去将所有的城市的酒店列表地址所在的元素获取到,也就是使用css选择器来筛选DOM结构
//锁定留个城市名模块 ReadOnlyCollection<IWebElement> elements = driver.FindElementsByClassName("des_cont"); foreach (var e in elements) { //每个字母对应的城市集合 ReadOnlyCollection<IWebElement> hreflist = e.FindElements(By.TagName("a")); foreach (var h in hreflist) { string cityname = h.GetAttribute("innerHTML"); string hotellisthref = h.GetAttribute("href"); Console.WriteLine(cityname hotellisthref); City city = new City(cityname, hotellisthref); if (!list.Contains(city)) { list.Add(city); } } }
因为携程网的城市按字母排序的,而且切换字母时的数据就是在一个页面中,所以可以一次性把所有的城市对应的酒店介绍地址获取到,下面就可以去分别访问每个城市的酒店列表,获取每个酒店更加详细的信息 ,这里因为单线程比较慢,所以开了多线程去跑,跑多线程的时候原来想把每个城市建一个文本文件记录的,但是多线程的执行方式会是的有很多重复数据写入(坑了自己好久),所以就将数据分组,然后一组一个文本文件就好了
分组代码:
int p = 10; //商 int value = list.Count / 10; //余数 int remainder = list.Count % 10; List<List<City>> citylist = new List<List<City>>(); for (int i = 0; i < p; i ) { List<City> grouplist = new List<City>(); if (i < p - 1) { for (int j = i * value; j < value * (i 1); j ) { grouplist.Add(list[j]); } } else { for (int j = i * value; j < list.Count; j ) { grouplist.Add(list[j]); } } string filename = "../../Data/File/Cash" DateTime.Now.ToString("yyyyMMddHHmmss") "_" i ".txt"; File.Create(filename); cachelist.Add(filename); citylist.Add(grouplist); }
获取每个酒店的详细页面地址
public async Task<string> StartDetailTask(List<City> list, string CacheFileName) { return await Task.Run(() => { var driver = new OpenQA.Selenium.PhantomJS.PhantomJSDriver("../../Phantomjs"); var result = string.Empty; try { foreach (var city in list) { driver.Navigate().GoToUrl(city.Url.ToString()); ReadOnlyCollection<IWebElement> elements = driver.FindElementsByClassName("searchresult_name"); foreach (var e in elements) { IWebElement w = e.FindElement(By.TagName("a")); string hotelname = w.GetAttribute("title"); string allhref = w.GetAttribute("href"); //string hotelhref = allhref.Substring(0, allhref.IndexOf('?') - 1); result = hotelname "|" allhref "rn"; Console.WriteLine(hotelname allhref); StreamWriter sw = new StreamWriter(CacheFileName); try { sw.Write(result); } catch (Exception) { throw; } finally { sw.Flush(); sw.Close(); } } } } catch (Exception e) { Console.WriteLine(e.StackTrace); ; } finally { driver.Close(); driver.Quit(); } return result; });
在访问 的过程中可以设置PhantomJS的一些属性,比如HideCommandPromptWindow属性可以控制是否弹出PhantomJS的命令框,LoadImages可以控制是否加载页面图片等
最后一步就是获取每个酒店的详细评论了,在获取房间评论的过程中因为网站需要滑动才会动态加载完毕,从而选择切换到评论,所以需要人为的控制窗口滑动
var driver = new PhantomJSDriver(driverService); //var driver = new ChromeDriver(@"C:Program Files (x86)GoogleChromeApplication"); driver.Navigate().GoToUrl("http://hotels.ctrip.com/hotel/434938.html"); //滚动到底部 Actions action = new Actions(driver); for (int i = 0; i < 4; i ) { action.MoveToElement(driver.FindElementByClassName("gns")).Perform(); }
其中“gns”是网站的底部一个元素的class,来定位网站的底部在哪里,然后控制div的店家来切换到评论窗口
//切换到评论 driver.FindElementById("commentTab").Click();
最后来抓取详细评论
//评论集合 ReadOnlyCollection<IWebElement> commentlist = driver.FindElementsByCssSelector("div[class^='comment_block']"); foreach (var comment in commentlist) { Console.WriteLine("用户账号:" comment.FindElement(By.ClassName("name")).FindElement(By.TagName("span")).GetAttribute("innerHTML")); Console.WriteLine("用户评分:" comment.FindElement(By.ClassName("score")).FindElement(By.ClassName("n")).GetAttribute("innerHTML")); Console.WriteLine("入住时间:" comment.FindElement(By.ClassName("date")).GetAttribute("innerHTML")); Console.WriteLine("房间类型:" comment.FindElement(By.CssSelector("a[class^='room J_baseroom_link']")).GetAttribute("innerHTML")); Console.WriteLine("详细评论" comment.FindElement(By.ClassName("J_commentDetail")).GetAttribute("innerHTML")); Console.WriteLine(); }
在这个过程中有一个问题没有解决,就是只能抓取5条评论,即使设置了等待时间或者等待条件也没有用,而等待条件的设置与chromedriver配合确可以完美解决,如果大家有什么好的解决方法可以提给我哦,等待条件的设置给大家看一下
//等待加载完毕 WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(5)); wait.Until<bool>((d) => { return d.FindElement(By.XPath("//*[@id='commentList']")).Displayed && d.FindElement(By.XPath("//*[@id='hotel_info_comment']/div[@id='commentList']")).Displayed && !d.FindElement(By.XPath("//*[@id='hotel_info_comment']/div[@id='commentList']")).Text.Contains("点评载入中"); });