网页爬虫抓取百度图片(基于请求拦截来实现网页资源采集类的图片漏抓)
优采云 发布时间: 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