HTML 表格的列顺序经常变动,硬编码列索引(cells[2])在结构改变后立刻失效。正确做法是先扫描表头行确定目标列的实际位置,再按索引取数据。
核心思路
1. 找到目标表格
2. 读取表头行,确定"销售"列的索引
3. 遍历数据行,按列索引取值
4. 匹配产品名称,验证数值范围
完整实现
import * as cheerio from "cheerio";
class PriceParser {
constructor() {
this.validationRules = {
gold: { min: 400, max: 1000 }, // 元/克
silver: { min: 3, max: 20 },
};
}
parsePrices(html) {
const $ = cheerio.load(html);
const result = { goldPrice: null, silverPrice: null };
$("table").each((_, table) => {
const rows = $(table).find("tr");
if (rows.length < 2) return;
// 扫描表头行,定位"销售"列
let saleColumnIndex = -1;
rows.first().find("th, td").each((i, cell) => {
if ($(cell).text().trim().includes("销售")) {
saleColumnIndex = i;
return false; // break
}
});
if (saleColumnIndex < 0) return;
// 遍历数据行
rows.each((rowIndex, row) => {
if (rowIndex === 0) return; // 跳过表头
const cells = $(row).find("td, th");
if (!cells.length) return;
const productName = $(cells[0]).text().trim();
// 匹配黄金9999
if (
(productName === "黄金9999" || productName === "黄金 9999") &&
!result.goldPrice
) {
result.goldPrice = this.extractPrice(
$(cells[saleColumnIndex]).text().trim(),
this.validationRules.gold
);
}
// 匹配白银(排除含"黄金"的行)
if (
productName === "白银" &&
!productName.includes("黄金") &&
!result.silverPrice
) {
result.silverPrice = this.extractPrice(
$(cells[saleColumnIndex]).text().trim(),
this.validationRules.silver
);
}
});
});
return result;
}
extractPrice(text, { min, max }) {
const match = text.match(/(\d+\.?\d*)/);
if (!match) return null;
const price = parseFloat(match[1]);
if (price < min || price > max) return null; // 超出合理范围
return price.toFixed(2);
}
}
// 使用
const parser = new PriceParser();
const { goldPrice, silverPrice } = parser.parsePrices(html);
关键点解析
动态列定位:用 .includes("销售") 而不是固定索引,表格新增或删除列时代码无需修改。
数值范围验证:validationRules 定义合理价格区间,过滤掉格式异常或明显错误的数据(如抓到表格里的其他数字)。
产品名精确匹配:productName === "黄金9999" 而不是 .includes("黄金"),防止把”黄金饰品9999”也匹配进来。!productName.includes("黄金") 在白银行再加一层保护。
多语言/别名的处理
如果表格里的产品名不统一(黄金9999、黄金 9999、Au9999 混用):
const GOLD_NAMES = new Set(["黄金9999", "黄金 9999", "Au9999", "足金9999"]);
if (GOLD_NAMES.has(productName) && !result.goldPrice) { ... }
调试技巧
遇到解析不到数据时,先打印表格 HTML 确认结构:
$("table").each((i, table) => {
console.log(`Table ${i}:`, $.html(table).slice(0, 500));
});
再检查表头文本是否有空格或全角字符:
rows.first().find("th, td").each((i, cell) => {
console.log(`Header ${i}: "${$(cell).text().trim()}"`);
});
页面改版后表格结构变化,这两步能快速定位问题所在。
