抓取动态网页(前情介绍前段时间开发了一个功能,解决过程中遇到的问题)

优采云 发布时间: 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来触发。以上就是对这个问题解决的回顾,希望能给大家带来一些思路和帮助。

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线