Java中的递归网络爬虫算法的并发性

时间:2018-11-07 19:57:06

标签: java web-scraping concurrency jsoup

我用Java编写了一个程序来查找网站的所有页面,从起始页面的URL开始(使用Jsoup作为webcrawler)。小型网站可以,但是对于200页或更多页面的网站来说太慢了:

public class SiteInspector {

private ObservableSet<String> allUrlsOfDomain; // all URLS found for site
private Set<String> toVisit; // pages that were found but not visited yet
private Set<String> visited; // URLS that were visited
private List<String> invalid; // broken URLs

public SiteInspector() {...}

public void getAllWebPagesOfSite(String entry) //entry must be startpage of a site
{
    toVisit.add(entry);
    allUrlsOfDomain.add(entry);
    while(!toVisit.isEmpty())
    {
        String next = popElement(toVisit);
        getAllLinksOfPage(next);  //expensive
        toVisit.remove(next);
    }
}


public void getAllLinksOfPage(String pageURL) {
    try {

        if (urlIsValid(pageURL)) {
            visited.add(pageURL);
            Document document = Jsoup.connect(pageURL).get();  //connect to pageURL (expensive network operation)
            Elements links = document.select("a");             //get all links from page 
            for(Element link : links)
            {
                String nextUrl = link.attr("abs:href");            // "http://..."
                if(nextUrl.contains(new URL(pageURL).getHost()))  //ignore URLs to external hosts
                {
                    if(!isForbiddenForCrawlers(nextUrl))           // URLS forbidden by robots.txt
                    {
                        if(!visited.contains(nextUrl))
                        {
                            toVisit.add(nextUrl);
                        }
                    }
                    allUrlsOfDomain.add(nextUrl);
                }
            }
        } 
        else
        {
            invalid.add(pageURL); //URL-validation fails
        }
    } 
    catch (IOException e) {
        e.printStackTrace();
    }
}

private boolean isForbiddenForCrawlers(String url){...}
private boolean urlIsValid(String url) {...}
public String popElement(Set<String> set) {...}

我知道我必须在额外的线程中运行昂贵的网络操作。

Document document = Jsoup.connect(pageURL).get();  //connect to pageURL

我的问题是我不知道如何在保持集合一致的同时正确地外包此操作(如何同步?)。如果可能的话,我想使用ThreadPoolExecutor来控制该过程中正在启动的线程数量。你们有解决办法的想法吗?预先感谢。

1 个答案:

答案 0 :(得分:2)

要使用线程并使集合保持一致,只需要创建一个线程即可接收要添加到集合中但创建为空的变量,因此线程完成后将其填充,然后将其添加到集合中。

一个简单的例子可能是:

  • Main.class

    for (String link : links) {
        String validUrl = null;
        taskThread = new Thread( new WebDownloadThreadHanlder(link, validUrl, barrier));
        taskThread.start();
    
        if (validUrl != null) {
            allUrlsOfDomain.add(validUrl);
        }
    }
    
    barrier.acquireUninterruptibly(links.size());
    
  • WebDownloadThreadHandler.class

    public class WebDownloadThreadHandler implements Runnable {
            private String link;
            private String validUrl;
            private Semaphore barrier;
    
            public ScopusThreadHandler(String link, String validUrl, Semaphore barrier) {
                this.link = link;
                this.validUrl = null;
                this.barrier = barrier;
            }
    
            public void run () {
                try {
                    Document document = Jsoup.connect(this.link).userAgent("Mozilla/5.0");
                    Elements elements = document.select(YOUR CSS QUERY);
    
                    /*
                    YOUR JSOUP CODE GOES HERE, AND STORE THE VALID URL IN: this.validUrl = THE VALUE YOU GET;
                    */
    
                } catch (IOException) {
                    e.printStackTrace();
                }
    
                this.barrier.release();
          }
    }
    

您在这里要做的是为要从中获取所有链接的每个网站创建一个线程,并将其存储到变量中,如果要从每个页面中检索多个无效链接,则可以使用设置并将其添加到全局集(附加)。关键是要使代码保持一致,您需要将检索到的值存储在使用 THIS 关键字作为参数传递线程的变量中。

希望有帮助!如果您还有其他需要,随时问我!