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
foreach (var e in elements)
{
//每个字母对应的城市集合
ReadOnlyCollection
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> citylist = new List
>();
for (int i = 0; i < p; i++)
{
List
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
{
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
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 + "\r\n";
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)\Google\Chrome\Application");
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
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
{
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("点评载入中");
});