前情介紹
前段時間開發(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>