Python Selenium дождитесь загрузки нескольких элементов
У меня есть список, который динамически загружается AJAX.
Сначала, во время загрузки, его код выглядит так:
<ul><li class="last"><a class="loading" href="#"><ins> </ins>Загрузка...</a></li></ul>
Когда список загружен, все его li и a изменяются. И это всегда больше, чем 1 ли.
Вот так:
<ul class="ltr">
<li id="t_b_68" class="closed" rel="simple">
<a id="t_a_68" href="javascript:void(0)">Category 1</a>
</li>
<li id="t_b_64" class="closed" rel="simple">
<a id="t_a_64" href="javascript:void(0)">Category 2</a>
</li>
...
Мне нужно проверить, загружен ли список, поэтому я проверяю, есть ли в нем несколько li.
Пока я пытался:
1) Пользовательское условие ожидания
class more_than_one(object):
def __init__(self, selector):
self.selector = selector
def __call__(self, driver):
elements = driver.find_elements_by_css_selector(self.selector)
if len(elements) > 1:
return True
return False
...
try:
query = WebDriverWait(driver, 30).until(more_than_one('li'))
except:
print "Bad crap"
else:
# Then load ready list
2) пользовательская функция, основанная на find_elements_by
def wait_for_several_elements(driver, selector, min_amount, limit=60):
"""
This function provides awaiting of <min_amount> of elements found by <selector> with
time limit = <limit>
"""
step = 1 # in seconds; sleep for 500ms
current_wait = 0
while current_wait < limit:
try:
print "Waiting... " + str(current_wait)
query = driver.find_elements_by_css_selector(selector)
if len(query) > min_amount:
print "Found!"
return True
else:
time.sleep(step)
current_wait += step
except:
time.sleep(step)
current_wait += step
return False
Это не так. работа, потому что драйвер (текущий элемент, переданный этой функции) теряется в DOM. Ул не изменился, но селен больше не может найти его по какой-то причине.
3) извольте подождать. Это просто отстой, потому что некоторые списки загружаются мгновенно, а некоторые занимают 10+ секунд для загрузки. Если я использую эту технику, я должен ждать максимальное время каждого события, что очень плохо для моего случая.
4) Также я не могу дождаться дочернего элемента с XPATH правильно. Этот просто ожидает, что появится ул.
try:
print "Going to nested list..."
#time.sleep(WAIT_TIME)
query = WebDriverWait(driver, 30).until(EC.presence_of_element_located((By.XPATH, './/ul')))
nested_list = child.find_element_by_css_selector('ul')
Пожалуйста, подскажите мне правильный способ убедиться, что несколько наследников элементов загружены для указанного элемента.
P.S. Все эти проверки и поиски должны быть относительно текущего элемента.
3 ответов:
(1) Вы не упомянули об ошибке, которую вы получаете с ним
(2) поскольку вы упомянули
...потому что драйвер (текущий элемент, переданный этой функции)...
Я предположу, что это на самом деле WebElement. В этом случае вместо передачи самого объекта в метод просто передайте селектор, который находит этот WebElement (в вашем случае,
ul). Если" драйвер потеряется в DOM", возможно, что повторное создание его внутри циклаwhile current_wait < limit:может смягчить задача(3) да,
time.sleep()приведет вас только к этому(4) поскольку элементы
li, загружаемые динамически, содержатclass=closed, вместо(By.XPATH, './/ul'), Вы можете попробовать(By.CSS_SELECTOR, 'ul > li.closed')(более подробно о селекторах CSS здесь )
Имея в виду Комментарии Mr.E. и Arran я сделал свой список полностью на CSS селекторах. Самое сложное было с моей собственной структурой списка и отметками (смена классов и т. д.), а также о создании необходимых селекторов на лету и сохранении их в памяти во время обхода.
Я избавился от ожидания нескольких элементов путем поиска всего, что не является состоянием загрузки. Вы также можете использовать селектор ":nth-child", как здесь:
#in for loop with enumerate for i selector.append(' > li:nth-child(%i)' % (i + 1)) # identify child <li> by its order posЭто мой жестко прокомментированное кодовое решение, например:
def parse_crippled_shifted_list(driver, frame, selector, level=1, parent_id=0, path=None): """ Traversal of html list of special structure (you can't know if element has sub list unless you enter it). Supports start from remembered list element. Nested lists have classes "closed" and "last closed" when closed and "open" and "last open" when opened (on <li>). Elements themselves have classes "leaf" and "last leaf" in both cases. Nested lists situate in <li> element as <ul> list. Each <ul> appears after clicking <a> in each <li>. If you click <a> of leaf, page in another frame will load. driver - WebDriver; frame - frame of the list; selector - selector to current list (<ul>); level - level of depth, just for console output formatting, parent_id - id of parent category (in DB), path - remained path in categories (ORM objects) to target category to start with. """ # Add current level list elements # This method selects all but loading. Just what is needed to exclude. selector.append(' > li > a:not([class=loading])') # Wait for child list to load try: query = WebDriverWait(driver, WAIT_LONG_TIME).until( EC.presence_of_all_elements_located((By.CSS_SELECTOR, ''.join(selector)))) except TimeoutException: print "%s timed out" % ''.join(selector) else: # List is loaded del selector[-1] # selector correction: delete last part aimed to get loaded content selector.append(' > li') children = driver.find_elements_by_css_selector(''.join(selector)) # fetch list elements # Walk the whole list for i, child in enumerate(children): del selector[-1] # delete non-unique li tag selector if selector[-1] != ' > ul' and selector[-1] != 'ul.ltr': del selector[-1] selector.append(' > li:nth-child(%i)' % (i + 1)) # identify child <li> by its order pos selector.append(' > a') # add 'li > a' reference to click child_link = driver.find_element_by_css_selector(''.join(selector)) # If we parse freely further (no need to start from remembered position) if not path: # Open child try: double_click(driver, child_link) except InvalidElementStateException: print "\n\nERROR\n", InvalidElementStateException.message(), '\n\n' else: # Determine its type del selector[-1] # delete changed and already useless link reference # If <li> is category, it would have <ul> as child now and class="open" # Check by class is priority, because <li> exists for sure. current_li = driver.find_element_by_css_selector(''.join(selector)) # Category case - BRANCH if current_li.get_attribute('class') == 'open' or current_li.get_attribute('class') == 'last open': new_parent_id = process_category_case(child_link, parent_id, level) # add category to DB selector.append(' > ul') # forward to nested list # Wait for nested list to load try: query = WebDriverWait(driver, WAIT_LONG_TIME).until( EC.presence_of_all_elements_located((By.CSS_SELECTOR, ''.join(selector)))) except TimeoutException: print "\t" * level, "%s timed out (%i secs). Failed to load nested list." %\ ''.join(selector), WAIT_LONG_TIME # Parse nested list else: parse_crippled_shifted_list(driver, frame, selector, level + 1, new_parent_id) # Page case - LEAF elif current_li.get_attribute('class') == 'leaf' or current_li.get_attribute('class') == 'last leaf': process_page_case(driver, child_link, level) else: raise Exception('Damn! Alien class: %s' % current_li.get_attribute('class')) # If it's required to continue from specified category else: # Check if it's required category if child_link.text == path[0].name: # Open required category try: double_click(driver, child_link) except InvalidElementStateException: print "\n\nERROR\n", InvalidElementStateException.msg, '\n\n' else: # This element of list must be always category (have nested list) del selector[-1] # delete changed and already useless link reference # If <li> is category, it would have <ul> as child now and class="open" # Check by class is priority, because <li> exists for sure. current_li = driver.find_element_by_css_selector(''.join(selector)) # Category case - BRANCH if current_li.get_attribute('class') == 'open' or current_li.get_attribute('class') == 'last open': selector.append(' > ul') # forward to nested list # Wait for nested list to load try: query = WebDriverWait(driver, WAIT_LONG_TIME).until( EC.presence_of_all_elements_located((By.CSS_SELECTOR, ''.join(selector)))) except TimeoutException: print "\t" * level, "%s timed out (%i secs). Failed to load nested list." %\ ''.join(selector), WAIT_LONG_TIME # Process this nested list else: last = path.pop(0) if len(path) > 0: # If more to parse print "\t" * level, "Going deeper to: %s" % ''.join(selector) parse_crippled_shifted_list(driver, frame, selector, level + 1, parent_id=last.id, path=path) else: # Current is required print "\t" * level, "Returning target category: ", ''.join(selector) path = None parse_crippled_shifted_list(driver, frame, selector, level + 1, last.id, path=None) # Page case - LEAF elif current_li.get_attribute('class') == 'leaf': pass else: print "dummy" del selector[-2:]
Это как я решил проблему, что я хочу подождать до определенного количества поста, где полная загрузка через AJAX
from selenium import webdriver from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By # create a new Chrome session driver = webdriver.Chrome() # navigate to your web app. driver.get("http://my.local.web") # get the search button seemore_button = driver.find_element_by_id("seemoreID") # Count the cant of post seemore_button.click() # Wait for 30 sec, until AJAX search load the content WebDriverWait(driver,30).until(EC.visibility_of_all_elements_located(By.CLASS_NAME, "post"))) # Get the list of post listpost = driver.find_elements_by_class_name("post")
Comments