Skill: freemanwangfuhan-coder/noon-product-search (version 1.0.0)

--- name: noon-product-search description: Noon 商品搜索工具。输入阿拉伯语关键词，使用 Chrome 浏览器在 noon.com/saudi-ar 搜索，返回第一页所有商品的标题、评分、评价数和价格。 --- # Noon 商品搜索工具 ## 功能 使用 Chrome 浏览器自动化，在 noon.com/saudi-ar 搜索阿拉伯语关键词，返回第一页所有商品的： - 商品标题 - 评分 - 评价数 - 价格 ## 使用方法 ### 命令行 ```bash node ~/.openclaw/workspace/skills/noon-product-search/index.js "阿语关键词" ``` ### 示例 ```bash node ~/.openclaw/workspace/skills/noon-product-search/index.js "عربة تسوق" ``` ## 输出示例 ``` ============================================================ 🔍 搜索: "عربة تسوق" ============================================================ 📦 商品 1 标题: عربة تسوق صغيرة محمولة... 评分: 4.5 评价数: (2,340) 价格: 159 ر.س 📦 商品 2 标题: ... ``` ## 依赖 - Chrome 浏览器（带调试端口 9222） - chrome-remote-interface ## 启动 Chrome 调试模式 ```bash open -a "Google Chrome" --args --remote-debugging-port=9222 --new-window "about:blank" ``` ## 注意事项 - 确保 Chrome 调试端口 9222 已开启 - 首次运行会自动安装依赖 - 如果搜索结果为空，尝试增加等待时间

path: cdp-search.js
content: const CDP = require('chrome-remote-interface');

(async () => {
  const client = await CDP({ port: 9222 });
  const { Page, Runtime } = client;

  try {
    await Page.enable();
    
    const keyword = 'لغز ممغنط';
    const url = 'https://www.noon.com/saudi-ar/search/?q=' + encodeURIComponent(keyword);
    
    console.log('Navigating to:', url);
    await Page.navigate({ url });
    
    console.log('Waiting for load...');
    await Page.loadEventFired();
    
    // Wait for content
    await new Promise(r => setTimeout(r, 15000));
    
    // Scroll to load more
    console.log('Scrolling...');
    for (let i = 0; i < 10; i++) {
      await Runtime.evaluate({ expression: 'window.scrollBy(0, 800)' });
      await new Promise(r => setTimeout(r, 1000));
    }
    
    // Get product info
    const result = await Runtime.evaluate({
      expression: `
        (() => {
          const products = [];
          const containers = document.querySelectorAll('[data-qa*="plp-product"], .ProductCard, [class*="productCard"]');
          console.log('Found containers:', containers.length);
          
          containers.forEach((el, i) => {
            try {
              const titleEl = el.querySelector('[data-qa*="productImagePLP"]');
              let title = titleEl ? titleEl.getAttribute('data-qa') || '' : '';
              title = title.replace('productImagePLP_', '').trim();
              
              if (!title || title.length < 3) return;
              
              const txt = el.innerText || '';
              let rating = txt.match(/(\d\.\d)/)?.[1] || 'N/A';
              let reviews = txt.match(/(\d+[\d,]*K?)/)?.[1] || 'N/A';
              let price = txt.match(/(\d+)\s*ر\.س/i)?.[1] || 'N/A';
              if (price !== 'N/A') price += ' ر.س';
              
              products.push({ title, rating, reviews, price });
            } catch(e) {}
          });
          
          return products;
        })()
      `
    });
    
    console.log('\n=== PRODUCTS ===');
    console.log(JSON.stringify(result.result.value, null, 2));
    
  } catch (e) {
    console.error('Error:', e.message);
  } finally {
    await client.close();
  }
})();


path: index.js
content: const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());

async function scrapeNoon(keyword) {
  let browser;
  
  try {
    console.log('🔌 启动浏览器...');
    browser = await puppeteer.launch({
      headless: false,
      args: ['--disable-blink-features=AutomationControlled']
    });
    
    const page = await browser.newPage();
    
    // 先访问主页初始化 session
    console.log('   初始化 session...');
    await page.goto('https://www.noon.com/saudi-ar/', {
      waitUntil: 'domcontentloaded',
      timeout: 90000
    });
    await new Promise(r => setTimeout(r, 8000));
    
    // 搜索
    const url = 'https://www.noon.com/saudi-ar/search/?q=' + encodeURIComponent(keyword.trim());
    console.log('🌐 访问: ' + url);
    
    await page.goto(url, {
      waitUntil: 'domcontentloaded',
      timeout: 90000
    });
    
    console.log('⏳ 等待加载...');
    await new Promise(r => setTimeout(r, 25000));
    
    // 点击加载更多按钮
    console.log('   点击加载更多...');
    for (let c = 0; c < 10; c++) {
      try {
        await page.evaluate(() => {
          document.querySelectorAll('button').forEach(b => {
            const text = b.innerText || '';
            if (text.includes('المزيد') || text.includes('more')) {
              b.click();
            }
          });
        });
        await new Promise(r => setTimeout(r, 2000));
      } catch (e) {}
    }
    
    // 滚动加载更多
    console.log('   滚动加载...');
    for (let i = 0; i < 60; i++) {
      await page.evaluate(() => window.scrollBy(0, 600));
      await new Promise(r => setTimeout(r, 600));
    }
    
    await new Promise(r => setTimeout(r, 3000));
    
    // 获取商品数据
    const products = await page.evaluate(() => {
      let prods = [];
      
      // noon 商品容器选择器
      const selectors = [
        '[data-qa*="plp-product"]',
        '[class*="ProductCard"]',
        '[class*="productCard"]'
      ];
      
      let containers = [];
      for (let sel of selectors) {
        containers = document.querySelectorAll(sel);
        if (containers.length > 0) break;
      }
      
      console.log('   找到容器:', containers.length);
      
      containers.forEach(el => {
        try {
          const txt = el.innerText || '';
          
          // 标题 - 从 data-qa 属性获取
          let title = '';
          const titleEl = el.querySelector('[data-qa*="productImagePLP"]');
          if (titleEl) {
            title = titleEl.getAttribute('data-qa') || '';
            title = title.replace('productImagePLP_', '').trim();
          }
          
          if (!title || title.length < 5) return;
          
          // 评分
          let rating = 'N/A';
          const ratingMatch = txt.match(/(\d\.\d)/);
          if (ratingMatch) rating = ratingMatch[1];
          
          // 评价数
          let reviews = 'N/A';
          const reviewsMatch = txt.match(/(\d+[\d,]*[Kk]?)\s*\)?/);
          if (reviewsMatch) reviews = reviewsMatch[1];
          
          // 价格
          let price = 'N/A';
          const priceEl = el.querySelector('[class*="price"], [class*="Price"], [class*="PriceValue"]');
          if (priceEl) {
            const pt = priceEl.innerText || '';
            const pm = pt.match(/(\d+)/);
            if (pm) price = pm[1] + ' ر.س';
          }
          
          // 备选：从文本找价格
          if (price === 'N/A') {
            const priceText = txt.replace(/\n/g, ' ');
            const pms = priceText.match(/(\d+)\s*ر\.س/i);
            if (pms) price = pms[1] + ' ر.س';
          }
          
          prods.push({ title, rating, reviews, price });
        } catch (e) {}
      });
      
      return prods;
    });
    
    await browser.close();
    
    // 去重
    const seen = new Set();
    const unique = products.filter(p => {
      const key = p.title.substring(0, 30);
      if (seen.has(key)) return false;
      seen.add(key);
      return true;
    });
    
    return unique.slice(0, 60);
    
  } catch (error) {
    if (browser) await browser.close();
    throw error;
  }
}

// 主程序
const args = process.argv.slice(2);

if (args.length === 0) {
  console.error('用法: node index.js "阿语关键词"');
  console.error('示例: node index.js "عربة تسوق"');
  process.exit(1);
}

async function main() {
  const keyword = args[0];
  
  console.log('============================================================');
  console.log('🔍 搜索: "' + keyword + '"');
  console.log('============================================================\n');
  
  try {
    const products = await scrapeNoon(keyword);
    
    if (!products || products.length === 0) {
      console.log('⚠️ 未找到商品');
      process.exit(1);
    }
    
    console.log('\n📦 共 ' + products.length + ' 个商品\n');
    
    products.forEach(function(p, i) {
      console.log('📦 商品 ' + (i + 1));
      console.log('   标题: ' + p.title.substring(0, 70) + (p.title.length > 70 ? '...' : ''));
      console.log('   评分: ' + p.rating);
      console.log('   评价数: ' + (p.reviews === 'N/A' ? 'N/A' : '(' + p.reviews + ')'));
      console.log('   价格: ' + p.price);
      console.log('');
    });
    
  } catch (error) {
    console.error('❌ 错误: ' + error.message);
    process.exit(1);
  }
}

main();


path: simple-search.js
content: const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());

async function scrape() {
  const browser = await puppeteer.launch({
    headless: true,
    args: ['--no-sandbox', '--disable-setuid-sandbox']
  });
  
  const page = await browser.newPage();
  await page.setViewport({ width: 1200, height: 800 });
  
  const keyword = 'لغز ممغنط';
  const url = 'https://www.noon.com/saudi-ar/search/?q=' + encodeURIComponent(keyword);
  
  console.log('Loading:', url);
  await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
  
  await new Promise(r => setTimeout(r, 8000));
  
  // Scroll
  for (let i = 0; i < 5; i++) {
    await page.evaluate(() => window.scrollBy(0, 500));
    await new Promise(r => setTimeout(r, 1000));
  }
  
  const products = await page.evaluate(() => {
    const results = [];
    // Try multiple selectors
    const els = document.querySelectorAll('[data-qa*="plp-product"], .ProductCard, .productCard, .product-card');
    
    els.forEach(el => {
      try {
        // Try to get title from various sources
        let title = '';
        const img = el.querySelector('img');
        if (img) {
          title = img.getAttribute('alt') || img.getAttribute('title') || '';
        }
        if (!title) {
          const titleEl = el.querySelector('[data-qa*="productTitle"]');
          if (titleEl) title = titleEl.innerText || '';
        }
        
        if (!title || title.length < 3) return;
        
        const txt = el.innerText || '';
        const price = txt.match(/(\d+[\d,]*)\s*ر\.س/i)?.[1] || 'N/A';
        const rating = txt.match(/(\d\.\d)/)?.[1] || 'N/A';
        const reviews = txt.match(/\((\d+[\d,]*K?)\)/)?.[1] || '0';
        
        results.push({ title: title.trim(), price, rating, reviews });
      } catch(e) {}
    });
    
    return results;
  });
  
  console.log('Found:', products.length);
  console.log(JSON.stringify(products, null, 2));
  
  await browser.close();
}

scrape().catch(console.error);


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)