在最近的互聯(lián)網(wǎng)項(xiàng)目開發(fā)中,需要獲取用戶的訪問ip信息進(jìn)行統(tǒng)計(jì)的需求,用戶的訪問方式可能會(huì)從微信內(nèi)置瀏覽器、Windows瀏覽器等方式對(duì)產(chǎn)品進(jìn)行訪問。
當(dāng)然,獲取這些關(guān)于ip的信息是合法的。但是,這些ip信息我們采用了其它第三方的服務(wù)來記錄,并不在我們的數(shù)據(jù)庫中。
這些ip信息是分組存放的,且每個(gè)分組都都是分頁(1頁10條)存放的,如果一次性訪問大量的數(shù)據(jù),API很有可能會(huì)報(bào)錯(cuò)。
(資料圖)
怎樣通過HTTP的方式去獲取到信息,并且模擬瀏覽器每頁每頁獲取10條的信息,且持久到數(shù)據(jù)庫中,就成了當(dāng)下亟需解決的問題。
通過以上的分析,可以有大致以下思路:
1、拿到該網(wǎng)頁http請(qǐng)求的url地址,同時(shí)獲取到調(diào)用該網(wǎng)頁信息的參數(shù)(如:header、param等);
2、針對(duì)分頁參數(shù)進(jìn)行設(shè)計(jì),由于需要不斷地訪問同一個(gè)接口,所以可以用循環(huán)+遞歸的方式來調(diào)用;
3、將http接口的信息進(jìn)行解析,同時(shí)保證一定的訪問頻率(大部分外部http請(qǐng)求都會(huì)有訪問頻率限制)讓返回的數(shù)據(jù)準(zhǔn)確;
4、整理和轉(zhuǎn)化數(shù)據(jù),按照一定的條件,批量持久化到數(shù)據(jù)庫中。
我們除了在項(xiàng)目自己寫API接口提供服務(wù)訪問外,很多時(shí)候也會(huì)使用到外部服務(wù)的API接口,通過調(diào)用這些API來返回我們需要的數(shù)據(jù)。
如:
釘釘開放平臺(tái)https://open.dingtalk.com/document/orgapp/user-information-creation、
微信開放平臺(tái)https://open.weixin.qq.com/cgi-bin/index?t=home/index&lang=zh_CN等等進(jìn)行開發(fā)。
這里分享一下從普通網(wǎng)頁獲取http接口url的方式,從而達(dá)到模擬http請(qǐng)求的效果:以谷歌chrome瀏覽器為例,打開調(diào)式模式,根據(jù)下圖步驟:尤其注意使用Fetch/XHR,右鍵該url—>copy—>copy as cURL(bash):隨后粘貼到Postman中,模擬http請(qǐng)求:粘貼的同時(shí),還會(huì)自動(dòng)帶上header參數(shù)(以下這4個(gè)參數(shù)比較重要,尤其是cookie):這樣,就可以訪問到所需的數(shù)據(jù)了,并且數(shù)據(jù)是json格式化的,這便于我們下一步的數(shù)據(jù)解析。
有幾個(gè)要點(diǎn)需要注意:1、分內(nèi)外兩層,內(nèi)外都需要獲取數(shù)據(jù),即外層獲取的數(shù)據(jù)是內(nèi)層所需要的;2、每頁按照10條數(shù)據(jù)來返回,直到該請(qǐng)求沒有數(shù)據(jù),則訪問下一個(gè)請(qǐng)求;3、遞歸獲取當(dāng)前請(qǐng)求的數(shù)據(jù),注意頁數(shù)的增加和遞歸終止條件。廢話不多說,直接上代碼:
public boolean getIpPoolOriginLinksInfo(HashMap commonPageMap, HashMap headersMap,String charset){ //接口調(diào)用頻率限制 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){ //遞歸計(jì)數(shù) 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"); // 獲取到的每個(gè)urlId,根據(jù)urlId去獲取ip信息(即內(nèi)層的業(yè)務(wù)邏輯,我這里忽略,本質(zhì)就是拿這里的參數(shù)傳到內(nèi)層的方法中去) 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; }
一般來說,調(diào)用外部API接口會(huì)有調(diào)用頻率的限制,即一段時(shí)間內(nèi)不允許頻繁請(qǐng)求接口,否則會(huì)返回報(bào)錯(cuò),或者禁止調(diào)用。基于這樣的限制,我們可以簡單設(shè)計(jì)一個(gè)接口校驗(yàn)頻率的方法,防止請(qǐng)求的頻率太快。廢話不多說,代碼如下:
/** * 調(diào)用頻率校驗(yàn),按照分鐘為單位校驗(yàn) * @param commonAPICheckData */ private void check(CommonAPICheckData commonAPICheckData){ int minuteCount = commonAPICheckData.getMinuteCount(); try { if (minuteCount < 2){ //接近頻率限制時(shí),休眠2秒 Thread.sleep(2000); }else { commonAPICheckData.setMinuteCount(minuteCount-1); } } catch (InterruptedException e) { throw new RuntimeException("-------外部API調(diào)用頻率錯(cuò)誤!--------"); } }
@Datapublic class CommonAPICheckData { /** * 每秒調(diào)用次數(shù)計(jì)數(shù)器,限制頻率:每秒鐘2次 */ private int secondCount = 3; /** * 每分鐘調(diào)用次數(shù)計(jì)數(shù)器,頻率限制:每分鐘100次 */ private int minuteCount = 100;}
爬出來的數(shù)據(jù)我這里是按照一條一條入庫的,當(dāng)然也可以按照批次進(jìn)行batch的方式入庫:
/** * 數(shù)據(jù)持久化 * @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; }
其實(shí),爬取數(shù)據(jù)的時(shí)候我們會(huì)遇到各種各樣的場(chǎng)景,這次分享的主要是分頁遞歸的相關(guān)思路。有的時(shí)候,如果網(wǎng)頁做了防爬的功能,比如在header上加簽、訪問前進(jìn)行滑動(dòng)圖片校驗(yàn)、在cookie上做加密等等,之后有時(shí)間還會(huì)接著分享。代碼比較粗糙,如果大家有其它建議,歡迎討論。如有錯(cuò)誤,還望大家指正。
關(guān)鍵詞:
版權(quán)與免責(zé)聲明:
1 本網(wǎng)注明“來源:×××”(非商業(yè)周刊網(wǎng))的作品,均轉(zhuǎn)載自其它媒體,轉(zhuǎn)載目的在于傳遞更多信息,并不代表本網(wǎng)贊同其觀點(diǎn)和對(duì)其真實(shí)性負(fù)責(zé),本網(wǎng)不承擔(dān)此類稿件侵權(quán)行為的連帶責(zé)任。
2 在本網(wǎng)的新聞頁面或BBS上進(jìn)行跟帖或發(fā)表言論者,文責(zé)自負(fù)。
3 相關(guān)信息并未經(jīng)過本網(wǎng)站證實(shí),不對(duì)您構(gòu)成任何投資建議,據(jù)此操作,風(fēng)險(xiǎn)自擔(dān)。
4 如涉及作品內(nèi)容、版權(quán)等其它問題,請(qǐng)?jiān)?0日內(nèi)同本網(wǎng)聯(lián)系。