项目需求是提供一个接口通过输入一个网页地址,抓取网页中的视频地址!例如打开一个 网页地址

image.png

需要将网页中的视频地址提取出来。作为前端开发人员的惯性思维,看到这个网页的html结构,这个不是很简单嘛,一行代码就搞定:document.querySelector('video source').src

image.png

嘻嘻,大功告成,准备摸鱼~

等等!这个只是在浏览器的控制台中拿到了视频的地址,但是如何转化成为提供一个接口,通过接口返回这个地址呢?初步猜想,使用get请求获取网页的html,然后分析dom结构,解析出video标签。

错误尝试

直接通过get请求页面的地址获取到的内容并不是我们在浏览器所看到的内容。目前的网页大多都是动态网页,即页面最终呈现的内容是通过加载js后执行脚本动态拼接的,因此页面中的video标签并不是直接从服务端拼接好的。

浏览器加载网页的请求截图,没有直接返回dom结构,全是加载一堆js和css文件

image.png

并且!很多网站都做了防爬措施,直接请求页面的地址会返回一个中间页面,例如抖音和微博的视频详情页面,直接请求会返回一个类似于认证的页面,初步分析了这个页面,这个中转页面应该是判断有没有相应cookie的信息,如果没有相应的信息,就会给浏览器设置cookie之类的信息,最后会走一个window.location.reload();让页面刷新一次(微博会直接走到一个Sina Visitor System的页面不会直接跳转到详情页面)。这个脚本在浏览器中会自动执行,因此会重新加载一次,看到最终的详情页面。但是get请求仅仅是获取到了中转页面的html,并没有走到真正的详情页面。

抖音详情页面get请求
https://www.douyin.com/video/7020764246476590339

image.png

微博详情页面get请求
https://weibo.com/tv/show/1034:4699424946061376?mid=4699425262272582

image.png

哎呀!连最终的网页信息都拿不到,怎么可能拿到页面视频地址呢?这下可不能愉快的摸鱼了

通过调研后决定采用 Node.js + Puppeteer来实现这个功能,本文主要记录项目的实现思路和开发部署中遇到的难点及其解决方案,仅供学习参考。

Puppeteer 是 Chrome 开发团队在 2017 年发布的一个 Node.js 包,用来模拟 Chrome 浏览器的运行.主要通过Puppeteer运行Chromium加载网页实现分析页面dom获取video标签,实现视频地址抓取

参考资料:

Puppeteer中文文档

https://github.com/puppeteer/puppeteer

开发环境(Windows)

决定使用puppeteerjs后里面在windows环境下进行开发,
windows环境为Node v12.16.2, puppeteerjs v2.1.1

puppeteerjs的最新版为13.1.1。但是puppeteerjs v3.0版本及以上需要Node v10及以上,因为我本地的开发环境Node为v12,服务器上的Node为v8,因此本地开发没问题,但是服务器上一直部署不成功,且服务器上面有很多其他项目都是基于node v8版本的,因此服务器上的node版本不宜升级。为保持和服务器版本一致,windows环境下的puppeteerjs也使用2.1.1版本;

直接上代码
server2.js

const puppeteer = require('puppeteer');

async function getVideoUrl () {
    const browser = await puppeteer.launch();// 打开浏览器
    const page = await browser.newPage();
    await page.emulate(puppeteer.devices['iPhone 6'])
    await page.goto('https://www.douyin.com/video/7020764246476590339'); // 跳转到指定页面
    await page.waitFor(2000)  // 延时2s加载页面 puppeteer2.1.1使用 waitFor ^13.0.1以上使用 waitForTimeout  
    const pageHtml = await page.content(); // 获取页面html Gets the full HTML contents of the page, including the doctype.
    console.log(pageHtml);
}
getVideoUrl()

执行node server2.js,输出的结果就是详情页面的html代码了

image.png

puppeteer.launch中的headless默认true,如果设置为false,会打开一个Chromium加载网页,并且能直接调试网页!

await puppeteer.launch({
    headless: false, // 是否无头浏览
});

image.png

拿到了html代码我们怎么进一步获取video标签呢?

直接使用dom分析视频标签

puppeteer给我们提供了相应的api,因为浏览器渲染dom已经请求接口需要时间,因为第一时间我们拿到都网页代码也不是完整的,因此我们需要加延时。

await page.waitForTimeout(2000); // 延时2s加载页面 puppeteer2.1.1使用 waitFor ^13.0.1以上使用 waitForTimeout  
const videoSrc = await page.$eval('video source', (el) => {
    let src = '';
    if (el && el.src) {
        src = el.src;
    }
    return src;
});

拦截接口

部分页面是直接通过请求接口获取到的视频地址,对于这种网页我们可以使用上面的方法,等页面加载完毕后分析dom,但是查阅puppeteer的文档时发现可以直接拦截接口,获取接口的返回信息,
因此,如果我们针对指定的详情,知道其请求规则,可以直接通过接口响应获取相应的数据。

// 注册响应监听事件
page.on('response', async (response) => {
    if (response.ok()) {
        const request = response.request();
        const reqUrl = request.url();
        if (reqUrl.indexOf('/api/getHttpVideoInfo.do') > -1) { // 拦截 /api/getHttpVideoInfo.do 接口
            const respData = await response.json();
            const video = respData.video;
            if (video && video.validChapterNum > 0){
                const currentChapter = video[`chapters${video.validChapterNum}`];
                if (currentChapter && currentChapter.length > 0 && currentChapter[0] && currentChapter[0].url) {
                    resolve(currentChapter[0].url)
                }
            }
        }
    }
})

这种方式是指针对有明确接口,切能拿到相应的请求参数的页面使用!

添加前端页面完善接口

完整的代码已提交到github,链接在后面给出

打开本地网页访问:localhost:18000
image.png

服务端部署(Linux)

服务端环境为linux环境,系统为CentOS-8,Node.js 版本为 v8.11.3,Linux环境和windows环境部署的时候有点区别,特别是安装puppeteer时需要注意
安装puppeteer时会报以下错误

ERROR: Failed to download Chromium r722234! Set "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" env variable to skip download.
Error: EACCES: permission denied, mkdir '/opt/video-url-analysis/node_modules/puppeteer/.local-chromium'

image.png

因为安装puppeteer时会安装Chromium,需要权限,因此在linux环境下使用以下命令安装

npm install puppeteer@2.1.1 --unsafe-perm=true --allow-root

安装完毕后启动程序,成功运行并抓取网页视频!

其他

linux下启动浏览器 headless需要设置为true,添加args参数

const browser = await puppeteer.launch({
    headless: true, // 是否启用无头浏览 默认为true
    args: [
        '--no-sandbox',
        '--disable-setuid-sandbox'
    ]
});

其他异常错误:
1.Failed to launch the browser process

Failed to launch the browser process
...
error while loading shared libraries: libXss.so.1: cannot open shared object file: No such file or directory

应该是系统缺少某些库或者组件(我这边使用命令后解决了这个问题)

sudo yum -y install libXScrnSaver-1.2.2-6.1.el7.x86_64

或者直接重新安装chromium,手动安装chromium后解决问题

sudo yum install -y chromium

2.使用yum安装软件依赖出错,一直提示找不到软件包

[root@localhost video-url-analysis]# sudo yum install -y chromium
上次元数据过期检查:0:00:47 前,执行于 2022年01月20日 星期四 21时35分27秒。
未找到匹配的参数: chromium
错误:没有任何匹配: chromium

原因是CentOS 8没有安装 epel 源的问题,安装 epel 源后问题解决:
yum install epel-release

代码

完整代码已上传 https://github.com/zhaosheng808/video-grab 欢迎 star,仅供学习参考,切勿用于非法途径

1.安装依赖

npm install

2.本地开发

npm run dev

打开本地网页访问:localhost:18000

总结

windows环境下开发比较顺利,由于本人是前端切图仔,服务器接触较少,所以linux服务端部署遇到的问题较多,因此记录一下解决问题的过程,方便后续开发者遇到问题能够顺利解决。
服务端知识有所欠缺,如有不足,还请海涵!


ZHAO_
449 声望11 粉丝

前端开发