Web抓取中的多级标记存在检查 - 提高了Python中的可读性

时间:2018-05-05 04:17:34

标签: python error-handling web-scraping beautifulsoup

我正在研究一个刮刀,该刮刀贯穿从同一模板构建的许多页面。每个页面都包含有关特定项目的一些信息。在乐观的情况下,我希望得到所有可用的数据,为简单起见,我们说这意味着名称,价格和描述。

页面结构如下:

<div id="content">
  <h1>Product name</h1>
  <table id="properties">
    <tbody>
      <tr id="manufacturer-row">
        <th>Manufacturer</th>
        <td>Some-Mark</td>
      </tr>
    </tbody>
  </table>
  <p>Full description of the product</p>
</div>

适用于此案例的条件:

  1. 标签是嵌套的,因此我需要测试每个级别的存在,
  2. 有些页面会遗漏一些数据 - 表格中的空列与丢失的表格一样,
  3. 有些网页根本没有内容,
  4. 标记中的空文本是有效值,但必须记录缺少的标记
  5. 缺少数据并非例外情况。
  6. 实际上,我测试检查每条信息的存在,这导致几乎难以理解的代码:

    content = soup.select_one("#content")
    if content:
        product_name_tag = content.select_one("h1")
        if product_name_tag:
            name = product_name_tag.text
        else:
            log("Product name tag not found")
    
        table = content.select_one("table")
        if table:
            manufacturer_tag = table.select_one("#manufacturer-row > td")
            if manufacturer_tag:
                manufacturer = manufacturer_tag.text
            else:
                log("Manufacturer tag not found")
        else:
            log("Table not found")
    else:
        log("Tag '#content' not found")
    
    return (
        name if name in locals() else None,
        manufacturer if manufacturer in locals() else None
    )
    

    在实际应用中,代码更难以阅读,因为我正在寻找的属性通常更嵌套,我需要在提取文本之前检查每个标记的存在。我想知道在代码可读性和简洁性方面是否有任何巧妙的方法来处理这个问题?我的想法:

    如果标记存在则创建一个提取标记文本的函数 - 会保存很少的行,但在实际应用中我必须使用正则表达式从文本中提取一些短语,因此单个函数是不够的。

    创建包装器,如果返回None而不是“else”代码,则记录缺失的部分 - 以提高可读性。

    将每个数据的提取分离到单独的函数,如_get_content_if_available,_get_name_if_available

    这些解决方案似乎都不够简洁,所以我想问你一些想法。

    我也想知道我的方法是否仅在满足某些条件时初始化变量,然后检查变量是否存在于当前上下文中是个好主意。

1 个答案:

答案 0 :(得分:0)

全部取决于您要如何构造代码。我的建议是使用ChainMap中的collections。使用ChainMap,您可以为标签/键指定默认值,然后仅解析不丢失的值。这样,您就不会在代码库中杂乱无章了:

data = """<div id="content">
  <h1>Product name</h1>
  <table id="properties">
    <tbody>
      <tr id="manufacturer-row">
        <th>Manufacturer</th>
        <td>Some-Mark</td>
      </tr>
    </tbody>
  </table>
  <p>Full description of the product</p>
</div>"""

from bs4 import BeautifulSoup
from collections import ChainMap

def my_parse(soup):
    def is_value_missing(k, v):
        if v is None:
            print(f'Value "{k}" is missing!') # or log it!
        return v is None

    d = {}
    d['product_name_tag'] = soup.select_one("h1")
    d['manufacturer_tag'] = soup.select_one("#manufacturer-row td")
    d['description'] = soup.select_one("p")
    d['other value'] = soup.select_one("nav")   # this is missing!
    return {k:v.text for k, v in d.items() if is_value_missing(k, v) == False}

soup = BeautifulSoup(data, 'lxml')
c = ChainMap(my_parse(soup), {'product_name_tag': '-default name tag-',
             'manufacturer_tag': '-default manufacturer tag-',
             'description': '-default description-',
             'other value': '-default other value-',
             })

print("Product name = ", c['product_name_tag'])
print("Other value = ", c['other value'])

这将打印:

Value "other value" is missing!
Product name =  Product name
Other value =  -default other value-