首页 > 解决方案 > Python,BeautifulSoup:在解析 HTML 表时,只返回一个 CSV 行或不断收到“AttributeError:'NoneType' object has no attribute 'text'”

问题描述

更新: HedgeHog 的回答奏效了。为了克服 numpy 问题,我卸载了 numpy-1.19.4 并安装了以前的版本 numpy-1.19.3。

[Python 3.9.0 和 BeautifulSoup 4.9.0。]

我正在尝试使用 Python 中的 BeautifulSoup 库来解析在司法部法律顾问办公室网站上找到的 HTML 表,并将数据写入 CSV 文件。该表格可在https://www.justice.gov/olc/opinions?keys=&items_per_page=40找到。

该表深深嵌套在 11 个<div>元素中。直到表格位置的 HTML 的删节美化版本是:

<html>
 <body>
  <section>
   <11 continually nested div elements>
    ...
    <table>
    </table>
    ...
   </divs>
  </section>
 </body>
</html>

该表是一个简单的三列表,顶部有一个标题行(位于<thead>元素内部),如下所示:

日期 标题 头注
2021 年 1 月 19 日 PREP 法案声明下的州和地方要求优先权 《公共准备和应急准备法》和卫生与公众服务部部长根据该法发布的 COVID -19 声明优先于州或地方要求,例如州许可法,这些要求将禁止或有效禁止符合条件的州许可药剂师订购管理 FDA 批准的 COVID -19 测试和 FDA 授权或 FDA 许可的 COVID -19 疫苗。

这些<tr>元素具有四个不同的类之一:

  1. <tr class="odd views-row-first">- 这仅存在于标题行之后的第一行。
  2. <tr class="even">- 出现在每个偶数表行上
  3. <tr class="odd">- 出现在第一行之后的每个奇数行
  4. <tr class="even views-row-last">- 出现在最后一行(用户可以选择每页查看 10、20 或 40 个项目,这意味着最后一行将始终是偶数)

<tr>元素中,每个<td>元素自然对应于一种数据类型(日期、标题、标题)。尽管有特定的<tr>类,但每个表行都遵循相同的一般格式:

<tr class="odd-or-even/first-or-last">
  <td class="views-field views-field-field-opinion-post-date active">
    <span class="date-display-single" . . . >
      01/01/1970
    </span>
  </td>
  <td class="views-field views-field-field-opinion-attachment-file">
    <a href="/olc/files/file-number/download">
      Title
    </a>
  </td>
  <td class="views-field views-field-field-opinion-overview">
    <p>
      Headnotes
    </p>
    <p>
      Some headnotes have multiple paragraph elements.
    </p>
  </td>
</tr>

我使用的所有 Python 脚本都是从这个开始的:

import requests
from bs4 import BeautifulSoup

r = requests.get("https://www.justice.gov/olc/opinions?keys=&items_per_page=40")
soup = BeautifulSoup(r.text, "html.parser")

f = open("olc-op.csv", "w", encoding="utf-8")
headers = "Date, Title, Headnotes \n"
f.write(headers)

我的修修补补主要集中在find_all()论点和for loop.

我遇到的问题是我的 CSV 文件中只有一行,或者这篇文章的标题中有错误。

由于<td>我要抓取的所有元素都在<tbody>元素内,所以我运行tbodyfind_all()

requests = soup.find_all("tbody")

for loopI 中指定<td>为元素,后跟应用于每个数据的类名:

for result in results:
    date = result.find("td", class_="views-field views-field-field-opinion-post-date active").text
    title = result.find("td", class_="views-field views-field-field-opinion-attachment-file").text
    headnotes = result.find("td", class_="views-field views-field-field-opinion-overview").text
    data = date + "," + title + "," + headnotes
    f.write(data)

上述代码在 CSV 文件中的输出为:

Date,Title,Headnotes

01/19/2021 ,
Preemption of State and Local Requirements Under a PREP Act Declaration ,
The Public Readiness and Emergency Preparedness Act and the COVID -19 declaration issued by the Secretary of Health and Human Services under that Act preempt state or local requirements, such as state licensing laws, that would prohibit or effectively prohibit qualifying state-licensed pharmacists from ordering and administering FDA-approved COVID -19 tests and FDA-authorized or FDA-licensed COVID -19 vaccines.

是的,数据在技术上用逗号分隔,但不是我想要的方式。标题行之后还有一些不需要的空格。

我用 替换了语句.text末尾的,它返回了以下 TypeError:.find().striped_strings

TypeError: unsupported operand type(s) for +: 'NoneType' and 'str'

为了尝试克服这个错误,我更改f.write(data)f.write(str(data))in for loop,并收到了相同的TypeError.

我做了一些进一步的研究,并将for loopfrom中的每个变量的结尾更改.striped_strings.get_text(strip=True)。我也将我的f.write()声明更改为

f.write(date + "," + title + "," + headnotes)

除了标题行之外,这些更改还产生了一个完美刮掉的表格行:

Date, Title, Headnotes 
01/19/2021,Preemption of State and Local Requirements Under a PREP Act Declaration,The Public Readiness and Emergency Preparedness Act and the COVID -19 declaration issued by the Secretary of Health and Human Services under that Act preempt state or local requirements, such as state licensing laws, that would prohibit or effectively prohibit qualifying state-licensed pharmacists from ordering and administering FDA-approved COVID -19 tests and FDA-authorized or FDA-licensed COVID -19 vaccines.

但我显然想遍历整个表格并获取所有表格行。

我尝试的倒数第二件事可能是在find_all()声明中更具体。我将其从未指定类更改为,因此(我认为)它会返回所有tbody元素 ,然后我可以解析特定元素。相反,我收到了这个错误:tr<tr><td>

AttributeError: 'NoneType' object has no attribute 'get_text'

我做的最后一个更改是改.get_text(strip=True).text,导致这篇文章的标题出现错误:

AttributeError: 'NoneType' object has no attribute 'text'

我哪里出错了?

标签: pythonhtmlbeautifulsouphtml-table

解决方案


Alternativ 是使用pandas

经常问自己——有没有更简单的方法来实现我的目标?

是的,您可以简单地使用 pandas 分两行来完成。在你的情况下,它会为你做所有的事情。

  1. 请求网址
  2. 搜索表格并抓取内容
  3. 将结果推送到 csv

我也尝试通过你的问题,并可能会回答它。

例子

import pandas as pd

pd.read_html('https://www.justice.gov/olc/opinions?keys=&items_per_page=40')[0].to_csv('olc-op.csv', index=False)

但是回答你的问题

对提出您的问题的努力感到兴奋,我会去一些奖励里程并告诉您会发生什么。

有两个要点阻碍了您实现目标。

  1. 选择正确的东西

    你的 csv 中只有一行的原因 - 你做了这个:

    soup.find_all("tbody")
    

    所以你的循环只循环一次,因为只有一个tbody- 你弄清楚了结构并谈到了<tr>但没有选择它们进行循环。

  2. 写你的台词

    即使您修复了上述问题,您也只会在 csv 中找到一行,因为\n您的字符串中缺少 。

希望这有助于理解,出了什么问题,你可以使用它以防万一pandas,动态服务内容的原因,......

例子

import requests
from bs4 import BeautifulSoup

r = requests.get("https://www.justice.gov/olc/opinions?keys=&items_per_page=40")
soup = BeautifulSoup(r.text, "html.parser")

with open("olc-op.csv", "a+", encoding="utf-8") as f:
    headers = "Date, Title, Headnotes \n"
    f.write(headers)

    for result in soup.select("tbody tr"):
        tds = result.find_all("td")
        date = tds[0].get_text(strip=True)
        title = tds[1].get_text(strip=True)
        headnotes = tds[2].get_text(strip=True)
        data = date + "," + title + "," + headnotes +'\n'
        f.writelines(data)

推荐阅读