一直想練習下java多線程抓取數據。
有天被我發現,鈴聲多多的官網(http://www.shoujiduoduo.com/main/)有大量的數據。
通過觀察他們前端獲取鈴聲數據的ajax
http://www.shoujiduoduo.com/ringweb/ringweb.php?type=getlist&listid={類別ID}&page={分頁頁碼}
很容易就能發現通過改變listId和page就能從服務器獲取鈴聲的json數據, 通過解析json數據,
可以看到都帶有{"hasmore":1,"curpage":1}這樣子的指示,通過判斷hasmore的值,決定是否進行下一頁的抓取。
但是通過上面這個鏈接返回的json中不帶有鈴聲的下載地址
很快就可以發現,點擊頁面的“下載”會看到
通過下面的請求,就可以獲取鈴聲的下載地址了
http://www.shoujiduoduo.com/ringweb/ringweb.php?type=geturl&act=down&rid={鈴聲ID}
所以,他們的數據是很容易被偷的。於是我就開始...
源碼已經發在github上。如果感興趣的童鞋可以查看
github:https://github.com/yongbo000/DuoduoAudioRobot
上代碼:
package me.yongbo.DuoduoRingRobot;import java.io.BufferedReader;import java.io.File;import java.io.FileWriter;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.net.URL;import java.net.URLConnection;import java.util.Iterator;import java.util.regex.Matcher;import java.util.regex.Pattern;import com.google.gson.Gson;import com.google.gson.JsonArray;import com.google.gson.JsonElement;import com.google.gson.JsonParser;/* * @author yongbo_ * @created 2013/4/16 * * */public class DuoduoRingRobotClient implements Runnable {public static String GET_RINGINFO_URL = "http://www.shoujiduoduo.com/ringweb/ringweb.php?type=getlist&listid=%1$d&page=%2$d";public static String GET_DOWN_URL = "http://www.shoujiduoduo.com/ringweb/ringweb.php?type=geturl&act=down&rid=%1$d";public static String ERROR_MSG = "listId為%1$d 的Robot發生錯誤,已自動停止。當前page為%2$d";public static String STATUS_MSG = "開始抓取數據,當前listId: %1$d,當前page: %2$d";public static String FILE_DIR = "E:/RingData/";public static String FILE_NAME = "listId=%1$d.txt";private boolean errorFlag = false;private int listId;private int page;private int endPage = -1;private int hasMore = 1;private DbHelper dbHelper;/** * 構造函數* @param listId 菜單ID * @param page 開始頁碼* @param endPage 結束頁碼* */public DuoduoRingRobotClient(int listId, int beginPage, int endPage) {this.listId = listId;this.page = beginPage;this.endPage = endPage;this.dbHelper = new DbHelper();}/** * 構造函數* @param listId 菜單ID * @param page 開始頁碼* */public DuoduoRingRobotClient(int listId, int page) {this(listId, page, -1);}/** * 獲取鈴聲* */public void getRings() {String url = String.format(GET_RINGINFO_URL, listId, page);String responseStr = httpGet(url);hasMore = getHasmore(responseStr);page = getNextPage(responseStr);ringParse(responseStr.replaceAll("//{/"hasmore/":[0-9]*,/"curpage/":[0-9]*//},", "").replaceAll(",]", "]"));}/** * 發起http請求* @param webUrl 請求連接地址* */public String httpGet(String webUrl){URL url;URLConnection conn;StringBuilder sb = new StringBuilder();String resultStr = "";try {url = new URL(webUrl);conn = url.openConnection();conn.connect();InputStream is = conn.getInputStream();InputStreamReader isr = new InputStreamReader(is);BufferedReader bufReader = new BufferedReader(isr);String lineText;while ((lineText = bufReader.readLine()) != null) {sb.append(lineText);}resultStr = sb.toString();} catch (Exception e) {errorFlag = true;//將錯誤寫入txtwriteToFile(String.format(ERROR_MSG, listId, page));}return resultStr;}/** * 將json字符串轉化成Ring對象,並存入txt中* @param json Json字符串* */public void ringParse(String json) {Ring ring = null;JsonElement element = new JsonParser().parse(json);JsonArray array = element.getAsJsonArray();// 遍歷數組Iterator<JsonElement> it = array.iterator();Gson gson = new Gson();while (it.hasNext() && !errorFlag) {JsonElement e = it.next();// JsonElement轉換為JavaBean對象ring = gson.fromJson(e, Ring.class);ring.setDownUrl(getRingDownUrl(ring.getId()));if(isAvailableRing(ring)) {System.out.println(ring.toString());//可選擇寫入數據庫還是寫入文本//writeToFile(ring.toString());writeToDatabase(ring);}}}/** * 寫入txt * @param data 字符串* */public void writeToFile(String data) {String path = FILE_DIR + String.format(FILE_NAME, listId);File dir = new File(FILE_DIR);File file = new File(path);FileWriter fw = null;if(!dir.exists()){dir.mkdirs();}try {if(!file.exists()){file.createNewFile();}fw = new FileWriter(file, true);fw.write(data);fw.write("/r/n");fw.flush();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}finally {try {if(fw != null){fw.close();}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}/** * 寫入數據庫* @param ring 一個Ring的實例* */public void writeToDatabase(Ring ring) {dbHelper.execute("addRing", ring);}@Overridepublic void run() {while(hasMore == 1 && !errorFlag){if(endPage != -1){if(page > endPage) { break; }}System.out.println(String.format(STATUS_MSG, listId, page));getRings();System.out.println(String.format("該頁數據寫入完成"));}System.out.println("ending...");}private int getHasmore(String resultStr){Pattern p = Pattern.compile("/"hasmore/":([0-9]*),/"curpage/":([0-9]*)"); Matcher match = p.matcher(resultStr); if (match.find()) { return Integer.parseInt(match.group(1)); } return 0;}private int getNextPage(String resultStr){Pattern p = Pattern.compile("/"hasmore/":([0-9]*),/"curpage/":([0-9]*)");Matcher match = p.matcher(resultStr);if (match.find()) {return Integer.parseInt(match.group(2));}return 0;}/** * 判斷當前Ring是否滿足條件。當Ring的name大於50個字符或是duration為小數則不符合條件,將被剔除。 * @param ring 當前Ring對象實例* */private boolean isAvailableRing(Ring ring){Pattern p = Pattern.compile("^[1-9][0-9]*$");Matcher match = p.matcher(ring.getDuration());if(!match.find()){return false;}if(ring.getName().length() > 50 || ring.getArtist().length() > 50 || ring.getDownUrl().length() == 0){return false;}return true;}/** * 獲取鈴聲的下載地址* @param rid 鈴聲的id * */public String getRingDownUrl(String rid){String url = String.format(GET_DOWN_URL, rid);String responseStr = httpGet(url);return responseStr;}}