返回博客
指南
拉卢卡·彭丘克2023年2月20日阅读时长:12分钟

如何像专业人士一样搜索 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。我特别关注的数据包括:

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

我将为每个部分附上截图,并借助 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 同意对话框阻碍了我们访问数据。

YouTube 的 Cookie 同意页面,带有“全部接受”或“全部拒绝”按钮

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

YouTube  cookie 同意对话框,浏览器开发者工具已突出显示“全部接受”按钮元素

我们还额外添加了一段等待时间,以便让导航过程顺利完成。代码如下所示:

await page.waitForSelector('button[aria-label="Accept all"]')

await page.click('button[aria-label="Accept all"]')

await page.waitForTimeout(10 * 1000)

频道信息

在下图的截图中,我们可以看到已突出显示了包含我们要提取的频道数据的区域。

YouTube 频道页面,其中浏览器开发者工具高亮显示了频道名称和订阅者数量等元数据

一个便于定位 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 为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文档中的相关部分。这里,我们需要提取一组元素,因此先查看父容器,然后遍历其中的每一个。

YouTube 频道视频网格,使用浏览器开发者工具突出显示视频缩略图和标题链接的 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 的请求进行身份验证:

仪表盘快速入门指南,包含三个步骤: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在无需任何基础设施开销的情况下,以企业级规模提取网页数据。