Javascript 迭代中的StaleElementReferenceError
我的应用程序从数据库中获取ID列表。我用光标迭代这些内容&对于每个ID,我使用Selenium将其插入URL,以获取页面上的特定项目。这是对关键字进行搜索&获取与该搜索最相关的项目。数据库中大约有1000个结果。在随机迭代中,1个驱动程序操作将抛出一个Javascript 迭代中的StaleElementReferenceError,javascript,node.js,selenium,selenium-webdriver,Javascript,Node.js,Selenium,Selenium Webdriver,我的应用程序从数据库中获取ID列表。我用光标迭代这些内容&对于每个ID,我使用Selenium将其插入URL,以获取页面上的特定项目。这是对关键字进行搜索&获取与该搜索最相关的项目。数据库中大约有1000个结果。在随机迭代中,1个驱动程序操作将抛出一个StaleElementReferenceError,其完整消息为: 陈旧元素引用:元素未附加到页面文档\n(会话信息:chrome=77.0.3865.75) 查看这些数据,我可以发现造成这种情况的两个常见原因是: 该元素已被完全删除 元素不再
StaleElementReferenceError
,其完整消息为:
陈旧元素引用:元素未附加到页面文档\n(会话信息:chrome=77.0.3865.75)
查看这些数据,我可以发现造成这种情况的两个常见原因是:
- 该元素已被完全删除
- 元素不再附加到DOM
index.js
const { MongoClient, ObjectID } = require('mongodb')
const fs = require('fs')
const path = require('path')
const { Builder, Capabilities, until, By } = require('selenium-webdriver')
const chrome = require('selenium-webdriver/chrome')
require('dotenv').config()
async function init() {
try {
const chromeOpts = new chrome.Options()
const ids = fs.readFileSync(path.resolve(__dirname, '..', 'data', 'primary_ids.json'), 'utf8')
const client = await MongoClient.connect(process.env.DB_URL || 'mongodb://localhost:27017/test', {
useNewUrlParser: true
})
const db = client.db(process.env.DB_NAME || 'test')
const productCursor = db.collection('product').find(
{
accountId: ObjectID(process.env.ACCOUNT_ID),
primaryId: {
$in: JSON.parse(ids)
}
},
{
_id: 1,
primaryId: 1
}
)
const resultsSelector = 'body #wrapper div.src-routes-search-style__container--2g429 div.src-routes-search-style__products--3rsz9'
const mostRelevantSelector = `${resultsSelector}
> div:nth-child(2)
> div.src-routes-search-product-item-raw-style__product--3vH_O:nth-child(1)`
const titleContainerSelector = `${mostRelevantSelector}
> div.src-routes-search-product-item-raw-style__mainPart--1HEWx
> div.src-routes-search-product-item-raw-style__containerText--3NefD
> div.src-routes-search-product-item-raw-style__description--3swql
> div.src-routes-search-product-item-raw-style__titleContainer--tazkH`
const productImageSelector = `${mostRelevantSelector}
> div.src-routes-search-product-item-raw-style__mainPart--1HEWx
> div.src-routes-search-product-item-raw-style__containerImages--1PfdF
> a.src-routes-search-product-item-raw-style__productImage--1Y42Y
> img`
const linkSelector = `${titleContainerSelector} > a`
const primaryIdSelector = `${titleContainerSelector} > p`
chromeOpts.setChromeBinaryPath('/usr/local/bin')
const driver = await new Builder()
.withCapabilities(Capabilities.chrome())
.forBrowser('chrome')
.build()
let newProds = {}
let product
let i = 0
while (await productCursor.hasNext()) {
i += 1
product = await productCursor.next()
let searchablePrimaryId = product.primaryId
let link
let primaryId
let pId
let href
let img
let imgSrc
if (product.primaryId.includes('#')) {
searchablePrimaryId = product.primaryId.substr(0, product.primaryId.indexOf('#'))
}
if (searchablePrimaryId.includes('-')) {
searchablePrimaryId = searchablePrimaryId.substr(0, searchablePrimaryId.indexOf('-'))
}
await driver.get(`https://icecat.biz/en/search?keyword=${encodeURIComponent(searchablePrimaryId.toLowerCase())}`)
link = await driver.wait(until.elementLocated(By.css(linkSelector)), 10000) // wait 10 seconds
img = await driver.wait(until.elementLocated(By.css(productImageSelector)), 10000)
imgSrc = await img.getAttribute('src')
primaryId = await driver.wait(until.elementLocated(By.css(primaryIdSelector)), 10000)
pId = await primaryId.getText()
href = await link.getAttribute('href')
const iceCatId = href.substr(href.lastIndexOf('-') + 1, href.length)
const _iceCatId = iceCatId.substr(0, iceCatId.indexOf('.html'))
const idFound = (searchablePrimaryId.toUpperCase() === pId.toUpperCase()) && !imgSrc.includes('logo-fullicecat')
newProds[product._id.toString()] = {
primaryId: product.primaryId,
iceCatId: idFound ? _iceCatId : 'N/A'
}
}
const foundProducts = Object.values(newProds).filter(prod => prod.iceCatId !== 'N/A')
console.log(`\nFound ${foundProducts.length}/${JSON.parse(ids).length}`)
fs.writeFileSync(path.resolve(__dirname, '..', 'data', 'new_products.json'), JSON.stringify(newProds, null, 4), 'utf8')
driver.quit()
} catch(err) {
throw err
}
}
init()
.then(res => {
console.log(res)
})
.catch(err => {
console.error(err)
})
为了进行调试,我在每个驱动程序操作周围放置了一个try…catch
,以查看哪个特定操作失败了,但它不起作用,因为它从来都不是一个失败的一致操作。例如,有时如果是elementLocated
行中的一行或其他行,那么它就是getAttribute
操作
如果在该场景中是后者,这就是为什么我对抛出此错误感到困惑的原因,因为selenium肯定在页面上找到了元素(即
链接
),但无法在元素上执行getAttribute('src')
?这就是为什么我对我所犯的错误感到困惑。我想我一定是在设置selenium来处理迭代时出错了。迭代次数从不高于110在您的情况下,第二个原因是元素不再连接到DOM。如果找到了WebElement
,并且随后刷新了DOM,则即使DOM没有更改,该元素也会变得过时,同一定位器将返回新的WebElement
通常,driver.get()
会一直阻止,直到页面完全加载,但是此站点正在运行JavaScript来加载搜索结果。您可以通过在开发人员工具控制台中运行document.readyState
来测试它,您将在搜索结果仍在加载时看到“complete”
结果
在找到结果之前,页面有一个微调器,希望它足以等待它出现并在抓取页面之前变得陈旧
await driver.get(`https://icecat.biz/en/search?keyword=${encodeURIComponent(searchablePrimaryId.toLowerCase())}`)
let spinner = driver.wait(until.elementIsVisible(By.className('src-routes-search-style__loader---acti')))
driver.wait(until.stalenessOf(spinner))
link = await driver.wait(until.elementLocated(By.css(linkSelector)), 10000)
您不必等待Ajax请求完成。该网站在您结束时检索并刷新dom,并且每隔几秒钟不断调用索引,因此dom可能会不断更新。您可能可以保存AJAX请求、获取结果、处理并再次启用AJAX。您是否可以尝试从img Src=wait img.getAttribute('Src')中删除“wait”。因为wait-for-img已经在它的前一行中处理过了。在您找到元素和对其执行操作之间,dom仍然通过客户端脚本进行更新时,可能会发生这种情况。捕获陈旧元素异常可能会有所帮助。如果捕获到该特定异常,请重新运行find元素/do something函数。(WebDriverWait只会抛出timeout或stale元素。)您可以在这里找到一个例子(java):它看起来更像是一个注释,而不是答案。这是一个很好的解决方案,让我解决了我的问题!我唯一更改的是
elementIsVisible
需要一个WebElement,而不是一个选择器,因此更改为elementLocated
。我还给了驱动程序.wait()
一分钟的超时时间。这在第一次时似乎没有必要,但在我取下它时坏了。我现在有:let spinner=wait driver.wait(直到.elementLocated(By.css(spinnerSelector))
wait driver.wait(直到.stalenessOf(spinner),60000)//1分钟