2014-02-09 2 views
1

Я использую красивый суп, чтобы попытаться очистить таблицу товаров с Oil-Price.net. Я могу найти первый div, стол, тело таблицы и строки тела таблицы. Но в одной из строк есть столбец, который я не могу найти, используя красивый суп. Когда я говорю python для печати всех таблиц в этой конкретной строке, он не отображает тот, который я хочу. Это мой код:Почему BeautifulSoup не находит определенный класс таблицы?

from urllib2 import urlopen 
from bs4 import BeautifulSoup 

html = urlopen('http://oil-price.net').read() 
soup = BeautifulSoup(html) 

div = soup.find("div",{"id":"cntPos"}) 
table1 = div.find("table",{"class":"cntTb"}) 
tb1_body = table1.find("tbody") 
tb1_rows = tb1_body.find_all("tr") 
tb1_row = tb1_rows[1] 
td = tb1_row.find("td",{"class":"cntBoxGreyLnk"}) 
print td 

Все распечатки - это None. Я даже пытаюсь напечатать каждую из строк, чтобы увидеть, могу ли я найти столбец вручную и ничего. `` Он покажет другим. Но не тот, который я хочу.

+1

«Но есть одна колонка в одной из строк, которую я не могу найти» Какой столбец, какая строка? –

+0

@KaranGoel: включен URL, и я смог воспроизвести проблему. –

+0

Возможный дубликат [Недостающие детали на результатах Beautiful Soup] (http://stackoverflow.com/questions/18614305/missing-parts-on-beautiful-soup-results) –

ответ

5

На странице используется разбитый HTML-код, и различные синтаксические анализаторы попытаются восстановить его по-разному. Установите lxml анализатор, он разбирает, что страница лучше:

>>> BeautifulSoup(html, 'html.parser').find("div",{"id":"cntPos"}).find("table",{"class":"cntTb"}).tbody.find_all("tr")[1].find("td",{"class":"cntBoxGreyLnk"}) is None 
True 
>>> BeautifulSoup(html, 'lxml').find("div",{"id":"cntPos"}).find("table",{"class":"cntTb"}).tbody.find_all("tr")[1].find("td",{"class":"cntBoxGreyLnk"}) is None 
False 

Это не означает, что lxml будет обрабатывать все сломанный HTML лучше, чем другие варианты синтаксического анализа. Также посмотрите на html5lib, реализацию pure-Python WHATWG HTML spec и, следовательно, более подробно следует, как текущие версии браузера обрабатывают разбитый HTML.

+0

BeautifulSoup (html, 'html5lib) решил мою проблему – Barny

+0

@Barny: yup, опять же, разные парсеры могут по-разному подойти к сломанному HTML. 'html5lib' реализует варианты, которые делают большинство современных браузеров, но поскольку он реализован в чистом Python, он также медленнее. –

5

Глядя на исходный код страницы:

<td class="cntBoxGreyLnk" rowspan="2" valign="top"> 
    <script type="text/javascript" src="http://www.oil-price.net/COMMODITIES/gen.php?lang=en"></script> 
    <noscript> To get live <a href="http://www.oil-price.net/dashboard.php?lang=en#COMMODITIES">gold, oil and commodity price</a>, please enable Javascript.</noscript> 

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

Если вы посмотрите на связанный URL сценария на http://www.oil-price.net/COMMODITIES/gen.php?lang=en вы увидите кучу JavaScript, как

document.writeln('<table summary=\"Crude oil and commodity prices (c) http://oil-price.net\" style=\"font-family: Lucida Sans Unicode, Lucida Grande, Sans-Serif; font-size: 12px; background: #fff; border-collapse: collapse; text-align: left; border-color: #6678b1; border-width: 1px 1px 1px 1px; border-style: solid;\">'); 
document.writeln('<thead>'); 
/* ... */ 
document.writeln('<tr>'); 
document.writeln('<td style=\"font-size: 12px; font-weight: bold; border-bottom: 1px solid #ccc; color: #1869bd; padding: 2px 6px; white-space: nowrap;\">'); 
document.writeln('<a href=\"http://oil-price.net/dashboard.php?lang=en#COMMODITIES\" style=\"color: #1869bd; text-decoration:none\">Heating Oil<\/a>'); 
document.writeln('<\/td>'); 
document.writeln('<td style=\"font-size: 12px; font-weight: normal; border-bottom: 1px solid #ccc; color: #000000; padding: 2px 6px; white-space: nowrap;\">'); 
document.writeln('3.05'); 
document.writeln('<\/td>'); 
document.writeln('<td style=\"font-size: 12px; font-weight: normal; border-bottom: 1px solid #ccc; color: green; padding: 2px 6px; white-space: nowrap;\">'); 
document.writeln('+1.81%'); 
document.writeln('<\/td><\/tr>'); 

При загрузке страницы, этот браузер запуска и динамически записывает в значениях, которые вы ищете. (В стороне: это совершенно архаичный, оскорбленный и, как правило, ужасный способ делать вещи, я могу только предположить, что кто-то думает об этом как о дополнительном уровне безопасности. Они заслуживают наказания за их несчастье!).

Теперь этот код довольно прямолинейный; вы могли бы, вероятно, захватить html-данные с регулярным выражением. Но (а) есть некоторые escape-коды, которые могут вызвать проблемы, (б) нет никакой гарантии, что они не могут запутать свой код в будущем, и (c) где это забавно?

PyV8 module обеспечивает прямолинейный способ выполнения кода javascript из Python и даже позволяет нам писать код Python, вызываемый javascript! Мы будем воспользоваться этим, чтобы получить данные в не obfuscatable образом:

import PyV8 
import requests 
from bs4 import BeautifulSoup 

SCRIPT = "http://www.oil-price.net/COMMODITIES/gen.php?lang=en" 

class Document: 
    def __init__(self): 
     self.lines = [] 

    def writeln(self, s): 
     self.lines.append(s) 

    @property 
    def content(self): 
     return '\n'.join(self.lines) 

class DOM(PyV8.JSClass): 
    def __init__(self): 
     self.document = Document() 

def main(): 
    # Create a javascript context which contains 
    # a document object having a writeln method. 
    # This allows us to capture the calls to document.writeln() 
    dom = DOM() 
    ctxt = PyV8.JSContext(dom) 
    ctxt.enter() 

    # Grab the javascript and execute it 
    js = requests.get(SCRIPT).content 
    ctxt.eval(js) 

    # The result is the HTML code you are looking for 
    html = dom.document.content 

    # html is now "<table> ... </table>" containing the data you are after; 
    # you can go ahead and finish parsing it with BeautifulSoup 
    tbl = BeautifulSoup(html) 
    for row in tbl.findAll('tr'): 
     print('/'.join(td.text.strip() for td in row.findAll('td'))) 

if __name__ == "__main__": 
    main() 

Это приводит к:

Crude Oil/99.88/+2.04% 
Natural Gas/4.78/-3.27% 
Gasoline/2.75/+2.40% 
Heating Oil/3.05/+1.81% 
Gold/1263.30/+0.45% 
Silver/19.92/+0.06% 
Copper/3.27/+0.37% 

, который данные, которые вы хотели.

Редактировать: Я больше не могу опустить его; это мертвый минимальный код, который выполняет эту работу. Но, может быть, я могу лучше объяснить, как это работает (это действительно не так страшно, как кажется!):

Модуль PyV8 обертывает переводчик Javascript Google V8 таким образом, что Python может взаимодействовать с ним.Вам нужно будет перейти на https://code.google.com/p/pyv8/downloads/list, чтобы загрузить и установить соответствующую версию, прежде чем вы сможете запустить мой код.

Язык javascript сам по себе не знает ничего о том, как взаимодействовать с внешним миром; он не имеет встроенных методов ввода или вывода. Это не очень полезно. Чтобы решить эту проблему, мы можем перейти в «контекстный объект», который содержит информацию о внешнем мире и как взаимодействовать с ним. Когда javascript запускается в веб-браузере, он получает объект контекста, который предоставляет все виды информации о браузере и текущей веб-странице и о том, как с ними взаимодействовать.

Код javascript от http://www.oil-price.net/COMMODITIES/gen.php?lang=en предполагает, что он будет запущен в браузере, где контекст имеет объект «документ», представляющий веб-страницу с методом «writeln», который добавляет текст в текущий конец веб-страницы стр. Когда страница загружается, скрипт загружается и запускается; он записывает текст (который, как раз, является действительным HTML) на страницу; это становится отображаемым как часть страницы, заканчивая как таблица товаров, которую вы хотели. Вы не можете получить таблицу с BeautifulSoup, потому что таблица не существует до запуска javascript, а BeautifulSoup не загружает и не запускает javascript.

Мы хотим запустить javascript; для этого нам нужен фальшивый контекст браузера, который имеет объект «document» с методом «writeln». Затем нам нужно сохранить информацию, которая передается в «writeln», и нам нужен способ вернуть ее, когда скрипт закончен. Мой класс DOM - это поддельный контекст браузера; при создании экземпляра (т. е. когда мы делаем один из них), он дает объект Document, называемый документом, который имеет метод writeln. Когда вызывается document.writeln, он добавляет строку текста в document.lines, и в любое время мы можем вызвать document.content, чтобы вернуть весь текст, написанный до сих пор.

Теперь: действие! В основной функции мы создаем фальшивый контекст браузера, устанавливаем его как текущий контекст интерпретатора и запускаем интерпретатор. Мы захватим код javascript и попросим интерпретатора оценить (т.е. запустить) его. (Обфускация исходного кода, которая может испортить статический анализ, не повлияет на нас, потому что код должен выдавать хороший результат при запуске, и мы фактически запускаем его!) Как только код будет закончен, мы получим окончательный вывод из документа .context; это таблица html, которую вы не смогли получить. Мы передаем это обратно в BeautifulSoup, чтобы вытащить данные, а затем распечатать данные.

Надеюсь, что это поможет!

+0

Ненавижу говорить об этом, но так или иначе вы можете опустить это для новичка. Я еще точно не нырнул в классы/объекты/методы. – user3047023

+0

Проблема не в динамической нагрузке; таблица находится прямо там. Но HTML-код не работает, и стандартный анализатор 'html.parser' не может восстановить повреждение. –

+0

@MartijnPieters: ты беспокоился об этом? Если вы загружаете страницу http://oil-price.net в браузере, то используйте «View Source», там есть таблица, потому что javascript запущен. Если вы загружаете его с urllib3 или запросами, это не так, потому что у него нет. Пожалуйста, проверьте это для себя, прежде чем «исправлять» меня: 'import urllib2',' html = urllib2.urlopen ('http://oil-price.net') .read() .splitlines() ',' print ('\ n'.join (HTML [588: 594])) '. –

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