Версия 1 от 2010-05-09 15:31:07

Убрать это сообщение

Beautiful Soup - это парсер для синтаксического разбора файлов HTML/XML, написанный на языке программирования Python, который может преобразовать даже неправильную разметку в дерево синтаксического разбора. Он поддерживает простые и естественные способы навигации, поиска и модификации дерева синтаксического разбора. В большинстве случаев он поможет программисту сэкономить часы и дни работы. Написанный на языке программирования Ruby порт называется Rubyful Soup.

Данный документ иллюстрирует основные возможности Beautiful Soup версии 3.0 на примерах. Вы увидите, для чего лучше использовать данную библиотеку, как она работает, как ее использовать, как добиться необходимых вам результатов и что делать, когда она не оправдывает ваших ожиданий.

Быстрый старт

Скачать Beautiful Soup можно здесь. Список изменений содержит отличия версии 3.0 от более ранних.

Подключить Beautiful Soup к вашему приложению можно с помощью одной из ниже приведенных строк:

   1 from BeautifulSoup import BeautifulSoup          # Для обработки HTML
   2 from BeautifulSoup import BeautifulStoneSoup     # Для обработки XML
   3 import BeautifulSoup                             # Для обработки и того и другого

Следующий код демонстрирует основные возможности Beautiful Soup. Можете скопировать его в сессию Python и запустить.

   1 from BeautifulSoup import BeautifulSoup
   2 import re
   3 
   4 doc = ['<html><head><title>Page title</title></head>',
   5        '<body><p id="firstpara" align="center">This is paragraph <b>one</b>.',
   6        '<p id="secondpara" align="blah">This is paragraph <b>two</b>.',
   7        '</html>']
   8 soup = BeautifulSoup(''.join(doc))
   9 
  10 print soup.prettify()
  11 # <html>
  12 #  <head>
  13 #   <title>
  14 #    Page title
  15 #   </title>
  16 #  </head>
  17 #  <body>
  18 #   <p id="firstpara" align="center">
  19 #    This is paragraph
  20 #    <b>
  21 #     one
  22 #    </b>
  23 #    .
  24 #   </p>
  25 #   <p id="secondpara" align="blah">
  26 #    This is paragraph
  27 #    <b>
  28 #     two
  29 #    </b>
  30 #    .
  31 #   </p>
  32 #  </body>
  33 # </html>

Продемонстрируем несколько способов навигации по супу:

   1 soup.contents[0].name
   2 # u'html'
   3 
   4 soup.contents[0].contents[0].name
   5 # u'head'
   6 
   7 head = soup.contents[0].contents[0]
   8 head.parent.name
   9 # u'html'
  10 
  11 head.next
  12 # <title>Page title</title>
  13 
  14 head.nextSibling.name
  15 # u'body'
  16 
  17 head.nextSibling.contents[0]
  18 # <p id="firstpara" align="center">This is paragraph <b>one</b>.</p>
  19 
  20 head.nextSibling.contents[0].nextSibling
  21 # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>

А вот как искать в супе определенные теги или теги с заданными атрибутами:

   1 titleTag = soup.html.head.title
   2 titleTag
   3 # <title>Page title</title>
   4 
   5 titleTag.string
   6 # u'Page title'
   7 
   8 len(soup('p'))
   9 # 2
  10 
  11 soup.findAll('p', align="center")
  12 # [<p id="firstpara" align="center">This is paragraph <b>one</b>. </p>]
  13 
  14 soup.find('p', align="center")
  15 # <p id="firstpara" align="center">This is paragraph <b>one</b>. </p>
  16 
  17 soup('p', align="center")[0]['id']
  18 # u'firstpara'
  19 
  20 soup.find('p', align=re.compile('^b.*'))['id']
  21 # u'secondpara'
  22 
  23 soup.find('p').b.string
  24 # u'one'
  25 
  26 soup('p')[1].b.string
  27 # u'two'

Изменять суп также весьма просто:

   1 titleTag['id'] = 'theTitle'
   2 titleTag.contents[0].replaceWith("New title")
   3 soup.html.head
   4 # <head><title id="theTitle">New title</title></head>
   5 
   6 soup.p.extract()
   7 soup.prettify()
   8 # <html>
   9 #  <head>
  10 #   <title id="theTitle">
  11 #    New title
  12 #   </title>
  13 #  </head>
  14 #  <body>
  15 #   <p id="secondpara" align="blah">
  16 #    This is paragraph
  17 #    <b>
  18 #     two
  19 #    </b>
  20 #    .
  21 #   </p>
  22 #  </body>
  23 # </html>
  24 
  25 soup.p.replaceWith(soup.b)
  26 # <html>
  27 #  <head>
  28 #   <title id="theTitle">
  29 #    New title
  30 #   </title>
  31 #  </head>
  32 #  <body>
  33 #   <b>
  34 #    two
  35 #   </b>
  36 #  </body>
  37 # </html>
  38 
  39 soup.body.insert(0, "This page used to have ")
  40 soup.body.insert(2, " &lt;p&gt; tags!")
  41 soup.body
  42 # <body>This page used to have <b>two</b> &lt;p&gt; tags!</body>

Приведем реальный пример. Скачаем ICC Commercial Crime Services weekly piracy report, произведем его синтаксический разбор с помощью Beautiful Soup и выведем на экран сообщения о случаях пиратства (piracy incidents):

   1 import urllib2
   2 from BeautifulSoup import BeautifulSoup
   3 
   4 page = urllib2.urlopen("http://www.icc-ccs.org/prc/piracyreport.php")
   5 soup = BeautifulSoup(page)
   6 for incident in soup('td', width="90%"):
   7     where, linebreak, what = incident.contents[:3]
   8     print where.strip()
   9     print what.strip()
  10     print

Синтаксический разбор документа

Для работы конструктору Beautiful Soup требуется документ XML или HTML в виде строки (или открытого файлоподобного объекта). Он произведет синтаксический разбор и создаст в памяти структуры данных, соответствующие документу.

Если обработать с помощью Beautiful Soup хорошо оформленный документ, то разобранная структура будет выглядеть также как и исходный документ. Но если его разметка будет содержать ошибки, то Beautiful Soup использует эвристические методы для построения наиболее подходящей структуры данных.

Синтаксический разбор HTML

Используйте класс BeautifulSoup для синтаксического разбора документа HTML. Несколько фактов, которые необходимо знать о BeautifulSoup:

Некоторые теги могут быть вложенными (<BLOCKQUOTE>), а некоторые - нет (<P>). Таблицы и списки тегов имеют естественный порядок вложенности. Например, теги <TD> появляются только в обрамлении тегов <TR> и никак иначе. Содержимое тега <SCRIPT> не будет участвовать в разборе HTML. Тег <META> может определять кодировку документа. Вот как это работает:

   1 from BeautifulSoup import BeautifulSoup
   2 html = "<html><p>Para 1<p>Para 2<blockquote>Quote 1<blockquote>Quote 2"
   3 soup = BeautifulSoup(html)
   4 print soup.prettify()
   5 # <html>
   6 #  <p>
   7 #   Para 1
   8 #  </p>
   9 #  <p>
  10 #   Para 2
  11 #   <blockquote>
  12 #    Quote 1
  13 #    <blockquote>
  14 #     Quote 2
  15 #    </blockquote>
  16 #   </blockquote>
  17 #  </p>
  18 # </html>

Обратите внимание на то, что BeautifulSoup вычисляет наиболее вероятные места для закрывающих тегов, даже если они отсутствуют в исходном документе.

Хотя приведенный документ HTML является невалидным, с ним все же можно работать. А вот действительно отвратительный документ. Помимо всего прочего он содержит тег <FORM>, который начинается вне тега <TABLE>, но закрывается внутри тега <TABLE>. (Пример такого HTML был найден на веб-сайте одной из ведущих веб-компаний.)

   1 from BeautifulSoup import BeautifulSoup
   2 html = """
   3 <html>
   4 <form>
   5  <table>
   6  <td><input name="input1">Row 1 cell 1
   7  <tr><td>Row 2 cell 1
   8  </form> 
   9  <td>Row 2 cell 2<br>This</br> sure is a long cell
  10 </body> 
  11 </html>"""

Beautiful Soup справится с обработкой и такого документа:

   1 print BeautifulSoup(html).prettify()
   2 # <html>
   3 #  <form>
   4 #   <table>
   5 #    <td>
   6 #     <input name="input1" />
   7 #     Row 1 cell 1
   8 #    </td>
   9 #    <tr>
  10 #     <td>
  11 #      Row 2 cell 1
  12 #     </td>
  13 #    </tr>
  14 #   </table>
  15 #  </form>
  16 #  <td>
  17 #   Row 2 cell 2
  18 #   <br />
  19 #   This 
  20 #   sure is a long cell
  21 #  </td>
  22 # </html>

Последняя ячейка таблицы находится вне тега <TABLE>; Beautiful Soup решил закрыть тег <TABLE> там, где закрыт тег <FORM>. Автор исходного документа планировал, вероятно, продлить действие тега <FORM> до конца таблицы, но Beautiful Soup не сможет об этом догадаться. Даже в таком необычном случае Beautiful Soup произведет синтаксический разбор и предоставит вам доступ ко всем данным документа.

Синтаксический разбор XML

Класс BeautifulSoup содержит эвристики, полностью аналогичные применяющимся в веб-браузерах, что позволяет делать предположения о замыслах авторов HTML файлов. Но в XML нет фиксированного порядка тегов такие эвристики здесь не пригодятся. Поэтому BeautifulSoup не сможет хорошо работать с XML.

Используйте класс BeautifulStoneSoup для синтаксического разбора документов XML. Это основной класс, не требующий знания диалекта XML и имеющий очень простые правила о вложенности тегов: Вот он в действии:

   1 from BeautifulSoup import BeautifulStoneSoup
   2 xml = "<doc><tag1>Contents 1<tag2>Contents 2<tag1>Contents 3"
   3 soup = BeautifulStoneSoup(xml)
   4 print soup.prettify()
   5 # <doc>
   6 #  <tag1>
   7 #   Contents 1
   8 #   <tag2>
   9 #    Contents 2
  10 #   </tag2>
  11 #  </tag1>
  12 #  <tag1>
  13 #   Contents 3
  14 #  </tag1>
  15 # </doc>

Одним из общеизвестных недостатков BeautifulStoneSoup является то, что он не знает о самозакрывающихся тегах. В HTML имеется фиксированный набор самозакрывающихся тегов, но в случае с XML все зависит от того, что записано в DTD. Вы можете сообщить BeautifulStoneSoup, что определенные теги являются самозакрывающимися, передав их имена через аргумент конструктора selfClosingTags:

   1 from BeautifulSoup import BeautifulStoneSoup
   2 xml = "<tag>Text 1<selfclosing>Text 2"
   3 print BeautifulStoneSoup(xml).prettify()
   4 # <tag>
   5 #  Text 1
   6 #  <selfclosing>
   7 #   Text 2
   8 #  </selfclosing>
   9 # </tag>
  10 
  11 print BeautifulStoneSoup(xml, selfClosingTags=['selfclosing']).prettify()
  12 # <tag>
  13 #  Text 1
  14 #  <selfclosing />
  15 #  Text 2
  16 # </tag>

Если это не работает

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