node puppeteer SPA 网页图片爬虫实战

前言

博主编程启蒙在高中,当时听说 python 写爬虫很厉害,于是学了一段时间的 python,但当时对于编程语言中的各种概念一无所知,所以学习起来十分困难,最终只能照着网上的教程抄抄简单的爬虫(特别是对于使用了异步加载的网页一筹莫展),最近在浏览某个网站时发现该网站的图片专栏的页面结构十分统一,由于懒得一个一个帖子点开看,决定写一个爬虫爬到本地。

爬虫

因为之前了解过使用 JS 写爬虫,于是很快就选择了puppeteer这个库来写爬虫。puppeteer是一个无头浏览器,可以不打开浏览器模拟浏览器的各种操作。

puppeteer的 API 基本都是异步的,所以我们需要一个 async 函数包裹,首先来介绍一下puppeteer主要的 API:

  • puppeteer.launch() 返回一个 browser 实例
  • browser.newPage() 返回一个新页面 当然我推荐使用(await browser.pages())[0] 这样就直接使用当前页面,不用新建页面了
  • page.goto(url) 页面跳转
  • page.evaluate(fn) 相当于在这个页面的控制台执行函数,所以不可访问外部的变量,外部也不可以访问里面的变量,最后会返回一个 promise 包裹 return 的结果

写爬虫的工具选择好后,接下来我们来分析一下页面,该网页的图片使用了懒加载,当图片到视窗的时候,src 才会被设置为真实的 url。

所以我们需要模拟滑动到底部的操作

await page.evaluate(async () => {
    await new Promise((resolve, reject) => {
        let totalHeight = 0
        let distance = 100
        const timer = setInterval(() => {
            var scrollHeight = document.body.scrollHeight
            window.scrollBy(0, distance)
            totalHeight += distance
            if (totalHeight >= scrollHeight) {
                clearInterval(timer)
                resolve()
            }
        }, 50)
    })
    return [...document.querySelectorAll("selector")].map(item => item.src)
})

在图片全部加载后,再去获取图片 src

爬取完成之后我们再把数据保存起来

async function mkdir(path, result) {
    fs.mkdir(path, { recursive: true }, err => {
        if (err) console.log(err)
        fs.writeFileSync(
            `./data/${typeName}${START_PAGE}-${END_PAGE}.json`,
            JSON.stringify(result),
            err => {
                if (err) console.log(err)
            },
        )
    })
}

这样我们的小爬虫就写好啦 😄

但当我测试的时候发现了一些问题 😥

由于网站在国外,国内访问时可能超时,puppeteer页面跳转默认超时时间 3000ms,所以我们需要自己设置 page.setDefaultNavigationTimeout(0),设为 0 则超时时间无限制。

在爬取图片时,有很多的请求是我们不关心的,我们能不能过滤掉这些请求呢?当然可以。puppeteer很贴心的给了我们设置请求过滤器的 API

const blockedResourceTypes = [
    "media",
    "font",
    "texttrack",
    "object",
    "beacon",
    "csp_report",
    "image", // 因为我们只是爬取图片地址,不需要加载出来
]
page.setRequestInterception(true)
page.on("request", async req => {
    // 根据请求类型过滤
    const resourceType = req.resourceType()
    if (blockedResourceTypes.includes(resourceType)) {
        req.abort()
    } else {
        req.continue()
    }
})

没有提示,不知道爬虫执行到哪了,加个进度显示吧

exports.formatProgress = function (current, total, title = "当前进度", barLength = 40) {
    let percent = ((current / total) * 100).toFixed(2)
    let done = Math.floor((current / total) * barLength)
    let left = barLength - done
    let str = `${title}:[${"".padStart(done, "#")}${"".padStart(
        left,
        "-",
    )}]   ${percent}% ${current}/${total}`
    return str
}

是不是很人性化呀

下载图片

爬取到数据之后,就得把图片下载下来了,不然爬它干嘛是吧

怎么下载呢?打开百度,哦不,打开 Google,

很快就查到一个方法,使用request库来请求,fs.createWriteStream来写入

request
    .get({
        url,
    })
    .on("error", function (err) {
        console.log(`request err: ${err} at ${url}`)
    })
    .pipe(
        fs
            .createWriteStream(`${dest}/${folder}/${name}`)
            .on("error", err => {
                console.log(`createWriteStream error: ${err} at ${url}`)
            })
            .on("close", err => {
                if (err) console.log(`createWriteStream close error: ${err} at ${url}`)
            }),
    )

看起来很美好,但我们真正使用的时候,还是会发现有很多问题

首先同步下载大量的图片速度太慢了,还有可能遇到too many files的报错,网上也提供了一些解决方法,我最终使用bagpipe来解决问题

const Bagpipe = require("bagpipe")
const bagpipe = new Bagpipe(10)
bagpipe.push(downloadImage, url, dest, item.title, reason => {
    if (reason) console.log(reason)
})

有些图片地址在国内无法正常访问怎么办?挂上代理

request.get({
    url,
    headers: {
        "User-Agent":
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36",
    },
    proxy: "http://0.0.0.0:1082/", // 需要你自己本地有代理工具
})

异步调用之后,fs.createWriteStream 无法正确创建文件报错,先用fs.writeFile创建一个空的文件

fs.writeFile(`${dest}/${folder}/${name}`, "", err => {
    fs.createWriteStream()
})

创建的目录有非法字符创建失败,可以先用正则过滤一遍

const illegalCharacterReg = /\/|\\|:|\*|\?|"|<|>|\|/g
fs.mkdir(`${dest}/${item.title.replace(illegalCharacterReg, "")}`, { recursive: true }, err => {...})

虽然下载的时候还会遇到一些问题socket hang upClient network socket disconnected before secure TLS connection was established,但博主的能力暂时无法解决,网上也没有找到十分有效的方法,就先写到这里吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值