Проблемы с навигацией по вложенному div с помощью BeautifulSoup/ETree, несмотря на правильный XPath

Я хочу извлечь поля под сводкой по этой ссылке. Я могу довольно легко извлечь имена полей (номер проекта, основная сфера деятельности, дата раскрытия информации и т. д.), используя Selenium и BeautifulSoup/ETree.

driver.get('https://disclosures.ifc.org/project-detail/AS/604080/india-climate-smart-cities-ppp-program')
soup = BeautifulSoup(driver.page_source, 'lxml')
dom = etree.HTML(str(soup))
fields = [x.text for x in dom.xpath('//div[@class=\'esrs-name\']')]

Однако я не могу использовать класс esrs-value, чтобы сделать то же самое для значений полей.

Я пробовал следующее:

values = [x.text for x in dom.xpath('//div[@class=\'esrs-name\']')]

что приводит только к (Бюджет проекта включает все мероприятия, финансируемые проектом). Я заметил, что элементы div не имеют одинаковой структуры для нескольких строк, и элемент div, содержащий (Бюджет проекта включает все мероприятия, финансируемые проектом), кажется выше, чем другие, но даже

values = [x.text for x in dom.xpath('//div[@class=\'col-12 col-sm-4 col-sm-4 col-lg-4\']/div[not(@class)]/div[@class=\'esrs-value\']')]

результат [].

Я пробовал использовать обычный BeautifulSoup:

result = soup.find_all("div", {"class":"esrs-value"})
for res in result:
    print(res.text)

который также просто возвращается (Бюджет проекта включает все мероприятия, финансируемые проектом).

При использовании проверки в Chrome я могу идентифицировать все 12 экземпляров класса div esrs-value, используя //div[@class='esrs-value'], поэтому я не уверен, что вообще происходит, потому что кажется, что он идентифицирует только один экземпляр esrs-value.

Редактировать:

Учитывая несоответствие результатов в первом ответе, я решил попробовать Replit (используя только bs4) и увидел тот же результат.

Редактировать 2:

Итак, просто чтобы уточнить, вы хотите составить список, состоящий из текстовых значений во всех тегах div с классом esrs-value?

Übermensch 09.02.2023 04:49

@Übermensch приносит извинения за задержку с ответом. и да, мне нужны сводные поля и их значения

ashah 14.02.2023 03:57
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
3
2
51
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы можете перемещаться вверх по дереву DOM с помощью атрибута bs4.parent, а затем искать esrs-value:

from bs4 import BeautifulSoup as soup
dom = soup(driver.page_source, 'html.parser')
results = [{'name':i.text, 'value':i.parent.select_one('div.esrs-value').get_text(strip=1)} 
            for i in dom.select('div.esrs-name')]

Выход:

[{'name': 'Project Number', 'value': '604080'}, {'name': 'Primary Business Area', 'value': 'Transaction Advisory'}, {'name': 'Disclosure Date', 'value': 'Mar 31, 2023'}, {'name': 'Country ', 'value': 'India'}, {'name': 'Region', 'value': 'South Asia'}, {'name': 'IFC Approval Date', 'value': 'IFC Approval Date Pending'}, {'name': 'Status', 'value': 'Active'}, {'name': 'Estimated Total Budget', 'value': 'Estimated Total Budget Pending'}, {'name': 'Last Updated Date', 'value': ''}, {'name': 'Project Estimated Start Date', 'value': 'Estimated Start Date Pending'}, {'name': 'Project Estimated End Date', 'value': 'Project Estimated End Date Pending'}, {'name': ' Project Description ', 'value': 'IFC has entered into Memorandum of Understanding (MoUs) with i) Kerala Infrastructure Investment Fund Board (KIFB); ii) PPP Department, Government of Goa; and iii) Gujarat Power Corporation Limited (GPCL). \n  \n  IFC will support KIFB and the Government of Goa in identification and screening of Public-Private Partnership (PPP) projects across infrastructure sectors and undertake pre-feasibility assessments of select projects. \n  \n  IFC will also support GPCL to conduct a pre-feasibility assessment for a potential pilot project to produce clean hydrogen-based renewable energy at one of GPCL’s sites in Gujarat.'}]

К сожалению, в коде я столкнулся с ошибкой «AttributeError: объект NoneType не имеет атрибута get_text». При удалении .get_text() я получаю словарь со всеми значениями как None, за исключением ключа «Расчетный общий бюджет», который дает мне весь тег div с «Бюджет проекта включает все мероприятия, финансируемые проектом».

ashah 14.02.2023 03:46

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

ashah 14.02.2023 04:02

@ashah Как ни странно, у меня все работает отлично: я использую ChromeDriver 108.0.5359.71 на Mac. Можете ли вы запустить document.querySelector('div.esrs-name') в консоли разработчика браузера или dom.select('div.esrs-name') и посмотреть, какие контейнеры возвращаются? Возможно, сайт отображает страницу с разными именами классов для целевых элементов в вашем окне Chrome.

Ajax1234 14.02.2023 04:20

При запуске dom.select('div.esrs-name') я получаю теги div с именами полей (номер проекта/сфера деятельности и т. д.). В консоли разработчика я получаю номер проекта — интересно, при попытке document.querySelector('div.esrs-value') я тоже получаю 604080 — но при печати dom.select('div.esrs-value') я получаю только тег со строкой «Бюджет проекта..» — так же, как последний абзац в оригинале почта. Это сводит меня с ума! Я использую ChromeDriver 110.0.5481.77 на Mac.

ashah 14.02.2023 04:42

@ashah Можешь запустить results = [{'name':i.text, 'value':i.parent} for i in dom.select('div.esrs-name')] и посмотреть, что связано с value?

Ajax1234 14.02.2023 05:00

Для value я получаю тег div с esrs-name. Что определенно кажется совершенно неправильным, но я вижу это даже на Replit. Отредактированный пост с выводом. next_sibling вместо parent возвращает все None, кроме «бюджета проекта..»

ashah 15.02.2023 04:58
Ответ принят как подходящий

Интересная у вас страничка...

Во-первых, относительно простая часть — получение данных. У IFC есть API, который возвращает красивый и чистый json со всеми данными без необходимости использования селена BeautifulSoup:

import requests
import json

req = requests.get('https://disclosuresservice.ifc.org/api/ProjectAccess/AdvisoryProject?projectId=604080')
#note: you can get the url of the api from the network tab from the developer view in the browser
data = req.json()

Вот и все. Теперь у вас есть данные, к которым вы можете получить доступ, например, так:

data['ProjectOverView']['Project_Description']

выходы:

'IFC has entered ... sites in Gujarat.'

Или:

data['Status']

Выход:

'Active'

и т. д.

Возникающая проблема заключается в наличии несоответствий между информацией на посадочной странице и в json. Я считаю, что скрипт заполняет таблицу целевой страницы «ожидающими» и т.п. в местах, где этого не должно быть. Возможно, я неправильно понимаю терминологию IFC, но также возможно, что в этом сценарии есть ошибки. Например, для IFC Approval Date на целевой странице отображается IFC Approval Date Pending. С другой стороны:

data['ApprovalDate']

возвращается

'2020-03-16T00:00:00'

Точно так же Project Estimated Start Date показывает Estimated Start Date Pending, но

data['EstimatedStartDate']

возвращает:

'2019-11-01T00:00:00'

Не могу объяснить эти вещи...

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

ashah 16.02.2023 04:46

@ashah Может быть способ сделать это (боюсь, я не эксперт по селену), но в любом случае вы должны опубликовать это как отдельный вопрос в соответствии с политикой SO.

Jack Fleeting 16.02.2023 12:18

Другие вопросы по теме