为什么BeautifulSoup没有找到特定的表类?

use*_*023 1 python beautifulsoup web-scraping

我正在使用Beautiful Soup尝试从Oil-Price.net上刮下商品表.我可以找到第一个div,table,table body和table body的行.但是其中一行中有一列我用美丽的汤找不到.当我告诉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
Run Code Online (Sandbox Code Playgroud)

所有打印都是无.我甚至尝试打印每一行,看看我是否可以手动找到列而不是任何内容."它会向别人展示.但不是我想要的那个.

Mar*_*ers 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
Run Code Online (Sandbox Code Playgroud)

这并不意味着将比其他解析器选项更好地lxml处理所有损坏的HTML.另外,看看WHATWG HTML规范html5lib的纯Python实现,因此更紧密地遵循当前浏览器实现如何处理损坏的HTML.


Hug*_*ell 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>
Run Code Online (Sandbox Code Playgroud)

您想要的数据被动态加载到页面中; 你不能用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>');
Run Code Online (Sandbox Code Playgroud)

加载页面时,将运行此javascript并动态写入您要查找的值.(顺便说一句:这是一种完全陈旧,诋毁,通常可怕的做事方式;我只能假设某人认为它是一个额外的安全层.他们应该因为他们的冒失而受到惩罚!).

现在,这段代码非常简单; 你可以用正则表达式获取html数据.但是(a)有一些逃避代码可能会导致问题,(b)不能保证他们将来不会混淆他们的代码,(c)那里的乐趣在哪里?

PyV8模块提供了一种执行从Python的javascript代码的一个直接的方法,甚至允许我们编写的JavaScript调用Python代码!我们将利用这一点以不可挽回的方式获取数据:

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()
Run Code Online (Sandbox Code Playgroud)

这导致:

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%
Run Code Online (Sandbox Code Playgroud)

这是你想要的数据.

编辑:我不能再愚蠢了; 这是完成工作的最低限度代码.但也许我可以更好地解释它是如何工作的(它真的不像看起来那么可怕!):

PyV8模块以一种Python可以与之交互的方式包装谷歌的V8 JavaScript解释器.在运行我的代码之前,您需要访问https://code.google.com/p/pyv8/downloads/list下载并安装相应的版本.

javascript语言本身并不知道如何与外部世界进行交互; 它没有内置的输入或输出方法.这不是非常有用.为了解决这个问题,我们可以传入一个"上下文对象",其中包含有关外部世界的信息以及如何与之交互.当javascript在Web浏览器中运行时,它会获得一个上下文对象,该对象提供有关浏览器和当前网页的各种信息以及如何与它们进行交互.

从JavaScript代码http://www.oil-price.net/COMMODITIES/gen.php?lang=en假定它会在浏览器中,在上下文具有代表的网页,"文档"对象中运行的有一个"writeln"方法,它将文本附加到网页的当前末尾.在加载页面时,脚本会被加载并运行; 它将文本(恰好是有效的HTML)写入页面; 这将作为页面的一部分呈现,最终成为您想要的商品表.你无法使用BeautifulSoup获取表,因为在javascript运行之前表不存在,并且BeautifulSoup不加载或运行javascript.

我们想运行javascript; 要做到这一点,我们需要一个虚假的浏览器上下文,其中包含一个带有"writeln"方法的"文档"对象.然后我们需要存储传递给"writeln"的信息,我们需要一种方法在脚本完成后将其恢复.我的DOM类是虚假的浏览器上下文; 当实例化时(即当我们创建其中一个时),它给自己一个名为document的Document对象,它有一个writeln方法.当调用document.writeln时,它会将文本行附加到document.lines,并且我们可以随时调用document.content来获取到目前为止所写的所有文本.

现在:行动!在main函数中,我们创建一个虚假的浏览器上下文,将其设置为解释器的当前上下文,并启动解释器.我们抓取javascript代码,并告诉解释器评估(即运行)它.(源代码混淆,可以搞砸了静态分析,不会影响到我们,因为代码已经在运行时产生良好的输出,而我们实际运行它!)一旦代码完成后,我们从文件的最终输出.context; 这是你无法获得的表格html.我们将其传递回BeautifulSoup以提取数据,然后打印数据.

希望有所帮助!