使用并发来提高数据抓取的效率

在做项目的时候,有一个增强是需要把两个列从一个表迁移到另一个表,在做正式的迁移之前需要对原始数据进行备份,备份的实现也比较简单,就是把数据从数据库中读出来然后写到CSV文件中,主键以及列与列之间用分号分隔。我count了一下,总共是有559行数据,数据量其实挺小,之前的实现并没有使用多线程并发取数据,因为真实代码是受公司保护的,我用伪代码来描述一下之前的实现:

public void foo() throws Exception {
    PrintWriter pw = new PrintWriter(xxx);
    List<Map> ml = service.getData();
    for (Map m : ml) {
        // compose string from map       pw.write(string);        pw.write("\n");
    }
    pw.close();
}

在for循环里面,数据需要依照ml的顺序一行一行写入文件,下一个写入操作必须等待上一个写入操作完成之后才能执行,这样执行完整个写入的时间就有点长,才559行数据花了16秒。

想想如果有几百万行数据呢,这个耗费的时间就很可观了,于是决定使用多线程来解决这个问题,在这个例子里面我想比较一下Callable和Runnable接口的性能,我先用Callable来实现并发:

 1 public void foo() throws Exception {
 2     final PrintWriter pw = new PrintWriter(xxx);
 3     List<Map> ml = service.getData();
 4     ExecutorService exec = Executors.newCachedThreadPool();
 5     for (final Map m : ml) {
 6         Callable cal = new Callable() {
 7             @Override
 8             public Object call() throws Exception {
 9                 // compose string
10                 pw.write(string);
11                 pw.write("\n");
12             }
13         };
14         exec.submit(cal);
15     }
16     pw.write("completed");
17     pw.close();
18 }

从运行结果可以看到,用了多线程之后该方法的执行只花费了不到1秒,没看错,性能整整提高了16倍之多,但是打开结果文件看的时候发现有几个问题需要修复:

问题一: 有的行有空格,有的行包含两条数据

问题二: 线程还没有完全结束,completed就打印出来了

可见在并发编程中,有的地方还是要注意,否则得到的结果就不是我们想要的。先来看看问题一,这个问题产生的原因是pw.write(string);和pw.write("\n");两者不满足原子性,因为线程是并发执行的,如果线程1和线程2同时在写string,这样就会把两行数据写到一行,如果同时写换行,则会出现多个换行的情况,这个问题的解决方案有两个:

1. 在pw.write("\n");后让当前正在执行的线程小睡一会儿,例如Thread.sleep(500);

2. 让pw.write(string);和pw.write("\n");两个写入操作满足原子性,也就是这两个操作分隔为两步。

第一种解决方案会造成运行时间加长,所以我采用第二种方案,就是这样:pw.write(sb.toString()+"\n"); 这下满足原子性要求了吧

问题二出现的原因是并发的线程还可有完全执行完,completed就打印了,处理起来也倍儿简单,在打印completed之前让当前线程小睡半秒,再执行,果然问题都没有了。

刚才是用Callable实现并发的,大家知道callable跟runnable的区别是,Callable是concurrent包中,JDK1.5后新增的,Callable的call方法可以有返回值也能抛出检查型异常,Runnable的run方法就不能了。现在使用Runnable来看看:

public void foo() throws Exception {
    final PrintWriter pw = new PrintWriter(xxx);
    List<Map> ml = service.getData();
    for (final Map m : ml) {
        Runnable cal = new Runnable() {
            @Override
            public void call() {
                // compose string
                pw.write(string);
                pw.write("\n");
            }
        };
        new Thread(cal).start();
    }
    pw.write("completed");
    pw.close();
}

可以看到,Runnable跟Callable的性能并没有什么区别。

时间: 2024-10-25 20:18:38

使用并发来提高数据抓取的效率的相关文章

大数据抓取采集框架(摘抄至http://blog.jobbole.com/46673/)

摘抄至http://blog.jobbole.com/46673/ 随着BIG DATA大数据概念逐渐升温,如何搭建一个能够采集海量数据的架构体系摆在大家眼前.如何能够做到所见即所得的无阻拦式采集.如何快速把不规则页面结构化并存储.如何满足越来越多的数据采集还要在有限时间内采集.这篇文章结合我们自身项目经验谈一下. 我们来看一下作为人是怎么获取网页数据的呢? 1.打开浏览器,输入网址url访问页面内容.2.复制页面内容的标题.作者.内容.3.存储到文本文件或者excel. 从技术角度来说整个过程

数据抓取的艺术(三):抓取Google数据之心得

本来是想把这部分内容放到前一篇<数据抓取的艺术(二):数据抓取程序优化>之中.但是随着任务的完成,我越来越感觉到其中深深的趣味,现总结如下: (1)时间     时间是一个与抓取规模相形而生的因素,数据规模越大,时间消耗往往越长.所以程序优化变得相当重要,要知道抓取时间越长,出错的可能性就越大,这还不说程序需要人工干预的情境.一旦运行中需要人工干预,时间越长,干预次数越多,出错的几率就更大了.在数据太多,工期太短的情况下,使用多线程抓取,也是一个好办法,但这会增加程序复杂度,对最终数据准确性产

Hibernate学习---第十一节:Hibernate之数据抓取策略&amp;批量抓取

1.hibernate 也可以通过标准的 SQL 进行查询 (1).将SQL查询写在 java 代码中 /** * 查询所有 */ @Test public void testQuery(){ // 基于标准的 sql 语句查询 String sql = "select * from t_person"; // 通过 createSQLQuery 获取 SQLQuery,而 SQLQuer 是 Query的子类 SQLQuery query = session.createSQLQue

数据抓取的艺术(三)

原文地址:http://blog.chinaunix.net/uid-22414998-id-3696649.html 本来是想把这部分内容放到前一篇<数据抓取的艺术(二):数据抓取程序优化>之中.但是随着任务的完成,我越来越感觉到其中深深的趣味,现总结如下: (1)时间     时间是一个与抓取规模相形而生的因素,数据规模越大,时间消耗往往越长.所以程序优化变得相当重要,要知道抓取时间越长,出错的可能性就越大,这还不说程序需要人工干预的情境.一旦运行中需要人工干预,时间越长,干预次数越多,出

数据抓取的艺术(二)

原文地址:http://blog.chinaunix.net/uid-22414998-id-3695673.html 续前文:<数据抓取的艺术(一):Selenium+Phantomjs数据抓取环境配置>. 程序优化:第一步开始: for i in range(startx,total): for j in range(starty,total): BASE_URL = createTheUrl([item[i],item[j]]) driver.get(BASE_URL) driver =

ngrep环回接口数据抓取方法,使用-d lo参数

ngrep环回接口数据抓取方法,使用-d lo参数,注意顺序: ngrep -W byline -d lo port 80

利用Selenium制作python数据抓取,以及对Selenium资源介绍

当当当~第三篇博客开始啦~ 这次的话题是数据抓取.终于到了核心部分的探讨,我的心情也是非常激动啊!如果大家baidu或者google(如果可以的话)数据抓取或者data crawling,将会找到数以千计的例子.但是大多数的代码非常的冗长,并且许多代码还是抓取静态数据之后,对动态JS写成的数据却毫无办法.或者,利用HTML解析网址后,再找到JS写的数据页面来寻找到所想要的数据. 但是!不知各位是否有发现过,如果打开chrome或者safari或者各种浏览器的审查元素.网页上能看到的数据,其实都会

delphi 用idhttp做web页面数据抓取 注意事项

这里不讨论webbrowse方式了 .直接采用indy的 idhttp  Get post 可以很方便的获取网页数据. 但如果要抓取大量数据 程序稳定运行不崩溃就不那么容易了.这几年也做了不少类似工具 总结了几点 好记性不如烂笔头. 内存泄露 获取页面文本 少不了用到html解析 具体到delphi 估计采用mshtml htmltotext 方法的不少,这个方案再大数据量时就会内存溢出 导致程序崩溃,而这并不是每个程序员都知道.解决的方案:采用自己的html解析类 这里我要感谢 武稀松(csd

Phantomjs+Nodejs+Mysql数据抓取(1.数据抓取)

概要: 这篇博文主要讲一下如何使用Phantomjs进行数据抓取,这里面抓的网站是太平洋电脑网估价的内容.主要是对电脑笔记本以及他们的属性进行抓取,然后在使用nodejs进行下载图片和插入数据库操作. 先进行所有页面的内容进行抓取 var page =require('webpage').create(); var address='http://product.pconline.com.cn/server/'; var fs = require('fs'); var mypath = 'ver