Вопрос:
Я использую Beautiful Soup, чтобы попробовать и очистить таблицу товаров от Oil-Price.net. Я могу найти первый div, стол, тело таблицы и строки тела таблицы. Но в одной из строк есть столбец, который я не могу найти, используя красивый суп. Когда я говорю python для печати всех таблиц в этой конкретной строке, он не отображает тот, который я хочу. Это мой код:
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. Я даже пытаюсь напечатать каждую из строк, чтобы увидеть, могу ли я найти столбец вручную и ничего. “Это покажет другим. Но не тот, который я хочу.
Лучший ответ:
На странице используется разбитый 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 и, следовательно, более подробно следует, как текущие версии браузера обрабатывают разбитый HTML.
Ответ №1
Глядя на источник страницы:
<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.
Если вы посмотрите ссылку на связанный скрипт по адресу 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>’);
Когда страница загружается, этот javascript запускается и динамически записывается в значения, которые вы ищете. (В стороне: это совершенно архаичный, оскорбленный и, как правило, ужасный способ делать вещи, я могу только предположить, что кто-то думает об этом как о дополнительном уровне безопасности. Они заслуживают наказания за их безрассудство!).
Теперь этот код довольно прямолинейный; вы могли бы, вероятно, захватить html-данные с регулярным выражением. Но (а) есть некоторые escape-коды, которые могут вызвать проблемы, (б) нет гарантии, что они не могли бы запутать свой код в будущем, и (c) где в этом весело?
Модуль PyV8 обеспечивает прямолинейный метод выполнения кода javascript из Python и даже позволяет нам писать код Python, вызываемый javascript! Мы воспользуемся этим, чтобы получить данные в несовместимом виде:
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, чтобы вытащить данные, а затем распечатать данные.
Надеюсь, это поможет!