今頭條!(Java)記一次通過API遞歸分頁“爬取”網(wǎng)頁數(shù)據(jù)的開發(fā)經(jīng)歷

    來源: 博客園2023-05-30 12:33:40
      

    前言

    在最近的互聯(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ù)庫中。

    一、模擬http請(qǐng)求

    我們除了在項(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ù)解析。

    二、遞歸+循環(huán)設(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ù)的增加和遞歸終止條件。廢話不多說,直接上代碼:

    點(diǎn)擊查看代碼
    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;    }

    三、解析數(shù)據(jù)(調(diào)用頻率限制)

    一般來說,調(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ǎn)擊查看代碼
    /**     * 調(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ò)誤!--------");        }    }
    CommonAPICheckData類代碼:
    點(diǎn)擊查看代碼
    @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ù)持久化

    爬出來的數(shù)據(jù)我這里是按照一條一條入庫的,當(dāng)然也可以按照批次進(jìn)行batch的方式入庫:

    點(diǎn)擊查看代碼
    /**   * 數(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;    }

    五、小結(jié)

    其實(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)鍵詞:

    責(zé)任編輯:sdnew003

    相關(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)系。