【问题标题】:Get element text with a partial string match using selenium (python)使用 selenium (python) 获取具有部分字符串匹配的元素文本
【发布时间】:2021-06-23 20:49:00
【问题描述】:

我正在尝试从深深嵌套在此网页的 html 中的 <strong> 标记中提取文本:https://www.marinetraffic.com/en/ais/details/ships/imo:9854612

例如:

强标签是网页上唯一包含字符串“立方米”的标签 我的目标是提取整个文本,即“138124 立方米液化气体”。当我尝试以下操作时,出现错误:

url =  "https://www.marinetraffic.com/en/ais/details/ships/imo:9854612"
driver.get(url)
time.sleep(3)
element = driver.find_element_by_link_text("//strong[contains(text(),'cubic meters')]").text
print(element)


NoSuchElementException: Message: no such element: Unable to locate element: {"method":"link text","selector":"//strong[contains(text(),'cubic meters')]"}

我在这里做错了什么?任何建议,将不胜感激! 编辑:以下也抛出错误:

element = driver.find_element_by_xpath("//strong[contains(text(),'cubic')]").text

【问题讨论】:

  • 你用错了方法;当您需要查找hyperlink with the specified text inside the link 时使用find_element_by_link_text
  • 我也试过element = driver.find_element_by_xpath("//strong[contains(text(),'cubic')]").text
  • 首先你可以检查你在driver.page_source 中的内容——也许你在没有strong 的情况下得到不同的HTML。或者可能首先获取所有strong 并为所有这些显示文本以查看是否有带有cubic 的文本
  • 代码适用于Firefox(),但不适用于Chrome()

标签: python selenium web-scraping selenium-chromedriver


【解决方案1】:

您的代码适用于Firefox(),但不适用于Chrome()

页面使用lazy loading,因此您必须滚动到Summary,然后它会加载带有预期strong的文本。

我使用了慢一点的方法 - 我搜索所有元素 class='lazyload-wrapper,并在循环中滚动到项目并检查是否有 strong。如果没有strong,则滚动到下一个class='lazyload-wrapper

from selenium import webdriver
import time

#driver = webdriver.Firefox()
driver = webdriver.Chrome()

url = "https://www.marinetraffic.com/en/ais/details/ships/imo:9854612"
driver.get(url)
time.sleep(3)

from selenium.webdriver.common.action_chains import ActionChains

actions = ActionChains(driver)
elements = driver.find_elements_by_xpath("//span[@class='lazyload-wrapper']")

for number, item in enumerate(elements):
    print('--- item', number, '---')
    #print('--- before ---')
    #print(item.text)
    
    actions.move_to_element(item).perform()
    time.sleep(0.1)
    
    #print('--- after ---')
    #print(item.text)
    
    try:
        strong = item.find_element_by_xpath("//strong[contains(text(), 'cubic')]")
        print(strong.text)
        break
    except Exception as ex:
        #print(ex)
        pass

结果:

--- item 0 ---
--- item 1 ---
--- item 2 ---
173400 cubic meters Liquid Gas

结果显示我可以使用elements[2] 跳过两个元素,但我不确定此文本是否总是在第三个元素中。


编辑:

在我创建我的版本之前,我测试了其他版本,这里是完整的工作代码

from selenium import webdriver
import time

#driver = webdriver.Firefox()
driver = webdriver.Chrome()

url = "https://www.marinetraffic.com/en/ais/details/ships/imo:9854612"
driver.get(url)
time.sleep(3)

def test0():
    elements = driver.find_elements_by_xpath("//strong")
    for item in elements:
        print(item.text)

    print('---')

    item = driver.find_element_by_xpath("//strong[contains(text(), 'cubic')]")
    print(item.text)

def test1a():
    from selenium.webdriver.common.action_chains import ActionChains

    actions = ActionChains(driver)
    element = driver.find_element_by_xpath("//div[contains(@class,'MuiTypography-body1')][last()]//div")
    actions.move_to_element(element).build().perform()
    text = element.text
    print(text)
    
def test1b():
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    time.sleep(0.5)
    text = driver.find_element_by_xpath("//div[contains(@class,'MuiTypography-body1')][last()]//strong").text
    print(text)
    
def test2():
    from bs4 import BeautifulSoup
    import re
    soup = BeautifulSoup(driver.page_source, "html.parser")
    soup.find_all(string=re.compile(r"\d+ cubic meters"))
    
def test3():
    from selenium.webdriver.common.action_chains import ActionChains

    actions = ActionChains(driver)
    elements = driver.find_elements_by_xpath("//span[@class='lazyload-wrapper']")
    
    for number, item in enumerate(elements, 1):
        print('--- number', number, '---')
        #print('--- before ---')
        #print(item.text)
        
        actions.move_to_element(item).perform()
        time.sleep(0.1)
        
        #print('--- after ---')
        #print(item.text)
        
        try:
            strong = item.find_element_by_xpath("//strong[contains(text(), 'cubic')]")
            print(strong.text)
            break
        except Exception as ex:
            #print(ex)
            pass

#test0()
#test1a()
#test1b()
#test2()
test3()

【讨论】:

  • 处理延迟加载的精彩介绍!
【解决方案2】:

您可以为此使用 BeautifulSoup,更准确地说是the string argument;来自 doc,“您可以搜索字符串而不是标签”。

作为参数,您还可以传递正则表达式模式。

>>> from bs4 import BeautifulSoup
>>> import re
>>> soup = BeautifulSoup(driver.page_source, "html.parser")
>>> soup.find_all(string=re.compile(r"\d+ cubic meters"))
['173400 cubic meters Liquid Gas']

如果您确定只有一个结果,或者您只需要第一个,您也可以使用find 而不是find_all

【讨论】:

  • 您的代码适用于Firefox(),但不适用于Chrome() - 这就是问题所在。原始代码也适用于Firefox(),但不适用于Chrome() - 所以这可能意味着OP使用Chrome()
  • 应该改变什么?搜索是在一个字符串上进行的,所以不同的浏览器不应该改变任何东西;我什至尝试从 Chrome 页面源执行这个 sn-p,一切正常。
  • 可能取决于窗口大小、浏览器版本、字体大小等。代码仅在可见时才使用 JavaScript 加载文本 - lazy loading(你甚至可以看到带有 class=lazyload-wraper" 的元素) -在 Chrome 上,我必须滚动窗口才能查看文本,然后将其添加到 HTML。出于某种原因,Firefox 一次加载全部 - 至少我的 Firefox 加载它。
【解决方案3】:

您的 xpath 是正确的,并且可以在 Chrome 中运行。你得到NoSuchElementException 因为元素没有加载3秒你等待并且不存在。

要等待元素使用WebDriverWait 类。它显式地等待元素的特定条件,在你的情况下,presents 就足够了。 在下面的代码中,Selenium 将等待元素在 HTML 中呈现 10 秒,每 500 毫秒轮询一次。您可以阅读WebDriverWait 和条件here

一些有用的信息:
不可见元素返回空字符串。在这种情况下,您需要等待元素的可见性,或者如果元素需要滚动才能滚动到它(添加示例)。
您也可以使用 JavaScript 从不可见元素中获取文本。

from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as ec
from selenium import webdriver

url = "https://www.marinetraffic.com/en/ais/details/ships/imo:9854612"
locator = "//strong[contains(text(),'cubic meters')]"

with webdriver.Chrome() as driver:  # type: webdriver
    wait = WebDriverWait(driver, 10)

    driver.get(url)

    cubic = wait.until(ec.presence_of_element_located((By.XPATH, locator)))  # type: WebElement
    print(cubic.text)

    # Below examples just for information and not need for the case

    # Example with scroll. Scroll to the element to make it visible
    cubic.location_once_scrolled_into_view
    print(cubic.text)
    
    # Example using JavaScript. Works for not visible elements.
    text = driver.execute_script("return arguments[0].textContent", cubic)
    print(text)

使用marinetraffic API 是正确的。

【讨论】:

    【解决方案4】:

    我猜你应该先滚动到那个元素,然后再尝试访问它,包括获取它的文本。

    from selenium.webdriver.common.action_chains import ActionChains
    
    actions = ActionChains(driver)
    element = driver.find_element_by_xpath("//div[contains(@class,'MuiTypography-body1')][last()]//div")
    actions.move_to_element(element).build().perform()
    text = element.text
    

    如果上述方法仍然不够好,您可以像这样滚动页面高度:

    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    time.sleep(0.5)
    the_text = driver.find_element_by_xpath("//div[contains(@class,'MuiTypography-body1')][last()]//strong").text
    

    【讨论】:

    • 你的想法是正确的——当我在 Selenium 中手动滚动并再次运行时,我明白了。但是您的代码不正确。您无法滚动到元素,因为 find_element_by_xpath 在滚动之前无法找到文本 cubic
    • 感谢您的大力帮助!我会尝试找到更好的定位器
    • @furas 更新了定位器。希望现在会好起来
    • 我认为它仍然会产生问题,因为它可能会将这个 strong 作为较长文本的一部分加载,并且它必须在不使用 strong 的情况下滚动到 div - 然后它会加载这个带有strong的文本
    • @furas 你能试试我的代码吗?我自己做不到,因为我的电脑上什至没有带硒的 Python :)
    猜你喜欢
    • 2021-04-19
    • 2018-04-06
    • 1970-01-01
    • 2021-01-19
    • 2021-09-14
    • 1970-01-01
    • 1970-01-01
    • 2017-07-15
    • 1970-01-01
    相关资源
    最近更新 更多