java多線程爬蟲執行個體

來源:互聯網
上載者:User
     很早就知道爬蟲的原理,但是一直沒有去實現過,今天寫起來還真遇到很多困難,尤其是多線程同步的問題。還是自己對多線程不熟,沒有大量實踐過的原因。

    先上我做的結果吧:

   

開始爬蟲.........................................當前有1個線程在等待當前有2個線程在等待當前有3個線程在等待當前有4個線程在等待當前有5個線程在等待.....................
耙梳頁http://dev.yesky.com成功,深度為2 是由線程thread-9來爬當前有7個線程在等待耙梳頁http://www.cnblogs.com/rexyoung/archive/2012/05/01/2477960.html成功,深度為2 是由線程thread-2來爬當前有8個線程在等待耙梳頁http://www.hjenglish.com 成功,深度為2 是由線程thread-0來爬當前有9個線程在等待耙梳頁http://www.cnblogs.com/snandy/archive/2012/05/01/2476675.html成功,深度為2 是由線程thread-5來爬當前有10個線程在等待總共爬了159個網頁總共耗時53秒

上面是爬部落格園的首頁,只爬了兩級深度,10個線程,總共耗時53秒,應該速度還算不錯的,下面是所有的代碼:

public class WebCrawler {ArrayList<String> allurlSet = new ArrayList<String>();//所有的網頁url,需要更高效的去重可以考慮HashSetArrayList<String> notCrawlurlSet = new ArrayList<String>();//未爬過的網頁urlHashMap<String, Integer> depth = new HashMap<String, Integer>();//所有網頁的url深度int crawDepth  = 2; //爬蟲深度int threadCount = 10; //線程數量int count = 0; //表示有多少個線程處於wait狀態public static final Object signal = new Object();   //線程間通訊變數public static void main(String[] args) {final WebCrawler wc = new WebCrawler();//wc.addUrl("http://www.126.com", 1);wc.addUrl("http://www.cnblogs.com", 1);long start= System.currentTimeMillis();System.out.println("開始爬蟲.........................................");wc.begin();while(true){if(wc.notCrawlurlSet.isEmpty()&& Thread.activeCount() == 1||wc.count==wc.threadCount){long end = System.currentTimeMillis();System.out.println("總共爬了"+wc.allurlSet.size()+"個網頁");System.out.println("總共耗時"+(end-start)/1000+"秒");System.exit(1);//break;}}}private void begin() {for(int i=0;i<threadCount;i++){new Thread(new Runnable(){public void run() {//System.out.println("當前進入"+Thread.currentThread().getName());//while(!notCrawlurlSet.isEmpty()){ ----------------------------------(1)//String tmp = getAUrl();//crawler(tmp);//}while (true) { //System.out.println("當前進入"+Thread.currentThread().getName());String tmp = getAUrl();if(tmp!=null){crawler(tmp);}else{synchronized(signal) {  //------------------(2)try {count++;System.out.println("當前有"+count+"個線程在等待");signal.wait();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}}},"thread-"+i).start();}}public synchronized  String getAUrl() {if(notCrawlurlSet.isEmpty())return null;String tmpAUrl;//synchronized(notCrawlurlSet){tmpAUrl= notCrawlurlSet.get(0);notCrawlurlSet.remove(0);//}return tmpAUrl;}//public synchronized  boolean isEmpty() {//boolean f = notCrawlurlSet.isEmpty();//return f;//}public synchronized void  addUrl(String url,int d){notCrawlurlSet.add(url);allurlSet.add(url);depth.put(url, d);}//耙梳頁sUrlpublic  void crawler(String sUrl){URL url;try {url = new URL(sUrl);//HttpURLConnection urlconnection = (HttpURLConnection)url.openConnection(); URLConnection urlconnection = url.openConnection();urlconnection.addRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)");InputStream is = url.openStream();BufferedReader bReader = new BufferedReader(new InputStreamReader(is));StringBuffer sb = new StringBuffer();//sb為爬到的網頁內容String rLine = null;while((rLine=bReader.readLine())!=null){sb.append(rLine);sb.append("/r/n");}int d = depth.get(sUrl);System.out.println("耙梳頁"+sUrl+"成功,深度為"+d+" 是由線程"+Thread.currentThread().getName()+"來爬");if(d<crawDepth){//解析網頁內容,從中提取連結parseContext(sb.toString(),d+1);}//System.out.println(sb.toString());} catch (IOException e) {//crawlurlSet.add(sUrl);//notCrawlurlSet.remove(sUrl);e.printStackTrace();}}//從context提取url地址public  void parseContext(String context,int dep) {    String regex = "<a href.*?/a>";//String regex = "<title>.*?</title>";String s = "fdfd<title>我 是</title><a href=\"http://www.iteye.com/blogs/tag/Google\">Google</a>fdfd<>";// String regex ="http://.*?>";Pattern pt = Pattern.compile(regex);Matcher mt = pt.matcher(context);while (mt.find()) {//System.out.println(mt.group());Matcher myurl = Pattern.compile("href=\".*?\"").matcher(mt.group());while(myurl.find()){String str = myurl.group().replaceAll("href=\"|\"", "");//System.out.println("網址是:"+ str);if(str.contains("http:")){ //取出一些不是url的地址if(!allurlSet.contains(str)){addUrl(str, dep);//加入一個新的urlif(count>0){ //如果有等待的線程,則喚醒synchronized(signal) {  //---------------------(2)count--;signal.notify();}}}}}}}}

在上面(1)(2)兩個地方卡了很久,兩個地方其實是一個知識點,都是多線程的知識:

一開始用了

//while(!notCrawlurlSet.isEmpty()){ ----------------------------------(1)//String tmp = getAUrl();//crawler(tmp);//}

一進入線程就判斷notCrawlurlSet為不為空白,但是是多線程的,一開始notCrawlurlSet不為空白,所以所有的線程都進入了迴圈,儘管getAul()方法我設定了synchronized,但是一旦一個線程從getAurl()方法出來,另外一個線程就會進去,看一開始的getAurl方法的代碼:

public synchronized  String getAUrl() {String tmpAUrl;//synchronized(notCrawlurlSet){tmpAUrl= notCrawlurlSet.get(0);notCrawlurlSet.remove(0);//}return tmpAUrl;}
每一次都會刪除一個notCrawlurlSet數組裡面的元素,導致第一個線程執行完getAUrl方法時,且notCrawlurlSet恰好為空白的時候,另外一個線程進入就會報錯,因為notCrawlUrlSet沒有元素,get(0)會報錯。後來把getAUrl函數改成:

public synchronized  String getAUrl() {if(notCrawlurlSet.isEmpty())return null;String tmpAUrl;//synchronized(notCrawlurlSet){tmpAUrl= notCrawlurlSet.get(0);notCrawlurlSet.remove(0);//}return tmpAUrl;}

線上程的run函數改成:

while (true) { //System.out.println("當前進入"+Thread.currentThread().getName());String tmp = getAUrl();if(tmp!=null){crawler(tmp);}else{synchronized(signal) {try {count++;System.out.println("當前有"+count+"個線程在等待");signal.wait();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}

即線程進入後就調用getAUrl函數,從notCrawlurlSet數組取url,如果沒有取到,則用signal來讓此線程等待,但是在哪裡喚醒呢。肯定在notCrawlurlSet有元素的時候喚醒,即notCrawlurlSet不能空的時候,這其中有個很重要的變數count,它表示正在等待的線程個數,只有count大於0才會喚醒線程,即只有有線程在等待的時候才會調用signal.notify(); 此段實現在parseContext函數裡面:
if(str.contains("http:")){ //取出一些不是url的地址if(!allurlSet.contains(str)){addUrl(str, dep);//加入一個新的urlif(count>0){ //如果有等待的線程,則喚醒synchronized(signal) {count--;signal.notify();}}}}

這個count變數還解決了我一個問題,當所有的線程啟動後,也正確的爬取網頁了,但是不知道怎麼結束這些線程,因為線程都是永久迴圈的,有了count變數,就知道有多少線程在等待,當等待的線程等於threadCount的時候,就表示已經爬完了,因為所有線程都在等待了,不會往notCrawlurlSet添加新的url了,此時已經爬完了指定深度的所有網頁。

寫下自己的一點感悟,明白原理是一回事,有時候實現起來也挺費神的。

代碼幾度修改,還有待完善的地方及我的思路:

1:爬取的網頁要存起來,該怎麼存放也是一個問題,目錄怎麼產生。網頁自動分類。等等,分類可以用考慮貝葉斯分類器,分好類之後安裝類別來儲存。

2:網頁去重問題,如果url太多,記憶體裝不下去怎麼辦。考慮先壓縮,比如MD5壓縮,同時MD5又能得到hash值,最簡單的是hash去重,或者可以考慮用bloom filter去重,還有一種方法是考慮用key-value資料庫來實現去重,不過我對key-value資料庫不是很瞭解,應該類似hash,但是效率的問題資料庫已經幫你解決了。

3:url不同的網頁也可能內容一樣,怎麼判斷網頁相似性問題。網頁相似性可以先提取網頁本文,方法有行塊函數法,提取本文後再可以用向量餘弦法來計算相似性。

4:增量抓取的問題,一個網頁抓取之後,什麼時候再重新來抓。可以針對具體的網頁的更新頻率來解決這個問題,如新浪首頁的新聞可能更新快一些,重新來爬的頻率會更快一點。

暫時想到這些,以後繼續完善。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.