使用HtmlUnit获取html页面
https://blog.csdn.net/johnson_moon/article/details/78457543
HtmlUnit簡介
- 官網介紹
- 中文翻譯
HtmlUnit使用場景
- httpClient的局限性
對于使用java實現的網頁爬蟲程序,我們一般可以使用apache的HttpClient組件進行HTML頁面信息的獲取,HttpClient實現的http請求返回的響應一般是純文本的document頁面,即最原始的html頁面。
對于一個靜態的html頁面來說,使用httpClient足夠將我們所需要的信息爬取出來了。但是對于現在越來越多的動態網頁來說,更多的數據是通過異步JS代碼獲取并渲染到的,最開始的html頁面是不包含這部分數據的。
上圖我們所見到的網頁,在最初的document加載完成之后,并不會看到紅框中的數據列表。瀏覽器通過執行異步JS請求,將獲取到的動態數據,渲染到最初的document頁面中,才最終變成了我們看到的網頁。而對于這部分需要執行JS代碼獲取的數據,httpClient就顯得無能為力了。雖然我們可以通過研究拿到JS執行的請求路徑再用java代碼獲取我們需要的這部分數據,且不說我們能不能夠從JS腳本中分析到這個請求路徑和請求參數,光是分析這部分源碼的代價就已經很高了。
- HtmlUnit來解決
通過上面的介紹,我們了解了現在很大一部分動態網頁,展現的數據都是通過異步JS請求獲取,然后再通過JS對頁面進行渲染得到的。那我們是不是可以進行這么一個假設,假設我們的爬蟲程序模擬了一個瀏覽器,在獲取html頁面之后,像瀏覽器一樣執行異步JS代碼,等到JS將html頁面渲染完成之后,就可以愉快的獲取頁面上的節點信息了。那么有沒有這樣的java程序呢?
答案是有的。
HtmlUnit就是這么一個程序庫,用來做出了界面展示意外所有的異步工作。由于沒有了展示這一塊耗時的工作,HtmlUnit加載完成一個完整的網頁要比實際的瀏覽器塊多了。并且根據不同配置,HtmlUnit可以模擬市面上常用的瀏覽器如Chrome、Firefox、IE瀏覽器等。
通過HtmlUnit庫,加載一個完整的Html頁面(圖片視頻除外),然后就可以將其轉換成我們常用的字串格式,用其他工具如Jsoup來獲取其中的元素了。當然也可以直接在HtmlUnit提供的對象中獲取網頁元素,甚至是操作如按鈕、表單等控件。除了不能像可見瀏覽器一樣用鼠標鍵盤瀏覽網頁之外,我們可以用HtmlUnit來模擬操作其他的一切操作,像登錄網站,撰寫博客等等都是可以完成的。當然網頁內容爬取是最簡單的一個應用了。
HtmlUnit使用方法
1.新建maven工程,添加HtmlUnit依賴:
<dependencies><dependency><groupId>net.sourceforge.htmlunit</groupId> <artifactId>htmlunit</artifactId> <version>2.27</version> </dependency> </dependencies>- 1
- 2
- 3
- 4
- 5
- 6
- 7
2.新建一個Junit TestCase來嘗試一下程序庫的使用
程序代碼注釋如下:
package xuyihao.util.depend;import com.gargoylesoftware.htmlunit.BrowserVersion; import com.gargoylesoftware.htmlunit.NicelyResynchronizingAjaxController; import com.gargoylesoftware.htmlunit.WebClient; import com.gargoylesoftware.htmlunit.html.HtmlPage; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.junit.Test; import java.util.List; /** * Created by xuyh at 2017/11/6 14:03. */ public class HtmlUtilTest { @Test public void test() { final WebClient webClient = new WebClient(BrowserVersion.CHROME);//新建一個模擬谷歌Chrome瀏覽器的瀏覽器客戶端對象 webClient.getOptions().setThrowExceptionOnScriptError(false);//當JS執行出錯的時候是否拋出異常, 這里選擇不需要 webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);//當HTTP的狀態非200時是否拋出異常, 這里選擇不需要 webClient.getOptions().setActiveXNative(false); webClient.getOptions().setCssEnabled(false);//是否啟用CSS, 因為不需要展現頁面, 所以不需要啟用 webClient.getOptions().setJavaScriptEnabled(true); //很重要,啟用JS webClient.setAjaxController(new NicelyResynchronizingAjaxController());//很重要,設置支持AJAX HtmlPage page = null; try { page = webClient.getPage("http://ent.sina.com.cn/film/");//嘗試加載上面圖片例子給出的網頁 } catch (Exception e) { e.printStackTrace(); }finally { webClient.close(); } webClient.waitForBackgroundJavaScript(30000);//異步JS執行需要耗時,所以這里線程要阻塞30秒,等待異步JS執行結束 String pageXml = page.asXml();//直接將加載完成的頁面轉換成xml格式的字符串 //TODO 下面的代碼就是對字符串的操作了,常規的爬蟲操作,用到了比較好用的Jsoup庫 Document document = Jsoup.parse(pageXml);//獲取html文檔 List<Element> infoListEle = document.getElementById("feedCardContent").getElementsByAttributeValue("class", "feed-card-item");//獲取元素節點等 infoListEle.forEach(element -> { System.out.println(element.getElementsByTag("h2").first().getElementsByTag("a").text()); System.out.println(element.getElementsByTag("h2").first().getElementsByTag("a").attr("href")); }); } }上面的例子將獲取到的頁面中消息列表的標題和超鏈接URL打印到控制臺,操作HTML文檔的庫是Jsoup,需要添加依賴:
<dependency><groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.8.3</version> </dependency>- 1
- 2
- 3
- 4
- 5
經過三十秒的等待,控制臺輸出的結果是這樣的:
十一月 06, 2017 2:17:05 下午 com.gargoylesoftware.htmlunit.IncorrectnessListenerImpl notify 警告: Obsolete content type encountered: 'application/x-javascript'. 十一月 06, 2017 2:17:06 下午 com.gargoylesoftware.htmlunit.javascript.StrictErrorReporter runtimeError 嚴重: runtimeError: message=[An invalid or illegal selector was specified (selector: '*,:x' error: Invalid selector: :x).] sourceName=[http://n.sinaimg.cn/lib/core/core.js] line=[1] lineSource=[null] lineOffset=[0] 十一月 06, 2017 2:17:06 下午 com.gargoylesoftware.htmlunit.IncorrectnessListenerImpl notify 警告: Obsolete content type encountered: 'application/x-javascript'. 2017-11-06 14:17:11.003:INFO::JS executor for com.gargoylesoftware.htmlunit.WebClient@618c5d94: Logging initialized @7179ms to org.eclipse.jetty.util.log.StdErrLog 十一月 06, 2017 2:17:11 下午 com.gargoylesoftware.htmlunit.javascript.host.WebSocket run 嚴重: WS connect error java.util.concurrent.ExecutionException: org.eclipse.jetty.websocket.api.UpgradeException: 0 null at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357) at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895) at com.gargoylesoftware.htmlunit.javascript.host.WebSocket$1.run(WebSocket.java:151) at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:672) at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:590) at java.lang.Thread.run(Thread.java:748) Caused by: org.eclipse.jetty.websocket.api.UpgradeException: 0 null at org.eclipse.jetty.websocket.client.WebSocketUpgradeRequest.onComplete(WebSocketUpgradeRequest.java:513) at org.eclipse.jetty.client.ResponseNotifier.notifyComplete(ResponseNotifier.java:193) at org.eclipse.jetty.client.ResponseNotifier.notifyComplete(ResponseNotifier.java:185) at org.eclipse.jetty.client.HttpExchange.notifyFailureComplete(HttpExchange.java:269) at org.eclipse.jetty.client.HttpExchange.abort(HttpExchange.java:240) at org.eclipse.jetty.client.HttpConversation.abort(HttpConversation.java:141) at org.eclipse.jetty.client.HttpRequest.abort(HttpRequest.java:748) at org.eclipse.jetty.client.HttpDestination.abort(HttpDestination.java:444) at org.eclipse.jetty.client.HttpDestination.failed(HttpDestination.java:224) at org.eclipse.jetty.client.AbstractConnectionPool$1.failed(AbstractConnectionPool.java:122) at org.eclipse.jetty.util.Promise$Wrapper.failed(Promise.java:136) at org.eclipse.jetty.client.HttpClient$1$1.failed(HttpClient.java:588) at org.eclipse.jetty.client.AbstractHttpClientTransport.connectFailed(AbstractHttpClientTransport.java:154) at org.eclipse.jetty.client.AbstractHttpClientTransport$ClientSelectorManager.connectionFailed(AbstractHttpClientTransport.java:199) at org.eclipse.jetty.io.ManagedSelector$Connect.failed(ManagedSelector.java:655) at org.eclipse.jetty.io.ManagedSelector$Connect.access$1300(ManagedSelector.java:622) at org.eclipse.jetty.io.ManagedSelector$1.failed(ManagedSelector.java:364) at org.eclipse.jetty.io.ManagedSelector$CreateEndPoint.run(ManagedSelector.java:604) ... 3 more Caused by: java.lang.NullPointerException at org.eclipse.jetty.io.ssl.SslClientConnectionFactory.newConnection(SslClientConnectionFactory.java:59) at org.eclipse.jetty.client.AbstractHttpClientTransport$ClientSelectorManager.newConnection(AbstractHttpClientTransport.java:191) at org.eclipse.jetty.io.ManagedSelector.createEndPoint(ManagedSelector.java:420) at org.eclipse.jetty.io.ManagedSelector.access$1600(ManagedSelector.java:61) at org.eclipse.jetty.io.ManagedSelector$CreateEndPoint.run(ManagedSelector.java:599) ... 3 more 十一月 06, 2017 2:17:16 下午 com.gargoylesoftware.htmlunit.IncorrectnessListenerImpl notify 警告: Obsolete content type encountered: 'application/x-javascript'. 十一月 06, 2017 2:17:21 下午 com.gargoylesoftware.htmlunit.IncorrectnessListenerImpl notify 警告: Obsolete content type encountered: 'text/javascript'. 十一月 06, 2017 2:17:21 下午 com.gargoylesoftware.htmlunit.IncorrectnessListenerImpl notify 警告: Obsolete content type encountered: 'text/javascript'. 時隔17年重溫《EUREKA》 宮崎葵:這次哭得很兇 http://ent.sina.com.cn/m/f/2017-11-06/doc-ifynmzrs7411439.shtml 模式單一成審美疲勞 超級英雄電影該如何突圍? http://ent.sina.com.cn/m/f/2017-11-06/doc-ifynmnae2196060.shtml 組圖:《天生不對》首映 薛凱琪不規則紅裙優雅可人 13 http://slide.ent.sina.com.cn/film/slide_4_704_247725.html 電影資料館達成線上售票合作 影迷不必排隊買票 http://ent.sina.com.cn/m/c/2017-11-06/doc-ifynmvuq8917282.shtml 組圖:詹妮弗加納去教堂路遇好友 白裙清新心情靚 4 http://slide.ent.sina.com.cn/film/h/slide_4_704_247702.html 《東方快車》發幕后特輯 唯美復古凸顯品質 http://ent.sina.com.cn/m/f/2017-11-06/doc-ifynnnsc7188105.shtml 組圖:梅根福克斯穿緊身衣身材火辣 踩拖鞋抱瑜伽墊 4 http://slide.ent.sina.com.cn/film/slide_4_704_247699.html忽略HtmlUnit執行時候的報錯信息,可以看到最后還是成功的將結果打印了出來了。
3.編寫工具類
嘗試了一下HtmlUnit加載網頁并解析之后,我們可以編寫一個工具類為之后的爬蟲程序的使用鋪路了,代碼如下:
import org.jsoup.Jsoup; import org.jsoup.nodes.Document;import com.gargoylesoftware.htmlunit.BrowserVersion; import com.gargoylesoftware.htmlunit.NicelyResynchronizingAjaxController; import com.gargoylesoftware.htmlunit.WebClient; import com.gargoylesoftware.htmlunit.html.HtmlPage; /** * <pre> * Http工具,包含: * 高級http工具(使用net.sourceforge.htmlunit獲取完整的html頁面,即完成后臺js代碼的運行) * </pre> * Created by xuyh at 2017/7/17 19:08. */ public class HttpUtils { /** * 請求超時時間,默認20000ms */ private int timeout = 20000; /** * 等待異步JS執行時間,默認20000ms */ private int waitForBackgroundJavaScript = 20000; private static HttpUtils httpUtils; private HttpUtils() { } /** * 獲取實例 * * @return */ public static HttpUtils getInstance() { if (httpUtils == null) httpUtils = new HttpUtils(); return httpUtils; } public int getTimeout() { return timeout; } /** * 設置請求超時時間 * * @param timeout */ public void setTimeout(int timeout) { this.timeout = timeout; } public int getWaitForBackgroundJavaScript() { return waitForBackgroundJavaScript; } /** * 設置獲取完整HTML頁面時等待異步JS執行的時間 * * @param waitForBackgroundJavaScript */ public void setWaitForBackgroundJavaScript(int waitForBackgroundJavaScript) { this.waitForBackgroundJavaScript = waitForBackgroundJavaScript; } /** * 將網頁返回為解析后的文檔格式 * * @param html * @return * @throws Exception */ public static Document parseHtmlToDoc(String html) throws Exception { return removeHtmlSpace(html); } private static Document removeHtmlSpace(String str) { Document doc = Jsoup.parse(str); String result = doc.html().replace(" ", ""); return Jsoup.parse(result); } /** * 獲取頁面文檔字串(等待異步JS執行) * * @param url 頁面URL * @return * @throws Exception */ public String getHtmlPageResponse(String url) throws Exception { String result = ""; final WebClient webClient = new WebClient(BrowserVersion.CHROME); webClient.getOptions().setThrowExceptionOnScriptError(false);//當JS執行出錯的時候是否拋出異常 webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);//當HTTP的狀態非200時是否拋出異常 webClient.getOptions().setActiveXNative(false); webClient.getOptions().setCssEnabled(false);//是否啟用CSS webClient.getOptions().setJavaScriptEnabled(true); //很重要,啟用JS webClient.setAjaxController(new NicelyResynchronizingAjaxController());//很重要,設置支持AJAX webClient.getOptions().setTimeout(timeout);//設置“瀏覽器”的請求超時時間 webClient.setJavaScriptTimeout(timeout);//設置JS執行的超時時間 HtmlPage page; try { page = webClient.getPage(url); } catch (Exception e) { webClient.close(); throw e; } webClient.waitForBackgroundJavaScript(waitForBackgroundJavaScript);//該方法阻塞線程 result = page.asXml(); webClient.close(); return result; } /** * 獲取頁面文檔Document對象(等待異步JS執行) * * @param url 頁面URL * @return * @throws Exception */ public Document getHtmlPageResponseAsDocument(String url) throws Exception { return parseHtmlToDoc(getHtmlPageResponse(url)); } }可以通過這樣的方式調用本工具:
import org.jsoup.nodes.Document; import org.junit.Test;public class HttpUtilsTest { private static final String TEST_URL = "http://www.google.com/"; @Test public void testGetHtmlPageResponse() { HttpUtils httpUtils = HttpUtils.getInstance(); httpUtils.setTimeout(30000); httpUtils.setWaitForBackgroundJavaScript(30000); try { String htmlPageStr = httpUtils.getHtmlPageResponse(TEST_URL); //TODO System.out.println(htmlPageStr); } catch (Exception e) { e.printStackTrace(); } } @Test public void testGetHtmlPageResponseAsDocument() { HttpUtils httpUtils = HttpUtils.getInstance(); httpUtils.setTimeout(30000); httpUtils.setWaitForBackgroundJavaScript(30000); try { Document document = httpUtils.getHtmlPageResponseAsDocument(TEST_URL); //TODO System.out.println(document); } catch (Exception e) { e.printStackTrace(); } } }源碼地址
https://github.com/johnsonmoon/HttpUtils.git轉載于:https://www.cnblogs.com/davidwang456/articles/8693050.html
總結
以上是生活随笔為你收集整理的使用HtmlUnit获取html页面的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: fastjson读取json配置文件
- 下一篇: Fiddler抓取手机APP数据包