Параллельность для рекурсивного алгоритма webcrawler в Java - PullRequest
0 голосов
/ 07 ноября 2018

Я написал программу на Java, чтобы найти все страницы веб-сайта, начиная с URL-адреса начальной страницы (используя Jsoup в качестве веб-сканера). Это нормально для небольших сайтов, но слишком медленно для сайтов с 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 голосов
/ 08 ноября 2018

Чтобы использовать потоки, а также поддерживать согласованность наборов, вам просто нужно создать поток, который получает переменную, которую вы хотите добавить в набор, но создает пустую, поэтому поток заполняет ее по завершении, а затем добавляет ее в набор.

Простой пример этого может быть:

  • 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 .

Надеюсь, это поможет! Если вам нужно что-то еще, не стесняйтесь спрашивать меня!

...