为何Selenium谷歌图片爬虫多查询返回相同图片src?
问题描述
我在Express服务器上搭建了谷歌图片爬虫,接收查询数组后本应返回每个查询对应首条图片的src值,但传入数组时会多次返回第一个查询的src结果。单查询场景运行正常,试过sleep、until等方法仍无效。另外想知道能否在后端通过并发方式提速,之前在客户端用Promise.all()实现时返回数组异常。
附现有代码:
let queryArr = ['cat', 'dog', 'cheeta', 'boy', 'girl', 'lion'] let driver = undefined; const startSelenium = async () => { const options = new Options(); options.addArguments('--headless') options.addArguments('--no-sandbox') options.addArguments('--incognito') driver = await new Builder().forBrowser('chrome').setChromeOptions(options).build(); } const Scrape = async (queryArr) => { if (!driver) { await startSelenium(); } try { let imgSources = []; for (const query of queryArr) { const url = `https://www.google.com/search?site=&tbm=isch&source=hp&biw=1873&bih=990&q=${query}`; await driver.get(url); const img = await driver.findElement(By.className('Q4LuWd')); const imgSrc = await img.getAttribute('src'); imgSources.push(imgSrc); } return imgSources; } catch (e) { console.log(`Scrape() error: \n${e}`); } };
解决方案
一、修复重复返回src的问题
问题核心是谷歌图片搜索的DOM加载机制:页面跳转后,旧页面的DOM元素可能仍留在内存中,findElement会直接拿到缓存的旧元素,而非当前页面的新元素。需要添加显式等待确保获取当前页面的有效元素,同时对查询参数做URL编码避免异常。
修改后代码:
// 确保导入until模块 const { Builder, Options, By, until } = require('selenium-webdriver'); let driver = undefined; const startSelenium = async () => { const options = new Options(); options.addArguments('--headless') options.addArguments('--no-sandbox') options.addArguments('--incognito') driver = await new Builder().forBrowser('chrome').setChromeOptions(options).build(); } const Scrape = async (queryArr) => { if (!driver) { await startSelenium(); } try { let imgSources = []; for (const query of queryArr) { // 对查询参数做URL编码,避免特殊字符破坏URL结构 const encodedQuery = encodeURIComponent(query); const url = `https://www.google.com/search?site=&tbm=isch&source=hp&biw=1873&bih=990&q=${encodedQuery}`; await driver.get(url); // 显式等待元素被定位到且可见,超时时间10秒 const img = await driver.wait(until.elementLocated(By.className('Q4LuWd')), 10000); await driver.wait(until.elementIsVisible(img), 10000); const imgSrc = await img.getAttribute('src'); imgSources.push(imgSrc); } return imgSources; } catch (e) { console.log(`Scrape() error: \n${e}`); } };
二、后端实现并发提速
单个Selenium Driver实例无法真正并发(同一时间只能处理一个页面),可以创建多个Driver实例,通过Promise.all并行处理多个爬取任务:
const { Builder, Options, By, until } = require('selenium-webdriver'); // 单个查询的独立爬取函数 const scrapeSingleQuery = async (query) => { const options = new Options(); options.addArguments('--headless') options.addArguments('--no-sandbox') options.addArguments('--incognito'); const driver = await new Builder().forBrowser('chrome').setChromeOptions(options).build(); try { const encodedQuery = encodeURIComponent(query); const url = `https://www.google.com/search?site=&tbm=isch&source=hp&biw=1873&bih=990&q=${encodedQuery}`; await driver.get(url); const img = await driver.wait(until.elementLocated(By.className('Q4LuWd')), 10000); await driver.wait(until.elementIsVisible(img), 10000); return await img.getAttribute('src'); } catch (e) { console.log(`Scrape ${query} error: \n${e}`); return null; } finally { // 无论成功失败,确保关闭Driver释放资源 await driver.quit(); } }; // 批量并发爬取函数 const Scrape = async (queryArr) => { // 限制并发数,避免触发谷歌反爬(建议3-5个以内) const batchSize = 3; let results = []; // 分批处理查询数组 for (let i = 0; i < queryArr.length; i += batchSize) { const batch = queryArr.slice(i, i + batchSize); const batchResults = await Promise.all(batch.map(query => scrapeSingleQuery(query))); results = [...results, ...batchResults]; } // 过滤爬取失败的null值 return results.filter(src => src !== null); };
内容的提问来源于stack exchange,提问作者Jeff_V
相关产品推荐
相关产品推荐

