Разобрать структуру HTML с помощью BeautifulSoup - PullRequest
0 голосов
/ 03 декабря 2018

Это структура HTML-документа, который мне нужно проанализировать (см. ОБНОВЛЕНИЕ 3):

    <div id="txt_123_C01" style="position:absolute; left:5px; top:206px; width:532px; height:8912px;">
        <div class="Normal-P1">
        <span class="Normal-C2">Main title 1<br></span></div>
        <div class="Normal-P1">
        <span class="Normal-C3">Optional Subtitle<br></span></div>
        <div class="Normal-P1">
        <span class="Normal-C3">Second Optional Subtitle</span>
        <span class="Normal-C4">Text blurb 1.<br></span></div>
        <div class="Normal-P1">
        <span class="Normal-C4">Text blurb 2.<br></span></div>
        <div class="Normal-P1">
        <span class="Normal-C4">Text blurb 4.<br></span></div>
        <span class="Normal-C4"><br></span></div>
        <div class="Normal-P1">
        <span class="Normal-C3"><br></span></div>

    <div class="Normal-P1">
        <span class="Normal-C2">Main title 2<br></span></div>
        <div class="Normal-P1">
        <span class="Normal-C3">Subtitle 1</span>
        <span class="Normal-C4"> Other text blurb 1.<br></span></div>
        <div class="Normal-P1">
        <span class="Normal-C4"> Other text blurb 2.<br></span></div>

Я хочу создать CSV, который выглядит следующим образом:

    Main Title     Optional Subtitle 1     Optional Subtitle 2        Text Blurb
    ----------     -------------------     -------------------       ------------------------     
    Main title 1   Optional Subtitle       Second Optional Subtitle   Text blurb1. Textblurb2. Text blurb 4.
    Main Title 2     Subtitle 1                                         Other text blurb 2.

Я попытался:

soup = BeautifulSoup(page,'xml')
divText = soup.find_all('div', {'class':'Normal-P1'})
for item in divText:
    spanTitle = soup.find_all('span',{'class':'Normal-C2'})
    spanOptopnal = soup.find_all('span',{'class':'Normal-C3'})

Однако этот подход не позволяет мне выделить классы Normal-P1 так, чтобы я перешел с C2 на C4 и затем начал снова.C3 между C4 и следующим C2 не всегда существует.В этих случаях C4 является последним тегом перед следующим C2.

Я рассмотрел вопрос о включении всех div в список, а затем разбил их на подсписки на основе C2 для их обработки.Я пытаюсь выяснить, есть ли более элегантное решение с использованием bs4.

ОБНОВЛЕНИЕ 1

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

Глядя на

   titles = soup.select(".Normal-P1 .Normal-C2")
   for entry in titles:
            print "entry:",entry
            parent = entry.parent
            print "parent: ",parent
            subtitles = [
                subtitle.text for subtitle in
                parent.select(' ~ .Normal-P1 .Normal-C3')
            ]
            print "subtitles:",subtitles

Я считаю, что subtitles содержит результаты извне родителя (т. Е. Все titles).Вывод выглядит следующим образом:

entry: <span class="Normal-C2">Main title 1<br/></span>
parent:  <div class="Normal-P1">
<span class="Normal-C2">Main title 1<br/></span></div>
subtitles: [Optional Subtitle,Second Optional Subtitle,Subtitle 1]


entry: <span class="Normal-C2">Main title 2<br/></span>
parent:  <div class="Normal-P1">
<span class="Normal-C2">Main title 2<br/></span></div>
subtitles: [Subtitle 1]

ОБНОВЛЕНИЕ 2

parent.select(" ~ .Normal-P1 .Normal-C3"), кажется, вызывает проблему.

Проблема здесь в HTML, предоставленном в решении: <span class="Normal-C4"><br></span> </div>.Отсутствует <div class="Normal-P1"> и закрывающий </div> в конце.При внесении этих изменений я вижу ту же проблему (все субтитры в документе отображаются для записи) и в этом примере HTML.

Я дважды проверил отступ, и это выглядит нормально для меня.Что я делаю неправильно?

ОБНОВЛЕНИЕ 3

Это полный HTML

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Hello World</title>
</head>
<body>
  <div id="txt_123_C01" style="position:absolute; left:5px; top:206px; width:532px; height:8912px;">
    <div class="Normal-P1">
      <span class="Normal-C2">Main title 1<br></span>
    </div>
    <div class="Normal-P1">
      <span class="Normal-C3">Optional Subtitle<br></span>
    </div>
    <div class="Normal-P1">
      <span class="Normal-C3">Second Optional Subtitle</span>
      <span class="Normal-C4">Text blurb 1.<br></span>
    </div>
    <div class="Normal-P1">
      <span class="Normal-C4">Text blurb 2.<br></span>
    </div>
    <div class="Normal-P1">
      <span class="Normal-C4">Text blurb 4.<br></span>
    </div>
    <div class="Normal-P1">
    <span class="Normal-C4"><br></span>
  </div>
  <div class="Normal-P1">
    <span class="Normal-C3"><br></span>
  </div>

  <div class="Normal-P1">
    <span class="Normal-C2">Main title 2<br></span>
  </div>
  <div class="Normal-P1">
    <span class="Normal-C3">New Subtitle 1</span>
    <span class="Normal-C4"> Other text blurb 1.<br></span>
  </div>
  <div class="Normal-P1">
    <span class="Normal-C4"> Other text blurb 2.<br></span>
  </div>
</div>
</body>
</html>

Это вывод, который я вижу

    entry: <span class="Normal-C2">Main title 1<br/></span>
    parent:  <div class="Normal-P1">

<span class="Normal-C2">Main title 1<br/></span>

    </div>
    subtitle: <span class="Normal-C3">Optional Subtitle<br/></span>
    subtitle: <span class="Normal-C3">Second Optional Subtitle</span>
    subtitle: <span class="Normal-C3"><br/></span>
    subtitle: <span class="Normal-C3">New Subtitle 1</span>
    entry: <span class="Normal-C2">Main title 2<br/></span>
    parent:  <div class="Normal-P1">
    <span class="Normal-C2">Main title 2<br/></span>
    </div>
    subtitle: <span class="Normal-C3">New Subtitle 1</span>

Этомой код в настоящее время:

file = filepath + "test-page.html"
parser = HTMLParser.HTMLParser()
pageFile = codecs.open(file, 'r', encoding='utf-8')
pageRaw = pageFile.read()
page = parser.unescape(pageRaw)

soup = bs4.BeautifulSoup(page,'lxml')
titles = soup.select(".Normal-P1 .Normal-C2")

for entry in titles:
    print "entry:",entry
    parent = entry.parent
    print "parent: ",parent

    for subtitle in parent.select(" ~ .Normal-P1 .Normal-C3"):
        print "subtitle:", subtitle

1 Ответ

0 голосов
/ 03 декабря 2018

Используйте CSS-селекторы, вы хотите настроить таргетинг на имена классов, которые вы делаете с помощью .Class-Name, а также братьев и сестер, то есть с помощью ParentTag ~ .Child-Class

Mozillas MDN Web Docs иметь хороший маленький учебник по ним.

Python-файл:

import bs4
import csv

entries = []

with open("example.html", "r") as page:
    soup = bs4.BeautifulSoup(page, 'lxml')

    # CSS Selectors for items with class Normal-P1 followed by
    # Normal-C2
    titles = soup.select(".Normal-P1 .Normal-C2")

    for entry in titles:
        entry_dict = {
            'Main Title': '',
            'Optional Subtitle 1': '',
            'Optional Subtitle 2': '',
            'Text Blurb': ''
        }
        parent = entry.parent

        entry_dict['Main Title'] = entry.text

        subtitles = [
            subtitle.text for subtitle in
            parent.select(' ~ .Normal-P1 .Normal-C3')
            # CSS Selector for siblings of the same parent element that have
            # classes Normal-P1 followed by Normal-C3
        ]
        try:
            entry_dict['Optional Subtitle 1'] = subtitles[0]
            entry_dict['Optional Subtitle 2'] = subtitles[1]
        except IndexError:
            pass

        entry_dict['Text Blurb'] = ' '.join(
            blurb.text for blurb in
            parent.select(' ~ .Normal-P1 .Normal-C4')
            # CSS Selector for siblings of the same parent element that have
            # classes Normal-P1 followed by Normal-C4
        )

        entries.append(entry_dict)

    with open('out.csv', 'w') as csv_file:
        fieldnames = [
            'Main Title',
            'Optional Subtitle 1',
            'Optional Subtitle 2',
            'Text Blurb'
        ]
        writer = csv.DictWriter(
            csv_file,
            fieldnames=fieldnames,
            quoting=csv.QUOTE_ALL,
        )
        writer.writeheader()
        for entry in entries:
            writer.writerow(entry)

Используемый HTML-файл:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Hello World</title>
</head>
<body>
  <div id="txt_123_C01" style="position:absolute; left:5px; top:206px; width:532px; height:8912px;">
    <div class="Normal-P1">
      <span class="Normal-C2">Main title 1<br></span>
    </div>
    <div class="Normal-P1">
      <span class="Normal-C3">Optional Subtitle<br></span>
    </div>
    <div class="Normal-P1">
      <span class="Normal-C3">Second Optional Subtitle</span>
      <span class="Normal-C4">Text blurb 1.<br></span>
    </div>
    <div class="Normal-P1">
      <span class="Normal-C4">Text blurb 2.<br></span>
    </div>
    <div class="Normal-P1">
      <span class="Normal-C4">Text blurb 4.<br></span>
    </div>
    <span class="Normal-C4"><br></span>
  </div>
  <div class="Normal-P1">
    <span class="Normal-C3"><br></span>
  </div>

  <div class="Normal-P1">
    <span class="Normal-C2">Main title 2<br></span>
  </div>
  <div class="Normal-P1">
    <span class="Normal-C3">Subtitle 1</span>
    <span class="Normal-C4"> Other text blurb 1.<br></span>
  </div>
  <div class="Normal-P1">
    <span class="Normal-C4"> Other text blurb 2.<br></span>
  </div>
</body>
</html>

csv output:

"Main Title","Optional Subtitle 1","Optional Subtitle 2","Text Blurb"

"Main title 1","Optional Subtitle","Second Optional Subtitle","Text blurb 1. Text blurb 2. Text blurb 4."

"Main title 2","Subtitle 1",""," Other text blurb 1.  Other text blurb 2."

edit: я не уверен, что вы делаете с HTMLParser, но в этом нет необходимости.BeautifulSoup прекрасно справляется с чтением файлов.

import bs4
import codecs


with codecs.open("example.html", "r", encoding='utf-8') as page:
    soup = bs4.BeautifulSoup(page, 'lxml')

    # CSS Selectors for items with class Normal-P1 followed by
    # Normal-C2
    titles = soup.select(".Normal-P1 .Normal-C2")

    for entry in titles:
        print("entry: ", entry.text)
        parent = entry.parent
        print("parent:", parent)

        subtitles = [
            subtitle.text for subtitle in
            parent.select(' ~ .Normal-P1 .Normal-C3')
            # CSS Selector for siblings of the same parent element that have
            # classes Normal-P1 followed by Normal-C3
        ]
        try:
            print("subtitle: ", subtitles[0])
            print('subtitle: ', subtitles[1])
        except IndexError:
            pass

выход

entry:  Main title 1
parent: <div class="Normal-P1">
<span class="Normal-C2">Main title 1<br/></span>
</div>
subtitle:  Optional Subtitle
subtitle:  Second Optional Subtitle
entry:  Main title 2
parent: <div class="Normal-P1">
<span class="Normal-C2">Main title 2<br/></span>
</div>
subtitle:  Subtitle 1
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...