转换 HTML 文件 to Pipe('|')带分隔符的文本文件

Convert HTML File to Pipe('|') Delimited Text file

提问人:Sooraj P George 提问时间:11/16/2023 最后编辑:Sooraj P George 更新时间:11/22/2023 访问量:99

问:

我收到了一个巨大的 HTML 表格数据,必须转换为带有单引号 (') 的管道分隔文本文件。 我正在寻找一个shell脚本来做到这一点。

下面是示例 html,

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>Data</title>
</head>
<body>
<table border=1>
<tr>
<td bgcolor=silver class='medium'>patientName</td>
<td bgcolor=silver class='medium'>Address</td>
<td bgcolor=silver class='medium'>Age</td>
</tr>

<tr>
<td class='normal' valign='top'>Sanju</td>
<td class='normal' valign='top'>My address, Pin:12345</td>
<td class='normal' valign='top'>1</td>
</tr>
</table>
</body></html>

以下是文本文件中的预期输出,

|'patientName'|'Address'|'Age'
|'Sanju'|'My address, Pin:12345'|'1'

我尝试使用Notepad ++手动完成,

  1. 删除了表格、正文和 html 标签
  2. 替换为 |'<td bgcolor=silver class='medium'>
  3. 将 \r\n 替换为
  4. 已删除和 .

对于较小的文件,它对我有用。但是,对于大文件,这需要时间,而且Notepad ++不起作用。

Linux Bash 外壳

评论

2赞 user1934428 11/16/2023
除非您确定 HTML 的格式总是像这样(例如,每个 HTML 恰好占据一行,并且属性始终按此顺序排列),否则我会使用 HTML 解析器来使您的脚本更加健壮。td
0赞 Léa Gris 11/16/2023
这是一个题外话,但无论如何都是一个大问题。基本上,在每周一段时间内,数据泄露、勒索软件团伙持有医疗数据,您正在处理从显示格式 HTML 中抽取的非常机密和关键的患者数据,在 shell 脚本中处理,该脚本旨在处理线性字符流和系统操作任务,而不是使用合适的语言处理来自真实数据源的结构化数据。这确实非常令人担忧。而你“收到了这个大的病人地址的html数据源”,如果你的系统有泄漏,我希望你有好的律师。
0赞 Ed Morton 11/17/2023
你可能需要对已经包含 s 的输入做一些事情,或者以后可能会搞砸你想用它做的任何事情。为什么您要输出 s 和 s 而不是带有 s 和 s 的 CSV,然后某种标准和许多工具可以直接读取?'foo'bar'foo'bar'|',"
0赞 Ed Morton 11/17/2023
你真的不想要在每条输出行的开头,是吗?那只是浪费一个字符,并引入一个在此之前不包含任何内容的前导字段。||
0赞 Sooraj P George 11/22/2023
@EdMorton 我们在加载 csv 文件时遇到了问题,因为它在数据中包含逗号和“。foo,bar“, ”foo“bar”,一些数据进入下一行。使用 sql developer 加载这些 csv 文件后,数据填充到错误的列。

答:

1赞 James Hibbard 11/16/2023 #1

编辑:正如评论中所指出的,HTML 解析器将是一个更强大的工具。还有,哎呀!我只是重新阅读了这个问题,并意识到您可能正在处理相当敏感的数据。更有理由正确地做到这一点。


要在 shell 中执行此操作,我们可以使用几个命令。

第一:sed

sed -n '/<table/,/<\/table>/p' input.html

这将读取一个名为的文件,并删除不在标签之间的任何内容。input.html<table>

然后,我们可以将其传递给:awk

awk '
BEGIN { RS = "</tr>"; FS = "\n"; OFS = "" }
{
    for (i = 1; i <= NF; i++) {
        gsub(/<[^>]*>/, "", $i);
        gsub(/^[ \t]+|[ \t]+$/, "", $i);
        if ($i != "") {
            printf("|'\''%s'\''", $i);
        }
    }
    if (NF > 0) {
        print "";
    }
}
' > output.txt

这将从每行中删除 HTML 标记,然后使用竖线和引号格式化并打印每个单元格。最后,它在每行的末尾添加一个换行符。

若要将所有这些放在一起,请创建一个名为(或类似内容)的脚本,并添加以下内容:convert.sh

#!/bin/bash

sed -n '/<table/,/<\/table>/p' input.html | awk '
BEGIN { RS = "</tr>"; FS = "\n"; OFS = "" }
{
    for (i = 1; i <= NF; i++) {
        gsub(/<[^>]*>/, "", $i);
        gsub(/^[ \t]+|[ \t]+$/, "", $i);
        if ($i != "") {
            printf("|'\''%s'\''", $i);
        }
    }
    if (NF > 0) {
        print "";
    }
}
' > output.md

使文件可执行:

chmod +x convert.sh

然后像这样运行它:

./convert.sh

这假定您在 的同一目录中运行脚本。如果不是这种情况,请相应地调整路径。input.html

它还假定该表格具有您帖子中的格式(因此属性(例如 or 不相关):classbgcolor

<table border=1>
<tr>
  <td bgcolor=silver class='medium'>patientName</td>
  <td bgcolor=silver class='medium'>Address</td>
  <td bgcolor=silver class='medium'>Age</td>
</tr>
<tr>
  <td class='normal' valign='top'>Sanju</td>
  <td class='normal' valign='top'>My address, Pin:12345</td>
  <td class='normal' valign='top'>1</td>
</tr>
<tr>
  <td class='normal' valign='top'>Jim</td>
  <td class='normal' valign='top'>München</td>
  <td class='normal' valign='top'>2</td>
</tr>
</table>

针对此运行脚本可以得到:

|'patientName'|'Address'|'Age'
|'Sanju'|'My address, Pin:12345'|'1'
|'Jim'|'München'|'2'

希望这能达到您想要的效果,但如果没有,可以修改脚本以考虑任何偏差。

最后,考虑对表使用(或至少注意)语义标记(例如,,)。请看这里<thead><tbody><tfoot>


更新

更新了脚本以考虑换行符:

#!/bin/bash

sed -n '/<table/,/<\/table>/p' input.html | awk '
BEGIN { RS = "</tr>"; FS = "<td[^>]*>"; OFS = "" }
{
  if (NF > 1) {
    printf("|");
    for (i = 2; i <= NF; i++) {
      gsub(/<[^>]*>/, "", $i);
      gsub(/\n/, " ", $i);
      gsub(/^[ \t]+|[ \t]+$/, "", $i);
      printf("'"'"%s"'"'", $i);
      if (i < NF) {
        printf("|");
      }
    }
    print "";
  }
}
' > output.txt

评论

0赞 Léa Gris 11/16/2023
此外,使用不正确的工具解析 HTML 是一个非常糟糕的主意。一般来说,解析 HTML 是一个非常糟糕的主意,因为 HTML 是一种具有非常宽松语法并使用嵌套元素的显示结构。Awk、sed 和正则表达式通常用于处理字符流,而不是嵌套的标签和属性。即使是用于 Data 的 XML,即 HTML 用于显示的 XML 在 shell 中也不合适,即使使用 .xmlstarlet
0赞 James Hibbard 11/16/2023
同意。理想情况下,我会使用像Ruby和Nokigiri这样的东西,因为它更强大。
0赞 Ed Morton 11/17/2023
您应该提到,多字符 RS 需要 GNU awk 或类似的东西。
0赞 Sooraj P George 11/21/2023
@JamesHibbard 感谢您的代码和解释。我已经尝试了这段代码,除了地址有两行之外,它运行良好。我尝试过,</td>作为 FS 而不是 \n,但没有工作。关于这个问题有什么建议吗?
0赞 James Hibbard 11/22/2023
您能否为包含两行的地址提供一些示例 HTML。也就是说,第二行是如何创建的?带标签?还是代码中的换行符?<br>
1赞 ufopilot 11/16/2023 #2
$ awk -F'>|<' '
   /<tr>/,/<\/tr>/ {
      if(NF==5) printf "|\47%s\47", $3
   } 
   /<\/tr>/{printf "\n"}
' file
|'patientName'|'Address'|'Age'
|'Sanju'|'My address, Pin:12345'|'1'
1赞 Ed Morton 11/17/2023 #3

如果您的输入始终与您在示例中显示的完全相同,那么将 GNU awk 用于多字符 RS:

$ cat tst.awk
BEGIN {
    RS  = "</?tr[^>]*>"
    FS  = "</?td[^>]*>"
    OFS = "|"
    qt  = "\047"
}
(NR%2) == 0 {
    for ( i=2; i<NF; i+=2 ) {
        gsub(qt,"&&",$i)   # one way to handle embedded quotes
        out = (i>2 ? out OFS : "") qt $i qt
    }
    print out
}

$ awk -f tst.awk file
'patientName'|'Address'|'Age'
'Sanju'|'My address, Pin:12345'|'1'

如果我们在示例输入文件中更改为,那么我们可以看到脚本如何按照 CSV 标准 RFC 4180 的要求加倍来处理嵌入的引号:SanjuPeter O'Toole

$ awk -f tst.awk file
'patientName'|'Address'|'Age'
'Peter O''Toole'|'My address, Pin:12345'|'1'

如果您确实想要在每个输出行的开头更改为 a。(i>2 ? out OFS : "") qt $i qt(i>2 ? out : "") OFS qt $i qt|

如果您决定要生成 CSV 而不是当前格式,只需更改 和 的值:OFSqt

    OFS = ","
    qt  = "\""

像所有不使用 HTML 解析器的答案一样,它是脆弱的。