Java基于Selenium動態(tài)抓取頁面

前情介紹

前段時間開發(fā)了一個功能,通過HttpClient訪問某個頁面,獲取頁面的全部html內(nèi)容霜浴;之后通過抓取過來的整個頁面展示在自己的網(wǎng)頁中;但是過了一段時間之后蓝纲,網(wǎng)頁升級了阴孟,網(wǎng)頁中的圖片都變成了動態(tài)加載,直接通過HttpClient無法獲取完整的頁面內(nèi)容税迷,圖片都是懶加載狀態(tài)無法展示正確內(nèi)容永丝。

解決

思路

既然沒辦法直接抓取靜態(tài)頁面,那么可以嘗試動態(tài)抓取頁面箭养;如何抓取動態(tài)頁面呢慕嚷,本文里采用了Selenium進(jìn)行動態(tài)抓取頁面。通過Selenium模擬正常打開一個瀏覽器毕泌,瀏覽網(wǎng)頁的過程喝检,當(dāng)瀏覽到頁面的最下方整個頁面加載完畢,此時再抓取整個網(wǎng)頁的內(nèi)容就可以完整的獲取頁面內(nèi)容撼泛。
Selenium的WebDriver可以模擬不同瀏覽器挠说,本文采用的是Chrome瀏覽器,其他支持的瀏覽器大家可以通過查看WebDriver的實(shí)現(xiàn)類來獲得答案愿题。

編碼

引用Selenium庫的jar包

    <dependency>
        <groupId>org.seleniumhq.selenium</groupId>
        <artifactId>selenium-java</artifactId>
        <version>3.141.59</version>
    </dependency>

因?yàn)橐MChrome瀏覽器损俭,所以要下載chromedriver蛙奖,到這基礎(chǔ)的工作就做完了,接下來是開發(fā)代碼撩炊。廢話不多說外永,直接上代碼。

public String selenium(String url) throws InterruptedException {
        // 設(shè)置 chromedirver 的存放位置
        System.getProperties().setProperty("webdriver.chrome.driver", "D:/lingy/chromedriver_win32/chromedriver.exe");
        // 設(shè)置無頭瀏覽器拧咳,這樣就不會彈出瀏覽器窗口
        ChromeOptions chromeOptions = new ChromeOptions();
        chromeOptions.addArguments("--headless");
        Long scrollSize = 1000L;
        WebDriver webDriver = new ChromeDriver(chromeOptions);
        webDriver.get(url);
        //設(shè)置瀏覽器的寬高
        Dimension dimension = new Dimension(1000, scrollSize.intValue());
        webDriver.manage().window().setSize(dimension);
        String html = "";
        //獲取JS執(zhí)行器伯顶,可以執(zhí)行js代碼來控制頁面
        JavascriptExecutor driver_js= ((JavascriptExecutor) webDriver);
        //獲取頁面的高度
        Long scrollHeight = (Long) driver_js.executeScript("return document.body.scrollHeight");
        logger.info("article hight is : {}",scrollHeight);
        //因?yàn)橐M鼠標(biāo)滾動瀏覽網(wǎng)頁的效果,所以設(shè)置了每次滾動的高度骆膝,然后通過整個頁面的高度計(jì)算出股東的次數(shù)
        Long loopNum = scrollHeight/scrollSize;
        loopNum = loopNum+1;
        logger.info("page need scroll times : {}",loopNum);
        for(int i =0 ; i < loopNum; i++){
            Long start = i*scrollSize;
            Long end = (i+1)*scrollSize;
            //根據(jù)滾動的次數(shù)祭衩,依次滾動頁面,為了圖片能加載完全阅签,停留1秒鐘
            driver_js.executeScript("scroll("+start+","+end+")");
            Thread.sleep(1000);
        }
        //返回頁面全部內(nèi)容
        html = (String)driver_js.executeScript("return document.documentElement.outerHTML");
        webDriver.close();
        return html;
}

到此就可以完整的獲取整個頁面的內(nèi)容了掐暮,但是這樣獲取原汁原味的內(nèi)容,不一定能很好的展示在我們自己的頁面上政钟,下面是實(shí)際操作中遇到的兩個問題路克。

解決過程中遇到的問題一

在展示抓取內(nèi)容的過程中遇到圖片跨域顯示的或者防盜鏈的問題。剛開始的解決思路是通過Nginx反向代理的方式养交,來代理圖片URL精算,但是因?yàn)樵淳W(wǎng)頁圖片防盜鏈或者其他原因無法進(jìn)行代理;
所以我用另外一種方式碎连,通過將圖片轉(zhuǎn)換成Base64格式的內(nèi)容替換原來的url來解決灰羽,這種方式確實(shí)可以解決這個問題但會引出新的問題,圖片轉(zhuǎn)換成Base64之后會將原網(wǎng)頁內(nèi)容變得非常大鱼辙,在存儲和展示過程中請求非常消耗存儲空間和請求時間廉嚼。
最后,我選擇了將圖片讀取到本地倒戏,在通過Nginx代理的方式來處理怠噪。

        //獲取網(wǎng)頁圖片元素
        List<WebElement> imgList = webDriver.findElements(By.tagName("img"));
//        data:image/gif;base64,
        for(WebElement img : imgList){
            String imgSrc = img.getAttribute("src");
            logger.info("img's src is : {}",imgSrc);
            String pattern = "^((http)|(https)).*";
            boolean imgSrcPattern = !StringUtils.isEmpty(imgSrc) && Pattern.matches(pattern, imgSrc);
            if(imgSrcPattern){
//                String strNetImageToBase64 = changImgUrlToBase64(imgSrc);
//                driver_js.executeScript("arguments[0].setAttribute(arguments[1],arguments[2])", img, "src", "data:image/png;base64,"+strNetImageToBase64);
                String imgUrlToImgFile = changImgUrlToImgFile(imgSrc);
                //通過JS來替換頁面圖片
                driver_js.executeScript("arguments[0].setAttribute(arguments[1],arguments[2])", img, "src", imgUrlToImgFile);
            }
        }

關(guān)鍵代碼: driver_js.executeScript("arguments[0].setAttribute(arguments[1],arguments[2])", img, "src", imgUrlToImgFile);

將圖片轉(zhuǎn)換成Base64的代碼

    private String changImgUrlToBase64(String imgSrc){
        String strNetImageToBase64 = "";
        try {
            URL imgURL = new URL(imgSrc);
            final HttpURLConnection conn = (HttpURLConnection) imgURL.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(3000);
            InputStream is = conn.getInputStream();
            ByteArrayOutputStream data = new ByteArrayOutputStream();
            // 將內(nèi)容讀取內(nèi)存中
            final byte[] by = new byte[1024];
            int len = -1;
            while ((len = is.read(by)) != -1) {
                data.write(by, 0, len);
            }
            // 對字節(jié)數(shù)組Base64編碼
            BASE64Encoder encoder = new BASE64Encoder();
            strNetImageToBase64 = encoder.encode(data.toByteArray());
            // 關(guān)閉流
            is.close();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return strNetImageToBase64;
    }

將圖片下載到本地的代碼

    private static String changImgUrlToImgFile(String imgSrc){
        String suffix = ".png";
        if(imgSrc.indexOf("gif") != -1){
            suffix = ".gif";
        }else if(imgSrc.indexOf("jpg") != -1){
            suffix = ".jpg";
        }else if(imgSrc.indexOf("jpeg") != -1){
            suffix = ".jpeg";
        }else if(imgSrc.indexOf("png") != -1){
            suffix = ".png";
        }
        String dir = "E:/lingy/asmImg/";
        String fileName = System.currentTimeMillis()+suffix;
        try {
            URL imgURL = new URL(imgSrc);
            final HttpURLConnection conn = (HttpURLConnection) imgURL.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(3000);
            InputStream is = conn.getInputStream();
            File imgFile = new File(dir+fileName);
            if(!imgFile.exists()){
                imgFile.createNewFile();
            }
//            FileOutputStream fos = new FileOutputStream(imgFile);
//            final byte[] by = new byte[1024];
//            int len = -1;
//            while ((len = is.read(by)) != -1) {
//                fos.write(by, 0, len);
//            }
//            is.close();
//            FileUtils.copyURLToFile(imgURL,imgFile);
            FileUtils.copyInputStreamToFile(is,imgFile);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (ProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        String imagRootPath = "http://localhost/asmImg/";
        return imagRootPath + fileName;
    }

解決過程中遇到的問題二

在抓取頁面過程中,由于需要模擬網(wǎng)頁滾動瀏覽的效果整個抓取非常耗時(頁面越長越耗時)杜跷,在前端對響應(yīng)時間有要求的情況下傍念,建議大家講抓取頁面的過程做成異步的。

總結(jié)

Selenium更強(qiáng)大的用處是進(jìn)行自動化測試葱椭,模擬瀏覽器行為捂寿,操作頁面元素等;自動化測試領(lǐng)域更加專業(yè)孵运,這里不做深入的探討秦陋,只是簡單的介紹一下如何動態(tài)抓取一個的過程。其中的關(guān)鍵是獲取頁面元素執(zhí)行JS腳本治笨,主要使用webDriver.findElements()驳概、driver_js.executeScript()兩個方法赤嚼。由于上邊的介紹只是一個簡單的嘗試,其他場景中會遇到更復(fù)雜的情況顺又,比如說有的頁面需要點(diǎn)擊一下才能展示全部內(nèi)容更卒,這就需要調(diào)用js去觸發(fā)。
以上就是本次解決問題的一個回顧稚照,希望能給大家?guī)硪欢ǖ乃悸泛蛶椭?/p>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蹂空,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子果录,更是在濱河造成了極大的恐慌上枕,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弱恒,死亡現(xiàn)場離奇詭異辨萍,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)返弹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進(jìn)店門锈玉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人义起,你說我怎么就攤上這事拉背。” “怎么了并扇?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵去团,是天一觀的道長抡诞。 經(jīng)常有香客問我穷蛹,道長,這世上最難降的妖魔是什么昼汗? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任肴熏,我火速辦了婚禮,結(jié)果婚禮上顷窒,老公的妹妹穿的比我還像新娘蛙吏。我一直安慰自己,他們只是感情好鞋吉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布鸦做。 她就那樣靜靜地躺著,像睡著了一般谓着。 火紅的嫁衣襯著肌膚如雪泼诱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天赊锚,我揣著相機(jī)與錄音治筒,去河邊找鬼屉栓。 笑死,一個胖子當(dāng)著我的面吹牛耸袜,可吹牛的內(nèi)容都是我干的友多。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼堤框,長吁一口氣:“原來是場噩夢啊……” “哼域滥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蜈抓,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤骗绕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后资昧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體酬土,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年格带,在試婚紗的時候發(fā)現(xiàn)自己被綠了撤缴。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡叽唱,死狀恐怖屈呕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情棺亭,我是刑警寧澤虎眨,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站镶摘,受9級特大地震影響嗽桩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜凄敢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一碌冶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧涝缝,春花似錦扑庞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至滩援,卻和暖如春栅隐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工约啊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留邑遏,地道東北人。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓恰矩,卻偏偏與公主長得像记盒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子外傅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評論 2 354