如何利用puppeteer爬取twitter图片/视频

本文章的完整代码在 Plumbiu/twid 可以看到,项目已经实现通过命令行的方式爬取推特图片/视频了,这里介绍一下基本思路。

Puppeteer 是一个 Node 工具库,它提供了一套高阶 API 来通过 DevTools 协议控制 Chromium 或 Chrome ------ 官方介绍。

简而言之,Puppeteer 提供了一套编程方法,模拟用户使用浏览器,例如打开某个网页,滑动页面等操作,也可以监听页面中的相应等,由于是直接操作浏览器,所以 Puppeteer 几乎可以爬取任意网站的内容。

如何爬取图片

图片/视频内容主要在 /user/media 页面,然而这个页面需要用户登录才可以,一般登录的信息都是在 Cookie 或者 Localstroge 里,而推特将登录信息放在了 cookie -> auth_token 字段上,如下图:

同时 puppeteer 也提供了设置 cookie 的方法:

ts 复制代码
import { launch } from 'puppeteer'

// 启动浏览器
const browser = await launch()
// 打开一个页面
const page = await browser.newPage()
// 页面跳转到 baseUrl 地址
await page.goto(baseUrl)
// 设置页面的 cookie 值
await page.setCookie({
  name: 'auth_token',
  value: token,
})
// 设置好后,跳转到对应的 media 页面
await page.goto(baseUrl + '/media')

现在,我们能打开推特用户的媒体页面了,紧接着就是监听我们对图片的请求:

ts 复制代码
// 定义一个 set 结构,用来存储图片地址
const images = new Set()
page.on('request', () => {
  // 请求的类型
  const reqType = req.resourceType()
  // 请求的地址
  const reqUrl = req.url()
  if (reqType === 'image') {
      // 如果请求类型为图片,将该地址添加到 images
      images.add(reqUrl)
  }
})

获取图片地址的方法有了,但是还有个问题,推特的图片是懒加载的,这意味着我们需要滚动,才能获取所有的图片,在翻阅 puppeteer 的 issue 时,找到了模拟浏览器滚动的方法:

ts 复制代码
async function scrollToBottom(page: Page) {
  // page.evaluate 运行在浏览器环境中
  await page.evaluate(async () => {
    await new Promise<void>((resolve, _reject) => {
      // 浏览器总高度
      let totalHeight = 0
      // 每一次滚动的距离
      const distance = 100
      // 定时器,每 500ms 滚动 100px 像素
      const timer = setInterval(() => {
        const scrollHeight = document.body.scrollHeight
        window.scrollBy(0, distance)
        totalHeight += distance
        // 如果总高度和滚动高度一致,那么说明我们滚动到底部了
        if (totalHeight >= scrollHeight) {
          clearInterval(timer)
          resolve()
        }
      }, 500)
    })
  })
}

继续完善我们之前的代码,只需要加上一句话

ts 复制代码
// 定义一个 set 结构,用来存储图片地址
const images = new Set()
page.on('request', () => {
  // 请求的类型
  const reqType = req.resourceType()
  // 请求的地址
  const reqUrl = req.url()
  if (reqType === 'image') {
      // 如果请求类型为图片,将该地址添加到 images
      images.add(reqUrl)
  }
})
// 添加这一句话,就可以模拟滚动了
await scrollToBottom()

另外在最后记得要把浏览器和页面关掉:

ts 复制代码
await page.close()
await browser.close()

图片下载

知道了图片的地址,那么就很好下载了,这里使用的是 got,因为它比 axios 少了 60kb 大小,而且对于下载图片视频来说完全够用

ts 复制代码
import { got } from 'got'

// images 是我们之前定义的 Set,这里转换为数组
Promise.all([...images].map(async (url) => {
  const res = await got.get(url, {
    // 这里我们伪造一下请求头
    headers: {
      'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36',
    },
    // 设置响应类型为 buffer
    responseType: 'buffer',
  })
  await fsp.writeFile(writePath, res.rawBody)
}))

视频下载

视频下载关键在于推特会返回一个以 UserMedia 开头的 json 文件,json 文件里会有一个 video_info 字段,在其中可以找到视频的真实地址:

由于 json 文件是响应回来的,因此我们需要监听页面的响应事件,和之前监听请求时间类似:

ts 复制代码
// 定义一个 Set,用于存储 videos 地址
const videos = new Set()
page.on('response', async (res) => {
    const url = res.url()
    // 如果地址里包含 UserMedia
    if (url.includes('UserMedia')) {
      // 将相应数据转换为字符串
      // 不转换为对象的原因是因为 json 字段嵌套的太深了
      // 我需要 xxx.yyy.zzz.ttt .... 嵌套几十层,这里我们选择正则匹配,具体可以看项目里的代码
      const requestSource = await res.text()
      // 处理函数
      resolveVideoInfo(requestSource, videos, user)
    }
  })

resolveVideoInfo 函数地址 core/src.utils

下载视频

视频需要流来下载,这部分 got 做的十分简单:

ts 复制代码
import { pipeline as streamPipeline } from 'node:stream/promises'
import fs from 'node:fs'
import { got } from 'got'

Promise.all([...videos].map(async (url) => {
  await streamPipeline(
    got.stream(url, {
      ...USER_AGENT_HEADER,
    }),
    // outputDir 是输出文件
    fs.createWriteStream(outputDir),
  )
}))
相关推荐
Nan_Shu_6141 小时前
Web前端面试题(2)
前端
知识分享小能手1 小时前
React学习教程,从入门到精通,React 组件核心语法知识点详解(类组件体系)(19)
前端·javascript·vue.js·学习·react.js·react·anti-design-vue
蚂蚁RichLab前端团队2 小时前
🚀🚀🚀 RichLab - 花呗前端团队招贤纳士 - 【转岗/内推/社招】
前端·javascript·人工智能
孩子 你要相信光2 小时前
css之一个元素可以同时应用多个动画效果
前端·css
萌萌哒草头将军2 小时前
Oxc 和 Rolldown Q4 更新计划速览!🚀🚀🚀
javascript·vue.js·vite
huangql5202 小时前
npm 发布流程——从创建组件到发布到 npm 仓库
前端·npm·node.js
Qlittleboy3 小时前
uniapp如何使用本身的字体图标
javascript·vue.js·uni-app
Days20503 小时前
LeaferJS好用的 Canvas 引擎
前端·开源
小白菜学前端3 小时前
vue2 常用内置指令总结
前端·vue.js
林_深时见鹿3 小时前
Vue + ElementPlus 自定义指令控制输入框只可以输入数字
前端·javascript·vue.js