今天,越来越多的网站正在使用Ajax,以获得花哨的用户体验,动态网页,以及更多的好理由。 抓取Ajax重的网站可能是棘手和痛苦的,我们将看到一些技巧,使其更容易。
准备条件
在开始之前,请阅读我之前写的文章,了解如何设置你的Java环境,并对HtmlUnitIntroduction to Web Scraping With Java和Handling Authentication有一个基本的了解。 阅读之后,你应该对Web Scraping更熟悉一些。
设 置
我们将看到的用Java爬取Ajax网站的第一个方法是通过使用PhantomJS与Selenium和GhostDriver。
PhantomJS是一个基于WebKit的无头网络浏览器(用于Chrome和Safari)。它的速度相当快,而且在渲染Dom方面做得很好,就像一个正常的网络浏览器一样。
- 首先你需要下载PhantomJS
- 然后把这个添加到你的pom.xml中。
com.github.detro phantomjsdriver 1.2.0
和这个:
org.seleniumhq.selenium selenium-java 2.53.1
##PhantomJS和Selenium
现在我们要使用Selenium和GhostDriver来 “引导 “PhantomJS。
我们将看到的例子是在一个新闻网站上的一个简单的 “查看更多 “按钮,执行ajax调用来加载更多的新闻。 所以你可能认为打开PhantomJS来点击一个简单的按钮是浪费时间和过度的?当然是这样的!
像往常一样,我们必须打开Chrome开发工具或你喜欢的检查器,看看如何选择 “加载更多 “按钮,然后点击它。
现在我们来看看一些代码:
private static String USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36"; private static DesiredCapabilities desiredCaps ; private static WebDriver driver ; public static void initPhantomJS(){ desiredCaps = new DesiredCapabilities(); desiredCaps.setJavascriptEnabled(true); desiredCaps.setCapability("takesScreenshot", false); desiredCaps.setCapability(PhantomJSDriverService.PHANTOMJS_EXECUTABLE_PATH_PROPERTY, "/usr/local/bin/phantomjs"); desiredCaps.setCapability(PhantomJSDriverService.PHANTOMJS_PAGE_CUSTOMHEADERS_PREFIX + "User-Agent", USER_AGENT); ArrayList cliArgsCap = new ArrayList(); cliArgsCap.add("--web-security=false"); cliArgsCap.add("--ssl-protocol=any"); cliArgsCap.add("--ignore-ssl-errors=true"); cliArgsCap.add("--webdriver-loglevel=ERROR"); desiredCaps.setCapability(PhantomJSDriverService.PHANTOMJS_CLI_ARGS, cliArgsCap); driver = new PhantomJSDriver(desiredCaps); driver.manage().window().setSize(new Dimension(1920, 1080)); }
设置PhantomJs和Selenium的代码太多了!我建议你读一下文档,看看你可以传递给PhantomJS的许多参数。
注意,你必须用你自己的phantomJs可执行路径替换/usr/local/bin/phantomjs
然后在一个主方法中:
System.setProperty("phantomjs.page.settings.userAgent", USER_AGENT); String baseUrl = "https://www.inshorts.com/en/read" ; initPhantomJS(); driver.get(baseUrl) ; int nbArticlesBefore = driver.findElements(By.xpath("//div[@class='card-stack']/div")).size(); driver.findElement(By.id("load-more-btn")).click(); // We wait for the ajax call to fire and to load the response into the page Thread.sleep(800); int nbArticlesAfter = driver.findElements(By.xpath("//div[@class='card-stack']/div")).size(); System.out.println(String.format("Initial articles : %s Articles after clicking : %s", nbArticlesBefore, nbArticlesAfter));
在这里,我们调用initPhantomJs()
方法来设置一切,然后我们用它的id选择按钮并点击它。
代码的另一部分是计算我们在页面上的文章数量,并打印出来,以显示我们已经加载了什么。
我们也可以用driver.getPageSource()
打印出整个dom,并在真正的浏览器中打开它,看看点击前后的区别。
我建议你看一下Selenium Webdriver的文档,有很多很酷的方法来操作DOM。
我使用了一个肮脏的解决方案,用我的Thread.sleep(800)
来等待Ajax调用完成。 它是肮脏的,因为它是一个任意的数字,如果我们可以只等待执行该Ajax调用的时间,刮刀可以运行得更快。
还有其他解决这个问题的方法。
public static void waitForAjax(WebDriver driver) { new WebDriverWait(driver, 180).until(new ExpectedCondition() { public Boolean apply(WebDriver driver) { JavascriptExecutor js = (JavascriptExecutor) driver; return (Boolean) js.executeScript("return jQuery.active == 0"); } }); }
如果你看一下我们点击按钮时执行的功能,你会发现它使用的是jQuery。
这段代码将等待,直到变量jQuery.active等于0(这似乎是一个jQuery的内部变量,计算正在进行的ajax调用的数量)。
如果我们知道Ajax调用应该渲染哪些DOM元素,我们就可以在WebDriverWait条件中使用那个id/class/xpath:
wait.until(ExpectedConditions.elementToBeClickable(By.xpath(xpathExpression))) [文中代码源自Scrapingbee]
总 结
所以我们已经看到了一点关于如何使用PhantomJS与Java的情况。当你有几十个Ajax调用,以及大量的Javascript被执行以正确渲染页面时,可能会很难抓取出你想要的数据,而PhantomJS/Selenium就是来拯救你的。