先决条件
首先,你需要确保你的电脑上已安装 Node.js。如果尚未安装,请访问Node.js 官方网站,并按照你所用操作系统的具体说明进行操作。需要注意的是,你应下载长期支持(LTS)版本,以确保使用的是稳定且受支持的版本。
接下来,您需要安装 Node.js 包管理器(NPM)。虽然安装 Node.js 时通常会自动包含该工具,但还是建议您再确认一下。
关于编码环境,您可以随意选择自己喜欢的任何 IDE。在本教程中,我将使用 Visual Studio Code,因为它灵活且易于使用,但任何 IDE 都可以。只需为您的项目创建一个新文件夹,然后打开终端。运行以下命令来创建一个新的 Node.js 项目:
npm init -y
这将为您的项目生成一个默认的package.json文件。您可以随时根据需要修改此文件。
现在是时候安装 TypeScript 以及 Node.js 的类型定义了。TypeScript 因其可选的静态类型检查而在 JavaScript 社区中广受欢迎,这有助于防止代码中的类型错误。要安装它,请运行以下命令:
npm install typescript @types/node --save-dev
要验证安装是否成功,您可以运行以下命令:
npx tsc --version
最后,您需要在项目目录的根目录下创建一个名为 tsconfig.json的配置文件。该文件用于定义项目的编译选项。如果您想进一步了解该文件及其属性,请查阅TypeScript 官方文档。
或者,您可以将以下代码复制并粘贴到您的tsconfig.json文件中:
{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "es2017",
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist"
},
"lib": ["es2015"]
}
在数据抓取过程中,我将使用 Puppeteer——这是一个适用于 Node.js 的无头浏览器库,它允许您通过编程方式控制网页浏览器并与网站进行交互。要安装 Puppeteer,请运行以下命令:
npm install puppeteer提取数据
在本指南中,我将抓取一个包含 DevOps 相关教程的 YouTube 频道:https://www.youtube.com/@TechWorldwithNana/videos。我特别关注的数据包括:
- 该频道的头像
- 频道名称
- 该频道的标题
- 该频道的订阅者数量
- 所有视频的标题
- 所有视频的观看次数
- 所有视频的缩略图
- 所有视频的网址
我将为每个部分附上截图,并借助 CSS 选择器在 DOM 中定位数据。除非目标网站以 DOM 结构不稳定著称,否则这是最简单直接的方法。
如果您刚接触 CSS 选择器,不妨看看这份全面的速查表,它能帮助您快速入门。
首先,我们创建一个src文件夹以及index.ts文件,用于编写代码。现在,我们只需打开浏览器并访问目标 URL:
import puppeteer from 'puppeteer';
async function scrapeChannelData(channelUrl: string): Promise<any> {
// Launch Puppeteer
const browser = await puppeteer.launch({
headless: false,
args: ['--start-maximized'],
defaultViewport: null
});
// Create a new page and navigate to the channel URL
const page = await browser.newPage();
await page.goto(channelUrl);
// Close the browser
await browser.close();
}
scrapeChannelData("https://www.youtube.com/@TechWorldwithNana/videos");
为了便于可视化调试,我将以非无头模式打开浏览器。如果您打算将应用场景扩展到大规模场景,建议您尝试使用无头模式。
要运行该脚本,您需要先将其编译,然后执行生成的 JavaScript 文件。为了简化操作,我们可以在package.json文件中定义一个脚本,由它来处理这两个步骤。只需按照以下方式编辑package.json文件中的 scripts 部分:
"scripts": {
"test": "npx tsc && node dist/index.js"
},
现在,要运行您的代码,您只需执行以下命令:
npm run test
从首次运行开始,我们就发现了一个问题:全屏的 Cookie 同意对话框阻碍了我们访问数据。

幸运的是,它显示在视口内,因此我们可以使用开发者工具查找其标识符并点击它。

我们还额外添加了一段等待时间,以便让导航过程顺利完成。代码如下所示:
await page.waitForSelector('button[aria-label="Accept all"]')
await page.click('button[aria-label="Accept all"]')
await page.waitForTimeout(10 * 1000)频道信息
在下图的截图中,我们可以看到已突出显示了包含我们要提取的频道数据的区域。

一个便于定位 HTML 元素的实用技巧是选择唯一的 CSS 选择器。例如,要提取频道头像,我会选择 ID 为avatar 的自定义 HTML 元素yt-img-shadow,然后提取其img 子元素 的src属性。
const channelAvatar = await page.evaluate(() => {
const el = document.querySelector('yt-img-shadow#avatar > img');
return el ? el.getAttribute('src') : null;
});
console.log(channelAvatar)
对于频道名称,我们使用了 ID 为text 的yt-formatted-string元素的文本内容。
const channelName = await page.evaluate(() => {
const el = document.querySelector('yt-formatted-string#text');
return el ? el.textContent : null;
});
console.log(channelName)
要获取频道标识符,我们将定位 ID 为channel-handle的yt-formatted-string元素,并提取其文本内容。
const channelHandle = await page.evaluate(() => {
const el = document.querySelector('yt-formatted-string#channel-handle');
return el ? el.textContent : null;
});
console.log(channelHandle)
最后,要获取频道订阅者数量,我们只需访问 id 为subscriber-count的yt-formatted-string元素,并获取其文本内容即可。
const subscriberCount = await page.evaluate(() => {
const el = document.querySelector('yt-formatted-string#subscriber-count');
return el ? el.textContent : null;
});
console.log(subscriberCount)
再次运行该脚本后,您应该会看到以下输出:
https://yt3.googleusercontent.com/kXyR8Aa32KXnZWVdkAFUYK5utM752kSJPHGtYiJ4ev6BmdFHi-dl1EFbI3TogmHBjszwc7m2=s176-c-k-c0x00ffffff-no-rj
TechWorld with Nana
@TechWorldwithNana
709K subscribers视频数据
接下来处理视频数据时,我也标出了HTML文档中的相关部分。这里,我们需要提取一组元素,因此先查看父容器,然后遍历其中的每一个。

我们沿用上一节的方法:选择一些唯一的 CSS 选择器来定位所需的数据,重点关注其 ID。代码大致如下:
const videos = await page.evaluate(() => {
const videosEls = Array.from(document.querySelectorAll('div#dismissible'))
return videosEls.map(video => {
const titleEl = video.querySelector('yt-formatted-string#video-title');
const viewsEl = video.querySelector('div#metadata-line > span');
const thumbnailEl = video.querySelector('yt-image.ytd-thumbnail > img');
const locationEl = video.querySelector('a#thumbnail');
return {
title: titleEl ? titleEl.textContent : null,
views: viewsEl ? viewsEl.textContent : null,
thumbnail: thumbnailEl ? thumbnailEl.getAttribute('src') : null,
location: locationEl ? locationEl.getAttribute('href') : null
}
})
})
console.log(videos)
运行代码后,输出结果应为一个 JavaScript 对象列表。每个对象都应包含视频的标题、观看次数、缩略图以及页面上每个视频元素的位置。
不过,你会发现,到了某个时候,你的列表开始变成这样:
{
title: 'GitLab CI/CD Full Course released - CI/CD with Docker | K8s | Microservices!',
views: '114K views',
thumbnail: null,
location: '/watch?v=F7WMRXLUQRM'
},
{
title: 'Kubernetes Security Best Practices you need to know | THE Guide for securing your K8s cluster!',
views: '103K views',
thumbnail: null,
location: '/watch?v=oBf5lrmquYI'
},
{
title: 'How I learn new technologies as a DevOps Engineer (without being overwhelmed)',
views: '366K views',
thumbnail: null,
location: '/watch?v=Cthla7KqU04'
},
{
title: 'Automate your Multi-Stage Continuous Delivery and Operations | with Keptn',
views: '59K views',
thumbnail: null,
location: '/watch?v=3EEZmSwMXp8'
},
尽管视频元素仍然有缩略图,且 CSS 选择器也没有改变,但提取的值却是null。这种情况通常发生在网站采用了懒加载机制时,这意味着列表的其余部分会在您滚动到页面底部时加载。
要解决这个问题,我们只需让脚本向下滚动频道页面即可。
async function autoScroll(page: any, scroll_number: number): Promise<any> {
await page.evaluate(async (scroll_number: number) => {
await new Promise((resolve) => {
let totalHeight = 0;
const timer = setInterval(() => {
const scrollHeight = window.innerHeight * scroll_number;
window.scrollBy(0, window.innerHeight);
totalHeight += window.innerHeight;
if (totalHeight > scrollHeight) {
clearInterval(timer);
resolve(true);
}
}, 1000);
});
}, scroll_number);
}
该函数的参数包括当前打开的页面和滚动次数。随后,它将按照 `scroll_number` 参数指定的次数,每次滚动与窗口高度相等的距离。这些滚动操作每隔 1 秒执行一次。
现在只需在提取视频列表的代码片段之前调用该函数即可。
await autoScroll(page, 10)
await page.waitForTimeout(2 * 1000)
我额外增加了2秒的等待时间,以便网站有足够的时间完全加载列表中的最后几个元素。再次运行脚本后,您将首先看到滚动效果的呈现,随后会发现列表中的所有元素都已显示缩略图。
避免受阻
尽管到目前为止这篇指南看起来似乎轻而易举,但网络爬虫通常会遇到多种挑战。尤其是YouTube,它采用了许多反机器人技术来防止自动化脚本提取其数据。
其中一些技术包括:
- 验证码:对于爬虫而言,破解验证码既费时又困难,这可以起到阻止机器人的作用。
- JavaScript 验证任务:可能包括解决数学问题、完成 CAPTCHA 验证或在页面上查找特定元素等任务。无法完成验证任务的机器人将被识别并可能被拦截。
- 用户代理检查:YouTube 可能会检查传入请求的用户代理字符串,以判断其来源是浏览器还是爬虫。如果用户代理字符串未被识别为有效的浏览器,该请求可能会被拦截。
- IP封禁:YouTube可能会屏蔽来自某些已知与机器人或爬虫活动相关的IP地址的请求。
- 蜜罐:YouTube 可能会使用蜜罐,即页面上仅对机器人可见的隐藏元素。如果检测到机器人与蜜罐进行交互,即可识别并阻止该机器人。
处理这些问题中的任何一个,都会显著增加爬虫代码的复杂性和成本。正因如此,爬虫 API 发挥着重要作用,因为它们默认已处理了这些问题,且价格更为低廉。
WebScrapingAPI 就是此类服务的一个例子。它提供了强大的功能,既能规避机器人检测机制,又能精准提取您所需的数据。
我们可以在这个小项目中安装 Node.js SDK,从而快速尝试使用 WebScrapingAPI:
npm i webscrapingapi
现在请访问主页注册一个账户,系统会自动为您生成 API 密钥并提供免费试用。您可以在仪表盘中找到该 API 密钥,并使用它来对发往 API 的请求进行身份验证:

就这样,你可以开始编写代码了!
import webScrapingApiClient from 'webscrapingapi';
const client = new webScrapingApiClient("YOUR_API_KEY");
async function exampleUsage(target_url: string) {
const api_params = {
'render_js': 1,
'proxy_type': 'datacenter',
'country': 'us',
'timeout': 60000,
'js_instructions': JSON.stringify([
{
action: "click",
selector: 'button[aria-label="Accept all"]',
timeout: 10000
}
]),
'extract_rules': JSON.stringify({
avatar: {
selector: "yt-img-shadow#avatar > img",
output: "@src",
},
name: {
selector: "yt-formatted-string#text",
output: "text",
},
handle: {
selector: "yt-formatted-string#channel-handle",
output: "text",
},
subscribers: {
selector: "yt-formatted-string#subscriber-count",
output: "text",
},
videoTitles: {
selector: "yt-formatted-string#video-title",
output: "text",
all: "1"
},
videoViews: {
selector: "div#metadata-line > span",
output: "text",
all: "1"
},
videoThumbnails: {
selector: "yt-image.ytd-thumbnail > img",
output: "@src",
all: "1"
},
videoLocations: {
selector: "a#thumbnail",
output: "@href",
all: "1"
},
})
}
const response = await client.get(target_url, api_params);
if (response.success) {
console.log(response.response.data);
} else {
console.log(response.error.response.data);
}
}
exampleUsage("https://www.youtube.com/@TechWorldwithNana/videos");
我们将前面描述的算法和 CSS 选择器转换为 API。通过点击“全部接受”按钮,“js_instructions”参数将处理 Cookie 提示窗口。最后,“extract_rules”参数将负责数据提取。
const scroll_number = 10
let scroll_index = 0
for (let i = 0; i < scroll_number; i++) {
const js_instructions_obj = JSON.parse(api_params.js_instructions)
js_instructions_obj.push({
action: "scrollTo",
selector: `ytd-rich-grid-row.ytd-rich-grid-renderer:nth-child(${scroll_index + 3})`,
block: "end",
timeout: 1000
})
api_params.js_instructions = JSON.stringify(js_instructions_obj)
scroll_index += 3
}
然后,在发送请求之前,请记得调整滚动逻辑。这会稍有不同,因为我们要让 API 滚动到视频的第三行,并重复此操作 10 次。
结论
在本文中,我们探索了令人兴奋的网络爬虫领域,并学习了如何使用 Node.js 和 Puppeteer 从 YouTube 抓取数据。我们介绍了进行网络爬虫所需的配置,以及从 YouTube 频道中提取数据的具体流程。
网络爬虫是获取网站数据的极佳工具。然而,必须注意它所伴随的各种挑战和注意事项,例如验证码、动态内容、速率限制或网站变更。
如果您正在考虑从 YouTube 或任何其他网站抓取数据,务必权衡利弊,并确定网页抓取是否是满足您需求的最佳方案。在某些情况下,使用 API 或从信誉良好的来源购买数据,可能比抓取数据更为合适。
无论您选择哪种方法,都必须遵守适用于您所访问数据的服务条款和版权法。如果您决定从 YouTube 抓取数据,请务必使用专业的数据抓取工具,以确保能够以安全、高效的方式获取准确、最新的数据。
希望这篇文章能对您开启网络爬虫学习之旅、了解如何抓取 YouTube 数据有所帮助!




