【问题标题】:Split <p> with a delimeter and set predetermined tags with class names using BeautifulSoup使用分隔符拆分 <p> 并使用 BeautifulSoup 获取带有类名的预定标签
【发布时间】:2020-06-23 21:50:53
【问题描述】:

这是我通过从 docx 转换为 html 获得的输入数据/文件。由于这个原因,该文件是“不漂亮”的 html。

<p><strong>TABLE 1  EUR A++ AT JON DOE'S PLACE 27-06-2020</strong></p><p>   13.30   1   Jennifer Shea (R)       27-06   29.99   Tea 1/1 Fish and chips or Salad and rice</p><p> 15.00   2   Micheal Beltran (R)     27-06   30.99   Wine    2/2 Vegan super silky puff pastry</p><p>    16.00   3   <strong>DARIUS GALLAGHER (IP)</strong>      27-06   29.99   Wine Premium    4/4 Pear pecan and blue cheese salad</p><p> 18.00   4   Ashanti Fields (R)      27-06   N/A Wine    2/0 </p>

将文件/数据读入 Python(使用 bs4),在第 2 行我们看到:

'\t13.30\t1\tJennifer Shea (R)\t\t27-06\t29.99\tTea\t1/1\tFish and chips or Salad and rice'

&lt;p&gt;&lt;/p&gt; 标记内的文本用制表符 ('\t') 分隔。

我想将每个&lt;p&gt;&lt;/p&gt; 行内容分成更小的部分(i) 使用'\t' 作为分隔符,(ii) 将元素标签更改为表格行和 (iii) 为每个项目设置(预定义)带有类名的标签。例如我想将上面的段落行转换为:

<table>
    <tbody>
        <tr>
            <td colspan="2" class="header-table-no">TABLE 1</td>
            <td colspan="2" class="header-currency">EUR</td>
            <td colspan="2" class="header-class">A++</td>
            <td colspan="2" class="header-place-date">AT JON DOE'S PLACE 27-06-2020</td>
        </tr>
        <tr>
            <td class="cooktable-time">13.30</td>
            <td class="cooktable-no">1</td>
            <td class="cooktable-name">Jennifer Shea (R)</td>
            <td class="cooktable-date">27-06</td>
            <td class="cooktable-price">29.99</td>
            <td class="cooktable-drink">Tea</td>
            <td class="cooktable-ppfood ">1/1</td>
            <td class="cooktable-menu">Fish and chips or Salad and rice</td>
        </tr>
    </tbody>
</table>

还有大约 3000 个表要循环,并且数据是一致的。 到目前为止,我已经设法拆分每一行并添加到一个列表中:

from bs4 import BeautifulSoup                                                     
soup = BeautifulSoup(open("simplesample.html"), features="lxml") 
paragraphs = soup.find_all("p")
delimeter ='\t'
total_list=[]

for i in range(len(paragraphs)): 
     plist = (paragraphs[i].get_text()).split(delimeter) 
     total_list.append(plist) 


for i in total_list: 
     print(i) 
# Output                                                                                  
['TABLE 1', 'EUR', 'A++', "AT JON DOE'S PLACE 27-06-2020"]
['', '13.30', '1', 'Jennifer Shea (R)', '', '27-06', '29.99', 'Tea', '1/1', 'Fish and chips or Salad and rice']
['', '15.00', '2', 'Micheal Beltran (R)', '', '27-06', '30.99', 'Wine', '2/2', 'Vegan super silky puff pastry']
['', '16.00', '3', 'DARIUS GALLAGHER (IP)', '', '27-06', '29.99', 'Wine Premium', '4/4', 'Pear pecan and blue cheese salad']
['', '18.00', '4', 'Ashanti Fields (R)', '', '27-06', 'N/A', 'Wine', '2/0', '']

编辑:这里的问题是,当我对从 get_text() 获得的数据使用 str.split() 方法时,我会丢失一些格式信息。例如:在原始数据中,第 3 行 'DARIUS GALLAGHER (IP)' 实际上在 &lt;strong&gt;&lt;/strong&gt; 标签内。但是使用get_text() 自然只会返回字符串值。因此输出变为非粗体。

更新:为了澄清,我想保持 &lt;strong&gt; 原样。除表头外,&lt;strong&gt;标签在数据中随机出现。

问题是:

  1. 是否有任何 bs4 方法通过保留当前格式来解决此问题,而不是将标签拆分为字符串并添加到列表中?
  2. 从这里开始,我如何使用 BeautifulSoup 使用预先确定的标签和类名来标记列表元素?

【问题讨论】:

  • 你能分享 URL(如果有的话)吗?通常,beautifulsoup 适合解析标签,而不是纯文本 - 因为 re 模块更好。在这种情况下,我看到它是两者的某种组合。
  • @AndrejKesely 不幸的是没有 URL。我以.docx 格式收到此文件,并使用其他一些工具将其转换为.html。原始文件包含数百个数据,就像我提供的一样。我不太擅长正则表达式,你有什么样的解决方案?谢谢。
  • 我发布了答案并建议了它的外观。

标签: html python-3.x parsing beautifulsoup


【解决方案1】:

如果没有真实数据,很难提出 100% 正确的解决方案,但这个示例可以帮助您入门(它仅使用 beautifulsoup 模块和 itertools.zip_longest 来填充缺失的 cooktable-menu 条目):

from bs4 import BeautifulSoup
from itertools import zip_longest


txt = '''<p><strong>TABLE 1\tEUR\tA++\tAT JON DOE'S PLACE 27-06-2020</strong></p><p>\t13.30\t1\tJennifer Shea (R)\t\t27-06\t29.99\tTea\t1/1\tFish and chips or Salad and rice</p><p>\t15.00\t2\tMicheal Beltran (R)\t\t27-06\t30.99\tWine\t2/2\tVegan super silky puff pastry</p><p>\t16.00\t3\t<strong>DARIUS GALLAGHER (IP)</strong>\t\t27-06\t29.99\tWine Premium\t4/4\tPear pecan and blue cheese salad</p><p>\t18.00\t4\tAshanti Fields (R)\t\t27-06\tN/A\tWine\t2/0\t</p>'''


template_header = '''        <tr>
            <td colspan="2" class="header-table-no">{table_no}</td>
            <td colspan="2" class="header-currency">{currency}</td>
            <td colspan="2" class="header-class">{class_}</td>
            <td colspan="2" class="header-place-date">{place_date}</td>
        </tr>'''

template_row = '''        <tr>
            <td class="cooktable-time">{time}</td>
            <td class="cooktable-no">{no}</td>
            <td class="cooktable-name">{name}</td>
            <td class="cooktable-date">{date}</td>
            <td class="cooktable-price">{price}</td>
            <td class="cooktable-drink">{drink}</td>
            <td class="cooktable-ppfood ">{ppfood}</td>
            <td class="cooktable-menu">{menu}</td>
        </tr>'''

template = '''<table>
    <tbody>
{header}
{rows}
    </tbody>
</table>'''


soup = BeautifulSoup(txt, 'html.parser')

# remove strong tags:
for strong in soup.select('strong'):
    p = strong.parent
    strong.unwrap()
    p.smooth()

all_p = soup.select('p')

table_no, currency, class_, place_date = all_p[0].get_text(strip=True).split('\t')
h = template_header.format(table_no=table_no, currency=currency, class_=class_, place_date=place_date)

all_data = []
for p in all_p[1:]:
    all_data.append(p.get_text(strip=True).split('\t'))

r = []
for time, no, name, _, date, price, drink, ppfood, menu in zip(*zip_longest(*all_data, fillvalue='')):
    r.append(template_row.format(time=time, no=no, name=name, date=date, price=price, drink=drink, ppfood=ppfood, menu=menu))

print(template.format(header=h, rows='\n'.join(r)))

打印:

<table>
    <tbody>
        <tr>
            <td colspan="2" class="header-table-no">TABLE 1</td>
            <td colspan="2" class="header-currency">EUR</td>
            <td colspan="2" class="header-class">A++</td>
            <td colspan="2" class="header-place-date">AT JON DOE'S PLACE 27-06-2020</td>
        </tr>
        <tr>
            <td class="cooktable-time">13.30</td>
            <td class="cooktable-no">1</td>
            <td class="cooktable-name">Jennifer Shea (R)</td>
            <td class="cooktable-date">27-06</td>
            <td class="cooktable-price">29.99</td>
            <td class="cooktable-drink">Tea</td>
            <td class="cooktable-ppfood ">1/1</td>
            <td class="cooktable-menu">Fish and chips or Salad and rice</td>
        </tr>
        <tr>
            <td class="cooktable-time">15.00</td>
            <td class="cooktable-no">2</td>
            <td class="cooktable-name">Micheal Beltran (R)</td>
            <td class="cooktable-date">27-06</td>
            <td class="cooktable-price">30.99</td>
            <td class="cooktable-drink">Wine</td>
            <td class="cooktable-ppfood ">2/2</td>
            <td class="cooktable-menu">Vegan super silky puff pastry</td>
        </tr>
        <tr>
            <td class="cooktable-time">16.00</td>
            <td class="cooktable-no">3</td>
            <td class="cooktable-name">DARIUS GALLAGHER (IP)</td>
            <td class="cooktable-date">27-06</td>
            <td class="cooktable-price">29.99</td>
            <td class="cooktable-drink">Wine Premium</td>
            <td class="cooktable-ppfood ">4/4</td>
            <td class="cooktable-menu">Pear pecan and blue cheese salad</td>
        </tr>
        <tr>
            <td class="cooktable-time">18.00</td>
            <td class="cooktable-no">4</td>
            <td class="cooktable-name">Ashanti Fields (R)</td>
            <td class="cooktable-date">27-06</td>
            <td class="cooktable-price">N/A</td>
            <td class="cooktable-drink">Wine</td>
            <td class="cooktable-ppfood ">2/0</td>
            <td class="cooktable-menu"></td>
        </tr>
    </tbody>
</table>

编辑:不提取&lt;strong&gt;标签的版本:

from bs4 import BeautifulSoup
from itertools import zip_longest, chain


txt = '''<p><strong>TABLE 1\tEUR\tA++\tAT JON DOE'S PLACE 27-06-2020</strong></p><p>\t13.30\t1\tJennifer Shea (R)\t\t27-06\t29.99\tTea\t1/1\tFish and chips or Salad and rice</p><p>\t15.00\t2\tMicheal Beltran (R)\t\t27-06\t30.99\tWine\t2/2\tVegan super silky puff pastry</p><p>\t16.00\t3\t<strong>DARIUS GALLAGHER (IP)</strong>\t\t27-06\t29.99\tWine Premium\t4/4\tPear pecan and blue cheese salad</p><p>\t18.00\t4\tAshanti Fields (R)\t\t27-06\tN/A\tWine\t2/0\t</p>'''

template_header = '''        <tr>
            <td colspan="2" class="header-table-no">{table_no}</td>
            <td colspan="2" class="header-currency">{currency}</td>
            <td colspan="2" class="header-class">{class_}</td>
            <td colspan="2" class="header-place-date">{place_date}</td>
        </tr>'''

template_row = '''        <tr>
            <td class="cooktable-time">{time}</td>
            <td class="cooktable-no">{no}</td>
            <td class="cooktable-name">{name}</td>
            <td class="cooktable-date">{date}</td>
            <td class="cooktable-price">{price}</td>
            <td class="cooktable-drink">{drink}</td>
            <td class="cooktable-ppfood">{ppfood}</td>
            <td class="cooktable-menu">{menu}</td>
        </tr>'''

template = '''<table>
    <tbody>
{header}
{rows}
    </tbody>
</table>'''


soup = BeautifulSoup(txt, 'html.parser')

all_p = soup.select('p')
table_no, currency, class_, place_date = all_p[0].get_text(strip=True).split('\t')
h = template_header.format(table_no=table_no, currency=currency, class_=class_, place_date=place_date)

all_data = []
for p in all_p[1:]:
    all_data.append([t.strip() for t in chain.from_iterable(t.split('\t') for t in p.find_all(text=True)) if t.strip()])

r = []
for time, no, name, date, price, drink, ppfood, menu in zip(*zip_longest(*all_data, fillvalue='')):
    r.append(template_row.format(time=time, no=no, name=name, date=date, price=price, drink=drink, ppfood=ppfood, menu=menu))

print(template.format(header=h, rows='\n'.join(r)))

【讨论】:

  • 感谢您的解决方案。由于隐私问题,我无法公开分享真实数据,但结构与我提供的相同,并且是重复的。答案如您所描述的那样有效。但问题是,它删除了&lt;strong&gt; 标签,我很想保留它们。我会相应地更新我的问题(也许它是模棱两可的)。除标题外,&lt;strong&gt; 标签随机出现。我尝试在您的解决方案中注释掉 # remove strong tags: 部分,但它弄乱了表结构。
  • @rushas 我编辑了我的答案 - 添加了一个没有提取强标签的版本。
  • 它仍然会删除强标签。这是name 单元格中表第 3 行的所需输出示例:&lt;td class="cooktable-name"&gt;&lt;strong&gt;DARIUS GALLAGHER (IP)&lt;/strong&gt;&lt;/td&gt; 请注意,&lt;strong&gt; 标记是从原始数据中保留的。
猜你喜欢
  • 1970-01-01
  • 2015-10-13
  • 1970-01-01
  • 1970-01-01
  • 2021-03-30
  • 1970-01-01
  • 1970-01-01
  • 2019-09-14
  • 2018-06-13
相关资源
最近更新 更多