Не удается найти и захватить несколько полей из неструктурированного html

#python #python-3.x #web-scraping #beautifulsoup

Вопрос:

Я пытаюсь извлечь четыре поля с веб-страницы, используя библиотеку BeautifulSoup. Трудно идентифицировать поля по отдельности, и именно по этой причине я обращаюсь за помощью.

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

Я пробовал с (минимальный рабочий пример):

 from bs4 import BeautifulSoup

html = """
  <p>
   <strong>
    Robert Romanoff
   </strong>
   <br/>
   146 West 29th Street, Suite 11W
   <br/>
   New York, New York 10001
   <br/>
   Telephone: (718) 527-1577
   <br/>
   Fax: (718) 276-8501
   <br/>
   Email:
   <a href="mailto:robert@absol.com">
    robert@absol.com
   </a>
   <br/>
   Additional Contact: William Locantro
   <br/>
   Email:
   <a href="mailto:bill@absol.com">
    bill@absol.com
   </a>
  </p>
"""
soup = BeautifulSoup(html,"lxml")
container = soup.select_one("p")
contact_name = container.strong.text.strip()
contact_email = [i for i in container.strings if "Email" in i][0].strip()
additional_contact = [i.strip() for i in container.strings if "Additional Contact" in i.strip()][0].strip('Additional Contact:')
additional_email = [i for i in container.strings if "Email" in i][1].strip()
print(contact_name,contact_email,additional_contact,additional_email)
 

Выходной ток:

 Robert Romanoff Email: William Locantro Email:
 

Ожидаемый результат:

 Robert Romanoff robert@absol.com William Locantro bill@absol.com
 

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

1. Не могли бы вы поделиться URL-адресом. Трудно найти решение, которое работало бы для всех случаев.

2. Проверьте правку @Ram.

Ответ №1:

Для более сложного анализа html/xml вам следует взглянуть на xpath то, что позволяет использовать очень мощные правила выбора.

В python он доступен в parsel пакете.

 from parsel import Selector

html = '...'
sel = Selector(html)
name = sel.xpath('//strong[1]/text()').get().strip()
email = sel.xpath("//text()[re:test(., 'Email')]/following-sibling::a/text()").get().strip()
name_additional = sel.xpath("//text()[re:test(., 'Additional Contact')]").re("Additional Contact: (. )")[0]
email_additional = sel.xpath("//text()[re:test(., 'Additional Contact')]/following-sibling::a/text()").get().strip()
print(name, email, name_additional, email_additional)
# Robert Romanoff robert@absol.com William Locantro bill@absol.com
 

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

1. Я был загипнотизирован вашим ответом, поэтому забыл спросить, что re:test() здесь происходит. Спасибо.

2. re:test это функция xpath, которая выполняет проверку соответствия регулярных выражений на целевом объекте. Сигнатура функции такова re:test(target, pattern, flags) . В этом случае мы проверяем . , что относится к «я» (текстовый узел), и наш шаблон "Additional Contact" , и мы не предоставляем никаких флагов (в качестве альтернативы вы можете указать "i" , например, игнорировать регистр). Другим способом выполнения той же функции было бы использование contains() функции: [contains(., "Additional Contact")] re:test() это просто более продвинутая версия этого.

3. подробнее об этом в parsel документах: parsel.readthedocs.io/en/latest/…

Ответ №2:

Ты можешь сделать вот так.

  • Выберите тот <div> , в котором есть нужные вам данные.
  • Создайте список данных, присутствующих внутри выбранного выше <div>
  • Повторите список и извлеките необходимые данные.

Вот код:

 from bs4 import BeautifulSoup
import requests

url = 'http://www.nyeca.org/find-a-contractor-by-name/'
r = requests.get(url)
soup = BeautifulSoup(r.text,"lxml")

d = soup.find_all('div', class_='sabai-directory-body')
for i in d:
    x = i.text.strip().split('n')
    data = [x[0].strip()]
    for item in x:
        if item.startswith('Email'):
            data.append(item.split(':')[1].strip())
        elif item.startswith('Additional'):
            data.append(item.split(':')[1].strip())
    print(data)
 

Содержит список сведений о подрядчике, а также дополнительные сведения (если таковые имеются).

 ['Ron Singh', 'rsingh@atechelectric.com']
['George Pacacha', 'Office@agvelectricalservices.com']
['Andrew Drazic', 'ADrazic@atjelectrical.com']
['Albert Barbato', 'Abarbato@abelectriccorp.com']
['Ralph Sica', 'Ralph.Sica@abm.com', 'Henry Kissinger', 'Henry.Kissinger@abm.com']
['Robert Romanoff', 'robert@absoluteelectric.com', 'William Locantro', 'bill@absoluteelectric.com']
.
.
 

Ответ №3:

Вот решение, которое вы можете попробовать,

 import re

soup = BeautifulSoup(html, "lxml")

names_ = [
    soup.select_one("p > strong").text.strip(),
    soup.find(text=re.compile("Additional Contact:")).replace('Additional Contact:', '').strip()
]

email_ = [i.strip() for i in soup.find_all(text=re.compile("absol"))]

print(" ".join(i   " "   j for i, j in zip(names_, email_)))
 

 Robert Romanoff robert@absol.com William Locantro bill@absol.com