前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >HttpUnit 基础知识

HttpUnit 基础知识

作者头像
Remember_Ray
发布2020-09-15 10:09:06
1.8K0
发布2020-09-15 10:09:06
举报
文章被收录于专栏:Ray学习笔记

HttpUnit

htmlunit是一款开源的Java页面分析工具,读取页面后,可以有效的使用htmlunit 分析页面上的内容。项目可以模拟浏览器运行,被誉为Java浏览器的开源实现。这个没有界面的浏览器,运行速度也是非常迅速的。

起步

依赖

代码语言:javascript
复制
<!-- https://mvnrepository.com/artifact/net.sourceforge.htmlunit/htmlunit -->
<dependency>
    <groupId>net.sourceforge.htmlunit</groupId>
    <artifactId>htmlunit</artifactId>
    <version>2.42.0</version>
</dependency>

示例

在接口中设置默认方法,子类不用实现即可调用。

代码语言:javascript
复制
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 的实例说明创建你自己拥有的特殊版本。

代码语言:javascript
复制
WebClient webClient = new WebClient(BrowserVersion.CHROME); //新建一个模拟谷歌Chrome浏览器的浏览器客户端对象

指定这个BrowserVersion 会改变用户代理发送到服务器的报头,也会改变一些JavaScript 的行为。

支持 JavaScript

HtmlUnit对JavaScript的支持是其最大的亮点,也是其最需要完善的地方。总的来说HtmlUnit是一款很棒的java工程,值得我们花一些时间来学习和尝试,给我们的武器库增加一件武器,也许什么时候你就会用到它。

代码语言:javascript
复制
webClient.getOptions().setJavaScriptEnabled(true); //很重要,启用JS

实际应用

结合 Jsoup + HtmlUtil,爬取凤凰网新闻为例子:

代码语言:javascript
复制
@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("div#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("div.text-3zQ3cZD4");
                if (contentElement.isEmpty()) {
                    contentElement = newsHtml.select("div.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("凤凰新闻抽取完成!");
    }
}

编写工具类

代码语言:javascript
复制
/**
 * @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("&nbsp;", "");
        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;
    }
}
代码语言:javascript
复制
/**
 * @Description: HttpUtils 工具类测试
 * @Author Ray
 * @Date 2020/8/6 0006 14:22
 * @Version 1.0
 */
@SpringBootTest
public 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();
        }
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-08-07|,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • HttpUnit
  • 起步
    • 依赖
      • 示例
        • 模仿特定浏览器
        • 支持 JavaScript
      • 实际应用
        • 编写工具类
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档