动态网页抓取(前情介绍前段时间开发了一个功能,解决过程中遇到的问题)
优采云 发布时间: 2021-09-12 00:07动态网页抓取(前情介绍前段时间开发了一个功能,解决过程中遇到的问题)
前期情况介绍
前段时间开发了一个函数,通过HttpClient访问某个页面,获取该页面的所有html内容;之后,抓取得到的整个页面显示在我的网页上;但是过了一段时间,网页升级了。网页中的图片变成了动态加载,无法通过HttpClient直接获取完整的页面内容。图片为懒加载状态,无法显示正确内容。
解决方案
由于没有办法直接抓取静态页面,可以尝试动态抓取页面;如何抓取动态页面?在本文中,Selenium 用于动态抓取页面。通过Selenium模拟正常打开浏览器浏览网页的过程,当浏览到页面底部加载整个页面时,此时抓取整个网页的内容就可以得到整个网页的内容时间。 Selenium 的 WebDriver 可以模拟不同的浏览器。本文使用 Chrome 浏览器。对于其他支持的浏览器,可以通过查看WebDriver的实现类得到答案。
编码
参考Selenium库的jar包
org.seleniumhq.selenium
selenium-java
3.141.59
复制代码
因为要模拟Chrome浏览器,所以需要下载chromedriver。基础工作已经完成,接下来就是开发代码了。废话不多说,直接上代码。
public String selenium(String url) throws InterruptedException {
// 设置 chromedirver 的存放位置
System.getProperties().setProperty("webdriver.chrome.driver", "D:/lingy/chromedriver_win32/chromedriver.exe");
// 设置无头浏览器,这样就不会弹出浏览器窗口
ChromeOptions chromeOptions = new ChromeOptions();
chromeOptions.addArguments("--headless");
Long scrollSize = 1000L;
WebDriver webDriver = new ChromeDriver(chromeOptions);
webDriver.get(url);
//设置浏览器的宽高
Dimension dimension = new Dimension(1000, scrollSize.intValue());
webDriver.manage().window().setSize(dimension);
String html = "";
//获取JS执行器,可以执行js代码来控制页面
JavascriptExecutor driver_js= ((JavascriptExecutor) webDriver);
//获取页面的高度
Long scrollHeight = (Long) driver_js.executeScript("return document.body.scrollHeight");
logger.info("article hight is : {}",scrollHeight);
//因为要模拟鼠标滚动浏览网页的效果,所以设置了每次滚动的高度,然后通过整个页面的高度计算出股东的次数
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;
//根据滚动的次数,依次滚动页面,为了图片能加载完全,停留1秒钟
driver_js.executeScript("scroll("+start+","+end+")");
Thread.sleep(1000);
}
//返回页面全部内容
html = (String)driver_js.executeScript("return document.documentElement.outerHTML");
webDriver.close();
return html;
}
复制代码
此时就可以完整的获取到整个页面的内容了,但是这种方式获取到的原创内容在我们自己的页面上可能无法很好的展示出来。以下是实际操作中遇到的两个问题。
解决过程中遇到的问题
在展示爬取内容的过程中,存在图片跨域展示或防盗链问题。最初的解决方案是使用Nginx反向代理来代理图片URL,但是因为源网页图片是防盗链或者其他原因,无法进行代理;所以我用了另一种方法,通过将图片转换成Base64格式的内容替换原来的URL来解决这个问题确实可以解决这个问题,但是会引入新的问题。图片转Base64后,原来的网页内容会变得非常大,存储和展示过程中的请求会消耗存储空间和请求。时间。最后选择本地读取图片,通过Nginx代理进行处理。
//获取网页图片元素
List 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);
}
}
复制代码
关键代码:driver_js.executeScript("arguments[0].setAttribute(arguments[1],arguments[2])", img, "src", imgUrlToImgFile);
图片转Base64代码
图片下载到本地的代码
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;
}
复制代码
解决过程中遇到的第二个问题
在爬取页面的过程中,由于需要模拟网页滚动的效果(页面越长越费时),整个爬取非常耗时。当前端对响应时间有要求的时候,建议大家说说爬取页面的过程,做成异步的。
总结
Selenium 在自动化测试、模拟浏览器行为、操作页面元素等方面功能更强大,自动化测试领域更专业,这里不做深入讨论,只简单介绍下过程动态抓取一个。关键是获取页面元素和执行JS脚本,主要使用webDriver.findElements()和driver_js.executeScript()两个方法。由于以上介绍只是简单的尝试,在其他场景中会遇到更复杂的情况。比如有些页面需要点击才能显示全部内容,这就需要调用js来触发。以上就是本次解题的回顾,希望能给大家带来一些思路和帮助。