Selenium:陈旧元素参考(调试时工作正常)

时间:2018-04-22 22:26:26

标签: c# selenium selenium-webdriver web-scraping selenium-chromedriver

我正在尝试使用C#中的Selenium抓取一个页面,其中有几页我可以通过单击" Next"页面上的按钮。我通常会收到错误,即存在陈旧元素引用,只有在没有断点的情况下运行它才会发生。如果我一步一步完成该程序,它的工作完全正常。我假设Selenium在没有等待的情况下跳过重要的东西(即使我已经实施了等待方法)。

对于代码,这是问题的主要逻辑:

foundVacancies.AddRange(FindVacanciesOnPage());
const string nextBtnXPath = "//*[@id=\"ContainerResultList\"]/div/div[3]/nav/ul/li[8]/a";
if (Driver.FindElements(By.XPath(nextBtnXPath)).Count != 0)
{
    while (TryClickingNextButton(nextBtnXPath))
    {
        foundVacancies.AddRange(FindVacanciesOnPage());
    }
}

此方法首先获取第一页上的所有项目,然后将它们添加到foundVacancies列表中。之后,它将尝试寻找" Next"按钮,如果没有足够的项目,它并不总是在那里。如果是,它将尝试单击它,刮擦页面,然后再次单击它,直到没有页面为止。调试时效果很好,但正常运行时出现了一些问题。

获取页面上所有项目的方法以及发生错误的位置:

private IEnumerable<string> FindVacanciesOnPage()
{
    var vacancies = new List<string>();

    var tableContainingAllVacancies = Driver.FindElement(By.XPath("//*[@id=\"ContainerResultList\"]/div/div[2]/div/ul"));
    var listOfVacancies = tableContainingAllVacancies.FindElements(By.XPath(".//li/article/div[1]/a"));

    foreach (var vacancy in listOfVacancies)
    {
        vacancies.Add(vacancy.FindElement(By.XPath(".//h2")).Text);
    }

    return vacancies;
}

这些项目位于<ul> HTML标记中并且有<li>个孩子,我将逐个查看,并获取其内部文本。 foreach循环中出现陈旧元素错误。我假设网络驱动程序没有时间重新加载DOM,因为它在断点时工作。但是,我确实有一种方法可以等到页面完全加载,这是我在下一页时使用的。

private bool TryClickingNextButton(string nextButtonXPath)
{
    var nextButton = Driver.FindElement(By.XPath(nextButtonXPath));

    var currentUrl = Driver.Url;
    ScrollElementIntoView(nextButton);
    nextButton.Click();
    WaitUntilLoaded();
    var newUrl = Driver.Url;

    return !currentUrl.Equals(newUrl);
}

我正在比较新旧网址,以确定这是否是最后一页。 WaitUntilLoaded方法如下所示:

var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(30));
wait.Until(x => ((IJavaScriptExecutor) Driver).ExecuteScript("return document.readyState").Equals("complete"));

奇怪的是,有时网页驱动程序在加载第一页后立即关闭,没有任何错误或任何结果。我花了很多时间在SO上进行调试和搜索,但似乎无法找到任何信息,因为代码在通过它进行破坏时工作得非常好。

我只尝试使用Chrome,有无头模式,但我不知道这可能是Chrome问题。

&#34;下一步&#34;按钮具有以下HTML:

<a href="" data-jn-click="nextPage()" data-ng-class="{'disabled-element':currentPage === totalPages}" tabindex="0">
    <span class="hidden-md hidden-sm hidden-xs">Next <span class="icon icon-pagination-single-forward"></span></span>
    <span class="hidden-lg icon icon-pagination-forward-enable"></span>
</a>

我无法找出data-jn-click是什么。我试图执行JavaScript nextPage();,但是没有做任何事情。

3 个答案:

答案 0 :(得分:0)

我对c#没有任何经验,所以如果错了请不要介意。 您正在使用findElements并将其存储到var listOfVacancies。我推荐了一些网站。你为什么不用ReadOnlyCollection<IWebElement>。最好将所有元素存储为List并迭代它。 所以代码就变成了,

ReadOnlyCollection<IWebElement> listOfVacancies = tableContainingAllVacancies.FindElements(By.XPath(".//li/article/div[1]/a"));

答案 1 :(得分:0)

If the elements that are going into listOfVacancies are being populated via an ajax call, then document.readystate won't catch that. Try using:

wait.Until(x => ((IJavaScriptExecutor) Driver).ExecuteScript("return jQuery.active").Equals("0"));

答案 2 :(得分:0)

我终于找到了解决这个问题的方法。它很脏,但它有效。我尝试了许多不同的方法来等待页面完全加载,但没有一个工作。所以我走了Thread.Sleep的黑暗道路,但它没有听起来那么糟糕:

private IEnumerable<string> FindVacanciesOnPage()
{
    return FindVacanciesOnPage(new List<string>(), 0, 50, 15000);
}

private IEnumerable<string> FindVacanciesOnPage(ICollection<string> foundVacancies, long waitedTime, int interval, long maxWaitTime)
{
    try
    {
        var list = Driver.FindElements(By.XPath("//*[@data-ng-bind=\"item.JobHeadline\"]"));
        foreach (var vacancy in list)
        {
            foundVacancies.Add(vacancy.Text);
        }
    }
    catch (Exception)
    {
        if (waitedTime >= maxWaitTime) throw;

        Thread.Sleep(interval);
        waitedTime += interval;

        return FindVacanciesOnPage(foundVacancies, waitedTime, interval, maxWaitTime);

    }

    return foundVacancies;
}

这将尝试获取项目,如果抛出异常,则等待一段时间直到再次尝试。当等待指定的最大时间时,最终抛出异常。