发布时间:2025-12-09 12:04:10 浏览次数:2
htmlunit是一款开源的Java页面分析工具,读取页面后,可以有效的使用htmlunit 分析页面上的内容。项目可以模拟浏览器运行,被誉为Java浏览器的开源实现。这个没有界面的浏览器,运行速度也是非常迅速的。
<!-- https://mvnrepository.com/artifact/net.sourceforge.htmlunit/htmlunit --><dependency> <groupId>net.sourceforge.htmlunit</groupId> <artifactId>htmlunit</artifactId> <version>2.42.0</version></dependency>在接口中设置默认方法,子类不用实现即可调用。
public interface NewsPuller { void pullNews(); default Document getHtmlFromUrl(String url, boolean useHtmlUnit) throws Exception { if (!useHtmlUnit) { return Jsoup.connect(url) //模拟火狐浏览器 .userAgent("Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)") .get(); } WebClient webClient = new WebClient(BrowserVersion.CHROME); //新建一个模拟谷歌Chrome浏览器的浏览器客户端对象 webClient.getOptions().setJavaScriptEnabled(true); //很重要,启用JS webClient.getOptions().setCssEnabled(false); //是否启用CSS, 因为不需要展现页面, 所以不需要启用 webClient.getOptions().setActiveXNative(false); webClient.getOptions().setCssEnabled(false); webClient.getOptions().setThrowExceptionOnScriptError(false); //当JS执行出错的时候是否抛出异常 webClient.getOptions().setThrowExceptionOnFailingStatusCode(false); //当HTTP的状态非200时是否抛出异常 //webClient.setAjaxController(new NicelyResynchronizingAjaxController());//设置支持AJAX webClient.getOptions().setUseInsecureSSL(true); webClient.getOptions().setTimeout(10 * 1000); HtmlPage rootPage = null; try { rootPage = webClient.getPage(url); webClient.waitForBackgroundJavaScript(10 * 1000); //异步JS执行需要耗时,所以这里线程要阻塞10秒,等待异步JS执行结束 String htmlStr = rootPage.asXml(); //直接将加载完成的页面转换成xml格式的字符串 //System.out.println(htmlStr); return Jsoup.parse(htmlStr); //获取html文档 } finally { webClient.close(); } }}有时你想模仿一个特殊的浏览器,这可以通过WebClient构造函数的com.gargoylesoftware.htmlunit.BrowserVersion 参数实现,其中已经提供一些常见浏览器的常量,但是,你可以通过BrowserVersion 的实例说明创建你自己拥有的特殊版本。
WebClient webClient = new WebClient(BrowserVersion.CHROME); //新建一个模拟谷歌Chrome浏览器的浏览器客户端对象指定这个BrowserVersion 会改变用户代理发送到服务器的报头,也会改变一些JavaScript 的行为。
HtmlUnit对JavaScript的支持是其最大的亮点,也是其最需要完善的地方。总的来说HtmlUnit是一款很棒的java工程,值得我们花一些时间来学习和尝试,给我们的武器库增加一件武器,也许什么时候你就会用到它。
webClient.getOptions().setJavaScriptEnabled(true); //很重要,启用JS结合 Jsoup + HtmlUtil,爬取凤凰网新闻为例子:
@Component("ifengNewsPuller")public class IfengNewsPuller implements NewsPuller { private static final Logger logger = LoggerFactory.getLogger(IfengNewsPuller.class); @Value("${news.ifeng.url}") private String url; @Autowired private NewsService newsService; private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Override public void pullNews() { logger.info("开始拉取凤凰新闻!"); // 1. 获取首页 Document html = null; try { html = getHtmlFromUrl(url, false); } catch (Exception e) { logger.error("==============获取凤凰首页失败: {} =============", url); e.printStackTrace(); return; } // 2. jsoup 获取新闻 <a> 标签 Elements newsATags = html.select("p#newsList") .select("ul.news_list-3wjAJJJM") .select("li") .select("a"); // 3.从<a>标签中抽取基本信息,封装成news HashSet<News> newsSet = new HashSet<>(); for (Element a : newsATags) { String url = a.attr("href"); String title = a.text(); News n = new News(); n.setSource("凤凰"); n.setUrl(url); n.setTitle(title); n.setCreateDate(new Date()); newsSet.add(n); } // 4.根据新闻url访问新闻,获取新闻内容 newsSet.parallelStream().forEach(news -> { logger.info("开始抽取凤凰新闻《{}》内容:{}", news.getTitle(), news.getUrl()); Document newsHtml = null; try { newsHtml = getHtmlFromUrl(news.getUrl(), false); Elements contentElement = newsHtml.select("p.text-3zQ3cZD4"); if (contentElement.isEmpty()) { contentElement = newsHtml.select("p.caption-3_nUnnKX h1"); } if (contentElement.isEmpty()) { return; } // 直接从头部信息获取部分数据 String time = newsHtml.head().select("meta[name=og:time ]").attr("content"); if (StringUtils.isNotBlank(time)) { news.setNewsDate(sdf.parse(time)); } String content = contentElement.toString(); String image = NewsUtils.getImageFromContent(content); news.setContent(contentElement.text()); news.setImage(image); newsService.saveNews(news); logger.info("抽取凤凰新闻《{}》成功!", news.getTitle()); } catch (Exception e) { logger.error("凤凰新闻抽取失败:{}", news.getUrl()); e.printStackTrace(); } }); logger.info("凤凰新闻抽取完成!"); }}/** * @Description: http工具(使用net.sourceforge.htmlunit获取完整的html页面,即完成后台js代码的运行) * 参考1: https://www.cnblogs.com/davidwang456/articles/8693050.html * 参考2: https://blog.csdn.net/hundan_520520/article/details/79387982 * @Author Ray * @Date 2020/8/6 0006 13:29 * @Version 1.0 */public class HttpUtils { /** * 请求超时时间,默认20000ms */ private int timeout = 200000; /** * 等待异步JS执行时间,默认20000ms */ private int waitForBackgroundJavaScript = 20000; /** * HttpUtils 实例 */ private static HttpUtils httpUtils; /** * 单例模式 - 私有化构造函数 */ private HttpUtils() { } /** * 单例模式 - 获取实例 */ public static HttpUtils getInstance() { if (null == httpUtils) { httpUtils = new HttpUtils(); } return httpUtils; } /** * 将网页内容返回为解析后的文档格式 * @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); } /** * 将网页地址返回为解析后的文档格式 * @param url 待解析的页面地址 * @return 解析后的文档 * @throws Exception */ private static Document connectToDoc(String url) throws Exception { return Jsoup.connect(url) //模拟火狐浏览器 .userAgent("Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)") .get(); } /** * 解析页面 * 默认解析静态页面,如果需要爬取动态数据,请调用重载方法并设置为 true * @param url * @return * @throws Exception */ public String getHtmlPageResponse(String url) throws Exception { return getHtmlPageResponse(url, false); } public String getHtmlPageResponse(String url, boolean useHtmlUnit) throws Exception { if (!useHtmlUnit) { return connectToDoc(url).toString(); } WebClient webClient = new WebClient(BrowserVersion.CHROME); //新建一个模拟谷歌Chrome浏览器的浏览器客户端对象 webClient.getOptions().setJavaScriptEnabled(true); //很重要,启用JS webClient.getOptions().setCssEnabled(false); //是否启用CSS, 因为不需要展现页面, 所以不需要启用 webClient.getOptions().setActiveXNative(false); webClient.getOptions().setCssEnabled(false); webClient.getOptions().setThrowExceptionOnScriptError(false); //当JS执行出错的时候是否抛出异常 webClient.getOptions().setThrowExceptionOnFailingStatusCode(false); //当HTTP的状态非200时是否抛出异常 webClient.setAjaxController(new NicelyResynchronizingAjaxController());//设置支持AJAX webClient.getOptions().setTimeout(timeout); //设置“浏览器”的请求超时时间 webClient.setJavaScriptTimeout(timeout); //异步JS执行需要耗时, 等待异步JS执行结束 HtmlPage rootPage; String result = ""; try { rootPage = webClient.getPage(url); //设置链接地址 webClient.waitForBackgroundJavaScript(waitForBackgroundJavaScript); //该方法阻塞线程 result = rootPage.asXml(); //直接将加载完成的页面转换成xml格式的字符串 } finally { webClient.close(); } return result; } /** * 获取页面文档Document对象 * 默认 false */ public Document getHtmlPageResponseAsDocument(String url) throws Exception { return parseHtmlToDoc(getHtmlPageResponse(url, false)); } /** * 获取页面文档Document对象 * (如果为 true 等待异步JS执行) */ public Document getHtmlPageResponseAsDocument(String url, boolean useHtmlUnit) throws Exception { return parseHtmlToDoc(getHtmlPageResponse(url, useHtmlUnit)); } public int getTimeout() { return this.timeout; } /** * 设置请求超时时间 */ public void setTimeout(int timeout) { this.timeout = timeout; } public int getWaitForBackgroundJavaScript() { return waitForBackgroundJavaScript; } /** * 设置获取完整HTML页面时等待异步JS执行的时间 */ public void setWaitForBackgroundJavaScript(int waitForBackgroundJavaScript) { this.waitForBackgroundJavaScript = waitForBackgroundJavaScript; }}/** * @Description: HttpUtils 工具类测试 * @Author Ray * @Date 2020/8/6 0006 14:22 * @Version 1.0 */@SpringBootTestpublic class HttpUtilsTest { private static final String TEST_URL_STATIC = "https://www.baidu.com/"; private static final String TEST_URL_NOT_STATIC = "http://www.ifeng.com/"; /** * 处理静态页面 */ @Test public void testGetHtmlPageResponse() { HttpUtils httpUtils = HttpUtils.getInstance(); httpUtils.setTimeout(30000); httpUtils.setWaitForBackgroundJavaScript(30000); try { String htmlPageStr = httpUtils.getHtmlPageResponse(TEST_URL_STATIC); 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_STATIC); System.out.println(document); } catch (Exception e) { e.printStackTrace(); } } /** * 处理非静态页面 */ @Test public void testGetHtmlPageResponse2() { HttpUtils httpUtils = HttpUtils.getInstance(); httpUtils.setTimeout(30000); httpUtils.setWaitForBackgroundJavaScript(30000); try { String htmlPageStr = httpUtils.getHtmlPageResponse(TEST_URL_NOT_STATIC, true); System.out.println(htmlPageStr); } catch (Exception e) { e.printStackTrace(); } } @Test public void testGetHtmlPageResponseAsDocument2() { HttpUtils httpUtils = HttpUtils.getInstance(); httpUtils.setTimeout(30000); httpUtils.setWaitForBackgroundJavaScript(30000); try { Document document = httpUtils.getHtmlPageResponseAsDocument(TEST_URL_NOT_STATIC, true); System.out.println(document); } catch (Exception e) { e.printStackTrace(); } }}