簡體   English   中英

在Java中獲取多個網頁的最快方法

[英]The fastest way to fetch multiple web pages in Java

我正在嘗試編寫一個快速的HTML scraper,在這一點上,我只是專注於在不解析的情況下最大化我的吞吐量。 我已緩存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來測試速度,讀取前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,但文獻表明我應該能夠每秒通過 300個 150個URL。 請注意,我可以訪問千兆以太網,雖然我目前正在我的20 Mbit連接上運行測試......在任何一種情況下,連接都沒有真正得到充分利用。

我已經嘗試過直接使用Socket連接,但我一定做錯了,因為那甚至更慢! 關於如何提高吞吐量的任何建議?

PS
我有一個大約100萬個熱門網址的列表,所以如果100不足以進行基准測試,我可以添加更多網址。

更新:
我所指文獻是關於Najork Web Crawler的文章,Najork說:

在17天內處理了8.91億個URL
那就是每秒606次下載[on] 4個Compaq DS20E Alpha服務器[帶] 4 GB主內存[,] 650 GB磁盤空間[和] 100 MBit / sec。
以太網ISP將帶寬限制為160Mbits / sec

所以它實際上是每秒150頁,而不是300頁。我的計算機是帶有4 GB RAM的Core i7,而我距離它還不遠。 我沒有看到任何說明他們特別使用的東西。

更新:
好的,算了......最后的結果都在! 事實證明,對於基准測試,100個URL有點太低了。 我把它提升到了1024個URL,64個線程,我為每次獲取設置了2秒的超時時間,我每秒最多可以達到21頁(實際上我的連接大約是10.5 Mbps,所以每秒21頁* 64KB每頁大約10.5 Mbps)。 以下是抓取器的外觀:

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();
    }
}

你確定你的總和嗎?

每秒300個URL,每個URL讀取64千字節

這需要: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以下,因此吞吐量看起來比你的頻道要快。 你不慢,你快!

這次關注你的成就。 我自己嘗試使用你的代碼,發現我每秒大約有3頁訪問主要網站。 如果我訪問我自己的網絡服務器下載靜態頁面,我,我的系統最大化。

在今天的互聯網上,主要網站通常需要一秒多的時間來生成頁面。 看了剛剛發送給我的數據包,頁面到達了多個TCP / IP數據包。 從英國這里下載www.yahoo.co.jp需要3秒鍾,下載amazon.com需要2秒,但facebook.com需要不到0.1秒。 不同的是facebook.com首頁是靜態的,而另外兩個是動態的。 對於人類而言,關鍵因素是第一個字節的時間,即瀏覽器可以開始執行某些操作的時間,而不是第65536個字節的時間。 沒有人優化:-)

所以這對於你來說意味着什么? 當你專注於熱門頁面時,我想你也會關注動態頁面,它們的發送速度與靜態頁面一樣快。 由於我查看的站點正在為其頁面發送多個數據包,這意味着如果您同時獲取多個頁面,因此數據包可能會在以太網上相互碰撞。

當兩個網站同時向您發送數據包時,會發生數據包沖突。 在某些時候,兩個網站的輸入必須協調到您的計算機的單線。 當兩個數據包相互疊加時,組合它們的路由器拒絕這兩個數據包,並指示兩個發送者在不同的短暫延遲后重新發送。 實際上,這會減慢兩個站點的速度。

所以:

1)這些天不會快速生成頁面。 2)以太網在處理多個同時下載時遇到問題。 3)靜態網站(曾經更常見)比動態網站更快,使用更少的數據包。

這一切都意味着最大化連接非常困難。

您可能會嘗試使用相同的測試來放置1000個64Kb文件並查看代碼下載速度有多快。 對我來說,你的代碼工作正常。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM