#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. Рад объяснить больше, если вы этого хотите.