繼【簡單的java采集程序】,這里將完成對整個網站的號碼段的采集任務。
【使用預編譯+批處理采集網頁內容至數據庫表中】
在之前我們用statement類來創建sql語句的執行對象,以實現插入字段到數據庫的操作,但由于插入的數據量較大,如果繼續用statement操作話,會很耗時間,我們用其子類PReparedStatement來進行操作。
PreparedStatement 可以實現sql語句的預編譯,我們只需要通過其setString()方法傳參即可,這樣不僅效率提高了,而且也會更加安全,可防止SQL注入。推薦相關文章
另外我們還可以調用其addBatch()方法 和 executeBatch()實現批量插入處理。
代碼如下,喜歡把數據庫鏈接作為一個單獨的類
import java.sql.DriverManager;import java.sql.SQLException;import com.MySQL.jdbc.Connection;public class database {public static String driver ="com.mysql.jdbc.Driver";public static String url="jdbc:mysql://127.0.0.1:3306/tele_dat?autoReconnect=true&characterEncoding=UTF-8";public static String user ="root";public static String passWord = "123456";public static java.sql.Connection conn = null;//返回一個數據庫連接對象public static Connection ConnectToDataBase(){try {Class.forName(driver);} catch (ClassNotFoundException e) {System.out.println("加載驅動失敗");e.printStackTrace();}try {conn = DriverManager.getConnection(url, user, password);System.out.println("連接成功");} catch (SQLException e) {System.out.println("連接出問題了");e.printStackTrace();}return (Connection) conn;}//測試連接數據庫public static void main(String args[]){database.ConnectToDataBase();}}
主體程序
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.net.URL;import java.sql.Connection;import java.sql.PreparedStatement;import java.util.regex.Matcher;import java.util.regex.Pattern;public class Crawl {private static Connection conn = database.ConnectToDataBase(); static String home_url = "http://www.hiphop8.com"; //網站首頁 static String pattern_pro_city = "<DIV class=title><SPAN>(.*?) - (.*?)<///SPAN><///DIV>"; //匹配省名,市名 static String pattern_number = ">(13//d{5}|15//d{5}|18//d{5}|147//d{4})<"; //匹配號碼段 static String pattern_pro ="//w{3}//.//w{7}//.//w{3}/////w{4}/////w+"; //省份URLstatic String pattern_city_hz="<LI><A href=/"(.*?)/" target=_blank>"; //城市URL的后綴 //編譯預處理相關選項 static String insertSQL = "insert ignore into number_segment(segment,province,city) values(?, ?, ?)"; static PreparedStatement pst = null; static int num_pro = 0;static int num_city=0; static int all_num_tele = 0;public static void main(String[] args) throws Exception {String PreStat = "insert ignore into number_segment(segment,province,city) values (?,?,?) "; pst = conn.prepareStatement(PreStat.toString()); Matcher mat_home = get(home_url,pattern_pro);long start = System.currentTimeMillis();while(mat_home.find()){num_pro++;System.out.println("------第"+num_pro+"個省-----");String city_url_qz = "http://"+mat_home.group()+"/";int len = city_url_qz.length();//這里換成StringBuffer來最字符串進行相加處理StringBuffer city_ur = new StringBuffer();city_ur.append(city_url_qz);Matcher mat_city_hz = get(city_url_qz,pattern_city_hz);while(mat_city_hz.find()) //通過拼接獲得 城市的完整url{num_city++;System.out.println("第"+num_city+"個市");String last_city_url=city_ur.append(mat_city_hz.group(1)).toString();//String last_city_url = city_url_qz + mat_city_hz.group(1);int len2 = last_city_url.length();One_City_Tele_to_DB(last_city_url);city_ur.delete(len,len2);}}long end = System.currentTimeMillis();long time = (end-start)/(1000*60);conn.close();System.out.println("查詢到的電話號碼段總數量:"+all_num_tele);System.out.println("花費的時間是:"+time);} public static void One_City_Tele_to_DB(String url) throws Exception { int this_city_num=0; String pro = null; String city = null; Matcher mat_pro_city = get(url,pattern_pro_city); //獲取省名字 市名字 while(mat_pro_city.find()) { String long_pro = mat_pro_city.group(1); pro = long_pro.substring(0, long_pro.length()-1); String long_city = mat_pro_city.group(2); city = long_city.substring(0, long_city.length()-10); System.out.println("省份:"+pro+" "+"城市:"+city+" 正在插入號碼段進數據庫"); } Matcher mat_number = get(url,pattern_number); //獲取號碼段 while(mat_number.find()) { pst.setString(1,mat_number.group(1)); pst.setString(2, pro); pst.setString(3, city); pst.addBatch(); this_city_num++; all_num_tele++; } pst.executeBatch(); //每次批量插入一個城市的號碼段 pst.clearBatch(); System.out.println("該市插入的號碼段的數量是:"+ this_city_num);} //正則匹配public static Matcher get(String str_url, String pattern) throws Exception { String urlsource = get_Html(str_url);Pattern p = Pattern.compile(pattern);Matcher m = p.matcher(urlsource);return m;}//獲取網頁內容public static String get_Html(String str_url) throws IOException{ URL url = new URL(str_url); String content="";StringBuffer page = new StringBuffer();try {BufferedReader in = new BufferedReader(new InputStreamReader(url .openStream()));while((content = in.readLine()) != null){page.append(content);}} catch (IOException e) {e.printStackTrace();} return page.toString(); }}
實際運行程序,發現有500多個重復的號碼段,因為襄樊市 改成 襄陽市,這兩個市的號碼段全部一樣,而數據庫表中是以segment(號碼)作為主鍵,所以要設置,當插入有相同主鍵的sql語句時,自動忽略跳過,方法就是在insert 后面加上ignore就可以了。
另外設置id為auto_increment,但如果把數據表里的數據清空之后,id不會從1重新開始,這時只要在mysql命令行下輸入 truncate table table_name 就可以實現id從1開始了。
運行結果截圖
顯然,6分鐘的速度還是太慢了,后面試了幾次都是在6~8分鐘之內(不過相對于不用批處理而直接用statement已經快很多了)。因此還得想辦法優化。
【使用智能批量處理+StringBuilder的append()方法】在插入mysql數據庫表中時,可以以insert ignore into number_segment(segment,province,city) values(?, ?, ?),vaulues(?),values(?,?,?)…的形式進行批量插入,上面使用setString()方法傳參還是太慢了,直接用StringBuilder的append()方法吧,我們都知道appedn()方法進行字符串相加處理的效率是很高的,所以我們每次在insert語句后面加入一段“vaulues(?,?,?),”這樣形式的字符串,然后批處理插入,這里通過變量all_tele_num進行批量處理的的控制,我們規定每次批量執行2000個數據段插入。
直接看代碼吧
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.net.URL;import java.sql.Connection;import java.sql.PreparedStatement;import java.util.regex.Matcher;import java.util.regex.Pattern;public class SecondCrawl {private static Connection conn = database.ConnectToDataBase();//預編譯 + StringBuilderstatic StringBuilder PreStat = new StringBuilder(); static String Qz = "insert ignore into number_segment(segment,province,city) values"; static String insertSQL = "insert ignore into number_segment(segment,province,city) values(?, ?, ?)"; static int len1 = Qz.length(); static PreparedStatement pst = null; static String home_url = "http://www.hiphop8.com"; static String pattern_pro_city = "<DIV class=title><SPAN>(.*?) - (.*?)<///SPAN><///DIV>"; //匹配省名,市名 static String pattern_number = ">(13//d{5}|15//d{5}|18//d{5}|147//d{4})<"; //匹配號碼段 static String pattern_pro ="//w{3}//.//w{7}//.//w{3}/////w{4}/////w+"; //省份URLstatic String pattern_city_hz="<LI><A href=/"(.*?)/" target=_blank>"; //城市URL的后綴 static int num_pro = 0;static int num_city=0;static int all_num_tele=0;public static void main(String[] args) throws Exception {Matcher mat_home = get(home_url,pattern_pro); conn.setAutoCommit(true); PreStat.append(Qz); pst = conn.prepareStatement(insertSQL); //預編譯long start = System.currentTimeMillis();while(mat_home.find()){num_pro++;System.out.println("------第"+num_pro+"個省-----");String city_url_qz = "http://"+mat_home.group()+"/";int len = city_url_qz.length();StringBuffer city_ur = new StringBuffer();city_ur.append(city_url_qz);Matcher mat_city_hz = get(city_url_qz,pattern_city_hz);while(mat_city_hz.find()) //獲得城市的url{num_city++;System.out.println("第"+num_city+"個市");String city_url=city_ur.append(mat_city_hz.group(1)).toString();int len2 = city_url.length();One_City_Tele_to_DB(city_url);city_ur.delete(len,len2);}}long end = System.currentTimeMillis();long time = (end-start)/(1000*60);pst.executeBatch(); //批處理執行最后面剩余的部分conn.close();System.out.println("查詢到的電話號碼段總數量:"+all_num_tele);System.out.println("花費的時間是:"+time+"分多鐘/n"+"以微秒為單位:"+(end-start)+"微秒");}//一個城市的手機號碼段處理函數public static void One_City_Tele_to_DB(String url) throws Exception { String city=null; String pro =null; int this_city_num = 0; Matcher mat_pro_city = get(url,pattern_pro_city); while(mat_pro_city.find()) { String long_pro = mat_pro_city.group(1); pro = long_pro.substring(0, long_pro.length()-1); String long_city = mat_pro_city.group(2); city = long_city.substring(0, long_city.length()-10); System.out.println("省份:"+pro+" "+"城市:"+city+" 正在插入號碼段進數據庫..."); } String temp = ",'"+pro+"','"+city+"'),"; Matcher mat_number = get(url,pattern_number); while(mat_number.find()) { PreStat.append("("+mat_number.group(1)).append(temp); this_city_num++; all_num_tele++; if(all_num_tele<=208000 && all_num_tele % 2000==0) { PreStat.deleteCharAt(PreStat.length()-1); //除去sql語句后的逗號 pst.addBatch(PreStat.toString()); pst.executeBatch(); pst.clearBatch(); PreStat.delete(len1, PreStat.length()); //情況sql語句后面部分以釋放空間 } } if(all_num_tele>208000) //后面不足2000部分的城市加入批處理中先不執行 { PreStat.deleteCharAt(PreStat.length()-1); pst.addBatch(PreStat.toString()); PreStat.delete(len1, PreStat.length()); } System.out.println("該市插入的號碼段的數量是:"+ this_city_num);} //正則匹配public static Matcher get(String str_url, String pattern) throws Exception { String urlsource = get_Html(str_url);Pattern p = Pattern.compile(pattern);Matcher m = p.matcher(urlsource);return m;}//獲取網頁內容public static String get_Html(String str_url) throws IOException{ URL url = new URL(str_url); String content="";StringBuffer page = new StringBuffer();try {BufferedReader in = new BufferedReader(new InputStreamReader(url .openStream()));while((content = in.readLine()) != null){page.append(content);}} catch (IOException e) {e.printStackTrace();} return page.toString(); }}
運行截圖
測試了幾次,運行時間是在2分鐘左右,又提速了不少,不過還是有很大的提升空間的,因為自己在測試時,如果程序僅僅是插入20多萬的sql語句,可以在幾秒鐘內完成。
說道再優化,自己的思路是把網站url采集和插入數據庫使用多線程進行并發操作,現在正在學習java的多線程,也在嘗試用多線程的方法寫采集程序,如果大家還有更好的方法,也可以給我留言,愿意和大家一起交流進步。
新聞熱點
疑難解答