理解 HTML 表格的结构
HTML 表格是标记结构化表格数据并以用户易于阅读和理解的方式进行展示的强大工具。表格由按行和列组织的数据构成,HTML 提供了多种不同的元素来定义和构建这些数据。一个表格必须至少包含以下元素:<table>、<tr>(表格行)和 <td>(表格数据)。 为了增强结构和语义价值,表格还可以包含 <th>(表格标题)元素,以及 <thead>、<tbody> 和 <tfoot> 元素。
让我们通过一个小示例来了解这些标签。
请注意第二个表格使用了更具体的语法
<thead> 标签将使第二个表格中的“水果”和“颜色”单元格显示为粗体。除此之外,您可以看到这两种语法在数据组织上达到了相同的效果。
在从网页抓取表格时,需注意可能会遇到语义具体程度各异的表格。换言之,有些表格可能包含更详细、更具描述性的 HTML 标签,而另一些则可能采用更简单、描述性较弱的语法。
使用 Node.js 和 cheerio 抓取 HTML 表格
欢迎来到有趣的部分!我们已经了解了 HTML 表格的结构和用途,现在是时候通过实践将这些知识付诸行动了。本教程的目标是 https://chartmasters.org/best-selling-artists-of-all-time/ 网站上的“史上最畅销艺人”表格。 我们将首先搭建开发环境并安装必要的库。接着,我们将探索目标网站并设计一些选择器来提取表格中的数据。之后,我们将编写代码实际抓取数据,最后将其导出为 CSV 和 JSON 等不同格式。
搭建开发环境
好,让我们开始这个全新的项目吧!开始之前,请确保已安装 Node.js。您可以从 https://nodejs.org/en/ 下载。
现在打开你常用的代码编辑器,进入项目目录,并在终端中运行:
npm init -y
这将初始化一个新项目并生成默认的 package.json 文件。
{
"name": "html_table_scraper", // the name of your project folder
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
为了简化操作,我们将使用“require”导入模块。但如果你想使用“import”语句导入模块,请在 package.json 文件中添加以下内容:
"type": "module",
// this will enable you to use the import statement
// ex: import * as cheerio from 'cheerio';
“main”: “index.js” 选项指定了作为程序入口点的文件名称。因此,现在你可以创建一个空的 index.js 文件。
我们将使用 cheerio 库来解析目标网站的 HTML。你可以通过以下命令安装它:
npm install cheerio
现在打开 index.js 文件,将其作为模块引入:
const cheerio = require('cheerio');
基本工作环境已配置完成。在下一章中,我们将探索“史上最畅销艺人”表格的结构。
使用开发者工具测试目标网站
通过检查开发者工具的“元素”选项卡,我们可以提取有关该表格结构的有价值信息:
该表格采用 <thead>、<tbody> 的格式存储。
所有表格元素都分配了描述性的 ID、类和角色。
在浏览器环境中,您可以直接访问网页的 HTML 代码。这意味着您可以使用 getElementsByTagName 或 querySelector 等 JavaScript 函数从 HTML 中提取数据。
基于此,我们可以利用开发者工具控制台测试一些选择器。
让我们使用 role="columnheader" 属性提取表头名称
现在,让我们使用 role="cell" 和 role="row" 属性提取第一行数据:
如您所见:
我们可以使用 “[role=columnheader]” 来选择所有标题元素。
我们可以使用“tbody [role=row]”来选择所有行元素。
对于每一行,我们可以使用 “[role=cell]” 来选择其单元格。
需要注意的是,PIC单元格中包含一张图片,我们需要编写一条特殊规则来提取其URL。
实现 HTML 表格抓取器
现在,让我们借助 Node.js 和 cheerio 来实现更高级的功能。
要在 Node.js 项目中获取网站的 HTML,你需要向该网站发起一个 fetch 请求。这会将 HTML 作为字符串返回,这意味着你无法使用 JavaScript DOM 函数来提取数据。这就是 cheerio 派上用场的地方。 Cheerio 是一个库,它允许你像在浏览器环境中一样解析和操作 HTML 字符串。这意味着你可以使用熟悉的 CSS 选择器从 HTML 中提取数据,就像在浏览器中一样。
另外需要注意的是,fetch 请求返回的 HTML 可能与你在浏览器中看到的 HTML 不同。这是因为浏览器会运行 JavaScript,而 JavaScript 可能会修改显示的 HTML。在 fetch 请求中,你获取的只是原始 HTML,不包含任何 JavaScript 修改。
仅出于测试目的,让我们向目标网站发起一个 fetch 请求,并将生成的 HTML 写入本地文件:
//index.js
const fs = require('fs');
(async () => {
const response = await fetch('https://chartmasters.org/best-selling-artists-of-all-time/');
const raw_html = await response.text();
fs.writeFileSync('raw-best-selling-artists-of-all-time.html', raw_html);
})();
// it will write the raw html to raw-best-selling-artists-of-all-time.html
// try it with other websites: https://randomwordgenerator.com/
你可以通过以下方式运行:
node index.js
运行后将得到:
表格结构保持不变。这意味着上一章中找到的选择器仍然适用。
好,现在让我们继续编写实际代码:
向 https://chartmasters.org/best-selling-artists-of-all-time/ 发送 Fetch 请求后,你需要将原始 HTML 加载到 cheerio 中:
const cheerio = require('cheerio');
(async () => {
const response = await fetch('https://chartmasters.org/best-selling-artists-of-all-time/');
const raw_html = await response.text();
const $ = cheerio.load(raw_html);
})();
加载 cheerio 后,让我们看看如何提取表头:
const headers = $("[role=columnheader]")
const header_names = []
headers.each((index, el) => {
header_names.push($(el).text())
})
//header_names
[
'#',
'PIC',
'Artist',
'Total CSPC',
'Studio Albums Sales',
'Other LPs Sales',
'Physical Singles Sales',
'Digital Singles Sales',
'Sales Update',
'Streams EAS (Update)'
]
以及第一行:
const first_row = $("tbody [role=row]")[0]
const first_row_cells = $(first_row).find('[role=cell]')
const first_row_data = []
first_row_cells.each((index, f_r_c) => {
const image = $(f_r_c).find('img').attr('src')
if(image) {
first_row_data.push(image)
}
else {
first_row_data.push($(f_r_c).text())
}
})
//first_row_data
[
'1',
'https://i.scdn.co/image/ab6761610000f178e9348cc01ff5d55971b22433',
'The Beatles',
'421,300,000',
'160,650,000',
'203,392,000',
'116,080,000',
'35,230,000',
'03/01/17',
'17,150,000 (01/03/23)'
]
还记得我们在浏览器开发者工具控制台中用 JavaScript 抓取 HTML 表格的情景吗?此时,我们复现了当时实现的相同功能,只不过是在 Node.js 项目的环境中。你可以回顾上一章,观察这两种实现方式之间的诸多相似之处。
接下来,让我们重写代码以抓取所有行:
const rows = $("tbody [role=row]")
const rows_data = []
rows.each((index, row) => {
const row_cell_data = []
const cells = $(row).find('[role=cell]')
cells.each((index, cell) => {
const image = $(cell).find('img').attr('src')
if(image) {
row_cell_data.push(image)
}
else {
row_cell_data.push($(cell).text())
}
})
rows_data.push(row_cell_data)
})
//rows_data
[
[
'1',
'https://i.scdn.co/image/ab6761610000f178e9348cc01ff5d55971b22433',
'The Beatles',
'421,300,000',
'160,650,000',
'203,392,000',
'116,080,000',
'35,230,000',
'03/01/17',
'17,150,000 (01/03/23)'
],
[
'2',
'https://i.scdn.co/image/ab6761610000f178a2a0b9e3448c1e702de9dc90',
'Michael Jackson',
'336,084,000',
'182,600,000',
'101,997,000',
'79,350,000',
'79,930,000',
'09/27/17',
'15,692,000 (01/06/23)'
],
...
]
既然已获取数据,接下来看看如何导出它。
导出数据
成功获取想要抓取的数据后,考虑如何存储这些信息至关重要。最常用的选项是 .json 和 .csv。请选择最符合您具体需求和要求的格式。
将数据导出为 JSON
若要将数据导出为 .json 格式,首先需将数据封装为符合 .json 格式的 JavaScript 对象。
我们有一个包含表头名称的数组(header_names),以及另一个包含行数据的数组(rows_data,即数组的数组)。.json 格式采用键值对形式存储信息。我们需要将数据打包成符合该规则的结构:
[ // this is what we need to obtain
{
'#': '1',
PIC: 'https://i.scdn.co/image/ab6761610000f178e9348cc01ff5d55971b22433',
Artist: 'The Beatles',
'Total CSPC': '421,300,000',
'Studio Albums Sales': '160,650,000',
'Other LPs Sales': '203,392,000',
'Physical Singles Sales': '116,080,000',
'Digital Singles Sales': '35,230,000',
'Sales Update': '03/01/17',
'Streams EAS (Update)': '17,150,000 (01/03/23)'
},
{
'#': '2',
PIC: 'https://i.scdn.co/image/ab6761610000f178a2a0b9e3448c1e702de9dc90',
Artist: 'Michael Jackson',
'Total CSPC': '336,084,000',
'Studio Albums Sales': '182,600,000',
'Other LPs Sales': '101,997,000',
'Physical Singles Sales': '79,350,000',
'Digital Singles Sales': '79,930,000',
'Sales Update': '09/27/17',
'Streams EAS (Update)': '15,692,000 (01/06/23)'
}
...
]
实现方法如下:
// go through each row
const table_data = rows_data.map(row => {
// create a new object
const obj = {};
// forEach element in header_names
header_names.forEach((header_name, i) => {
// add a key-value pair to the object where the key is the current header name and the value is the value at the same index in the row
obj[header_name] = row[i];
});
// return the object
return obj;
});
现在,您可以使用 JSON.stringify() 函数将 JavaScript 对象转换为 .json 字符串,然后将其写入文件。
const fs = require('fs');
const table_data_json_string = JSON.stringify(table_data, null, 2)
fs.writeFile('table_data.json', table_data_json_string, (err) => {
if (err) throw err;
console.log('The file has been saved as table_data.json!');
})将数据导出为 CSV
.csv 格式代表“逗号分隔值”。若要将表格保存为 .csv 文件,需采用类似以下格式的写法:
id,name,age // the table headers followed by the rows
1,Alice,20
2,Bob,25
3,Charlie,30
我们的表格数据由一个标题名称数组(header_names)和另一个数组(rows_data,即数组的数组)组成,后者包含行数据。以下是将这些数据写入 .csv 文件的方法:
let csv_string = header_names.join(',') + '\n'; // add the headers
// forEach row in rows_data
rows_data.forEach(row => {
// add the row to the CSV string
csv_string += row.join(',') + '\n';
});
// write the string to a file
fs.writeFile('table_data.csv', csv_string, (err) => {
if (err) throw err;
console.log('The file has been saved as table_data.csv!');
});避免被封禁
您是否曾遇到过这样的情况:在尝试抓取网站数据时,发现目标页面并未完全加载?这会让人感到沮丧,尤其是当您知道该网站使用 JavaScript 生成内容时。我们无法像普通浏览器那样执行 JavaScript,这可能导致数据不完整,甚至因短时间内发送过多请求而被网站封禁。
WebScrapingApi 正是解决这一问题的方案。通过我们的服务,您只需向 API 发起请求,它便会为您处理所有复杂任务。它将执行 JavaScript、轮换代理,甚至处理验证码。
以下是向 <target_url> 发送简单 fetch 请求并将响应写入文件的方法:
const fs = require('fs');
(async () => {
const result = await fetch('https://api.webscrapingapi.com/v1?' + new URLSearchParams({
api_key: '<api_key>',
url: '<target_url>',
render_js: 1,
proxy_type: 'residential',
}))
const html = await result.text();
fs.writeFileSync('wsa_test.html', html);
})();
您可通过在 https://www.webscrapingapi.com/ 注册新账户获取免费 API_KEY
通过指定 render_js=1 参数,您将启用 WebScrapingAPI 的无头浏览器访问功能,该功能可在向您返回最终抓取结果前,先渲染 JavaScript 页面元素。
访问 https://docs.webscrapingapi.com/webscrapingapi/advanced-api-features/proxies,了解我们轮换代理的功能。
结论
在本文中,我们了解了使用 JavaScript 抓取 HTML 表格的强大功能,以及它如何帮助我们从网站中提取有价值的数据。我们探讨了 HTML 表格的结构,并学习了如何结合 Node.js 使用 cheerio 库来轻松抓取其中的数据。 此外,我们还探讨了多种数据导出方式,包括 CSV 和 JSON 格式。遵循本文所述的步骤,您现在应该已经为在任何网站上抓取 HTML 表格打下了坚实的基础。
无论您是经验丰富的专家,还是刚刚开始您的第一个抓取项目,WebScrapingAPI 都将全程为您提供支持。我们的团队随时乐意解答您的任何疑问,并为您的项目提供指导。因此,如果您遇到困难或需要帮助,请随时联系我们。




