之前有看過一段時間爬蟲,了解了爬蟲的原理,以及一些實現的方法,本項目完成於半年前,一直放在那里,現在和大家分享出來。
網絡爬蟲簡單的原理就是把程序想象成為一個小蟲子,一旦進去了一個大門,這個小蟲子就像進入了新世界一樣,只要符合他的口味的東西就會放在自己的袋子里,但是他還不滿足,只要見到可以打開的門,他都要進去看看,里面有沒有他想要的東西有就裝起來,直到每個門里都看了一遍,確定沒有了之后,他才肯放棄,這樣下來,他的袋子已經裝滿了想要的東西。
上述內容表述起來就是:網絡爬蟲就是一個自動提取網頁內容的程序,這個程序的行為像一個蟲子似的,爬來爬去。一般的網絡爬蟲都有一個或者多個網頁的url作為開始,從開始的網頁上獲取url,並把符合條件的內容保存下來,這樣一直進行下去,直到條件不符合的時候,程序執行結束。
以下只是簡單的一個爬蟲,爬取一個下載網站上的迅雷下載鏈接,用到了兩個輔助隊列,一個存鏈接作為判斷當前鏈接是否已經打開過,另一個是進行操作的隊列,存進去的鏈接都會進行操作。最后獲取到的下載鏈接存在set集合中,以保證鏈接不會重復。
-------------------------------以下是一個分析源碼的過程,后期發現這個編碼沒有用上---------------------------------------------------------------------
由於在網頁上顯示的只是一串文字,當用戶點擊后,他的鏈接會經過url編碼,編碼成迅雷能夠識別的鏈接,網站上使用的是javascript的url編碼,java自帶了一個url編碼,和網站上的不一致,我們需要java的url編碼的源碼,通過分析源碼其實很難簡單地發現對什么字符編碼,對什么不進行編碼,和javascript的url編碼比較之后,對其進行改造,經過比對,發現java的編碼,對'@','[',']',':','/'不處理,只需要增加他們進去就行
static { dontNeedEncoding = new BitSet(256); int i; for (i = 'a'; i <= 'z'; i++) { dontNeedEncoding.set(i); } for (i = 'A'; i <= 'Z'; i++) { dontNeedEncoding.set(i); } for (i = '0'; i <= '9'; i++) { dontNeedEncoding.set(i); } dontNeedEncoding.set(' '); /* encoding a space to a + is done * in the encode() method */ dontNeedEncoding.set('-'); dontNeedEncoding.set('_'); dontNeedEncoding.set('.'); dontNeedEncoding.set('*'); dfltEncName = AccessController.doPrivileged( new GetPropertyAction("file.encoding") ); }改造后的url編碼類,去掉了沒有用的語句,增加對對'@','[',']',':','/'特殊字符的編碼
package url; import java.io.UnsupportedEncodingException; import java.io.CharArrayWriter; import java.net.URLEncoder; import java.nio.charset.Charset; import java.nio.charset.IllegalCharsetNameException; import java.nio.charset.UnsupportedCharsetException; import java.util.BitSet; public class URLEncoders { static BitSet dontNeedEncoding; static final int caseDiff = ('a' - 'A'); static String dfltEncName = null; static { dontNeedEncoding = new BitSet(256); int i; for (i = 'a'; i <= 'z'; i++) { dontNeedEncoding.set(i); } for (i = 'A'; i <= 'Z'; i++) { dontNeedEncoding.set(i); } for (i = '0'; i <= '9'; i++) { dontNeedEncoding.set(i); } dontNeedEncoding.set(' '); /* * encoding a space to a + is done in the * encode() method */ dontNeedEncoding.set('-'); dontNeedEncoding.set('_'); dontNeedEncoding.set('.'); dontNeedEncoding.set('*'); dontNeedEncoding.set('@'); dontNeedEncoding.set('['); dontNeedEncoding.set(']'); dontNeedEncoding.set(':'); dontNeedEncoding.set('/'); } @Deprecated public static String encode(String s) { String str = null; try { str = encode(s, dfltEncName); } catch (UnsupportedEncodingException e) { } return str; } public static String encode(String s, String enc) throws UnsupportedEncodingException { boolean needToChange = false; StringBuffer out = new StringBuffer(s.length()); Charset charset; CharArrayWriter charArrayWriter = new CharArrayWriter(); if (enc == null) throw new NullPointerException("charsetName"); try { charset = Charset.forName(enc); } catch (IllegalCharsetNameException e) { throw new UnsupportedEncodingException(enc); } catch (UnsupportedCharsetException e) { throw new UnsupportedEncodingException(enc); } for (int i = 0; i < s.length();) { int c = (int) s.charAt(i); if (dontNeedEncoding.get(c)) { if (c == ' ') { c = '+'; needToChange = true; } out.append((char) c); i++; } else { do { charArrayWriter.write(c); if (c >= 0xD800 && c <= 0xDBFF) { if ((i + 1) < s.length()) { int d = (int) s.charAt(i + 1); if (d >= 0xDC00 && d <= 0xDFFF) { charArrayWriter.write(d); i++; } } } i++; } while (i < s.length() && !dontNeedEncoding.get((c = (int) s.charAt(i)))); charArrayWriter.flush(); String str = new String(charArrayWriter.toCharArray()); byte[] ba = str.getBytes(charset); for (int j = 0; j < ba.length; j++) { out.append('%'); char ch = Character.forDigit((ba[j] >> 4) & 0xF, 16); if (Character.isLetter(ch)) { ch -= caseDiff; } out.append(ch); ch = Character.forDigit(ba[j] & 0xF, 16); if (Character.isLetter(ch)) { ch -= caseDiff; } out.append(ch); } charArrayWriter.reset(); needToChange = true; } } return (needToChange ? out.toString() : s); } }
所以當我研究好了以上的編碼之后才發現,迅雷會識別未經過編碼的鏈接,三種鏈接迅雷都可以下載,如下圖:

所以以上就當是提高了一下分析源碼的能力……
爬蟲代碼並沒有做任何的優化,可以說效率非常低,程序用到了遞歸,並且在程序運行的過程中打開的鏈接會形成一個環形,也就是打開一個鏈接,之后程序執行的過程中會再次找到這個鏈接,所以這條路線就斷了。
程序在執行過程中,如果當前訪問的網址時間過長,會拋出異常,也會影響效率。

從運行時間和有效鏈接可以看出效率很低
其實爬蟲的代碼很簡單,從網頁獲取代碼,對其進行解析這么一個過程
主程序:並沒有調用其他的方法,那個編碼類也沒有用上
package function; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import queue.Queue; import java.io.IOException; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.TreeSet; import java.util.regex.Pattern; public class Crawler { public static Queue q1 = new Queue(); public static Queue q2 = new Queue(); public static Set<String> set = new TreeSet<String>(); public static int i = 0; public static void main(String[] args) { Document doc = null; try { long begin = System.currentTimeMillis(); doc = Jsoup.connect("http://www.dytt8.net/index.htm").get(); Elements links = doc.select("a[href]"); for (Element link : links) { String linkHref = link.attr("href"); Pattern pattern = Pattern.compile("^/html/+(.)+.html"); Pattern pattern0 = Pattern .compile("http://www.dytt8.net/html/+(.)+.html"); Pattern pattern1 = Pattern.compile("^ftp://+((.)+)+"); if (pattern.matcher(linkHref).matches() == true || pattern0.matcher(linkHref).matches() == true) { q1.insertQueue(linkHref); q2.insertQueue(linkHref); open("http://www.dytt8.net" + q1.outQueue()); } } Iterator<String> it = set.iterator(); // while(it.hasNext()){ // String url=(String)it.next(); // int last=url.lastIndexOf("."); // int last1=url.lastIndexOf("]"); // // System.out.print(url.substring(last1+1, last)+" "); // System.out.println(URLEncoders.encode(url,"utf-8")); // } System.out.println("一共爬取" + q2.size() + "條鏈接"); long end = System.currentTimeMillis(); System.out.println("用時" + (end - begin) + "ms"); System.out.println("一共" + set.size() + "條下載鏈接"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static void open(String url) { Document doc = null; try { doc = Jsoup.connect(url).get(); Elements links = doc.select("a[href]"); for (Element link : links) { String linkHref = link.attr("href"); Pattern pattern = Pattern.compile("^/html/+(.)+.html"); Pattern pattern0 = Pattern .compile("http://www.dytt8.net/html/+(.)+.html"); Pattern pattern1 = Pattern.compile("^ftp://+((.)+)+"); if (pattern.matcher(linkHref).matches() == true || pattern0.matcher(linkHref).matches() == true) { q1.insertQueue(linkHref); q2.insertQueue(linkHref); if (q2.contains(linkHref) == false) { open("http://www.dytt8.net" + q1.outQueue()); } } else if (pattern1.matcher(linkHref).matches() == true) { System.out.println(linkHref); set.add(linkHref); } } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }試着弄了一下github,所以把這個非常不完美的項目放在了上面,就當練手,項目地址: https://github.com/Ai-yoo/Java_Applaction.git
