当元素无特定标识符时,如何用JavaScript抓取网站数据?
解决Otomoto车辆详情抓取的选择器不稳定问题
核心优化思路
- 抛弃动态生成类名,改用结构选择器+data-testid:页面中
ooa-开头的类名是动态生成的(大概率由CSS-in-JS框架生成),随时可能变更,完全不可靠。转而利用稳定的data-testid属性和固定的DOM结构定位元素。 - 增强字段匹配容错性:避免用
includes做模糊匹配,改用精确匹配或忽略大小写的匹配,防止文本细微差异导致抓取失败。 - 预设默认值:初始化数据时给所有字段设置默认值
N/A,避免出现undefined。
改进后的代码
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) { if (msg.text === 'report_back') { // 初始化所有字段为默认值,避免undefined let data = { cena: 'N/A', waluta: 'N/A', marka: 'N/A', model: 'N/A', url: window.location.toString() }; try { // 价格抓取:若类名相对稳定则保留,否则改用结构选择器 const priceElement = document.querySelector('.offer-price__number'); if (priceElement) { const priceText = priceElement.innerText; const numb = priceText.match(/\d+/g)?.join("") || 'N/A'; data.cena = numb; } const currencyElement = document.querySelector('.offer-price__currency'); if (currencyElement) { data.waluta = currencyElement.innerText.trim(); } // 核心改进:用data-testid定位详情项,再通过子元素顺序取值 const detailItems = document.querySelectorAll('[data-testid="advert-details-item"]'); detailItems.forEach(item => { // 第一个子元素是标签,第二个是值,无需依赖动态类名 const labelElement = item.querySelector(':first-child'); const valueElement = item.querySelector(':nth-child(2)'); if (labelElement && valueElement) { const label = labelElement.innerText.trim(); const value = valueElement.innerText.trim(); // 精确匹配标签文本,避免误匹配 switch(label) { case 'Marka pojazdu': data.marka = value; break; case 'Model pojazdu': data.model = value; break; // 在这里添加更多需要抓取的字段 } } }); // 保持字段顺序 const fieldsOrder = ['cena', 'waluta', 'marka', 'model', 'url']; const orderedRow = fieldsOrder.reduce((acc, field) => { acc[field] = data[field]; return acc; }, {}); sendResponse(JSON.stringify(orderedRow)); } catch (error) { sendResponse(JSON.stringify({ error: error.message })); } } });
关键改进点说明
- 初始化默认值:一开始给所有字段设置
N/A,即使某个元素抓取失败,也不会出现undefined。 - 结构选择器替代动态类名:
- 用
[data-testid="advert-details-item"]定位所有详情块,这个属性是给测试用的,网站更新时通常不会轻易修改。 - 用
:first-child和:nth-child(2)定位每个块内的标签和值,完全不依赖易变的类名。
- 用
- 精确匹配标签:用
switch做精确匹配,比includes更可靠,避免因标签文本有额外空格或其他内容导致误判。 - 可选链操作符(?.):在
priceText.match(/\d+/g)?.join("")中使用,防止match返回null时调用join报错。
额外建议
如果页面是动态加载的(比如滚动加载或异步渲染),当前代码可能会因元素未渲染完成而抓取失败。这种情况下可以加MutationObserver监听DOM变化,直到目标元素出现再执行抓取逻辑:
function waitForElement(selector) { return new Promise(resolve => { if (document.querySelector(selector)) { return resolve(document.querySelector(selector)); } const observer = new MutationObserver(mutations => { if (document.querySelector(selector)) { observer.disconnect(); resolve(document.querySelector(selector)); } }); observer.observe(document.body, { childList: true, subtree: true }); }); } // 使用示例:在抓取前等待价格元素出现 waitForElement('.offer-price__number').then(() => { // 执行抓取逻辑 });
内容的提问来源于stack exchange,提问作者Michał Godlewski
相关产品推荐
相关产品推荐

