【问题标题】:The fastest way to fetch multiple web pages in Java在 Java 中获取多个网页的最快方法
【发布时间】:2011-08-06 23:51:52
【问题描述】:

我正在尝试编写一个快速的 HTML 抓取工具,此时我只专注于在不解析的情况下最大化我的吞吐量。我已经缓存了 URL 的 IP 地址:

public class Data {
    private static final ArrayList<String> sites = new ArrayList<String>();
    public static final ArrayList<URL> URL_LIST = new ArrayList<URL>();
    public static final ArrayList<InetAddress> ADDRESSES = new ArrayList<InetAddress>();

    static{
        /*
        add all the URLs to the sites array list
        */

        // Resolve the DNS prior to testing the throughput 
        for(int i = 0; i < sites.size(); i++){

            try {
                URL tmp = new URL(sites.get(i));
                InetAddress address = InetAddress.getByName(tmp.getHost());
                ADDRESSES.add(address);
                URL_LIST.add(new URL("http", address.getHostAddress(), tmp.getPort(), tmp.getFile()));
                System.out.println(tmp.getHost() + ": " + address.getHostAddress());
            } catch (MalformedURLException e) {
            } catch (UnknownHostException e) {
            }
        }
    }
}

我的下一步是测试 100 个 URL 的速度,方法是从 Internet 获取它们,读取前 64KB 并转到下一个 URL。我创建了一个FetchTaskConsumer 的线程池,并尝试运行多个线程(i7 四核机器上的 16 到 64 个线程),每个消费者的外观如下:

public class FetchTaskConsumer implements Runnable{
    private final CountDownLatch latch;
    private final int[] urlIndexes;
    public FetchTaskConsumer (int[] urlIndexes, CountDownLatch latch){
        this.urlIndexes = urlIndexes;
        this.latch = latch;
    }

    @Override
    public void run() {

        URLConnection resource;
        InputStream is = null;
        for(int i = 0; i < urlIndexes.length; i++)
        {
            int numBytes = 0;
            try {                   
                resource = Data.URL_LIST.get(urlIndexes[i]).openConnection();

                resource.setRequestProperty("User-Agent", "Mozilla/5.0");

                is = resource.getInputStream();

                while(is.read()!=-1 && numBytes < 65536 )
                {
                    numBytes++;
                }

            } catch (IOException e) {
                System.out.println("Fetch Exception: " + e.getMessage());
            } finally {

                System.out.println(numBytes + " bytes for url index " + urlIndexes[i] + "; remaining: " + remaining.decrementAndGet());
                if(is!=null){
                    try {
                        is.close();
                    } catch (IOException e1) {/*eat it*/}
                }
            }
        }

        latch.countDown();
    }
}

我最多可以在大约 30 秒内浏览 100 个 URL,但文献表明我应该能够每秒浏览 300150 个 URL。请注意,我可以访问千兆以太网,尽管我目前在家中使用 20 Mbit 连接运行测试......在任何一种情况下,连接都从未真正得到充分利用。

我尝试过直接使用Socket 连接,但我一定是做错了什么,因为这样更慢!关于如何提高吞吐量的任何建议?

附言
我有一个大约 100 万个流行 URL 的列表,因此如果 100 个不足以进行基准测试,我可以添加更多 URL。

更新:
literature I'm referring 是与 Najork Web Crawler 相关的论文,Najork 声明:

在 17 天内处理了 8.91 亿个 URL
即 ~ 每秒 606 次下载 [on] 4 Compaq DS20E Alpha Servers [with] 4 GB main 内存[,] 650 GB 磁盘空间 [和] 100 MBit/秒。
以太网 ISP 速率限制带宽为 160Mbits/秒

所以它实际上是每秒 150 页,而不是 300 页。我的计算机是 Core i7 和 4 GB RAM,我离它还很远。我没有看到任何说明他们特别使用的东西。

更新:
好的,统计一下……最终结果出来了!事实证明,100 个 URL 对于基准测试来说有点太低了。我将它增加到 1024 个 URL,64 个线程,我为每次提取设置了 2 秒的超时时间,并且我能够达到每秒 21 个页面(实际上我的连接速度约为 10.5 Mbps,所以每秒 21 个页面 * 64KB每页约为 10.5 Mbps)。下面是 fetcher 的样子:

public class FetchTask implements Runnable{
    private final int timeoutMS = 2000;
    private final CountDownLatch latch;
    private final int[] urlIndexes;
    public FetchTask(int[] urlIndexes, CountDownLatch latch){
        this.urlIndexes = urlIndexes;
        this.latch = latch;
    }

    @Override
    public void run() {

        URLConnection resource;
        InputStream is = null;
        for(int i = 0; i < urlIndexes.length; i++)
        {
            int numBytes = 0;
            try {                   
                resource = Data.URL_LIST.get(urlIndexes[i]).openConnection();

                resource.setConnectTimeout(timeoutMS);

                resource.setRequestProperty("User-Agent", "Mozilla/5.0");

                is = resource.getInputStream();

                while(is.read()!=-1 && numBytes < 65536 )
                {
                    numBytes++;
                }

            } catch (IOException e) {
                System.out.println("Fetch Exception: " + e.getMessage());
            } finally {

                System.out.println(numBytes + "," + urlIndexes[i] + "," + remaining.decrementAndGet());
                if(is!=null){
                    try {
                        is.close();
                    } catch (IOException e1) {/*eat it*/}
                }
            }
        }

        latch.countDown();
    }
}

【问题讨论】:

  • 为刮板设置浏览器用户代理不是好习惯。
  • 文学?你的意思是说javadocs?我找不到任何与 URLConnection 相关的每秒 300 个 URL。
  • URLConnection 主要是每 500 毫秒获取一个页面,java 在这个目的上很慢
  • @Mat,有时我必须模拟浏览器,因为网站可能会更改其内容,具体取决于页面是提供给浏览器还是机器人,如果我正在抓取内容,那么我不会不想错过对用户有价值的内容。我仍然遵守 robots.text 的规定。
  • @Lirik:这样做的网站是故意这样做的。您很可能违反了他们的使用政策。

标签: java performance url web-crawler


【解决方案1】:

你确定你的金额吗?

每秒 300 个 URL,每个 URL 读取 64 KB

这需要:300 x 64 = 19,200 千字节/秒

转换为位:19,200 千字节/秒 = ( 8 * 19,200 ) 千位/秒

所以我们有:8*19,200 = 153,600 千比特/秒

然后到 Mb/s:153,600 / 1024 = 150 兆比特/秒

...但您只有一个 20 Mb/s 的频道。

但是,我想您获取的许多 URL 的大小都在 64Kb 以下,因此吞吐量似乎比您的频道快。你不慢,你快!

【讨论】:

  • 即使在 20 MB/s(我的家庭连接)下,我至少应该能够以每秒 40 页的速度将其最大化...我还差得很远(30 个中有 100 个 URL)秒,大约是每秒 3 页)!我也可以使用 100 Mbps 的连接,但如果我能在达到 100 Mbps 之前将我的家庭连接最大化,我会很高兴。
  • 我很抱歉 - 我更关注您的期望而不是您的成就。我会看看我能弄清楚的。
  • 这是一个很好的观点......即使它并没有真正回答我的问题:)。现在我正在研究 Nutch 以及他们如何设法获取如此多的 URL 而不会遇到与我相同的问题。也许他们没有使用URLConnection...
【解决方案2】:

这次专注于你的成就。我自己尝试使用您的代码,发现我每秒也有大约 3 页访问主要网站。但是,如果我访问我自己的网络服务器下载静态页面,我的系统就会达到极限。

在当今的互联网上,一个主要网站通常需要一秒钟以上的时间来生成一个页面。查看了他们刚刚发送给我的数据包,该页面以多个 TCP/IP 数据包的形式到达。在英国,下载 www.yahoo.co.jp 需要 3 秒,下载 amazon.com 需要 2 秒,但 facebook.com 需要不到 0.1 秒。不同之处在于 facebook.com 的首页是静态的,而其他两个是动态的。对于人类来说,关键因素是第一个字节的时间,也就是浏览器可以开始做某事的时间,而不是第 65536 个字节的时间。没有人对此进行优化:-)

那么这对您意味着什么?当您专注于流行页面时,我想您也专注于动态页面,它们的发送速度不如静态页面快。由于我查看的网站正在为其页面发送多个数据包,这意味着如果您同时获取许多页面,因此数据包可能会在以太网上相互碰撞。

当两个网站同时向您发送数据包时,就会发生数据包冲突。在某些时候,必须将来自两个网站的输入协调到连接到您计算机的单线中。当两个数据包相互重叠到达时,合并它们的路由器将两者都拒绝,并指示两个发送方在不同的短延迟后重新发送。实际上,这会减慢两个网站的速度。

所以:

1) 这些天来生成页面的速度没有那么快。 2) 以太网无法同时处理多个下载。 3) 静态网站(过去更为常见)比动态网站速度更快,使用的数据包更少。

这一切都意味着最大化你的连接真的很难。

您可以尝试我所做的相同测试,即放置 1000 个 64Kb 文件并查看您的代码下载它们的速度。对我来说,您的代码运行良好。

【讨论】:

  • Simon,我将批量大小增加到 300 个 URL,并且在 60 秒内得到它们......所以我变得更好:每秒 5 个 URL。我引用的文献也是阅读前 64KB。 Nutch 似乎也做得很好:每秒 40 页。现在我将我的初始批次 URL 设置为 1000,以查看性能是否再次提高。
  • 我现在添加了 1024 个 URL,使用相同的代码,现在每秒最多 21 个页面(在大约 47 秒内获取 1024 个)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多