如何使用硒在Linkedin搜索页面上加载懒内容

时间:2019-01-06 17:53:03

标签: java selenium selenium-webdriver web-scraping

摘要

我正尝试在LinkedIn搜索页面上抓取帐户的所有首次连接的个人资料链接。但是由于该页面会动态加载其余内容(向下滚动),所以我无法获得位于页面末尾的“下一页”按钮。


问题描述

https://linkedin.com/search/results/people/?facetGeoRegion=["tr%3A0"]&facetNetwork=["F"]&origin=FACETED_SEARCH&page=YOUR_PAGE_NUMBER

我可以使用硒和上面的链接导航到搜索页面。我想知道有多少页面可以浏览它们,只需更改上面链接的page=变量即可。

要实现我想检查Next按钮是否存在的功能。只要有下一个按钮,我就会请求下一页进行抓取。但是,如果您没有向下滚动到页面底部(即“下一步”按钮所在的位置),您将找不到Next按钮,也找不到其他配置文件的信息,因为它们尚未加载

这是当您不向下滚动并使用firefox截屏工具截取整个页面的截屏时的外观。

linkedIn


我如何实现

我可以通过将向下滚动动作硬编码到我的代码中并使驱动程序等待visibilityOfElementLocated来解决此问题。但是我想知道是否有比我的方法更好的方法。并且,如果通过这种方法,驱动程序无法找到Next按钮,则程序将以退出代码1退出。

当我向下滚动页面时检查请求时,这只是图像请求等,如下所示。当我向下滚动页面时,我不知道页面如何加载有关配置文件的更多信息。

networks inspection


源代码

这是我在代码中实现它的方式。该应用只是一个简单的实现,正在尝试在页面上找到Next按钮。

package com.andreyuhai;

import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

public class App 
{
    WebDriver driver;

    public static void main( String[] args )
    {
        Bot bot = new Bot("firefox", false, false, 0, 0, null, null, null);

        int pagination = 1;

        bot.get("https://linkedin.com");
        if(bot.attemptLogin("username", "pw")){
            bot.get("https://www.linkedin.com/" +
                    "search/results/people/?facetGeoRegion=" +
                    "[\"tr%3A0\"]&origin=FACETED_SEARCH&page=" + pagination);


            JavascriptExecutor js = (JavascriptExecutor) bot.driver;

            js.executeScript("scrollBy(0, 2500)");

            WebDriverWait wait = new WebDriverWait(bot.driver, 10);
            wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//button[@class='next']/div[@class='next-text']")));

            WebElement nextButton = bot.driver.findElement(By.xpath("//button[@class='next']/div[@class='next-text']"));


            if(nextButton != null ) {
                System.out.println("Next Button found");
                nextButton.click();
            }else {
                System.out.println("Next Button not found");
            }
        }
    }
}

另一个我不知道的工具:LinkedIn Spider

此扩展名为linkedIn Spider

这也确实可以达到我想要达到的目的,但是我不确定使用JavaScript。但是,当我在同一搜索页面上运行此扩展程序时。这样做不会向下滚动或加载其他页面,也不会一一提取数据。


所以我的问题是:

  1. 您能否解释一下LinkedIn是如何实现这一目标的?我的意思是,如果不发出任何请求等,当我向下滚动时它将如何加载配置文件信息。我真的不知道这一点。我将不胜感激任何源链接或解释。

  2. 您有什么更好的想法(我的意思是更快)来实现我要实现的目标?

  3. 能否请您解释一下LinkedIn Spider在不向下滚动等情况下如何工作。

2 个答案:

答案 0 :(得分:6)

我已经检查了div结构以及linkedin显示结果的方式。因此,如果直接点击URL并通过以下xpath进行检查://li[contains(@class,'search-result')],您会发现所有结果均已加载到页面上,但是linkedin一次只能显示5个结果,并在滚动时显示显示下5个结果,但是所有结果均已加载到页面上,并可以通过提及的xpath找到。

请参阅此图片,该图片突出显示了div结构和结果,当您找到在到达url时输入xpath的结果时:https://imgur.com/Owu4NPh
将页面滚动到底部然后使用相同的xpath查找结果后,请参考这张突出显示div结构和结果的图像:https://imgur.com/7WNR830

您可能会看到结果集相同,但是最后5个结果中

  • 标记中有一个额外的search-result__occlusion-hint部分,并且通过此linkedin隐藏了下5个结果并仅显示前5个结果首先。

    现在是实现部分,我仅在页面上滚动整个结果时才选中“下一步”按钮,因此无需滚动到确定的坐标,因为可以针对不同的屏幕尺寸和窗口进行更改,因此可以结果显示在一个webelement列表中,并获取其大小,然后滚动到该列表的最后一个元素。在这种情况下,如果总共有10个结果,则页面将滚动到第10个结果,如果只有4个结果,则页面将滚动到第4个结果,滚动后,您可以检查是否存在“下一步”按钮页面与否。为此,您可以检查“下一个”按钮Web元素列表的列表大小,如果列表大小大于0,则表示页面上存在下一个按钮,如果不大于0,则表示下一步。按钮不在列表中,您可以在此处停止执行。

    因此要实现它,我采用了一个布尔值,该布尔值的初始值为true,并且代码将循环运行,直到该布尔值变为false为止,并且当Next按钮列表的大小等于0时,它将变为false。

    请参考以下代码:

    public class App 
    {    
        WebDriver driver;
    
      // For initialising javascript executor
      public Object executeScript(String script, Object... args) {
        JavascriptExecutor exe = (JavascriptExecutor) driver;
        return exe.executeScript(script, args);
      }
    
      // Method for scrolling to the element
      public void scrollToElement(WebElement element) {
        executeScript("window.scrollTo(arguments[0],arguments[1])", element.getLocation().x, element.getLocation().y);
    
        }
    
      public static void main(String[] args) {
        // You can change the driver to bot according to your usecase
        driver = new FirefoxDriver();
        // Add your direct URL here and perform the login after that, if necessary
        driver.get(url);
        // Wait for the URL to load completely
        Thread.sleep(10000);
        // Initialising the boolean
        boolean nextButtonPresent = true;
        while (nextButtonPresent) {
            // Fetching the results on the page by the xpath
            List<WebElement> results = driver.findElements(By.xpath("//li[contains(@class,'search-result')]"));
            // Scrolling to the last element in the list
            scrollToElement(results.get(results.size() - 1));
            Thread.sleep(2000);
    
            // Checking if next button is present on the page
            List<WebElement> nextButton = driver.findElements(By.xpath("//button[@class='next']"));
            if (nextButton.size() > 0) {
                // If yes then clicking on it
                nextButton.get(0).click();
                Thread.sleep(10000);
            } else {
                // Else setting the boolean as false
                nextButtonPresent = false;
                System.out.println("Next button is not present, so ending the script");
            }
          }
       }
    }
    
  • 答案 1 :(得分:1)

    我观察到的是,内容已经加载到页面中,当我们向下滚动时,它将显示给我们。

    但是,如果我们通过使用类名“ next”手动加载页面来检查“ Next>”按钮,如下所示,

      

    // button [@ class ='next']

    在向下滚动之前我们无法找到它,因为它对我们不可见。但是,通过使用下面的XPath,我们可以确定所有配置文件链接的计数,无论它们是否显示?

      

    // h3 [包含(@class,'搜索结果__总数')] / parent :: div / ul / li

    当您想从页面中获取所有配置文件链接时,我们可以使用上面的XPath帮助来做到这一点。我们将使用上述XPath获取链接计数,然后一次滚动到每个元素视图,然后以如下方式获取配置文件链接:

    // Identifying the all the profile links
    List<WebElement> totalProfileLinks = driver.findElements(By.xpath("//h3[contains(@class, 'search-results__total')]/parent::div/ul/li"));
    // Looping for getting the profile link
    for(int i=1;i<totalProfileLinks.size();i++) {
        // Scrolling so that it will be visible
        ((JavascriptExecutor) driver).executeScript("arguments[0].scrollIntoView(true);", totalProfileLinks.get(i));
        // Fetching the anchor node
        final WebElement link = driver.findElement(By.xpath("(//h3[contains(@class, 'search-results__total')]/parent::div/ul/li//div[contains(@class, 'search-result__info')]//a)["+i+"]"));
        // Avoiding the StaleElementReferenceException
        new FluentWait<WebDriver>(driver).withTimeout(1, TimeUnit.MINUTES).pollingEvery(1, TimeUnit.SECONDS).ignoring(StaleElementReferenceException.class).until(new Function<WebDriver, WebElement>() {
            public WebElement apply(WebDriver arg0) {
                return link;
            }
        });
        // Fetching and printing the link from anchor node
        System.out.println(link.getAttribute("href").trim());
    }
    

    因此,如果我们要首先单击“下一步>”按钮,则需要检查其是否存在(因为在获取配置文件链接时已滚动,因此也会显示“下一步”按钮)。我们可以使用driver.findElements();方法的帮助来获取该元素计数的匹配项,并将其存储在某些List中(因为它返回WebElements列表),如下所示:

    List<WebElement> nextButton = driver.findElements(By.className("next"));
    

    使用上述技术的好处是,如果没有元素匹配,脚本也不会失败,如果没有匹配,我们将有一个空列表。

    然后,我们可以使用List接口的size()方法来获取匹配计数,如下所示:

    int size = nextButton.size();
    

    如果大小大于0,则表示存在该元素,否则不存在,我们可以检查以下情况:

    if(size > 0) {
        nextButton.get(0).click(); // Do some operation like clicking on it
        System.out.println("=> 'Next >' button is there and clicked on it...");
    } else {
        System.out.println("=> 'Next >' button is NOT there...");
    }
    

    在加载内容且元素可见后,我们将使用JavaScriptExecutor来定位并单击它。

    将上述代码包装在while循环中,并在每次单击上一个“ Next>”按钮后,每次都检查“ Next>”按钮的存在,如下所示:

    boolean next = true;
    while(next) {
        // Checking 'Next >' button is there or not in the page
        List<WebElement> nextButton = driver.findElements(By.className("next"));
        // If the 'Next >' button is there then clicking on it otherwise stopping the execution
        if(nextButton.size() > 0) {
            doClickUsingJSE(nextButton.get(0));
            System.out.println("=> 'Next >' button is there and clicked on it...");
        } else {
            next = false;
            System.out.println("=> 'Next >' button is NOT there so stopping the execution...");
        }
        Thread.sleep(1000);
    }
    
    如果上述代码中的'if'条件失败,则

    循环将中断,因为'next'将变为'false'。而且,如果我们使用Fluent Wait,那么它将有助于我们避免某些“异常”,例如“ WebDriverException”和“ StaleElementReferenceException”。因此,我编写了一个单独的方法,该方法将通过避免某些异常来等待元素,并在条件满足时单击该元素。

    检查以下代码:

    private static void doClickUsingJSE(final WebElement element) {
        // Using the Fluent Wait to avoid some exceptions like WebDriverException and StaleElementReferenceException
        Wait<WebDriver> wait = new FluentWait<WebDriver>(driver).withTimeout(1, TimeUnit.MINUTES).pollingEvery(1, TimeUnit.SECONDS).ignoring(WebDriverException.class, StaleElementReferenceException.class);
        WebElement waitedElement = wait.until(new Function<WebDriver, WebElement>() {
            public WebElement apply(WebDriver driver) {
                return element;
            }
        });
        wait.until(ExpectedConditions.visibilityOf(waitedElement));
        wait.until(ExpectedConditions.elementToBeClickable(waitedElement));
        // Clicking on the particular element using the JavaScriptExcecutor
        ((JavascriptExecutor) driver).executeScript("arguments[0].click();", waitedElement);
        }
    

    正如我之前提到的JavaScriptExecutor一样,我也仅在上述方法中使用了它。

    尝试下面的端到端工作代码:

    import java.util.List;
    import java.util.concurrent.TimeUnit;
    import org.openqa.selenium.By;
    import org.openqa.selenium.JavascriptExecutor;
    import org.openqa.selenium.Keys;
    import org.openqa.selenium.StaleElementReferenceException;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebDriverException;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.chrome.ChromeDriver;
    import org.openqa.selenium.support.ui.ExpectedConditions;
    import org.openqa.selenium.support.ui.FluentWait;
    import org.openqa.selenium.support.ui.Wait;
    
    import com.google.common.base.Function;
    
    public class BasePage 
    {
        // Declaring WebDriver
        private static WebDriver driver;
    
        private static void doClickUsingJSE(final WebElement element) {
            // Using the Fluent Wait to avoid some exceptions like WebDriverException and StaleElementReferenceException
            Wait<WebDriver> wait = new FluentWait<WebDriver>(driver).withTimeout(1, TimeUnit.MINUTES).pollingEvery(1, TimeUnit.SECONDS).ignoring(WebDriverException.class, StaleElementReferenceException.class);
            WebElement waitedElement = wait.until(new Function<WebDriver, WebElement>() {
                public WebElement apply(WebDriver driver) {
                    return element;
                }
            });
            wait.until(ExpectedConditions.visibilityOf(waitedElement));
            wait.until(ExpectedConditions.elementToBeClickable(waitedElement));
            // Clicking on the particular element using the JavaScriptExcecutor
            ((JavascriptExecutor) driver).executeScript("arguments[0].click();", waitedElement);
        }
    
        public static void main( String[] args ) throws Exception
        {
            System.setProperty("webdriver.chrome.driver", "C:\\NotBackedUp\\chromedriver.exe");
    
            // Initializing the Chrome Driver
            driver = new ChromeDriver();
    
            // Launching the LinkedIn site
            driver.get("https://linkedin.com/search/results/people/?facetGeoRegion=[\"tr%3A0\"]&facetNetwork=[\"F\"]&origin=FACETED_SEARCH&page=YOUR_PAGE_NUMBER");
    
            // You can avoid this and it to your convience way
            // As there are no connections in my page, I have used like this
            //------------------------------------------------------------------------------------
            // Switching to the login from - iframe involved
            driver.switchTo().frame(driver.findElement(By.className("authentication-iframe")));
    
            // Clicking on the Sign In button
            doClickUsingJSE(driver.findElement(By.xpath("//a[text()='Sign in']")));
    
            // Entering the User Name
            WebElement element = driver.findElement(By.id("username"));
            doClickUsingJSE(element);
            element.sendKeys("something@gmail.com");
    
            // Entering the Password
            element = driver.findElement(By.id("password"));
            doClickUsingJSE(element);
            element.sendKeys("anything"+Keys.ENTER);
    
            // Clicking on the People drop down
            Thread.sleep(8000);
            element = driver.findElement(By.xpath("//span[text()='People']"));
            doClickUsingJSE(element);
    
            // Selecting the All option
            Thread.sleep(2000);
            element = driver.findElement(By.xpath("//ul[@class='list-style-none']/li[1]"));
            element.click();
    
            // Searching something in the LinkedIn search box
            Thread.sleep(3000);
            element = driver.findElement(By.xpath("//input[@role='combobox']"));
            doClickUsingJSE(element);
            element.sendKeys("a"+Keys.ENTER);
            Thread.sleep(8000);
            //------------------------------------------------------------------------------------
    
            boolean next = true;
            while(next) {
                // Identifying the all the profile links
                List<WebElement> totalProfileLinks = driver.findElements(By.xpath("//h3[contains(@class, 'search-results__total')]/parent::div/ul/li"));
                // Looping for getting the profile link
                for(int i=1;i<totalProfileLinks.size();i++) {
                    // Scrolling so that it will be visible
                    ((JavascriptExecutor) driver).executeScript("arguments[0].scrollIntoView(true);", totalProfileLinks.get(i));
                    // Fetching the anchor node
                    final WebElement link = driver.findElement(By.xpath("(//h3[contains(@class, 'search-results__total')]/parent::div/ul/li//div[contains(@class, 'search-result__info')]//a)["+i+"]"));
                    // Avoiding the StaleElementReferenceException
                    new FluentWait<WebDriver>(driver).withTimeout(1, TimeUnit.MINUTES).pollingEvery(1, TimeUnit.SECONDS).ignoring(StaleElementReferenceException.class).until(new Function<WebDriver, WebElement>() {
                        public WebElement apply(WebDriver arg0) {
                            return link;
                        }
                    });
                    // Fetching and printing the link from anchor node
                    System.out.println(link.getAttribute("href").trim());
                }
    
                // Checking 'Next >' button is there or not in the page
                List<WebElement> nextButton = driver.findElements(By.className("next"));
                // If the 'Next >' button is there then clicking on it otherwise stopping the execution
                if(nextButton.size() > 0) {
                    doClickUsingJSE(nextButton.get(0));
                    System.out.println("=> 'Next >' button is there and clicked on it...");
                } else {
                    next = false;
                    System.out.println("=> 'Next >' button is NOT there so stopping the execution...");
                }
                Thread.sleep(1000);
            }
    
        }
    }
    

    我希望它对您有帮助...编码愉快...