You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

当元素无特定标识符时,如何用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 }));
        }
    }
});

关键改进点说明

  1. 初始化默认值:一开始给所有字段设置N/A,即使某个元素抓取失败,也不会出现undefined。
  2. 结构选择器替代动态类名:
    • 用[data-testid="advert-details-item"]定位所有详情块,这个属性是给测试用的,网站更新时通常不会轻易修改。
    • 用:first-child和:nth-child(2)定位每个块内的标签和值,完全不依赖易变的类名。
  3. 精确匹配标签:用switch做精确匹配,比includes更可靠,避免因标签文本有额外空格或其他内容导致误判。
  4. 可选链操作符(?.):在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 22:55:16