返回博客
指南
Raluca PenciucLast updated on Mar 31, 20262 min read

如何像专业人士一样抓取YouTube数据:全面指南

如何像专业人士一样抓取YouTube数据:全面指南

您是否需要从某个网站获取数据,但现有的API却无法满足需求?网络爬虫正是您一直在寻找的解决方案。借助网络爬虫,您可以更全面、更灵活地从网站中提取数据。

本文将通过深入解析如何抓取最受欢迎的视频分享平台之一——YouTube,带您探索网络爬虫的世界。虽然YouTube确实提供了用于访问数据的API,但网络爬虫能为您从YouTube频道中提取数据提供更广泛的选择。

我们将首先搭建开发环境并介绍网络爬虫的先决条件,随后进入实际操作环节,讲解如何爬取 YouTube 数据。在此过程中,我们将提供提升网络爬虫技能的实用技巧,并探讨为何使用专业的爬虫工具可能比自行开发更优。

读完本文,您将掌握像专业人士一样高效抓取 YouTube 数据的知识与技能!

先决条件

首先,请确保您的计算机已安装 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。我特别关注的数据是:

  • 频道的头像
  • 频道名称
  • 频道用户名
  • 频道订阅者数量
  • 所有视频的标题
  • 所有视频的观看次数
  • 所有视频的缩略图
  • 所有视频的 URL

我将为每个部分提供截图,并借助 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 为 textyt-formatted-string 元素的文本内容。

const channelName = await page.evaluate(() => {

    const el = document.querySelector('yt-formatted-string#text');

    return el ? el.textContent : null;

});

console.log(channelName)

要获取频道ID,我们需要定位ID为channel-handleyt-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-countyt-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,它实施了许多反机器人技术来防止自动化脚本提取其数据。

其中一些技术包括:

  • 验证码(CAPTCHA):对爬虫而言,破解验证码既费时又困难,这本身就能起到阻止机器人的作用。
  • 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 数据抓取方法有所帮助!

关于作者
Raluca Penciuc, 全栈开发工程师 @ WebScrapingAPI
Raluca Penciuc全栈开发工程师

Raluca Penciuc 是 WebScrapingAPI 的全栈开发工程师,主要负责开发爬虫、优化规避机制,并探索可靠的方法以降低在目标网站上的被检测概率。

开始构建

准备好扩展您的数据收集规模了吗?

加入2,000多家企业,使用WebScrapingAPI在无需任何基础设施开销的情况下,以企业级规模提取网络数据。