Я хочу извлечь поля под сводкой по этой ссылке. Я могу довольно легко извлечь имена полей (номер проекта, основная сфера деятельности, дата раскрытия информации и т. д.), используя 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:
@Übermensch приносит извинения за задержку с ответом. и да, мне нужны сводные поля и их значения
Вы можете перемещаться вверх по дереву 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 Как ни странно, у меня все работает отлично: я использую ChromeDriver 108.0.5359.71 на Mac. Можете ли вы запустить document.querySelector('div.esrs-name')
в консоли разработчика браузера или dom.select('div.esrs-name')
и посмотреть, какие контейнеры возвращаются? Возможно, сайт отображает страницу с разными именами классов для целевых элементов в вашем окне Chrome.
При запуске dom.select('div.esrs-name')
я получаю теги div с именами полей (номер проекта/сфера деятельности и т. д.). В консоли разработчика я получаю номер проекта — интересно, при попытке document.querySelector('div.esrs-value')
я тоже получаю 604080 — но при печати dom.select('div.esrs-value')
я получаю только тег со строкой «Бюджет проекта..» — так же, как последний абзац в оригинале почта. Это сводит меня с ума! Я использую ChromeDriver 110.0.5481.77 на Mac.
@ashah Можешь запустить results = [{'name':i.text, 'value':i.parent} for i in dom.select('div.esrs-name')]
и посмотреть, что связано с value
?
Для value
я получаю тег div с esrs-name
. Что определенно кажется совершенно неправильным, но я вижу это даже на Replit. Отредактированный пост с выводом. next_sibling
вместо parent
возвращает все None
, кроме «бюджета проекта..»
Интересная у вас страничка...
Во-первых, относительно простая часть — получение данных. У 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 Может быть способ сделать это (боюсь, я не эксперт по селену), но в любом случае вы должны опубликовать это как отдельный вопрос в соответствии с политикой SO.
Итак, просто чтобы уточнить, вы хотите составить список, состоящий из текстовых значений во всех тегах
div
с классомesrs-value
?