网页爬虫抓取百度图片(基于请求拦截来实现网页资源采集类的图片漏抓)

优采云 发布时间: 2021-11-27 15:06

  网页爬虫抓取百度图片(基于请求拦截来实现网页资源采集类的图片漏抓)

  背景:我负责一个网络资源采集 项目。之前Java后端直接通过HTTP请求获取网页源码,然后通过jsoup解析网页,根据标签提取图片。但是,在最近的一次客户演练中,出现了漏图的情况。对网页进行了具体分析,发现是一张用css样式写的图片,确实没有被之前的静态爬虫方法覆盖。

  现在想想,之前的静态爬虫方法还是太简单了,已经不能胜任现在复杂的前端网页了。可能存在以下问题:

  1. 网页源代码没有渲染,会漏掉一些新的渲染资源。比如显示的内容需要根据ajax返回的结果进行渲染,例如:

  阿贾克斯测试

  2. 基于标签匹配提取图片,难以覆盖所有图片标签

  src href 或一些自定义属性 data-src、v-lazy 等。

  cool.jpg

cool.jpg

  3.无法提取样式中写入的图片

  .element {background: url('cool.jpg');}

Some content

  如果要提取css样式的图片,单靠静态爬虫肯定不行。这时候,我们就需要使用浏览器来真正加载和渲染整个网页一次。由于我们的系统在去年就已经做了这个技术选型,所以我们使用puppeteer对网页进行截图。所以这次在之前的截图服务的基础上,整合了资源采集接口,也方便了系统的改造。

  傀儡师

  

  Puppeteer 是一个 Node.js 工具引擎,提供了一系列 API 来通过 CDP 协议控制 Chromium/Chrome 浏览器

  基于 page.on('request') 块图片

  所以我们第一个需求【获取一个网页中的所有图片】可以基于请求拦截来实现。大概的代码如下:

  let imageList = [];

page.on('response', async response => {await handleImg(response, imageList);});

async function handleImg(response, imageList) {

const imgUrl = response.url();

let statusCode = response.status();

let requestResourceType = response.request().resourceType();

//通过判断请求的resourceType ,我们可以拦截到所有的图片请求,包括css样式加载的、自定义属性加载的图片

if (requestResourceType === 'image' ) {

//处理图片请求,计算响应Hash,上传图片等

imageList.push(imageMap);

}

}

  但是,拦截方式带来了新的问题:

  无法获取图片在网页中的具体位置,即xpath

  由于项目中很多关键概念都依赖于xpath,所以也需要想办法定位截取的图片。

  最初的想法是通过page.content()获取网页的源代码,然后遍历源代码,通过比较元素的src链接来定位截取图片的xpath。

   let pageContent;

pageContent = await page.content();

  但是,这又回到了原来的问题:基于css加载的图片在源码中没有src等资源属性,orz

  比如这个:

  

  由于对前端不熟悉,想了很久。有没有办法获取网页元素的CSS属性值?

  经过搜索和尝试,发现window.getComputedStyle()方法正好符合我们的要求

  let backgroundImg = getComputedStyle(element, '').backgroundImage;

  打开F12,在控制台试试,不错

  

  好的,下一步是更关键的点

  windows 对象需要浏览器支持。

  而我们通过上面的page.content()得到网页的源码,是在nodejs环境中获取的

  所以我们需要通过page.evaluate()将我们的遍历方法注入到浏览器环境中执行

  

  注意理解和区分两组js环境:

  运行 Puppeteer 的 Node.js 环境和运行 Puppeteer 的 Page DOM 是两个独立的环境

  嗯,一个通过treeWalker遍历DOM,提取所有元素资源链接的方法就出来了

  async function findAllLinks() {

function stringToLowerCase(str) {

if (typeof str === 'string') {

return str.toLowerCase()

} else {

return ''

}

}

//获取DOM的Xpath

function getXpath(element) {

let xpath = ""

for (let me = element, k = 0; me && me.nodeType === 1; element = element.parentNode, me = element, k += 1) {

let i = 0

while ((me = me.previousElementSibling)) {

if (me.tagName === element.tagName) {

i += 1

}

}

const elementTag = stringToLowerCase(element.tagName)

let id = i + 1

id = '[' + id + ']';

xpath = '/' + elementTag + id + xpath

}

return xpath

}

window.LINKS_RESULT = [];

let treeWalker = document.createTreeWalker(

document.body.parentElement,

NodeFilter.SHOW_ELEMENT,

{

acceptNode: function (node) {

return NodeFilter.FILTER_ACCEPT;

}

},

);

while (treeWalker.nextNode()) {

let element = treeWalker.currentNode;

let src = element.src;

let href = element.href;

let background = element.background;

let backgroundImg = getComputedStyle(element, '').backgroundImage;

if (src) {

LINKS_RESULT.push({

"xpath": getXpath(element),

"url": src

});

} else if (href) {

LINKS_RESULT.push({

"xpath": getXpath(element),

"url": href

});

} else if (background) {

LINKS_RESULT.push({

"xpath": getXpath(element),

"url": background

});

} else if (backgroundImg && backgroundImg.startsWith('url("http')) {

backgroundImg = backgroundImg.substring(5, backgroundImg.lastIndexOf('"'));

LINKS_RESULT.push({

"xpath": getXpath(element),

"url": backgroundImg

});

}

}

}

  最后,提取的所有链接都收录 xpath 信息。通过对比url的绝对路径,我们可以找到之前截获的图片链接的xpath。

  总结一下步骤:

  1. puppeteer端截取所有图片保存在List-A

  2. 在浏览器端注入遍历代码,获取网页的所有链接和对应元素的xpath,将结果保存在List-B中,返回给puppeteer端

  3. 遍历 List-A 并在 List-B 中定位 xpath

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线