開篇
作為全球運用最廣泛的語言,Java 憑借它的高效性,可移植性(跨平台),代碼的健壯性以及強大的可擴展性,深受廣大應用程序開發者的喜愛. 作為一門強大的開發語言,正則表達式在其中的應用當然是必不可少的,而且正則表達式的掌握能力也是那些高級程序員的開發功底之體現,做一名合格的網站開發的程序員(尤其是做前端開發),正則表達式是必備的.
最近,由於一些需要,用到了java和正則,做了個的足球網站的數據采集程序;由於是第一次做關於java的html頁面數據采集,必然在網上查找了很多資料,但是發現運用如此廣泛的java在使用正則做html采集方面的(中文)文章是少之又少,都是簡單的談了下java正則的概念,沒有真正用在實際網頁html采集,實例教程更是寥寥無幾(雖然java有它自己的Html Parser,而且十分強大),但個人覺得作為如此深入人心的正則表達式,理應有其相關的java實例教程,而且應該很多很全.於是在完成java版的html數據采集程序之后,本人便打算寫個關於正則表達式在java上的html頁面采集,以便有相關興趣的讀者更好的學習.
本期概述
這期我們來學習下如何讀取網頁源代碼,並通過group正則 動態抓取我們需要的網頁數據.同時在接下來的幾期,我們將繼續學習[數據存儲]如何將抓取的比賽數據存到數據庫(MySql), [數據查詢] 怎樣查詢我們想看的比賽記錄,以及[遠程操作]通過客戶端遠程訪問和操作服務器來進行數據的采集,存儲和查詢.
關於group正則
說到正則表達式是如何幫助java進行html頁面采集,這里需要提一下正則表達式中的group方法(代碼如下).
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Group 類 用於匹配和抓取 html頁面的數據
* @author SoFlash - 博客園 http://www.cnblogs.com/longwu
*/
public class Group {
public static void main(String[] args) {
// Pattern 用於編譯正則 這里用到了3個正則 分別用括號()包住
// 第1個正則用於匹配URL 當然這里的正則不一定准確 這個匹配URL的正則就是錯誤的 只是在這里剛好能匹配出來
// 第2個正則是用於匹配標題 SoFlash的
// 第3個正則用於匹配日期
/* 這里只用了一條語句便把url,標題和日期全部給匹配出來了 */
Pattern p = Pattern
.compile("='(\\w.+)'>(\\w.+[a-zA-Z])-(\\d{1,2}\\.\\d{1,2}\\.\\d{4})");
String s = "<a href='http://www.cnblogs.com/longwu'>SoFlash-12.22.2011</a>";
Matcher m = p.matcher(s);
while (m.find()) {
// 通過調用group()方法里的索引 將url,標題和日期全部給打印出來
System.out.println("打印出url鏈接:" + m.group(1));
System.out.println("打印出標題:" + m.group(2));
System.out.println("打印出日期:" + m.group(3));
System.out.println();
}
System.out.println("group方法捕獲的數據個數:" + m.groupCount() + "個");
}
}
我們看下輸出結果:
打印出url鏈接:http://www.cnblogs.com/longwu
打印出標題:SoFlash
打印出日期:12.22.2011
group方法捕獲的數據個數:3個
想了解更多的正則在java方面應用的朋友請看 JAVA 正則表達式 (超詳細)
如果之前沒有學過正則的可以看看這個 揭開正則表達式的神秘面紗
頁面采集實例
好了 group方法介紹完了 我們來使用下group正則采集某個足球網站頁面的數據
頁面鏈接: 2011-2012賽季英超球隊戰績
首先我們讀取整個html頁面,並打印出來(代碼如下).
public static void main(String[] args) {
String strUrl = "http://www.footballresults.org/league.php?all=1&league=EngPrem";
try {
// 創建一個url對象來指向 該網站鏈接 括號里()裝載的是該網站鏈接的路徑
// 更多可以看看 http://wenku.baidu.com/view/8186caf4f61fb7360b4c6547.html
URL url = new URL(strUrl);
// InputStreamReader 是一個輸入流讀取器 用於將讀取的字節轉換成字符
// 更多可以看看 http://blog.sina.com.cn/s/blog_44a05959010004il.html
InputStreamReader isr = new InputStreamReader(url.openStream(),
"utf-8"); // 統一使用utf-8 編碼模式
// 使用 BufferedReader 來讀取 InputStreamReader 轉換成的字符
BufferedReader br = new BufferedReader(isr);
// 如果 BufferedReader 讀到的內容不為空
while (br.readLine() != null) {
// 則打印出來 這里打印出來的結果 應該是整個網站的
System.out.println(br.readLine());
}
br.close(); // 讀取完成后關閉讀取器
} catch (IOException e) {
// 如果出錯 拋出異常
e.printStackTrace();
}
}
打印出來的結果是整個html頁面的源代碼(部分截圖如下).
到這里,html源代碼已經成功采集下來了.但是,我們想要的並不是整個html源代碼, 而是網頁上的比賽數據.
首先,我們分析下 html源代碼結構 來到 2011-2012賽季英超球隊戰績 頁面,右鍵 點擊 '查看源文件' (其它瀏覽器可能叫源代碼或者相關).
我們來看看 其內部的html代碼結構 以及我們需要的數據.
其對應的頁面數據
這時候,強大的正則表達式派上用場了, 我們需要寫幾個用來抓取球隊數據的正則.
這里需要用到3個正則表達式: 日期正則,2個球隊正則(主隊和客隊)以及比賽結果正則.
String regularDate = "(\\d{1,2}\\.\\d{1,2}\\.\\d{4})"; //日期正則
String regularTwoTeam = ">[^<>]*</a>"; //球隊正則
String regularResult = ">(\\d{1,2}-\\d{1,2})</TD>"; //比賽結果正則
寫好正則, 我們便可以使用該正則來抓取我們想要得到的數據了.
首先,我們寫一個GroupMethod類, 里面包含了regularGroup()方法,用於抓取html頁面數據.
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* GroupMethod類 用於匹配並抓去 Html上我們想要的內容
* @author SoFlash - 博客園 http://www.cnblogs.com/longwu
*/
public class GroupMethod {
// 傳入2個字符串參數 一個是pattern(我們使用的正則) 另一個matcher是html源代碼
public String regularGroup(String pattern, String matcher) {
Pattern p = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(matcher);
if (m.find()) { // 如果讀到
return m.group();// 返回捕獲的數據
} else {
return ""; // 否則返回一個空字符串
}
}
}
然后在主函數里實施html頁面的數據抓取.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
/**
* Main主函數 用於數據采集
* @author SoFlash - 博客園 http://www.cnblogs.com/longwu
*/
public class Main {
public static void main(String[] args) {
// 首先用一個字符串 來裝載網頁鏈接
String strUrl = "http://www.footballresults.org/league.php?all=1&league=EngPrem";
try {
// 創建一個url對象來指向 該網站鏈接 括號里()裝載的是該網站鏈接的路徑
// 更多可以看看 http://wenku.baidu.com/view/8186caf4f61fb7360b4c6547.html
URL url = new URL(strUrl);
// InputStreamReader 是一個輸入流讀取器 用於將讀取的字節轉換成字符
// 更多可以看看 http://blog.sina.com.cn/s/blog_44a05959010004il.html
InputStreamReader isr = new InputStreamReader(url.openStream(),
"utf-8"); // 統一使用utf-8 編碼模式
// 使用 BufferedReader 來讀取 InputStreamReader 轉換成的字符
BufferedReader br = new BufferedReader(isr);
String strRead = ""; // 新增一個空字符串strRead來裝載 BufferedReader 讀取到的內容
// 定義3個正則 用於匹配我們需要的數據
String regularDate = "(\\d{1,2}\\.\\d{1,2}\\.\\d{4})";
String regularTwoTeam = ">[^<>]*</a>";
String regularResult = ">(\\d{1,2}-\\d{1,2})</TD>";
// 創建一個GroupMethod類的對象 gMethod 方便后期調用其類里的 regularGroup方法
GroupMethod gMethod = new GroupMethod();
int i =0; //定義一個i來記錄循環次數 即收集到的球隊比賽結果數
int index = 0; // 定義一個索引 用於獲取分離 2個球隊的數據 因為2個球隊正則是相同的
// 開始讀取數據 如果讀到的數據不為空 則往里面讀
while ((strRead = br.readLine()) != null) {
/**
* 用於捕獲日期數據
*/
String strGet = gMethod.regularGroup(regularDate, strRead);
//如果捕獲到了符合條件的 日期數據 則打印出來
if (!strGet.equals("")) {
System.out.println("Date:" + strGet);
//這里索引+1 是用於獲取后期的球隊數據
++index; //因為在html頁面里 源代碼里 球隊數據是在剛好在日期之后
}
/**
* 用於獲取2個球隊的數據
*/
strGet = gMethod.regularGroup(regularTwoTeam, strRead);
if (!strGet.equals("") && index == 1) { //索引為1的是主隊數據
// 通過substring方法 分離出 主隊數據
strGet = strGet.substring(1, strGet.indexOf("</a>"));
System.out.println("HomeTeam:" + strGet); //打印出主隊
index++; //索引+1之后 為2了
// 通過substring方法 分離出 客隊
} else if (!strGet.equals("") && index == 2) { //這里索引為2的是客隊數據
strGet = strGet.substring(1, strGet.indexOf("</a>"));
System.out.println("AwayTeam:" + strGet); //打印出客隊
index = 0;
}
/**
* 用於獲取比賽結果
*/
strGet = gMethod.regularGroup(regularResult, strRead);
if (!strGet.equals("")) {
//這里同樣用到了substring方法 來剔除'<' 和 "</TD>" 標簽 來獲取我們想要的比賽結果
strGet = strGet.substring(1, strGet.indexOf("</TD>"));
System.out.println("Result:" + strGet);
System.out.println();
i++;
}
}
// 當讀完數據后 記得關閉 BufferReader
br.close();
System.out.println("共收集到"+i+"條比賽記錄");//打印出循環次數
} catch (IOException e) {
// 如果出錯 拋出異常
e.printStackTrace();
}
}
}
運行查看
我們來看看輸出結果(部分截圖-初始階段)
對比下 html上的數據 (部分截圖-初始階段)
輸出結果(部分截圖-結束階段)
對比下 html上的數據 (部分截圖-結束階段)
好了, 這樣的html數據采集就完成了. :)
當然這里只是抓取了一個頁面的內容,如果感興趣 想抓去更多的頁面內容, 你可以分析下該鏈接后的聯盟名, 例如 league=EngPrem
通過改變league名來獲取所有聯盟的比賽數據; 你也可以簡單寫個數組來裝載所有的球隊名稱;
當然還有更智能的方法, 寫個采集數據的方法從http://www.footballresults.org/allleagues.php 頁面源代碼里獲取所有聯盟的名字(如下圖).
然后來附加到 "http://www.footballresults.org/league.php?all=1&league=" 鏈接后面 來補齊鏈接, 進而循環讀取各個聯盟比賽頁面的內容.
附上源代碼下載 htmlDataCollection.zip