IMDb 是互联网上最大的电影和电视公开目录之一,收录了数百万部作品的事实元数据:影片名称、上映年份、用户综合评分、类型、片长以及导演信息。研究发行趋势的学者、构建个人影片数据库的爱好者,以及正在开发推荐功能原型的开发者,都会访问这些公开的作品页面,而这些元数据以相当稳定的布局呈现其中。
本指南介绍如何使用 JavaScript 和 Node.js 配合 Cheerio 抓取 IMDb 电影数据。你将构建一个小型可运行的爬虫,通过 Crawling API 获取 IMDb 公开作品页面,解析电影片名、年份、IMDb 评分、类型、片长和导演,并将结果导出为 JSON 和 CSV。整个教程仅限于公开的事实性电影元数据,靠近末尾的合法性说明不是套话,请在正式抓取大量数据之前认真阅读。
你将构建什么
一个 Node.js 脚本:接收 IMDb 公开作品 URL,通过 Crawling API 获取渲染后的 HTML,并提取该影片的结构化记录。我们以The Shawshank Redemption为贯穿全文的示例,每部作品提取以下事实字段:
- 片名 页面主视觉区展示的主要电影标题,例如 "The Shawshank Redemption"。
- 年份 标题旁标注的上映年份。
- 评分 IMDb 用户综合评分(满分 10 分)。
- 类型 IMDb 为该作品分配的类型标签,例如 "Drama"。
- 片长 影片的标注时长。
- 导演 影片的署名导演。
为什么普通请求在 IMDb 上会失败
如果你用普通 HTTP 客户端请求 IMDb 作品 URL,很少能拿到你期望的元数据。有两个因素对你不利。第一,IMDb 的许多作品页面内容是由 JavaScript 在浏览器端渲染的,因此初始 HTML 只是一个薄壳,直到页面脚本运行并填充评分、演职员表和详细行之后才完整呈现。第二,IMDb 会监控自动化流量:来自数据中心的 IP 和不像真实浏览器的请求模式,在到达渲染完成的页面之前就会被限速或遭遇挑战。
因此,一个可用的 IMDb 爬虫需要在单次请求中同时具备两点:一个能真正渲染页面的浏览器,以及一个被平台识别为真实访客的 IP。你可以自己搭建无头浏览器加轮换住宅代理池,但维护这套组合才是大部分工作量所在。Crawling API 将两者合并为一次调用:你发送 URL,它在可信 IP 后渲染页面,并将完整 HTML 返回给你,供 Cheerio 解析。
Crawling API 提供两种令牌:普通令牌和 JavaScript 令牌。IMDb 会在浏览器中填充评分和演职员表,因此本指南中的每次请求都应使用JavaScript 令牌。普通令牌返回未渲染的薄壳,选择器将无法匹配到任何内容。
前提条件
在编写代码之前,你需要准备好以下几样东西。每一项都不费时。
基础 JavaScript 和 Node.js 知识。你应该能够编写和运行 Node 脚本,并使用 npm 安装包。如果你是 Node 新手,官方文档和任何入门课程都能帮你达到本教程所需的水平。如需更完整的入门指引,可参考我们关于使用 Node.js 构建网络爬虫的指南。
Node.js 16 或更高版本。使用 node --version 确认你的版本。如果尚未安装,可从 Node.js 官网下载,或通过 nvm 等版本管理器安装。
Crawlbase 账号和令牌。注册账号,打开控制台,从账号文档页面复制你的 JavaScript 令牌。免费套餐最多提供 20,000 次请求,无需绑定信用卡,且仅对成功的请求计费。请像保管密码一样保管该令牌:它用于验证你的请求身份,不要将其提交到版本控制系统。
搭建项目
创建项目文件夹,初始化项目,并安装爬虫所需的两个库。
node --version mkdir imdb-scraper && cd imdb-scraper npm init -y npm install crawlbase cheerio
两个依赖各司其职:crawlbase 是 Crawling API 的官方 Node 客户端,cheerio 使用类 jQuery 的 API 解析返回的 HTML,让你可以通过 CSS 选择器提取各个字段。在此文件夹下创建 scraper.js 文件,并按以下步骤添加代码。
第一步:获取渲染后的作品页面
首先获取完整的页面。导入 CrawlingAPI 类,用你的 JavaScript 令牌初始化,然后请求 IMDb 公开作品 URL。本例使用 The Shawshank Redemption,地址为 https://www.imdb.com/title/tt0111161/。在解析之前检查状态码,可以让失败尽早暴露而非静默发生。
const { CrawlingAPI } = require('crawlbase'); const api = new CrawlingAPI({ token: 'YOUR_CRAWLBASE_TOKEN' }); const imdbPageURL = 'https://www.imdb.com/title/tt0111161/'; api .get(imdbPageURL) .then((response) => { if (response.statusCode === 200) { console.log(response.body.slice(0, 500)); } }) .catch((error) => console.error('API request error:', error));
使用 node scraper.js 运行脚本,你应该能在 body 顶部看到真实的 IMDb 作品标记,而不是被削减的薄壳。这证明渲染正常工作,在你编写任何选择器之前就能得到确认。Crawling API 使用你提供的 JavaScript 令牌在真实浏览器中渲染页面,因此评分和演职员表都已包含在你收到的 HTML 中。
刚才那个请求返回了一个完整渲染的 IMDb 作品页面,你这边无需部署无头浏览器或代理。Crawling API 在真实浏览器中运行页面,在服务端轮换住宅 IP,并处理 IMDb 对自动化流量的各种挑战,让你只需一次调用便能获得完整 HTML 供解析。先在免费套餐中指向一个公开作品,再添加你的解析器。
第二步:用 Cheerio 解析电影字段
拿到渲染后的 HTML,将其加载到 Cheerio 中,通过选择器读取每个字段。IMDb 对大多数你需要的元数据使用稳定的 data-testid 属性进行标记,比生成的 class 名称更易于定位。我们从页面主视觉区提取片名和年份,从综合评分区块提取评分,从标签列表提取类型,从作品详情行提取片长和导演。防御性地读取每个字段,可以避免单个缺失值导致整个抓取崩溃。
const cheerio = require('cheerio'); function parseMovieFromHTML(html) { const $ = cheerio.load(html); const getText = (selector) => $(selector).first().text().trim(); // Read every chip in a labelled metadata row, joined into one string const getRowItems = (selector) => $(selector) .map((_, el) => $(el).text().trim()) .get() .join(', '); const title = getText( '[data-testid="hero__pageTitle"] .hero__primary-text', ); // The first metadata link under the hero title is the release year const year = getText( '[data-testid="hero__pageTitle"] + ul li:first-child a', ); const rating = getText( '[data-testid="hero-rating-bar__aggregate-rating__score"] span', ); const genre = getRowItems( '.ipc-chip-list--baseAlt .ipc-chip__text', ); const runtime = getRowItems( '[data-testid="title-techspec_runtime"] .ipc-metadata-list-item__content-container', ); const director = getRowItems( 'li:contains("Director") a.ipc-metadata-list-item__list-content-item--link:first', ); return { title, year, rating, genre, runtime, director }; }
以下几个细节确保了提取的准确性。片名来自 [data-testid="hero__pageTitle"] .hero__primary-text 主视觉元素,年份是紧随其后的第一个元数据链接。IMDb 综合评分位于 [data-testid="hero-rating-bar__aggregate-rating__score"],类型标签在 .ipc-chip-list--baseAlt .ipc-chip__text 列表中,片长在 title-techspec_runtime 详情行中。导演从包含 "Director" 标签的演职员表行中读取,取第一个带链接的名字。将行内条目合并为一个字符串,可以保持输出扁平化,便于存储。
IMDb 的 class 名称(ipc-* 及哈希后缀)在生成后可能随时变更;data-testid 属性相对更稳定,但也不能保证永远不变。把这些选择器视为起始模板,而非固定合同。当某个字段返回为空时,在浏览器开发者工具中重新审查线上页面并更新选择器。定期维护选择器是任何生产级爬虫的正常工作,不代表出了什么问题。
第三步:组合完整脚本并导出 JSON 和 CSV
现在将获取和解析逻辑串联成一个可运行的脚本,并将记录以 JSON 和 CSV 两种格式写入磁盘。使用简单脚本可以减少移动部件;如有需要,之后再将其封装为接口。
const fs = require('fs'); const { CrawlingAPI } = require('crawlbase'); const cheerio = require('cheerio'); const api = new CrawlingAPI({ token: 'YOUR_CRAWLBASE_TOKEN' }); async function crawl(pageUrl) { const response = await api.get(pageUrl); if (response.statusCode === 200) return response.body; console.error(`Request failed: ${response.statusCode}`); return null; } function toCsv(row) { const headers = [ 'title', 'year', 'rating', 'genre', 'runtime', 'director', ]; const escape = (value) => `"${String(value).replace(/"/g, '""')}"`; const values = headers.map((h) => escape(row[h])); return [headers.join(','), values.join(',')].join('\n'); } async function main() { const url = 'https://www.imdb.com/title/tt0111161/'; const html = await crawl(url); if (!html) return; const movie = parseMovieFromHTML(html); fs.writeFileSync('movie.json', JSON.stringify(movie, null, 2)); fs.writeFileSync('movie.csv', toCsv(movie)); console.log(`Saved ${movie.title} to JSON and CSV`); } main();
将第二步中的 parseMovieFromHTML 函数粘贴到同一文件中,以便 main 调用。使用 node scraper.js 运行后,你将得到两个文件:movie.json 包含完整结构化记录,movie.csv 可直接在电子表格中打开。toCsv 辅助函数会为每个字段加上引号,并将嵌入的引号转义为双引号,这一点很重要,因为片名和类型列表中经常包含逗号。
输出示例
JSON 文件包含一个对象,字段为片名、年份、IMDb 评分、类型、片长和导演。
{ "title": "The Shawshank Redemption", "year": "1994", "rating": "9.3", "genre": "Drama", "runtime": "2h 22m", "director": "Frank Darabont" }
CSV 以相同记录加一行表头呈现,可直接导入 Excel、Google Sheets 或任何读取分隔符文件的数据管道。
title,year,rating,genre,runtime,director "The Shawshank Redemption","1994","9.3","Drama","2h 22m","Frank Darabont"
扩展至多部作品
抓取单部作品只是演示;真实任务是在一批影片中收集元数据。由于每个 IMDb 作品页面共享相同的主视觉区和详情行结构,你已经编写的解析器无需修改即可适用于所有作品。维护一份作品 URL 列表,通过 Crawling API 逐一获取,用同一函数解析,并收集结果。在请求之间加入短暂延迟,以保持在 IMDb 的速率限制之内。
async function scrapeTitles(urls) { const movies = []; for (const url of urls) { const html = await crawl(url); if (!html) continue; const movie = parseMovieFromHTML(html); movies.push(movie); console.log(`Parsed ${movie.title || url}`); // Pace requests so you stay under the rate limit await new Promise((r) => setTimeout(r, 2000)); } return movies; }
如果待处理的作品积压量较大,不希望同步等待,异步 Crawler 允许你推送 URL 并收集结果,无需为每个请求保持连接。关于此类渲染密集型 JavaScript 页面的更多内容,可参考我们的抓取 JavaScript 网站指南。
保持不被封锁
即便渲染问题已解决,IMDb 仍会监控形似爬虫的流量。以下几个习惯有助于保持任务顺畅运行,适用于任何大型公开网站。
- 控制请求频率。在每次获取之间引入延迟,而不是在紧密循环中频繁请求。分散请求是保持在 IMDb 速率限制之内最有效的单一措施。
- 善用轮换。住宅 IP 池将请求分散到众多真实用户地址,使任何单一地址都不会触发限制。Crawling API 为你处理这一切;如果你自行搭建,这一环节最为关键。
- 关注状态码。当运行过程中开始返回挑战或非 200 响应时,说明当前速率或 IP 层级已不够用。将其视为需要退让的信号,而非可以忽略的噪音。
关于更完整的应对策略,请参阅如何在不被封锁的情况下抓取网站。如果你希望从其他娱乐来源获取类似元数据,同样的"获取后解析"模式也可直接应用于抓取 Rotten Tomatoes 和 Goodreads 评分。
抓取 IMDb 是否合法?
抓取 IMDb 是否被允许,取决于 IMDb 的使用条款、你所在的司法管辖区以及你对数据的用途。IMDb 的条款限制自动化访问及内容重用,因此无论你的技术手段多么谨慎,抓取行为都可能与这些条款相抵触。本文中的代码无法改变这一点,它只是让技术层面的工作得以实现。请阅读 IMDb 的使用条款和 robots.txt,尊重其中明确的速率预期,并将两者视为你能收集内容的边界。出于个人研究目的、对少量公开事实字段的有限收集,与 IMDb 明令禁止(未经明确授权不得进行)的大规模或商业性提取,性质截然不同。
本指南有意将范围限定为公开的事实性电影元数据:任何人无需登录即可在公开作品页面看到的片名、上映年份、用户综合评分、类型、片长和署名导演。这些是事实性的目录数据,不是个人数据,也是安全范围所在。本指南不涉及同一页面上的受版权保护内容。剧情简介、用户评论、编辑文字、海报和剧照均受版权保护。不要大量转载评论、简介或图片,也不要将其当作你自己的内容发布。将你的使用限定在少量事实字段,并保持适度的数量。
如果你的项目需要超出少数公开字段的数据,官方授权渠道才是正确路径,而非更聪明的爬虫。IMDb 针对非商业用途提供了可授权的官方数据集,并通过 IMDb 及其母公司为生产需求提供商业数据许可。当你需要大批量数据、有保证的结构或商业使用权时,这些才是正确的工具,且附有明确的使用和署名条款。如果你不确定某种用途是否被允许,请签订数据协议,而非假定沉默等于同意。
核心要点
- IMDb 在客户端渲染元数据。普通请求返回的是薄壳,因此在解析之前必须通过可信 IP 使用 JavaScript 令牌渲染页面。
- Crawling API 通过单次调用完成两件事。它在真实浏览器中渲染页面,并轮换住宅 IP,返回供 Cheerio 解析的完整 HTML。
-
Cheerio 负责字段提取。定位主视觉区片名、综合评分区块、类型标签,以及片长和导演详情行,优先使用
data-testid属性,并预期生成的 class 名称会漂移。 - 扩展并导出。对作品 URL 列表复用同一解析器,控制请求频率,并将结构化记录同时写入 JSON 和 CSV。
- 坚守公开事实数据。仅收集片名、年份、评分、类型、片长和导演,不转载评论、简介或图片,遵守使用条款和 robots.txt,如需大量数据或商业用途,优先使用 IMDb 官方数据集或授权数据源。
常见问题
我可以用 JavaScript 以外的语言构建 IMDb 爬虫吗?
可以。本指南使用 JavaScript 配合 Cheerio,但同样的方法适用于任何语言。Crawling API 提供多种语言的库和 SDK,因此你可以用相同的方式获取渲染后的 HTML,并使用你的技术栈偏好的 HTML 解析器进行解析,例如 Python 中的 BeautifulSoup。选择器和字段保持不变,只有解析语法有所不同。
为什么普通请求从 IMDb 返回的数据不完整?
因为 IMDb 使用 JavaScript 在浏览器端填充作品页面的大部分内容,并监控自动化流量。来自数据中心 IP 的原始 HTTP 请求通常会返回不含评分和演职员表的薄壳,或者遭遇挑战页面。要获取完整页面,必须在可信 IP 后渲染,这正是使用 JavaScript 令牌时 Crawling API 为你处理的事情。
我的选择器返回空值,发生了什么变化?
几乎可以肯定是 IMDb 的标记发生了变化。其生成的 ipc-* class 名称无通知即可变更,因此上个月有效的选择器可能已失效。优先使用更稳定的 data-testid 属性,在浏览器开发者工具中重新检查线上页面,更新 parseMovieFromHTML 中的选择器,即可恢复正常。定期维护选择器是任何生产级爬虫的正常工作。
IMDb 有官方 API 或数据集吗?
IMDb 不提供通用的公开 API,但它确实发布了可供个人和非商业用途下载的官方数据集,并通过 IMDb 及其母公司提供商业数据授权。对于生产需求、大批量数据或商业重用,授权数据集或数据源是正确的、经过认可的途径。这个公开数据爬虫最适合用于研究、原型开发以及无需官方协议的小规模分析。
我也可以抓取评论、剧情简介和海报吗?
这超出了本指南的范围。评论、简介、编辑文字、海报和剧照均受版权保护,即使它们显示在公开页面上,大量转载仍会侵犯版权。请将你的收集限定在本文涵盖的事实字段,即片名、年份、评分、类型、片长和导演;如需受保护内容,请使用 IMDb 官方数据集或申请授权。
抓取 IMDb 时会被封锁吗?
如果你从单一地址发送过多过快的请求,就可能被封。Crawling API 通过为你轮换住宅 IP 来降低这一风险,但你仍应控制请求频率,在每次获取之间添加延迟,并关注状态码,以便在出现挑战时及时退让。这些习惯在任何大型公开网站上都同样适用。
大规模爬取任何站点,无需与基础设施对抗。
Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。
