爬虫实战3——道客巴巴文档免费下载(使用puppeteer获取canvas标签内容)

之前写过爬虫爬取豆丁网的资料,这次轮到了道客巴巴了,但是在写爬虫的时候发现其文档资料并不是以图片形式显示的,而是用canvas标签绘制渲染的,于是遇到了瓶颈。原本是打算使用python进行canvas元素内容的获取,但经过网上搜索引擎的学习后,这里决定使用puppeteer来实现对canvas的爬取。

想要一起学习的直接往下看,想要白嫖最后劳动成果(最后的方法可以不会任何代码)的直接看最后即可

环境

  • Chrome浏览器
  • Sublime Text 3代码编辑器
  • puppeteer
  • nodejs

前期准备

环境搭建

  1. 安装nodejs,网上教程一搜一大把,这里不再赘述
  2. 安装puppeteer,直接在cmd运行以下命令即可:
npm i puppeteer

需求分析

这里想要实现的是将道客巴巴的文档先分别下载其每页的内容为图片,然后合并图片为pdf,因此步骤分解如下:

  1. 打开浏览器
  2. 打开指定页面
  3. 由于文档页数过多,需要点击继续阅读按钮展开后续页面
  4. 等待几秒钟使canvas元素内容渲染(否则canvas内容就是默认的图片)
  5. 使页面向下滑动
  6. 下载该页canvas内容图片
  7. 重复4、5、6步骤,直到页面滑动到底部或图片下载完毕为止
  8. 合并图片为pdf

代码编写

首先,编写代码打开浏览器,加载指定页面,点击继续阅读展开整个页面后回到页面顶部:

const puppeteer = require('puppeteer');
puppeteer.launch({
    ignoreHTTPSErrors: true,//是否忽略https证书错误
    headless: false,//是否以无头模式加载
    timeout: 0,
    args: ['--start-maximized']//全屏显示浏览器
}).then(async browser => {
    const page = await browser.newPage();//创建新页面
    await page.setJavaScriptEnabled(true);
    await page.setViewport({width: 3500, height: 1600});//设置页面大小,这里设置为自己电脑屏幕大小
    await page.goto('https://www.doc88.com/p-0364916429851.html?r=1');//页面跳转到指定链接
    // 点击继续阅读
    const continueBtn = await page.$('#continueButton');//获取到继续阅读按钮的dom元素
    await continueBtn.click();//点击按钮

    // 点击按钮后将页面滑动至顶部
    await page.evaluate(() => {
        document.scrollingElement.scrollTop = 0;
    })
})

然后,每隔一定时间(这里设置为10s),滑动页面,下载图片:

    let scrollEnable = true;
    let scrollStep = 1613;//设置页面滚动步长,这里以道客巴巴渲染的每页canvas的高度为步长
    let index = 0;
    while (scrollEnable) {
        await sleep(10000);//等待10s使canvas元素内容渲染
        index ++;
        // 滑动页面,滑动的距离为上面的滚动步长
        scrollEnable = await page.evaluate((scrollStep) => {
            let scrollTop = document.scrollingElement.scrollTop;
            document.scrollingElement.scrollTop = scrollTop + scrollStep;
            return document.body.clientHeight > scrollTop + 1080? true:false;
        }, scrollStep)
        // 每个canvas的元素id
        let page_id = 'page_' + index;
        // 下载每页canvas内容为图片
        await download_each_page(page, page_id);
    }

然后书写延时的函数:

//延时函数
function sleep(delay) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            try {
                resolve(1)
            } catch (e) {
                reject(0)
            }
        }, delay)
    })
}

最后书写下载每页canvas内容的函数:

let download_each_page = async (page, page_id) => {
    // 获取canvas内容,获取到的内容为base64编码的图片
    const result = await page.evaluate((page_id) => {
        return document.getElementById(page_id).toDataURL()
    }, page_id);
    // console.log(await result);
    // 将获取到的canvas内容去掉base64图片标识符
    let base64Data = await result.replace(/^data:image\/\w+;base64,/, "");
    // 将base64内容转为二进制文件流
    let dataBuffer = new Buffer(base64Data, 'base64');
    // 将图片内容写入文件
    fs.writeFile(page_id + '.png', dataBuffer, (err) => {
        if (err) {
            console.log(page_id, 'ojbk')
        } else {
            console.log(page_id, 'ok')
        }
    })
}

最终的完整代码如下:

const puppeteer = require('puppeteer');
const fs = require('fs');

console.log('start....');

//延时函数
function sleep(delay) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            try {
                resolve(1)
            } catch (e) {
                reject(0)
            }
        }, delay)
    })
}

let download_each_page = async (page, page_id) => {
    // 获取canvas内容,获取到的内容为base64编码的图片
    const result = await page.evaluate((page_id) => {
        return document.getElementById(page_id).toDataURL()
    }, page_id);
    // console.log(await result);
    // 将获取到的canvas内容去掉base64图片标识符
    let base64Data = await result.replace(/^data:image\/\w+;base64,/, "");
    // 将base64内容转为二进制文件流
    let dataBuffer = new Buffer(base64Data, 'base64');
    // 将图片内容写入文件
    fs.writeFile(page_id + '.png', dataBuffer, (err) => {
        if (err) {
            console.log(page_id, 'ojbk')
        } else {
            console.log(page_id, 'ok')
        }
    })
}

puppeteer.launch({
    ignoreHTTPSErrors: true,//是否忽略https证书错误
    headless: false,//是否以无头模式加载
    timeout: 0,
    args: ['--start-maximized']//全屏显示浏览器
}).then(async browser => {
    const page = await browser.newPage();//创建新页面
    await page.setJavaScriptEnabled(true);
    await page.setViewport({width: 3500, height: 1600});//设置页面大小,这里设置为自己电脑屏幕大小
    await page.goto('https://www.doc88.com/p-0364916429851.html?r=1');//页面跳转到指定链接
    // 点击继续阅读
    const continueBtn = await page.$('#continueButton');//获取到继续阅读按钮的dom元素
    await continueBtn.click();//点击按钮

    // 点击按钮后将页面滑动至顶部
    await page.evaluate(() => {
        document.scrollingElement.scrollTop = 0;
    })

    let scrollEnable = true;
    let scrollStep = 1613;//设置页面滚动步长,这里以道客巴巴渲染的每页canvas的高度为步长
    let index = 0;
    while (scrollEnable) {
        await sleep(10000);//等待10s使canvas元素内容渲染
        index ++;
        // 滑动页面,滑动的距离为上面的滚动步长
        scrollEnable = await page.evaluate((scrollStep) => {
            let scrollTop = document.scrollingElement.scrollTop;
            document.scrollingElement.scrollTop = scrollTop + scrollStep;
            return document.body.clientHeight > scrollTop + 1080? true:false;
        }, scrollStep)
        // 每个canvas的元素id
        let page_id = 'page_' + index;
        // 下载每页canvas内容为图片
        await download_each_page(page, page_id);
    }
})

程序运行

在程序根目录的cmd输入以下命令即可运行:

node downloadDoc88.js 

下载的图片存储在同目录下:


下载好的图片

然而,图片相比原画略糊,猜测可能是在页面渲染时,获取到的电脑屏幕的分辨率不一致,因此造成了这样的结果。

至于为什么没有写图片合并的函数,因为对图片画质不满意,加上后面找到了更好的方式,因此就烂尾了。下面介绍傻瓜式不用写代码的更好的方式免费下载道客巴巴的文档。

傻瓜式道客巴巴文档下载

看完这个教程就可以忘记上述所有程序了,这里不需要会代码,更不需要写代码,新手小白也能学会的傻瓜式免费下载道客巴巴文档的教程。

首先,手动打开Chrome浏览器,手动点击继续阅读按钮,手动滑动页面使所有页面全部渲染完成,然后,在页面该元素右键单击,选择检查

选择检查

然后,在出来的页面源码中,找到

标签,右键,选择Delete element

删除页面元素

然后,在屏幕空白处(非文档内容处)右键单击,选择打印

选择打印

然后选择目标打印机为另存为PDF,然后选择保存

另存为pdf

之后就可以看到保存下来的文档了,虽然也不及原画清晰,但已经比爬虫效果好了,而且页面并没有广告之类的元素,干干净净:


下载下来的文档某一页

你可能感兴趣的:(爬虫实战3——道客巴巴文档免费下载(使用puppeteer获取canvas标签内容))