2012-03-14 2 views
5

Я использую простой HTMLParser для анализа веб-страницы с кодом, который всегда хорошо сформирован (он автоматически генерируется). Он работает хорошо, пока он не попадет в кусок данных с надписью «&» - кажется, что это делает его двумя отдельными фрагментами данных и обрабатывает их отдельно. (То есть, он дважды вызывает «handle_data».) Я сначала подумал, что снятие «&» решит проблему, но я не думаю, что это так. Есть ли у кого-нибудь предложения о том, как я могу заставить мой парсер лечить, например, «Paradise Bakery and Cafe» (то есть «Paradise Bakery & Café») как единый элемент данных, а не как два?Python HTMLParser, делящий данные на &

Спасибо большое, BSG

P.S. Пожалуйста, не говорите мне, что я действительно должен использовать BeautifulSoup. Я знаю. Но в этом случае я знал, что разметка гарантированно будет хорошо сформирована каждый раз, и я нашел HTMLParser более удобным для работы, чем BeautifulSoup. Благодарю.

Я добавляю свой код - спасибо!

#this class, extending HTMLParser, is written to process HTML within a <ul>. 
#There are 6 <a> elements nested within each <li>, and I need the data from the second 
#one. Whenever it encounters an <li> tag, it sets the 'is_li' flag to true and resets 
#the count of a's seen to 0; whenever it encounters an <a> tag, it increments the count 
#by 1. When handle_data is called, it checks to make sure that the data is within 
#1)an li element and 2) an a element, and that the a element is the second one in that 
#li (num_as == 2). If so, it adds the data to the list. 

class MyHTMLParser(HTMLParser): 
pages = [] 
is_li = 'false' 
#is_li 
num_as = 0 

def _init_(self): 
    HTMLParser._init_(self) 
    self.pages = [] 
    self.is_li = 'false' 
    self.num_as = 0 
    self.close_a = 'false' 
    sel.close_li = 'false' 
    print "initialized" 


def handle_starttag(self, tag, attrs): 
     if tag == 'li': 
      self.is_li = 'true' 
      self.close_a = 'false' 
      self.close_li = 'false' 


     if tag == 'a' and self.is_li == 'true': 
      if self.num_as < 7: 
       self.num_as += 1 
       self.close_a = 'false' 

      else: 
       self.num_as = 0 
       self.is_li = 'false' 

def handle_endtag(self, tag): 
    if tag == 'a': 
     self.close_a = 'true' 

    if tag == 'li': 
     self.close_li = 'true' 
     self.num_as = 0 

def handle_data(self, data): 
    if self.is_li == 'true': 
     if self.num_as == 2 and self.close_li == 'false' and self.close_a == 'false': 
      print "found data", data 
      self.pages.append(data) 

def get_pages(self): 
    return self.pages 

ответ

8

Это потому, что & является началом объекта HTML. Отображаемый & должен быть представлен как &amp; в HTML (хотя в браузерах будет отображаться &, за которым следует пробел как амперсанд, я считаю, что это технически недействительно).

Вам просто нужно написать handle_data() для размещения нескольких вызовов, например, с использованием переменной-члена, который получает значение [], когда вы видите ваш начальный тег и прилагается к каждому вызову handle_data() и затем вступил в строка, когда вы видите свой конечный тег.

Я взломал его внизу. Ключевые строки, которые я добавил, содержат комментарий # *****. Я также взял на себя смелость использовать правильные булевы для ваших флагов, а не строк, поскольку это позволяет сделать код намного чище (надеюсь, я не испортил это). Я также поменял свой __init__() на reset() (так что ваш объект парсера можно было повторно использовать) и удалил лишние переменные класса. Наконец, я добавил методы handle_entityref() и handle_charref() для обработки объектов с экранированными символами.

class MyHTMLParser(HTMLParser): 

    def reset(self): 
     HTMLParser.reset(self) 
     self.pages = [] 
     self.text  = []      # ***** 
     self.is_li = False 
     self.num_as = 0 
     self.close_a = False 
     self.close_li = False 

    def handle_starttag(self, tag, attrs): 
      if tag == 'li': 
       self.is_li = True 
       self.close_a = False 
       self.close_li = False 

      if tag == 'a' and self.is_li: 
       if self.num_as < 7: 
        self.num_as += 1 
        self.close_a = False 
       else: 
        self.num_as = 0 
        self.is_li = False 

    def handle_endtag(self, tag): 
     if tag == 'a': 
      self.close_a = True 
     if tag == 'li': 
      self.close_li = True 
      self.num_as = 0 
      self.pages.append("".join(self.text))  # ***** 
      self.text = []        # ***** 

    def handle_data(self, data): 
     if self.is_li: 
      if self.num_as == 2 and not self.close_li and not self.close_a: 
       print "found data", data 
       self.text.append(data)    # ***** 

    def handle_charref(self, ref): 
     self.handle_entityref("#" + ref) 

    def handle_entityref(self, ref): 
     self.handle_data(self.unescape("&%s;" % ref)) 

    def get_pages(self): 
     return self.pages 

Основная идея заключается в том, что вместо добавления к self.pages при каждом вызове handle_data() вы вместо того, чтобы добавить к self.text. Затем вы найдете какое-то другое событие, которое будет происходить раз для каждого текстового элемента (я выбрал, когда вы видите тег </li>, но может быть, когда вы видите , я не могу сказать, не видя некоторых ваших данных тоже), присоединяйтесь к тем биты текста и добавить , что - pages.

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

+0

Я добавил свой код - большое вам спасибо за ваше предложение! – bsg

+0

Добавлено мои изменения в ваш код. – kindall

+0

Он работал красиво - большое вам спасибо! И спасибо за очистку моего кода - верьте или нет, мои самообученные навыки Python каким-то образом не включали правильный способ делать булевы. – bsg

2

Unescaping & приведет к странному поведению &amp;. Я создал класс, который не разбивает данные на куски в объектах &. Вы можете найти его HERE.

Смежные вопросы