在最近的互聯網項目開發中,需要獲取用戶的訪問ip信息,併進行後續統計分析。 這些ip信息是在第三方的服務中分組存放的,且每個分組都都是分頁(1頁10條)存放的,如果一次性訪問大量的數據,API很有可能會報錯。 怎樣通過HTTP的方式去獲取到信息,並且模擬瀏覽器每頁每頁獲取10條的信息,且持久到資料庫... ...
前言
在最近的互聯網項目開發中,需要獲取用戶的訪問ip信息進行統計的需求,用戶的訪問方式可能會從微信內置瀏覽器、Windows瀏覽器等方式對產品進行訪問。
當然,獲取這些關於ip的信息是合法的。但是,這些ip信息我們採用了其它第三方的服務來記錄,並不在我們的資料庫中。
這些ip信息是分組存放的,且每個分組都都是分頁(1頁10條)存放的,如果一次性訪問大量的數據,API很有可能會報錯。
怎樣通過HTTP的方式去獲取到信息,並且模擬瀏覽器每頁每頁獲取10條的信息,且持久到資料庫中,就成了當下亟需解決的問題。
通過以上的分析,可以有大致以下思路:
1、拿到該網頁http請求的url地址,同時獲取到調用該網頁信息的參數(如:header、param等);
2、針對分頁參數進行設計,由於需要不斷地訪問同一個介面,所以可以用迴圈+遞歸的方式來調用;
3、將http介面的信息進行解析,同時保證一定的訪問頻率(大部分外部http請求都會有訪問頻率限制)讓返回的數據準確;
4、整理和轉化數據,按照一定的條件,批量持久化到資料庫中。
一、模擬http請求
我們除了在項目自己寫API介面提供服務訪問外,很多時候也會使用到外部服務的API介面,通過調用這些API來返回我們需要的數據。
如:
釘釘開放平臺https://open.dingtalk.com/document/orgapp/user-information-creation、
微信開放平臺https://open.weixin.qq.com/cgi-bin/index?t=home/index&lang=zh_CN等等進行開發。
這裡分享一下從普通網頁獲取http介面url的方式,從而達到模擬http請求的效果:
以谷歌chrome瀏覽器為例,打開調式模式,根據下圖步驟:
尤其註意使用Fetch/XHR,右鍵該url—>copy—>copy as cURL(bash):
隨後粘貼到Postman中,模擬http請求:
粘貼的同時,還會自動帶上header參數(以下這4個參數比較重要,尤其是cookie):
這樣,就可以訪問到所需的數據了,並且數據是json格式化的,這便於我們下一步的數據解析。
二、遞歸+迴圈設計
有幾個要點需要註意:
1、分內外兩層,內外都需要獲取數據,即外層獲取的數據是內層所需要的;
2、每頁按照10條數據來返回,直到該請求沒有數據,則訪問下一個請求;
3、遞歸獲取當前請求的數據,註意頁數的增加和遞歸終止條件。
廢話不多說,直接上代碼:
點擊查看代碼
public boolean getIpPoolOriginLinksInfo(HashMap<String, Object> commonPageMap, HashMap<String, String> headersMap,String charset){
//介面調用頻率限制
check(commonAPICheckData);
String linkUrl = "https://xxx.xxx.com/api/v1/xxx/xxx";
HashMap<String, Object> linkParamsMap = new HashMap<>();
linkParamsMap.put("page",commonPageMap.get("page"));
linkParamsMap.put("size",commonPageMap.get("size"));
String httpLinkResponse = null;
//封裝好的工具類,可以直接用apache的原生httpClient,也可以用Hutool的工具包
httpLinkResponse = IpPoolHttpUtils.doGet(linkUrl,linkParamsMap,headersMap,charset);
JSONObject linkJson = null;
JSONArray linkArray = null;
linkJson = JSON.parseObject(httpLinkResponse).getJSONObject("data");
linkArray = linkJson.getJSONArray("resultList");
if (linkArray != null){
//遞歸計數
if (!commonPageMap.get("page").toString().equals("1")){
commonPageMap.put("page","1");
}
// 每10條urlId,逐一遍歷
for (Object linkObj : linkJson.getJSONArray("resultList")) {
JSONObject info = JSON.parseObject(linkObj.toString());
String urlId = info.getString("urlId");
// 獲取到的每個urlId,根據urlId去獲取ip信息(即內層的業務邏輯,我這裡忽略,本質就是拿這裡的參數傳到內層的方法中去)
boolean flag = getIpPoolOriginRecords(urlId, commonPageMap, headersMap, charset);
if (!flag){
break;
}
}
Integer page = Integer.parseInt(linkParamsMap.get("page").toString());
if (page <= Math.ceil(Integer.parseInt(linkJson.getString("total"))/10)){
Integer newPage = Integer.parseInt(linkParamsMap.get("page").toString()) + 1;
linkParamsMap.put("page", Integer.toString(newPage));
// 遞歸分頁拉取
getIpPoolOriginLinksInfo(linkParamsMap,headersMap,charset);
}
else {
return true;
}
}
return true;
}
三、解析數據(調用頻率限制)
一般來說,調用外部API介面會有調用頻率的限制,即一段時間內不允許頻繁請求介面,否則會返回報錯,或者禁止調用。基於這樣的限制,我們可以簡單設計一個介面校驗頻率的方法,防止請求的頻率太快。
廢話不多說,代碼如下:
點擊查看代碼
/**
* 調用頻率校驗,按照分鐘為單位校驗
* @param commonAPICheckData
*/
private void check(CommonAPICheckData commonAPICheckData){
int minuteCount = commonAPICheckData.getMinuteCount();
try {
if (minuteCount < 2){
//接近頻率限制時,休眠2秒
Thread.sleep(2000);
}else {
commonAPICheckData.setMinuteCount(minuteCount-1);
}
} catch (InterruptedException e) {
throw new RuntimeException("-------外部API調用頻率錯誤!--------");
}
}
點擊查看代碼
@Data
public class CommonAPICheckData {
/**
* 每秒調用次數計數器,限制頻率:每秒鐘2次
*/
private int secondCount = 3;
/**
* 每分鐘調用次數計數器,頻率限制:每分鐘100次
*/
private int minuteCount = 100;
}
四、數據持久化
爬出來的數據我這裡是按照一條一條入庫的,當然也可以按照批次進行batch的方式入庫:
點擊查看代碼
/**
* 數據持久化
* @param commonIpPool
*/
private boolean getIpPoolDetails(CommonIpPool commonIpPool){
try {
//list元素去重,ip欄位唯一索引
commonIpPoolService.insert(commonIpPool);
} catch (Exception e) {
if (e instanceof DuplicateKeyException){
return true;
}
throw new RuntimeException(e.getMessage());
}
return true;
}
五、小結
其實,爬取數據的時候我們會遇到各種各樣的場景,這次分享的主要是分頁遞歸的相關思路。
有的時候,如果網頁做了防爬的功能,比如在header上加簽、訪問前進行滑動圖片校驗、在cookie上做加密等等,之後有時間還會接著分享。
代碼比較粗糙,如果大家有其它建議,歡迎討論。
如有錯誤,還望大家指正。