将线程实现到Java Web Crawler中

这是我写的原始网络爬虫:(仅供参考)

https://github.com/domshahbazi/java-webcrawler/tree/master 

这是一个简单的网络爬虫,它访问给定的初始网页,从页面中删除所有链接并将它们添加到队列(LinkedList),然后逐个弹出它们,每次访问,循环再次开始。 为了加速我的程序和学习,我尝试使用线程实现,这样我就可以同时运行多个线程,在更短的时间内索引更多的页面。 以下是每个class级:

主要课程

 public class controller { public static void main(String args[]) throws InterruptedException { DataStruc data = new DataStruc("http://www.imdb.com/title/tt1045772/?ref_=nm_flmg_act_12"); Thread crawl1 = new Crawler(data); Thread crawl2 = new Crawler(data); crawl1.start(); crawl2.start(); } } 

爬虫类 (线程)

 public class Crawler extends Thread { /** Instance of Data Structure **/ DataStruc data; /** Number of page connections allowed before program terminates **/ private final int INDEX_LIMIT = 10; /** Initial URL to visit **/ public Crawler(DataStruc d) { data = d; } public void run() { // Counter to keep track of number of indexed URLS int counter = 0; // While URL's left to visit while((data.url_to_visit_size() > 0) && counter<INDEX_LIMIT) { // Pop next URL to visit from stack String currentUrl = data.getURL(); try { // Fetch and parse HTML document Document doc = Jsoup.connect(currentUrl) .userAgent("Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36") .referrer("http://www.google.com") .timeout(12000) .followRedirects(true) .get(); // Increment counter if connection to web page succeeds counter++; /** .select returns a list of elements (links in this case) **/ Elements links = doc.select("a[href]"); // Relative URL // Add newly found links to stack addLinksToQueue(links); } catch (IOException e) { //e.printStackTrace(); System.out.println("Error: "+currentUrl); } } } public void addLinksToQueue(Elements el) { // For each element in links for(Element e : el) { String theLink = e.attr("abs:href"); // 'abs' prefix ensures absolute url is returned rather then relative url ('www.reddit.com/hello' rather then '/hello') if(theLink.startsWith("http") && !data.oldLink(theLink)) { data.addURL(theLink); data.addVisitedURL(theLink); // Register each unique URL to ensure it isnt stored in 'url_to_visit' again System.out.println(theLink); } } } } 

DataStruc类

 public class DataStruc { /** Queue to store URL's, can be accessed by multiple threads **/ private ConcurrentLinkedQueue url_to_visit = new ConcurrentLinkedQueue(); /** ArrayList of visited URL's **/ private ArrayList visited_url = new ArrayList(); public DataStruc(String initial_url) { url_to_visit.offer(initial_url); } // Method to add seed URL to queue public void addURL(String url) { url_to_visit.offer(url); } // Get URL at front of queue public String getURL() { return url_to_visit.poll(); } // URL to visit size public int url_to_visit_size() { return url_to_visit.size(); } // Add visited URL public void addVisitedURL(String url) { visited_url.add(url); } // Checks if link has already been visited public boolean oldLink(String link) { for(String s : visited_url) { if(s.equals(link)) { return true; } } return false; } } 

DataStruc是共享数据结构类,它将由Crawler.java线程的每个实例同时访问。 DataStruc有一个Queue来存储要访问的链接,以及一个arraylist来存储被访问的URL,以防止进入循环。 我使用ConcurrentLinkedQueue存储要访问的URL,因为我看到它负责并发访问。 我不需要与我的访问url的arraylist进行并发访问,因为我需要能够做的就是添加到此并迭代它以检查匹配。

我的问题是,当我比较使用单个线程VS使用2个线程(在同一个URL上)的操作时间时,我的单线程版本似乎工作得更快。 我觉得我已经错误地实现了线程,如果有人可以查明问题,我想要一些提示吗?

谢谢!

补充:看我的评论,我认为在Crawler中检查

 // While URL's left to visit while((data.url_to_visit_size() > 0) && counter 

是错的。 由于第一个线程轮询了唯一的URL,因此第二个线程将立即停止。

你可以忽略剩下的,但留给历史......

我对这类“可以并行运行的大块”的一般方法是:

  1. 使每个爬虫成为可调用的。 可能是Callable>
  2. 将它们提交给ExecutorService
  3. 完成后, 逐个获取结果并将其添加到列表中。

使用此策略,根本不需要使用任何并发列表。 缺点是你没有得到太多的实时反馈,因为他们是runnìng。 而且,如果他们返回的是巨大的,你可能需要担心记忆。

这会满足你的需求吗? 您将不得不担心addVisitedURL因此您仍然需要将其作为并发数据结构。

补充:由于您从一个URL开始,因此该策略不适用。 您可以在访问第一个URL后应用它。

 class controller { public static void main(String args[]) throws InterruptedException { final int LIMIT = 4; List seedList = new ArrayList<>(); //1 seedList.add("https://www.youtube.com/"); seedList.add("https://www.digg.com/"); seedList.add("https://www.reddit.com/"); seedList.add("https://www.nytimes.com/"); DataStruc[] data = new DataStruc[LIMIT]; for(int i = 0; i < LIMIT; i++){ data[i] = new DataStruc(seedList.get(i)); //2 } ExecutorService es = Executors.newFixedThreadPool(LIMIT); Crawler[] crawl = new Crawler[LIMIT]; for(int i = 0; i < LIMIT; i++){ crawl[i] = new Crawler(data[i]); //3 } for(int i = 0; i < LIMIT; i++){ es.submit(crawl[i]) // 4 } } } 

你可以尝试一下

  1. 创建种子列表

  2. 创建datastruc的对象并将seedlist添加到每个对象中

  3. 创建爬网数组并逐个将datastruc对象传递给它们
  4. 将爬网对象传递给激励器