Селен не возвращает правильное количество дочерних элементов?

#python #selenium #selenium-webdriver #web-scraping

#python #селен #selenium-webdriver #очистка веб-страниц

Вопрос:

Я пробовал несколько способов получить дочерние элементы этого раздела, но, похоже, это совершенно не так. Веб-сайт, который я использую, это https://www.thecompleteuniversityguide.co.uk/courses/details/computing-bsc/57997898 за исключением того, что я должен делать это несколько раз на нескольких страницах, поэтому я просто сосредотачиваюсь на том, чтобы убедиться, что только одна веб-страница может вернуть то, что мне нужно. В настоящее время это то, что у меня есть:

 import time
from selenium.webdriver import Chrome
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

opts = Options()
opts.add_argument('--headless')

driver = Chrome(options = opts, executable_path = 'D:ProgramsPythonchromedriver.exe')

driver.get("https://www.thecompleteuniversityguide.co.uk/courses/details/computing-bsc/57997898")

closeButton = driver.find_element_by_xpath("//a[@id='closeFilter']")
closeButton.click()
driver.find_element_by_xpath("//a[@id='acceptCookie']").click()

modules_container = driver.find_element_by_xpath("//div[@data-sub-sec='Modules']").find_element_by_class_name("cdsb_rt")
numberOfModulesByYear = len(modules_container.find_elements_by_xpath("//div[@class='mdldv']"))

for moduleYear in range(numberOfModulesByYear):
      print("_"*75)
      moduleYearButtonString = "//div[@class='mdldv' and @data-module-sections='{}']".format(str(moduleYear))
      module_year = modules_container.find_element_by_xpath(moduleYearButtonString)
      module_year_a = module_year.find_element_by_tag_name("a")
      while module_year_a.find_element_by_tag_name("span").get_attribute("class") != "icon icon-minus": 
            module_year_a.click()
            print(module_year_a.find_element_by_tag_name("span").get_attribute("class"))
      numberOfModules = module_year.find_elements_by_xpath("//div[@class='mdiv']")
      print(len(numberOfModules))

driver.close()
 

Вывод:

 ___________________________________________________________________________
icon icon-minus
0
___________________________________________________________________________
icon icon-minus
10
___________________________________________________________________________
icon icon-minus
10
___________________________________________________________________________
icon icon-minus
15
 

Однако в порядке модулей на веб-сайте это 5,5,4,1
Кто-нибудь знает, как это исправить?
(Мне также кажется, что Selenium не ищет дочерние элементы исключительно внутри элемента, а также возвращает дочерние элементы в своих родственных элементах, чего я НЕ хочу.

Редактировать: до сих пор понятия не имею, почему он возвращает дочерние дочерние элементы, но я решил, что мне придется подождать, пока он загрузится:

 for moduleYear in range(numberOfModulesByYear):
      print("_"*75)
      moduleYearButtonString = "//div[@class='mdldv' and @data-module-sections='{}']".format(str(moduleYear))
      module_year = modules_container.find_element_by_xpath(moduleYearButtonString)
      module_year_a = module_year.find_element_by_tag_name("a")
      while module_year_a.find_element_by_tag_name("span").get_attribute("class") != "icon icon-minus": 
            module_year_a.click()
      while len(module_year.find_elements_by_xpath("//div[@class='mdiv']")) - previousNumberOfModules == 0:
            time.sleep(0.01)
      numberOfModules = len(module_year.find_elements_by_xpath("//div[@class='mdiv']")) - previousNumberOfModules
      previousNumberOfModules = len(module_year.find_elements_by_xpath("//div[@class='mdiv']"))
      print(numberOfModules)
 

Все еще понятия не имею, почему он возвращает дочерние дочерние элементы, хотя, когда я указал точный элемент, я хочу, чтобы он смотрел внутрь.

Комментарии:

1. Я вижу 5,4,1 с точки зрения разделов по годам. Можете ли вы пояснить, что именно должно быть возвращено с этой страницы?

2. Я пытаюсь получить все имена и описания модулей под ними (если они есть)

Ответ №1:

 import time
from selenium.webdriver import Chrome
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from bs4 import BeautifulSoup as BSoup

opts = Options()
opts.add_argument('--headless')

driver = Chrome(executable_path = 'D:ProgramsPythonchromedriver.exe', options = opts)

driver.get("https://www.thecompleteuniversityguide.co.uk/courses/details/computing-bsc/57997898")

closeButton = driver.find_element_by_xpath("//a[@id='closeFilter']")
closeButton.click()
driver.find_element_by_xpath("//a[@id='acceptCookie']").click()

modules_container = driver.find_element_by_xpath("//div[@data-sub-sec='Modules']").find_element_by_class_name("cdsb_rt")
numberOfModulesByYear = len(modules_container.find_elements_by_xpath("//div[@class='mdldv']"))
previousNumberOfModules = 0
numberOfModulesTab = []

print("-"*100)
for moduleYear in range(numberOfModulesByYear):
      moduleYearButtonString = "//div[@class='mdldv' and @data-module-sections='{}']".format(str(moduleYear))
      module_year = modules_container.find_element_by_xpath(moduleYearButtonString)
      print(module_year.get_attribute("innerHTML"))
      module_year_a = module_year.find_element_by_tag_name("a")
      time.sleep(0.5)
      while module_year_a.find_element_by_tag_name("span").get_attribute("class") == "icon icon-add": 
            module_year_a.click()
      while len(module_year.find_elements_by_xpath("//div[@class='mdiv']")) - previousNumberOfModules == 0:
            time.sleep(0.01)
      listOfModules = module_year.find_elements_by_xpath("//div[@class='mdiv']")
      numberOfModules = len(listOfModules) - previousNumberOfModules
      previousNumberOfModules = len(module_year.find_elements_by_xpath("//div[@class='mdiv']"))
      numberOfModulesTab.append(numberOfModules)
      print("-"*100)

alreadyExistingModules = []
moduleIndex = 0
numberOfDescriptions = 0

allTheModules = modules_container.find_elements_by_xpath("//div[@class='mdiv']")
for moduleNumber, module in enumerate(allTheModules):
      outputLines = False
      module_a = module.find_elements_by_tag_name("a")
      if len(module_a) > 0:
            module_a = module_a[0]
            soup = BSoup(module_a.get_attribute("innerHTML"), "html.parser")
            moduleName = soup.find("span", class_='mdltxt').getText()
            if not (moduleName in alreadyExistingModules):
                  outputLines = True
                  moduleIndex  = 1
                  alreadyExistingModules.append(moduleName)
                  print(moduleName.rstrip())
                  while module_a.find_element_by_tag_name("span").get_attribute("class") == "icon icon-add":
                        driver.execute_script("arguments[0].click()", module_a)
                  while len(module.find_elements_by_xpath("//p[@class='mdesc']"))-numberOfDescriptions == 0:
                        time.sleep(0.01)
                  soup = BSoup(module.get_attribute("innerHTML"), "html.parser")
                  print(soup.find("p", class_="mdesc").getText().rstrip())
                  numberOfDescriptions  = 1
      else:
            soup = BSoup(module.get_attribute("innerHTML"), "html.parser")
            moduleName = soup.find("span", class_='mtxt').getText()
            if not (moduleName in alreadyExistingModules):
                  outputLines = True
                  moduleIndex  = 1
                  alreadyExistingModules.append(moduleName)
                  print(moduleName.rstrip())
                  
      if outputLines:
            print("-"*100)

driver.close()
 

Приходилось ждать в определенные моменты, потому что java script выполняется не сразу, а иногда выполняется дважды… и по какой-то причине он возвращал то, чего я не хотел, поэтому мне пришлось использовать BeautifulSoup, чтобы получить то, что я хотел, но это работает так 🙂

Ответ №2:

Вы могли бы сделать все это с requests помощью, поскольку страница использует ajax-запросы для получения информации о модуле для курсов. Если вы посещаете страницу данного курса, вы можете извлечь идентификаторы модулей для первоначального запроса ajax, чтобы получить модули; затем извлеките идентификаторы описания для каждого модуля для использования в последующем запросе.

Я использовал Session для повышения эффективности повторного использования tcp. Кроме того, попытался абстрагироваться в надежде, что вы сможете использовать с другим идентификатором курса (в этот момент начинает казаться, что его можно переписать как класс)……

 import requests, pprint
from bs4 import BeautifulSoup as bs

course_id = '57997898'

url_desc = 'https://www.thecompleteuniversityguide.co.uk/cug2/ajax/get-course-modules-description.html'
url_modules = 'https://www.thecompleteuniversityguide.co.uk/cug2/ajax/get-course-modules.html'

headers = {
    'User-Agent': 'Mozilla/5.0',
    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
    'Referer': 'https://www.thecompleteuniversityguide.co.uk/courses/details/computing-bsc/'   course_id,
}

results = {}

with requests.Session() as s:
    s.headers = headers
    r = s.get(f'https://www.thecompleteuniversityguide.co.uk/courses/details/computing-bsc/{course_id}#modules')
    soup = bs(r.content, 'lxml')
    module_ids = {i.a.text.strip():i.select_one('.mdtt')['data-module-group'] for i in soup.select('.mdldv')} #gather the module ids

    for k,v in module_ids.items():
        results[k] = {}
        data = {'moduleGroupId': v, 'courseId': course_id}
        r = s.post(url_modules,data=data) 
        soup = bs(r.text, 'lxml')
        modules = {i.select_one('.mdltxt').text.strip():i.a['data-modules-list'] 
                   for i in soup.select('.mdiv') if i.select_one('.mdltxt')}
        # print(modules)
        if modules: #check there are actually modules (last doesn't have any)
            for k2,v2 in modules.items():
                data = {'moduleGroupId': v, 'moduleId': v2}
                r = s.post(url_desc, data=data)
                soup = bs(r.content, 'lxml') 
                desc = soup.select_one('.mdesc').text.strip()
                results[k][k2] = desc
                # print(desc)
pprint.pprint(results)
 

Комментарии:

1. Я боялся использовать запросы, потому что это выглядело сложным, но я на 100% буду использовать это, если это быстрее 🙂

2. Рад объяснить больше, если вы этого хотите.