htmlunit爬虫优化方案

标签: htmlunit 爬虫 优化 | 发表时间:2019-10-10 08:25 | 作者:lyongq04
出处:https://www.iteye.com
发现很多人搞爬虫会把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爬虫性能差,而且能够做黑帽。

已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [htmlunit 爬虫 优化] 推荐:

htmlunit爬虫优化方案

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

java模拟浏览器包htmlunit,selenium

- - BlogJava-首页技术区
发现一个很不错的模拟浏览器包htmlunit,它可以直接执行访问网站地址,并执行相应的JavaScript脚本;这个功能对于网站爬虫有很大的帮助,一些网站使用了ajax,如果使用简单的http访问只能抓到原始的html源码,但对于页面内执行的ajax却无法获取;使用这个包后,可以将执行ajax后的html源码一并抓取下来.

网络爬虫

- - 四火的唠叨
文章系本人原创,转载请保持完整性并注明出自 《四火的唠叨》. 最近在写一个程序,去爬热门事件和热门关键词网站上的数据. 网络爬虫也叫做网络蜘蛛,是一种互联网机器人,把需要的网页撷取下来,组织成适当格式存储. 它是搜索引擎的重要组成部分,虽然从技术实现上来说,它的难度往往要小于对于得到的网页信息的处理.

Google 图片爬虫

- - 吴良超的学习笔记
这里的 Google 图片爬虫指的是爬取在 Google 上通过关键词搜索得到的图片,由于最近需要一些特定领域的图片,而且现有的数据库满足不了要求,因此就想通过 Google 搜索筛选出这些特定领域的图片,然后下载下来后再进行人工筛选. 这里采用了两种方法,区别在于是否需要解析网页端的 JS 代码.

JSOUP实现简单爬虫

- - ITeye博客
这个说是简单爬虫 其实连个爬虫也算不上吧 功能太精简了.... 流程很简单: 输入几个初始的网页 然后通过JSOUP获取网页中的a标签的href的值. 接着把新得到的地址放入任务队列中. 实现中的worker是一个单线程的派发器 用于产生Parser. Parser用于完成网页的保存 网页的解析 以及入队列操作.

最全Python爬虫总结

- - CSDN博客综合推荐文章
最近总是要爬取一些东西,索性就把Python爬虫的相关内容都总结起来了,自己多动手还是好. (2)保存爬取的图片/视频和文件和网页. (7)某个网站的站内所有目录爬虫. (9)爬虫框架Scrapy   . 二,保存爬取的图片/视频和文件和网页. #图片/视频和文件和网页的地址抓取下来后,利用模块urllib里的urlretrieve()方法下载下来:.

爬虫需谨慎!那些你不知道的爬虫反爬虫套路 学起来

- - IT瘾-bigdata
爬虫与反爬虫,是一个很不阳光的行业. 第一是,这个行业是隐藏在地下的,一般很少被曝光出来. 很多公司对外都不会宣称自己有爬虫团队,甚至隐瞒自己有反爬虫团队的事实. 这可能是出于公司战略角度来看的,与技术无关. 第二是,这个行业并不是一个很积极向上的行业. 很多人在这个行业摸爬滚打了多年,积攒了大量的经验,但是悲哀的发现,这些经验很难兑换成闪光的简历.

Scrapy爬虫笔记【1-基本框架】

- - CSDN博客研发管理推荐文章
Scrapy 是一款抓取网页并从中提取结构化数据的应用程序框架,它具有非常广泛的应用场景,如:数据挖掘、信息处理和历史信息归档等. 尽管 Scrapy 最初被设计用于 屏幕抓取(准确地说是 网页抓取),但您也可以仅使用它的 API 来提取数据(就像. Amazon Associates Web Services)或作为通用的网页爬虫来使用.

JAVA爬虫Nutch、WebCollector的正则约束

- - CSDN博客互联网推荐文章
爬虫爬取时,需要约束爬取的范围. 基本所有的爬虫都是通过正则表达式来完成这个约束. 代表"http://www.xinhuanet.com/"后加任意个任意字符(可以是0个). 通过这个正则可以约束爬虫的爬取范围,但是这个正则并不是表示爬取新华网所有的网页. 新华网并不是只有www.xinhuanet.com这一个域名,还有很多子域名,类似:news.xinhuanet.com.

Python写爬虫与网页解析

- - 互联网实践
Python写个简单爬虫,并作网页解析,还是非常高效的. urllib2是urllib得增强版,httplib更为底层,可以理解为urllib是对httplib的抽象. httplib是一个相对底层的http请求模块,其上有专门的包装模块,如urllib内建模块,goto等第三方模块,但是封装的越高就越不灵活,比如urllib模块里请求错误时就不会返回结果页的内容,只有头信息,对于某些需要检测错误请求返回值的场景就不适用,所以就得用这个模块了.