`

htmlunit爬虫优化方案

阅读更多
发现很多人搞爬虫会把python作为首选技术,理由是简单,作为一家公司技术栈,多出一样语言是要多出很多维护成本的;本人最熟悉的还是java,所以对java内存浏览器技术htmlunit做了一次研究,发现原生的htmlunit的性能及对多线程的支持不是那么友好,特别是使用代理ip后,oom是很正常的,监控程序并查看源码总结问题原因:
  • 1、js执行器执行js是使用多线程执行,在关闭js执行线程的时候,使用com.gargoylesoftware.htmlunit.javascript.background.DefaultJavaScriptExecutor这个类的时候,有段代码。

引用
private void killThread() {
        if (eventLoopThread_ == null) {
            return;
        }
        try {
            eventLoopThread_.interrupt();
            eventLoopThread_.join(10_000);
        }
        catch (final InterruptedException e) {
            LOG.warn("InterruptedException while waiting for the eventLoop thread to join ", e);
            // ignore, this doesn't matter, we want to stop it
        }
        if (eventLoopThread_.isAlive()) {
            if (LOG.isWarnEnabled()) {
                LOG.warn("Event loop thread "
                        + eventLoopThread_.getName()
                        + " still alive at "
                        + System.currentTimeMillis());
                LOG.warn("Event loop thread will be stopped");
            }

            // Stop the thread
            eventLoopThread_.stop();
        }
    }

上面代码的问题:
引用
eventLoopThread_.interrupt();
eventLoopThread_.join(10_000);

不要觉得interrupt真的就会关闭线程,比如正在执行io操作或者同样该线程在处于sleep状态,interrupt就不会终止线程,所以这里住线程要等待eventLoopThread执行10秒才会继续往下跑。

2、DefaultJavaScriptExecutor在使用外部线程池开启webclient抓取网页的时候,经常会出现线程不关闭的情况,问题代码如下:

引用
public void run() {
        final boolean trace = LOG.isTraceEnabled();
        // this has to be a multiple of 10ms
        // otherwise the VM has to fight with the OS to get such small periods
        final long sleepInterval = 10;
        while (!shutdown_.get() && !Thread.currentThread().isInterrupted() && webClient_.get() != null) {
            final JavaScriptJobManager jobManager = getJobManagerWithEarliestJob();

            if (jobManager != null) {
                final JavaScriptJob earliestJob = jobManager.getEarliestJob();
                if (earliestJob != null) {
                    final long waitTime = earliestJob.getTargetExecutionTime() - System.currentTimeMillis();

                    // do we have to execute the earliest job
                    if (waitTime < 1) {
                        // execute the earliest job
                        if (trace) {
                            LOG.trace("started executing job at " + System.currentTimeMillis());
                        }
                        jobManager.runSingleJob(earliestJob);
                        if (trace) {
                            LOG.trace("stopped executing job at " + System.currentTimeMillis());
                        }

                        // job is done, have a look for another one
                        continue;
                    }
                }
            }

            // check for cancel
            if (shutdown_.get() || Thread.currentThread().isInterrupted() || webClient_.get() == null) {
                break;
            }

            // nothing to do, let's sleep a bit
            try {
                Thread.sleep(sleepInterval);
            }
            catch (final InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }


此处问题代码:
引用
while (!shutdown_.get() && !Thread.currentThread().isInterrupted() && webClient_.get() != null)

外部线程内部关闭webclient.close()的时候,当外部线程要主动关闭本线程的时候,就像outStream没把out.close()写在finally里面,永远不会关闭js执行器线程。

  • 3、其实htmlunit性能差还有一个最重要的问题所在,就是每次抓取同一个页面,都要去下载相同的资源,htmlunit下载页面的代码是在类com.gargoylesoftware.htmlunit.HttpWebConnection里面(js,css,jpg)


主要的方法代码如下:
引用
  /**
     * Reads the content of the stream and saves it in memory or on the file system.
     * @param is the stream to read
     * @param maxInMemory the maximumBytes to store in memory, after which save to a local file
     * @return a wrapper around the downloaded content
     * @throws IOException in case of read issues
     */
    public static DownloadedContent downloadContent(final InputStream is, final int maxInMemory) throws IOException {
        if (is == null) {
            return new DownloadedContent.InMemory(null);
        }

        try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            final byte[] buffer = new byte[1024];
            int nbRead;
            try {
                while ((nbRead = is.read(buffer)) != -1) {
                    bos.write(buffer, 0, nbRead);
                    if (bos.size() > maxInMemory) {
                        // we have exceeded the max for memory, let's write everything to a temporary file
                        final File file = File.createTempFile("htmlunit", ".tmp");
                        file.deleteOnExit();
                        try (OutputStream fos = Files.newOutputStream(file.toPath())) {
                            bos.writeTo(fos); // what we have already read
                            IOUtils.copyLarge(is, fos); // what remains from the server response
                        }
                        return new DownloadedContent.OnFile(file, true);
                    }
                }
            }
            catch (final ConnectionClosedException e) {
                LOG.warn("Connection was closed while reading from stream.", e);
                return new DownloadedContent.InMemory(bos.toByteArray());
            }
            catch (final EOFException e) {
                // this might happen with broken gzip content
                LOG.warn("EOFException while reading from stream.", e);
                return new DownloadedContent.InMemory(bos.toByteArray());
            }

            return new DownloadedContent.InMemory(bos.toByteArray());
        }
    }


修改代码,只要把重复下载的代码缓存起来,就可以大大增加抓取性能,同时还可以动态修改网页js。

4、htmlunit设置requestTimeout时是无法单独设置conectiontimeout和socketTimeout,比方说设置requestTimeout=10000,那么 htmlclient的conectiontimeout=10000和socketTimeout=10000,这是有问题的,conectiontimeout一般情况应该设置低于100毫秒为宜,设置代码在 com.gargoylesoftware.htmlunit.HttpWebConnection
方法:
引用
private static RequestConfig.Builder createRequestConfigBuilder(final int timeout, final InetAddress localAddress) {
        final RequestConfig.Builder requestBuilder = RequestConfig.custom()
                .setCookieSpec(HACKED_COOKIE_POLICY)
                .setRedirectsEnabled(false)
                .setLocalAddress(localAddress)

                // timeout
                .setConnectTimeout(timeout)
                .setConnectionRequestTimeout(timeout)
                .setSocketTimeout(timeout);
        return requestBuilder;
    }



综上,把上述几点都完善,htmlunit不比只能多进程的python爬虫性能差,而且能够做黑帽。
分享到:
评论

相关推荐

    htmlunit-2.31.jar

    标签"爬虫"和"抓取"强调了HTMLUnit在Web抓取领域的应用,它可以用于构建自动化的网络爬虫,从网页上批量收集和处理数据。而"解析"标签则提示我们HTMLUnit能够帮助解析复杂的HTML结构,提取所需的信息。 在实际使用...

    htmlunit-2.30核心文件

    这个"htmlunit-2.30"压缩包包含了HTMLUnit库的2.30版本,这是一个关键的更新,因为它可能包含了对新特性的支持、性能优化或者修复了一些已知问题。不用通过SourceForge下载,意味着这个版本可能是国内镜像站点提供的...

    htmlunit-2.26-bin

    7. **性能优化**:由于HTMLUnit是基于Java的,因此可以利用多线程技术并行处理多个网页,提高抓取效率。同时,合理设置`WebClient`的配置,如超时时间、重试策略等,也能提升性能。 8. **与其他库的集成**:...

    htmlunit-2.27-src.zip

    HTMLUnit是一个强大的Java库,它...总之,"htmlunit-2.27-src.zip"是一个宝贵的开发资源,提供了深入理解HTMLUnit运作机制的机会,无论是为了优化现有项目,还是为了学习Web自动化和爬虫技术,都具有很高的学习价值。

    htmlunit-2.14.jar下载,全套

    7. **Android兼容**:尽管HTMLUnit主要是为Java桌面环境设计的,但描述中提到“Android”,意味着这个特定的版本可能已经过优化,能够在Android平台上运行,这对于移动设备上的爬虫开发很有价值。 在使用HTMLUnit...

    java网络爬虫搜索引擎

    Java网络爬虫搜索引擎是开发用于抓取互联网上信息的...同时,面对各种反爬策略,还需要灵活应对,不断优化爬虫的稳定性和效率。通过学习和实践,你可以构建出强大的网络爬虫系统,用于抓取、分析和利用互联网上的信息。

    htmlunit-2.9-bin.zip )

    1. **无头浏览**:HTMLUnit可以在后台运行,无需用户界面,这对于自动化测试、爬虫或者需要大量网页处理的项目非常有用。 2. **JavaScript支持**:通过Rhino引擎,HTMLUnit能够执行JavaScript代码,这意味着它可以...

    Java开源的网络爬虫

    综上所述,"Java开源的网络爬虫"是一个全面的解决方案,涵盖网络请求、HTML解析、数据存储等多个方面,为开发者提供了构建高效、可扩展的爬虫的工具和平台。通过学习和使用这样的开源项目,开发者可以提升自己的网络...

    Java 项目-基于网络爬虫技术的网络新闻分析.zip

    总之,这个Java项目结合了网络爬虫技术和数据库管理,提供了一套完整的解决方案,用于从网络上抓取和分析新闻数据。学习和理解这个项目将有助于提升你在Java编程、网络爬虫和数据处理方面的技能。

    基于java的搜索引擎排名优化技术的实现(开题报告+源码+论文+所有需要的文档)

    8. **源码与文档**:项目提供了开题报告、论文和所有需要的文档,这有助于理解项目的整体设计思路、实施步骤和技术细节,以及优化过程中遇到的问题和解决方案。 9. **毕业设计**:此项目适合作为毕业设计,因为它...

    java采集工具(源码)

    Java采集工具是一款基于HTMLUnit库的数据采集程序,其源码提供了深入理解网页抓取和解析技术的机会。...通过对源码的学习,开发者可以深化对Java、HTMLUnit和XPath的理解,提升在网络爬虫领域的技能。

    徐凯 2017-08-291

    在舆情分析项目中,徐凯利用Jsoup、HtmlUnit等爬虫技术获取数据,通过Spring Cloud、Kafka和Spark2.2等工具进行数据处理。项目中引入了贝叶斯算法,通过数据爬取、标记、分词、模型训练和实时预测,实现了对公司新闻...

    java web项目实战大全源码搜索引擎

    通过学习这个项目的源码,开发者可以提升在Java Web开发、搜索引擎构建和网络爬虫方面的技能,从而能够构建自己的搜索解决方案或者改进现有的系统。对于想要深入理解Web应用全栈开发的人员来说,这是一个非常有价值...

    基于java的文本搜索引擎的设计与实现源码.zip

    1. **爬虫模块**:Java爬虫负责抓取网页内容,通常使用Jsoup或HtmlUnit等库解析HTML,提取文本信息。同时,爬虫需要处理URL管理、反爬虫策略以及多线程爬取等问题。 2. **预处理模块**:包括HTML去噪、分词、词干化...

    基于java小型搜索引擎的研究与实现.doc

    在设计过程中,将结合最新的技术趋势,如Ajax以提升交互性,HtmlUnit用于模拟浏览器行为,进一步优化搜索引擎的功能和性能。 总的来说,基于Java的小型搜索引擎研究与实现是一项旨在提高信息检索质量和效率的项目,...

Global site tag (gtag.js) - Google Analytics