网页爬虫抓取百度图片(保存新浪博客的历史文章备份到电脑本地,功能很强大 )

优采云 发布时间: 2021-12-24 15:04

  网页爬虫抓取百度图片(保存新浪博客的历史文章备份到电脑本地,功能很强大

)

  原因

  想把新浪博客文章的历史备份到本地,用Puppeteer写个爬虫试试。

  之前喜欢用Python3+Selenium,但是发现Puppeteer好像很容易安装,功能也很强大。

  第一次使用Puppeteer,查了一些资料。

  本文参考:从无头浏览器保存图像

  本教程中的方法非常丰富,但是在爬取新浪博客的过程中,文章中的很多方法都遇到了障碍,最后发现并实践了一个更好的方法。

  第一次尝试:node.js 直接请求下载文件

  这是我自己的想法。最简单的方法就是拿到图片src后直接请求下载到本地。

  const downloadFile = async (url, filePath) => {

return axios({

method: "get",

url: url,

responseType: "stream",

}).then((response) => {

response.data.pipe(fs.createWriteStream(filePath));

});

};

复制代码

  通过axios下载保存图片失败,新浪退回图片,防止盗链。原理暂时不清楚。

  

  看来我们需要想办法绕过它。

  第二次尝试:从新的 Canvas 中提取图像

  Extract Image from a New Canvas,这种方法是创建一个空白的canvas元素,将目标图片写入其中,然后将图片数据提取为DataURL。

  如果图像数据来自不同的来源,这将导致画布被污染,并且试图从画布中获取图像数据将导致错误。

  const getDataUrlThroughCanvas = async (selector) => {

// Create a new image element with unconstrained size.

const originalImage = document.querySelector(selector);

const image = document.createElement('img');

image.src = originalImage.src;

// Create a canvas and context to draw onto.

const canvas = document.createElement('canvas');

const context = canvas.getContext('2d');

canvas.width = image.width;

canvas.height = image.height;

// Ensure the image is loaded.

await new Promise((resolve) => {

if (image.complete || (image.width) > 0) resolve();

image.addEventListener('load', () => resolve());

});

context.drawImage(image, 0, 0);

return canvas.toDataURL();

};

复制代码

  我尝试过并将其重写为批处理选择器版本。运行后,由于跨域问题出现错误:Uncaught DOMException: Failed to execute'toDataURL' on'HTMLCanvasElement': Tainted canvases may not be export 它可以通过 image.setAttribute("crossOrigin",' 解决Anonymous'),但是又报跨域错误,解决失败。

  第三次尝试:调用浏览器执行fetch方法下载图片

  (Re-)Fetch the Image from the Server,这种方法是将JS代码注入浏览器执行,直接在浏览器层面进行操作。

  const assert = require('assert');

const getDataUrlThroughFetch = async (selector, options = {}) => {

const image = document.querySelector(selector);

const url = image.src;

const response = await fetch(url, options);

if (!response.ok) {

throw new Error(`Could not fetch image, (status ${response.status}`);

}

const data = await response.blob();

const reader = new FileReader();

return new Promise((resolve) => {

reader.addEventListener('loadend', () => resolve(reader.result));

reader.readAsDataURL(data);

});

};

try {

const options = { cache: 'no-cache' };

const dataUrl = await page.evaluate(getDataUrlThroughFetch, '#svg', options);

const { mime, buffer } = parseDataUrl(dataUrl);

assert.equal(mime, 'image/svg+xml');

fs.writeFileSync('logo-fetch.svg', buffer, 'base64');

} catch (error) {

console.log(error);

}

复制代码

  这种方法的实践也产生了错误。看了控制台的提示,也是因为跨域。

  应该是新浪做了跨域限制。注入的JS不是新浪的,无法正常获取新浪的图片。

  第四次尝试:通过Chromium的DevTools协议获取图片

  使用DevTools协议提取图片,也就是说我们访问的是Puppeteer的api控制的浏览器。原理类似于我们F12打开开发者工具界面,在Sources中找到对应下载的图片。

  const assert = require('assert');

const getImageContent = async (page, url) => {

const { content, base64Encoded } = await page._client.send(

'Page.getResourceContent',

{ frameId: String(page.mainFrame()._id), url },

);

assert.equal(base64Encoded, true);

return content;

};

try {

const url = await page.evaluate(() => document.querySelect('#svg').src)

const content = await getImageContent(page, url);

const contentBuffer = Buffer.from(content, 'base64');

fs.writeFileSync('logo-extracted.svg', contentBuffer, 'base64');

} catch (e) {

console.log(e);

}

复制代码

  就这样,我也跑了,第一次看到图片被一张一张的下载到本地,很是欣慰。

  但是这种方法有一定几率对图片内容造成损坏,出现绿色背景。我怀疑是因为看的时候图片没有下载完整,所以又放弃了。

  

  第五次尝试:【原创终极方案】直接通过page.waitForResponse方法获取响应

  结合第四次尝试中图片加载不完整的思考,基于对Puppeteer文档的查询,找到了那个教程中没有提到的方法:page.waitForResponse。

  page.waitForResponse 方法是等待一个响应结束继续执行,并返回到 Puppeteer Response 实例。

  使用示例:

  const firstResponse = await page.waitForResponse('https://example.com/resource');

const finalResponse = await page.waitForResponse(response => response.url() === 'https://example.com' && response.status() === 200);

return finalResponse.ok();

复制代码

  因此,我们在获取到图片的url后,可以直接使用该方法在浏览器中等待图片下载加载完毕,在加载后返回的Response对象中获取图片数据,更加简单高效比上一个教程中的方法。

  const imgResp = await detailPage.waitForResponse(img.real_src, {

timeout: 10000,

});

const buffer = await imgResp.buffer();

const imgBase64 = buffer.toString("base64");

fs.writeFileSync(

`data/images/${index}_${img.index}.jpg`,

imgBase64,

"base64"

);

复制代码

  对我来说,这是我发现并成功实践的最终和最推荐的解决方案。

  踩了这么多坑,记录下来供大家参考。

  

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线