通常情況下,做網站的都會給自己的網站添加一個Icon,瀏覽器上一長排的標簽頁,用Icon來區分就顯得更加醒目。現在想找一個沒有Icon的網站並不好找,可見沒有Icon的網站是多么的業余啊。"什么?你問Icon是什么?你走吧,這是討論技術的地方!"
想知道如何獲取Icon,就要弄明白怎樣設置Icon。先討論一下設置Icon,再介紹獲取Icon,並提供相應Java代碼以供參考。
一. 設置網站Icon
設置Icon有兩種方式:
1. 看一下我們專業的博客園,看到灰色部分了嗎,在head標簽中有個link標簽,將rel設置為"shortcut icon",href 設置為Icon的位置,type設置成實際圖標類型就OK了。這個Icon文件不是必須以favicon.ico命名,也可以選擇png等其他格式的圖片。
2. 如果用第一種方式,每個頁面都要寫link,是不是挺麻煩的,可能會用模板之類的東西自動生成,這個我不懂啦。如果瀏覽器發現html中沒有寫明Icon位置,就自動到網站根目錄下嘗試讀取favicon.ico文件。再看一下我們專業的博客園,看到了嗎,Icon顯示出來了。注意:根目錄下的文件就必須以favicon.ico命名了。考慮到瀏覽器兼容性,大部分的網站除了在html中指定Icon的位置,同時也會在網站根目錄下存放Icon文件。
二. 獲取網站Icon
知道怎么設置Icon,獲取Icon就很簡單了。解析html相對來說比較麻煩,可以直接到網站根目錄下嘗試讀取favicon.ico。如果沒有,再解析html(話說我試了很多常用網站,都可以從根目錄下讀取,想找個根目錄下不存放Icon的網站還真不容易,這時我想到了12306,試了一下果然沒有啊,事情並沒有想象的那么簡單,12306會奇葩到你想不到,后邊再說)。思路就是這樣,很簡單,但是在實現的過程中會有很多細節問題。用java代碼實現一下吧,並詳細說明可能遇到的細節問題。
下邊是獲取Icon地址的入口函數,傳入網絡地址即可。
// 獲取Icon地址 public static String getIconUrlString( String urlString ) throws MalformedURLException { urlString = getFinalUrl( urlString ); URL url = new URL( urlString ); String iconUrl = url.getProtocol() + "://" + url.getHost() + "/favicon.ico";// 保證從域名根路徑搜索 if ( hasRootIcon( iconUrl ) ) return iconUrl; return getIconUrlByRegex( urlString ); }
getFinalUrl是獲取網址經過3XX跳轉之后的url地址,如果沒有跳轉就返回原來的url。防止有些網址會出現跳轉的情況,所以先搞到跳轉之后的網址在進行獲取。java的HttpUrlConnection默認情況下會自動跳轉,為什么還要手動獲取呢?比如www.rayli.com會跳轉到www.rayli.com.cn,我要訪問 www.rayli.com/favicon.ico,我希望跳轉到www.rayli.com.cn/favicon.ico。但實際情況卻跳轉到了www.rayli.com.cn,這樣就造成我判斷錯誤,所以需要手動解析跳轉后的地址。
hasRootIcon函數判斷網站根目錄下是否存在favicon.ico文件,注意在傳入url之前要保證傳入的是根路徑地址,因為有些網站跳轉過后並不是跳轉到根目錄,當正常響應並且存在返回內容時就認為有指定文件。getConnection函數是根據url獲取一個HttpUrlConnection。
// 判斷在根目錄下是否有Icon private static boolean hasRootIcon( String urlString ) { HttpURLConnection connection = null; try { connection = getConnection( urlString ); connection.connect(); return HttpURLConnection.HTTP_OK == connection.getResponseCode() && connection.getContentLength() > 0; } catch ( Exception e ) { e.printStackTrace(); return false; } finally { if ( connection != null ) connection.disconnect(); } }
getIconUrlByRegex是根據正則表達式從html中獲取Icon地址,getHead方法是獲取網頁的head結束標簽之前的文本,然后用正則表達式匹配內容,這里的正則表達式有兩個,這是因為rel和href的順序是不固定的。匹配到以后判斷一下是否為相對路徑,如果是的話做進一步處理。
private static final Pattern[] ICON_PATTERNS = new Pattern[] { Pattern.compile( "rel=[\"']shortcut icon[\"'][^\r\n>]+?((?<=href=[\"']).+?(?=[\"']))" ), Pattern.compile( "((?<=href=[\"']).+?(?=[\"']))[^\r\n<]+?rel=[\"']shortcut icon[\"']" ) };
// 從html中獲取Icon地址 private static String getIconUrlByRegex( String urlString ) { try { String headString = getHead( urlString ); for ( Pattern iconPattern : ICON_PATTERNS ) { Matcher matcher = iconPattern.matcher( headString ); if ( matcher.find() ) { String iconUrl = matcher.group( 1 ); if ( iconUrl.contains( "http" ) ) return iconUrl; if ( iconUrl.charAt( 0 ) == '/' ) {//判斷是否為相對路徑或根路徑 URL url = new URL( urlString ); iconUrl = url.getProtocol() + "://" + url.getHost() + iconUrl; } else { iconUrl = urlString + "/" + iconUrl; } return iconUrl; } } } catch ( Exception e ) { e.printStackTrace(); } return null; }
三. 測試一下
整個流程就是這樣的,測試一下吧,爬取hao123的一級域名,一共一百多個。代碼如下,沒啥好說的,結果顯示除了沒有Icon的網站和不能正常響應的網站,都可以得到Icon地址。但是,有一個網站除外,那就是我們偉大的12306。訪問www.12306.cn時,其中有一段js,判斷如果網址是www.12306.cn則locayion到http://www.12306.cn/mormhweb/。我靠,你用js跳轉我就直接沒轍了。哎,隨它去吧,各位可知道用js跳轉的壞處呀,如果禁用了js你看到的就是白花花的一片,像棉花、像銀子、像白雲、像一張白紙啊,親,我的想象力又被你激發了!
// 爬取一級域名 private static Set<String> getUrls( String urlString ) { Set<String> urlSet = new HashSet<String>(); Pattern pattern = Pattern .compile( "(http|https)://www\\..+?\\.(aero|arpa|biz|com|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|[a-z]{2})" ); Matcher matcher = pattern.matcher( getHtml( urlString ) ); while ( matcher.find() ) { urlSet.add( matcher.group() ); } return urlSet; }
public static void main( String[] args ) throws IOException { long startTime = System.currentTimeMillis(); Set<String> urlSet = getUrls( "http://www.hao123.com/" ); for ( String urlString : urlSet ) { System.out.println( urlString ); System.out.println( getIconUrlString( urlString ) ); } System.out.println( urlSet.size() ); System.out.println("耗時:"+(System.currentTimeMillis() - startTime)+" ms" ); }
特殊情況比較多,代碼肯定有不完善的地方,歡迎交流指正,完整代碼供下載: IconFinder.java