Пожалуйста, помогите проанализировать эту html-таблицу, используя BeautifulSoup и lxml pythonic.

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

Меня интересуют три столбца с различным количеством строк в зависимости от страницы и времени ее проверки. Решение BeautifulSoup и lxml заслуживает высокой оценки. Таким образом, я могу попросить администратора установить lxml на dev. машина.

Желаемый результат:

Website                    Last Visited          Last Loaded
http://google.com          01/14/2011 
http://stackoverflow.com   01/10/2011
...... more if present

Ниже приведен пример кода с запутанной веб-страницы:

<table border="2" width="100%">
  <tbody><tr>
    <td width="33%" class="BoldTD">Website</td>
    <td width="33%" class="BoldTD">Last Visited</td>
    <td width="34%" class="BoldTD">Last Loaded</td>
  </tr>
  <tr>
    <td width="33%">
      <a href="http://google.com"</a>
    </td>
    <td width="33%">01/14/2011
            </td>
    <td width="34%">
            </td>
  </tr>
  <tr>
    <td width="33%">
      <a href="http://stackoverflow.com"</a>
    </td>
    <td width="33%">01/10/2011
            </td>
    <td width="34%">
            </td>
  </tr>
</tbody></table>

person ThinkCode    schedule 21.01.2011    source источник
comment
Что бы вы хотели в результате? Словарная статья с названием сайта и датами? Каков источник html? Это в вашей власти?   -  person Spaceghost    schedule 21.01.2011
comment
К сожалению, источник html не находится под моим контролем. Словарная статья будет работать, просто нет. строк различаются для каждой страницы, как показано в разделе «Желаемый результат». С таблицей не связан класс, поэтому, если в содержимом таблицы есть «веб-сайт», мы получаем эти данные.   -  person ThinkCode    schedule 21.01.2011


Ответы (3)


Вот версия, использующая HTMLParser. Я попробовал содержимое pastebin.com/tu7dfeRJ. Он справляется с метатегом и объявлением типа документа, оба из которых помешали версии ElementTree.

from HTMLParser import HTMLParser

class MyParser(HTMLParser):
  def __init__(self):
    HTMLParser.__init__(self)
    self.line = ""
    self.in_tr = False
    self.in_table = False

  def handle_starttag(self, tag, attrs):
    if self.in_table and tag == "tr":
      self.line = ""
      self.in_tr = True
    if tag=='a':
     for attr in attrs:
       if attr[0] == 'href':
         self.line += attr[1] + " "

  def handle_endtag(self, tag):
    if tag == 'tr':
      self.in_tr = False
      if len(self.line):
        print self.line
    elif tag == "table":
      self.in_table = False

  def handle_data(self, data):
    if data == "Website":
      self.in_table = 1
    elif self.in_tr:
      data = data.strip()
      if data:
        self.line += data.strip() + " "

if __name__ == '__main__':
  myp = MyParser()
  myp.feed(open('table.html').read())

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

person Spaceghost    schedule 25.01.2011

>>> from lxml import html
>>> table_html = """"
...         <table border="2" width="100%">
...                       <tbody><tr>
...                         <td width="33%" class="BoldTD">Website</td>
...                         <td width="33%" class="BoldTD">Last Visited</td>
...                         <td width="34%" class="BoldTD">Last Loaded</td>
...                       </tr>
...                       <tr>
...                         <td width="33%">
...                           <a href="http://google.com"</a>
...                         </td>
...                         <td width="33%">01/14/2011
...                                 </td>
...                         <td width="34%">
...                                 </td>
...                       </tr>
...                       <tr>
...                         <td width="33%">
...                           <a href="http://stackoverflow.com"</a>
...                         </td>
...                         <td width="33%">01/10/2011
...                                 </td>
...                         <td width="34%">
...                                 </td>
...                       </tr>
...                     </tbody></table>"""
>>> table = html.fromstring(table_html)
>>> for row in table.xpath('//table[@border="2" and @width="100%"]/tbody/tr'):
...     for column in row.xpath('./td[position()=1]/a/@href | ./td[position()>1]/text() | self::node()[position()=1]/td/text()'):
...             print column.strip(),
...     print
... 
Website Last Visited Last Loaded
 http://google.com 01/14/2011 
 http://stackoverflow.com 01/10/2011 
>>> 

вуаля;) конечно, вместо печати вы можете добавить свои значения во вложенные списки или дикты;)

person virhilo    schedule 21.01.2011
comment
Спасибо за реализацию lxml! Я еще не проверил это, так как на наших машинах еще не установлен lxml. Это должен сделать администратор, так что ждем :( Можем ли мы преобразовать этот код в BeautifulSoup для быстрой проверки? - person ThinkCode; 21.01.2011
comment
Небольшая опечатка: измените «table = lxml.fromstring(table_html)» и «table = html.fromstring(table_html)», и он запустится. - person Spaceghost; 21.01.2011
comment
вы можете использовать реализацию ElementTree etree (которую вы можете легко установить с помощью easy_install или pip ), чтобы протестировать ее, но я не уверен, поддерживает ли она полный синтаксис xpath. - person virhilo; 21.01.2011
comment
В HTML есть ошибка ("‹a href=google.com‹/a›" должно быть ' ‹a href=google.com›‹/a›' ), но, как ни странно, lxml нормально работает с это, а не ElementTree. - person Spaceghost; 21.01.2011
comment
Установка lxml на centOS оказывается сложной задачей :( - person ThinkCode; 24.01.2011

Вот версия, которая использует elementtree и ограниченный XPath, который он предоставляет:

from xml.etree.ElementTree import ElementTree

doc = ElementTree().parse('table.html')

for t in doc.findall('.//table'):
  # there may be multiple tables, check we have the right one
  if t.find('./tbody/tr/td').text == 'Website':
    for tr in t.findall('./tbody/tr/')[1:]: # skip the header row
      tds = tr.findall('./td')
      print tds[0][0].attrib['href'], tds[1].text.strip(), tds[2].text.strip()

Результаты:

http://google.com 01/14/2011
http://stackoverflow.com 01/10/2011 
person Spaceghost    schedule 21.01.2011
comment
Я получаю эту ошибку: doc = ElementTree().parse('test.htm') Файл /usr/local/Python2.6/lib/python2.6/xml/etree/ElementTree.py, строка 586, в синтаксическом анализаторе. фид(данные) Файл /usr/local/Python2.6/lib/python2.6/xml/etree/ElementTree.py, строка 1245, в фиде self._parser.Parse(data, 0) xml.parsers.expat.ExpatError : синтаксическая ошибка: строка 2, столбец 61 - person ThinkCode; 22.01.2011
comment
Он жалуется на ваш xml.. строка 2, столбец 61. Вы хотите опубликовать первые строки этого файла? Либо здесь, либо в pastebin? - person Spaceghost; 22.01.2011
comment
Хммм, похоже, он ищет строгий, хорошо сформированный текст?pastebin.com/8BZQyB3b - person ThinkCode; 22.01.2011
comment
Два вопроса: Вы на Windows? это полный файл? - person Spaceghost; 22.01.2011
comment
Я на центосе. Нет, это не полный файл, вот он: pastebin.com/tu7dfeRJ - person ThinkCode; 24.01.2011
comment
Кажется, что ElementTree не нравится описание DOCTYPE в вашем полном HTML-файле, а также значения атрибутов без кавычек. - person Spaceghost; 25.01.2011
comment
HTML не находится под моим контролем, и хотя я могу удалить определенные строки, я работаю над более динамичным решением. Я застрял с решением BeautifulSoup, но я не могу правильно проанализировать данные td. Я должен задать новый вопрос, может быть? Спасибо за вашу постоянную поддержку! - person ThinkCode; 25.01.2011
comment
Я добавил новый ответ, используя HTMLParser. - person Spaceghost; 26.01.2011