抓取动态网页(前情介绍前段时间开发了一个功能,解决过程中遇到的问题)
优采云 发布时间: 2022-04-09 22:09抓取动态网页(前情介绍前段时间开发了一个功能,解决过程中遇到的问题)
介绍
前段时间开发了一个功能,通过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;
}
复制代码
解决过程中遇到的问题2
在爬取一个页面的过程中,因为需要模拟网页滚动的效果,所以整个爬取非常耗时(页面越长越耗时)。当前端对响应时间有要求的时候,建议大家谈谈爬取页面的过程。变成异步的。
总结
Selenium 在自动化测试、模拟浏览器行为、操作页面元素等方面功能比较强大,自动化测试领域比较专业,这里就不深入讨论了,简单介绍一下如何动态抓取的过程一。关键是获取页面元素和执行JS脚本,主要使用webDriver.findElements()和driver_js.executeScript()两种方法。由于以上介绍只是一个简单的尝试,其他场景会遇到更复杂的情况。比如有些页面需要点击才能显示全部内容,这就需要调用js来触发。以上就是对这个问题解决的回顾,希望能给大家带来一些思路和帮助。