在最近的互聯網項目開發中,需要獲取用戶的訪問ip信息進行統計的需求,用戶的訪問方式可能會從微信內置瀏覽器、Windows瀏覽器等方式對產品進行訪問。
當然,獲取這些關于ip的信息是合法的。但是,這些ip信息我們采用了其它第三方的服務來記錄,并不在我們的數據庫中。
這些ip信息是分組存放的,且每個分組都都是分頁(1頁10條)存放的,如果一次性訪問大量的數據,API很有可能會報錯。
(資料圖)
怎樣通過HTTP的方式去獲取到信息,并且模擬瀏覽器每頁每頁獲取10條的信息,且持久到數據庫中,就成了當下亟需解決的問題。
通過以上的分析,可以有大致以下思路:
1、拿到該網頁http請求的url地址,同時獲取到調用該網頁信息的參數(如:header、param等);
2、針對分頁參數進行設計,由于需要不斷地訪問同一個接口,所以可以用循環+遞歸的方式來調用;
3、將http接口的信息進行解析,同時保證一定的訪問頻率(大部分外部http請求都會有訪問頻率限制)讓返回的數據準確;
4、整理和轉化數據,按照一定的條件,批量持久化到數據庫中。
我們除了在項目自己寫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 commonPageMap, HashMap headersMap,String charset){ //接口調用頻率限制 check(commonAPICheckData); String linkUrl = "https://xxx.xxx.com/api/v1/xxx/xxx"; HashMap 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調用頻率錯誤!--------"); } }
@Datapublic 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上做加密等等,之后有時間還會接著分享。代碼比較粗糙,如果大家有其它建議,歡迎討論。如有錯誤,還望大家指正。
關鍵詞:
版權與免責聲明:
1 本網注明“來源:×××”(非商業周刊網)的作品,均轉載自其它媒體,轉載目的在于傳遞更多信息,并不代表本網贊同其觀點和對其真實性負責,本網不承擔此類稿件侵權行為的連帶責任。
2 在本網的新聞頁面或BBS上進行跟帖或發表言論者,文責自負。
3 相關信息并未經過本網站證實,不對您構成任何投資建議,據此操作,風險自擔。
4 如涉及作品內容、版權等其它問題,請在30日內同本網聯系。