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

Puppeteer技术方案:如何批量识别页面同类重复元素?

基于Puppeteer实现批量识别同类元素的最优方案

针对无代码爬虫工具的「获取全部」需求,核心是从用户选中的单个元素出发,自动生成能匹配所有同类元素的选择器,而非依赖单一的绝对路径。下面是适配各类网站结构的最优实现思路:

1. 优先提取元素稳定特征生成CSS选择器

这是效率最高、兼容性最好的方案,核心是过滤掉动态生成的属性,只保留语义化、稳定的特征:

  • 过滤动态Class:排除带随机字符(如哈希串)、长度过短的Class,只保留语义化Class(如result-item、country-name)。
  • 优先使用Data属性:抓取data-testid、data-item-id这类专为测试/爬虫设计的属性,这类属性几乎不会随页面更新变化。
  • 关联稳定父节点:如果元素自身特征不足,向上查找最近的带ID或稳定Class的父节点,组合成层级选择器。

Puppeteer代码实现示例

async function getUniversalSelector(elementHandle) {
  const elInfo = await elementHandle.evaluate(el => {
    // 过滤稳定Class
    const stableClasses = Array.from(el.classList).filter(cls => 
      cls.length >= 3 && !/[0-9a-f]{6,}/i.test(cls)
    );

    // 提取Data属性
    const dataAttrs = {};
    Array.from(el.attributes).forEach(attr => {
      if (attr.name.startsWith('data-')) {
        dataAttrs[attr.name] = attr.value;
      }
    });

    // 查找最近的稳定父节点选择器
    let parentSelector = '';
    let parent = el.parentElement;
    while (parent && parent !== document.body) {
      if (parent.id) {
        parentSelector = `#${parent.id}`;
        break;
      }
      const parentStableCls = Array.from(parent.classList).filter(cls => 
        cls.length >=3 && !/[0-9a-f]{6,}/i.test(cls)
      );
      if (parentStableCls.length > 0) {
        parentSelector = `.${parentStableCls.join('.')}`;
        break;
      }
      parent = parent.parentElement;
    }

    return {
      tag: el.tagName.toLowerCase(),
      stableClasses,
      dataAttrs,
      parentSelector
    };
  });

  // 构建基础选择器
  let selectorParts = [elInfo.tag];
  if (elInfo.stableClasses.length) {
    selectorParts.push(`.${elInfo.stableClasses.join('.')}`);
  }
  Object.entries(elInfo.dataAttrs).forEach(([key, val]) => {
    selectorParts.push(`[${key}="${val}"]`);
  });

  let fullSelector = selectorParts.join('');
  if (elInfo.parentSelector) {
    fullSelector = `${elInfo.parentSelector} ${fullSelector}`;
  }

  // 验证选择器匹配数量
  const matches = await elementHandle.page.$$(fullSelector);
  if (matches.length > 1) return fullSelector;

  // 降级:父节点下的同标签元素
  if (elInfo.parentSelector) {
    const fallbackSelector = `${elInfo.parentSelector} ${elInfo.tag}`;
    const fallbackMatches = await elementHandle.page.$$(fallbackSelector);
    if (fallbackMatches.length > 1) return fallbackSelector;
  }

  // 最终回退:生成无索引的XPath
  const rawXPath = await elementHandle.evaluate(el => {
    const path = [];
    let node = el;
    while (node && node !== document.body) {
      let index = 1;
      let sibling = node.previousElementSibling;
      while (sibling) {
        if (sibling.tagName === node.tagName) index++;
        sibling = sibling.previousElementSibling;
      }
      path.unshift(`${node.tagName.toLowerCase()}[${index}]`);
      node = node.parentElement;
    }
    path.unshift('body');
    return `//${path.join('/')}`;
  });
  return rawXPath.replace(/\[(\d+)\]/g, '');
}

2. 针对非结构化列表的相似性判断

对于谷歌搜索结果、Instagram用户列表这类无统一强特征的场景,可以结合元素的结构相似性筛选:

  • 获取选中元素的父容器,遍历容器内所有同层级元素;
  • 对比元素的子节点数量、标签结构、文本模式(如是否包含链接、图片),筛选出结构高度相似的元素;
  • 用Puppeteer的elementHandle.evaluate获取元素的HTML结构摘要,通过哈希值或相似度算法判断同类。

3. 动态内容的适配

对于动态加载的列表(如滚动加载),需要配合Puppeteer的页面滚动API,先触发内容加载,再执行选择器匹配:

// 滚动加载全部内容的辅助函数
async function scrollToEnd(page) {
  await page.evaluate(async () => {
    while (document.scrollingElement.scrollTop + window.innerHeight < document.scrollingElement.scrollHeight) {
      document.scrollingElement.scrollTop += window.innerHeight;
      await new Promise(resolve => setTimeout(resolve, 500));
    }
  });
}

内容的提问来源于stack exchange,提问作者Kyle Wilson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 15:00:46