Java中的简单Web爬虫 - 多线程但速度慢:)

时间:2014-03-04 22:15:50

标签: java multithreading web-crawler

我已经搜索过但找不到答案 - 这不是“代码明智”,而是性能更明智。如果你记得我的问题的答案,我会很高兴被指出:)

我使用了一个非常简单的架构 - 我有一个主线程,其中包含要“抓取”的URL列表以及抓取的URL列表(使用其HTML)。两种结构都是并发的。 我为5或6个起始站点中的每一个创建了一个初始线程,然后:

  1. 每个线程将自己添加到主线程中的“当前活动线程”列表中。我们达到一定的最大值后不会添加任何线程(我可以使用执行程序,但暂时选择不执行,抱歉给您带来不便:))。
  2. 然后从给定的URL下载HTML,通过Jsoup提取所有链接。
  3. 如果这些网址不在“要抓取”的网址列表中,则会将其添加到那里。
  4. 当此过程完成且线程即将从start()退出时,它会从当前活动线程列表中删除它。
  5. 主线程一直循环:如果“要爬网”的URL列表不为空,并且我们还没有达到最大线程数(再次 - 抱歉缺少执行程序和繁忙-wait ...),创建一个新的线程,遍历列表中的下一个URL等。

    问题是 - 这个过程有效,但太慢。我的连接速度大约是5Mbits(已测试)。这些网站以每秒约1次的速度进行爬网(slowwww ....)。我打印了下载的html的大小(简单地通过doc.html()。size())并由我自己计算,在25秒的时间内,我使用大约71Kbs。很明显,即使使用这种简单的设计,我也会做错事,您的意见将非常受欢迎。

    注1 - 我知道抓取器礼貌和robots.txt。我会尽快使用这些,为什么我爬得这么慢。 注2 - 真的,我知道不那么好的设计:)但是据我所知,它不应该考虑爬行速度。

    感谢阅读!

    public class Crawler {
    public static final int MAX = 500;
    public static final int MAX_THREADS = 100;
    
    public static final String[] initialUrls = {"http://www.bbc.com",
                                                "http://www.facebook.com",
                                                "http://www.apple.com",
                                                "http://www.twitter.com",
                                                "http://www.tumblr.com",
                                                "http://www.microsoft.com"};
    
    public static Vector<String> urls;
    public static ConcurrentHashMap<String,Vector<String>> db;
    public static Vector<Thread> threads;
    
    public static void main(String[] args) 
    {
        db = new ConcurrentHashMap<String, Vector<String>>();
        urls = new Vector<String>();
        threads = new Vector<Thread>();
    
        for(int i = 0; i < initialUrls.length; i++)
        {
            Thread t = new Parser(db, urls, initialUrls[i]);
            threads.add(t);
            t.start();
        }
    
        String address;
        while(db.size() < MAX)
        {
            if((urls.size() != 0) && (threads.size() < MAX_THREADS))
            {
                address = urls.get(0);
                urls.remove(0);
                Thread t = new Parser(db, urls, address);
                t.start();
            }
        }
    
        try
        {
            for(Thread t : threads)
            {
                t.join();
            }
        }
        catch(Exception e)
        {
    
        }
    
        // do something
    }
    

    }

    public class Parser extends Thread{ 
    public Vector<String> urls;
    public ConcurrentHashMap<String, Vector<String>> db;
    public String start;
    
    public Parser(ConcurrentHashMap<String, Vector<String>> db, Vector<String> urls, String start)
    {
        this.db = db;
        this.start = start;
        this.urls = urls;
    }
    
    public void start()
    {
        Crawler.threads.add(this);
        parsePage(start);
        Crawler.threads.remove(this);
    }
    
    private void parsePage(String page)
    {
        if(db.size() >= Crawler.MAX)
        {
            return;
        }
    
        try
        {
            Document doc = Jsoup.connect(page).get();
            doc = Jsoup.parse(doc.toString());
    
            Vector<String> pageText = doc.html();
    
            if(db.size() < Crawler.MAX)
            {
                db.put(page, pageText);
            }
    
            String newPage;
            Elements links = doc.select("a[href]");
            for(int i = 0; i < links.size(); i++)
            {
                newPage = links.get(i).attr("abs:href");
                if(!db.containsKey(newPage) && !urls.contains(newPage))
                {
                    urls.add(newPage);
                }
            }
        }
        catch(IllegalArgumentException e)
        {
        }
        catch(IOException e)
        {
        }
    }
    

    }

1 个答案:

答案 0 :(得分:1)

使用ExecutorServiceMap<String, Future<X>>

首先:创建一个ExecutorService

final ExecutorService executor = Executors.newFixedThreadPool(...);

第二:创建一个方法,该方法将返回Callable<X>,其中X是您要返回的内容:

private Callable<X> callableForURL(final String url)
{
    // implementation here
}

第三步:获取您的网址列表,将作业提交给他们:

for (final String url: initialUrls)
    map.put(url, executor.submit(callableForURL(url)));

第四步:走你的Map并在每个值上调用.get()以获得结果。

查看ExecutorServiceExecutors的javadoc。