使用 <td>XMLStarlet 通过 Bash 将元素 () 插入到 HTML 表格的每一行中



我想从链接列表中提取每个HTML表。我使用的代码如下:

wget -O - "https://example.com/section-1/table-name/financial-data/" | xmllint --html --xpath '//*[@id="financial-data"]/div/table/tbody' - 2>/dev/null >> /Applications/parser/output.txt

这很好地效果很好,但是,鉴于这不是我要提取的唯一表格,它会使我难以确定哪个 financial-data 属于哪个表。在这种情况下,它将仅解析一张表格附加到SDTOUT看起来像这样的输出文件的表:

<tbody>
                    <tr class="text-right">
                      <td class="text-left">Sep 08, 2017</td>
                      <td>4605.16</td>     
                      <td>4661.00</td>
                      <td>4075.18</td>
                      <td>4228.75</td>
                      <td>2,700,890,000</td>
                      <td>76,220,200,000</td>
                    </tr>
                    <tr class="text-right">
                      <td class="text-left">Sep 07, 2017</td>
                      <td>4589.14</td>     
                      <td>4655.04</td>
                      <td>4491.33</td>
                      <td>4599.88</td>
                      <td>1,844,620,000</td>
                      <td>75,945,000,000</td>
                    </tr>
...
</tbody>

但是我正在寻找:

<tbody>
                    <tr class="text-right">
                      <td>TABLE-NAME</td>
                      <td class="text-left">Sep 08, 2017</td>
                      <td>4605.16</td>     
                      <td>4661.00</td>
                      <td>4075.18</td>
                      <td>4228.75</td>
                      <td>2,700,890,000</td>
                      <td>76,220,200,000</td>
                    </tr>
                    <tr class="text-right">
                      <td>TABLE-NAME</td>
                      <td class="text-left">Sep 07, 2017</td>
                      <td>4589.14</td>     
                      <td>4655.04</td>
                      <td>4491.33</td>
                      <td>4599.88</td>
                      <td>1,844,620,000</td>
                      <td>75,945,000,000</td>
                    </tr>
...
</tbody>

table-name 是特定资产的名称。可以使用出现在表所在的同一URL中的XPath /html/body/div[3]/div/div[1]/div[3]/div[1]/h1/text()提取名称,或者是从链接本身/table-name/

我无法弄清楚语法。

nb :我故意省略了wget命令中的 -q标志,因为我想查看执行脚本时终端中正在发生的事情。

谢谢!


update

根据@danielhaley,这可以通过XMLStarlet完成,但是,当我阅读文档时,我找不到有关如何使用它的示例。

正确的语法是什么?我是否首先必须通过xmllint --html --xpath解析HTML表,然后应用xmlstarlet

这是我到目前为止发现的:

-i or --insert <xpath> -t (--type) elem|text|attr -n <name> -v (--value) <value>
-a or --append <xpath> -t (--type) elem|text|attr -n <name> -v (--value) <value>

新更新

根据此链接,我遇到了像这样轻松添加子节点的脚本:

wget -O - "https://example.com/section-1/table-name/financial-data/" |
xmllint --html --xpath '//*[@id="financial-data"]/div/table/tbody' - 2>/dev/null |
xmlstarlet ed --subnode "/tbody/tr" --type elem -n td -v "Hello World" >> /Applications/parser/output.txt

将以下内容写入stdout:

<tbody>
                    <tr class="text-right">
                      <td class="text-left">Sep 08, 2017</td>
                      <td>4605.16</td>     
                      <td>4661.00</td>
                      <td>4075.18</td>
                      <td>4228.75</td>
                      <td>2,700,890,000</td>
                      <td>76,220,200,000</td>
                      <td>Hello World</td>
                    </tr>
                    <tr class="text-right">
                      <td class="text-left">Sep 07, 2017</td>
                      <td>4589.14</td>     
                      <td>4655.04</td>
                      <td>4491.33</td>
                      <td>4599.88</td>
                      <td>1,844,620,000</td>
                      <td>75,945,000,000</td>
                      <td>Hello World</td>
                    </tr>
...
</tbody>

到目前为止很好,但是,这重现了一些默认文本,称为文本字符串使用选项-v,即在这种情况下,在这种情况下是" Hello World"。我希望用资产的实际名称替换此文本字符串。如前所述, table-name 在表所在的同一页面中找到,可以通过其他XPath访问,因此我尝试了以下代码:

wget -O - "https://example.com/section-1/table-name/financial-data/" |
header=$(xmllint --html --xpath '/html/body/div[3]/div/div[1]/div[3]/div[1]/h1' -) |
xmllint --html --xpath '//*[@id="financial-data"]/div/table/tbody' - 2>/dev/null |
xmlstarlet ed --subnode "/tbody/tr" --type elem -n td -v "$header" >> /Applications/parser/output.txt

在这里,您可以清楚地看到我尝试声明一个包括资产名称的变量$header。这不起作用,并使我的输出文件空白,可能是因为声明是错误的,或者管道的语法不正确。

我如何将根据XPath(引用资产名称的名称)插入新创建的子节点<td>?变量是我想出的第一件事。可以做否则吗?

您应该尝试在将输出附加到output.txt之前插入附加列。确保所需的表格存储在变量中。您想做

之类的事情
tbl=testtbl
echo "<tbody>
                    <tr class="text-right">
                      <td class="text-left">Sep 08, 2017</td>
                      <td>4605.16</td>
                      <td>4661.00</td>
                      <td>4075.18</td>
                      <td>4228.75</td>
                      <td>2,700,890,000</td>
                      <td>76,220,200,000</td>
                    </tr>
                    <tr class="text-right">
                      <td class="text-left">Sep 07, 2017</td>
                      <td>4589.14</td>
                      <td>4655.04</td>
                      <td>4491.33</td>
                      <td>4599.88</td>
                      <td>1,844,620,000</td>
                      <td>75,945,000,000</td>
                    </tr>
" | sed 's#.*<tr.*#&n      <td>'"${tbl}"'</td>#'

sed命令中,普通斜线被"#"代替,因此您不要逃脱</td>中的斜线。
当您使用apporox的文件alltables.txt时。1160桌,您货车制作这样的循环:

while IFS= read -r tbl; do
   wget -O - "https://example.com/section-1/table-name/financial-data/" |
      xmllint --html --xpath '//*[@id="financial-data"]/div/table/tbody' - 2>/dev/null |
      sed 's#.*<tr.*#&n      <td>'"${tbl}"'</td>#' >> /Applications/parser/output.txt
done < alltables.txt

您可能可以使用XMLStarlet中的ed(edit)命令来执行此操作,但是我不太了解XMLSTARLET,足以给您一个简单的答案。

另外,就像您说的那样,看起来您必须通过XMLLINT将HTML传递或使用fo XMLSTARLET命令,然后再将其传递给XMLSTARLET ed。它看起来不像ed支持--html

我要做的是将XMLSTARLET tr(变换)命令与XSLT样式表一起使用。

这很冗长,但是比试图用Regex解析HTML/XML要安全得多。扩展也容易得多。

这是XSLT。我添加了评论以帮助您了解发生了什么。

XSLT 1.0 (stylesheet.xsl)

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes" omit-xml-declaration="yes"/>
  <xsl:strip-space elements="*"/>
  <!--Parameter to capture the table name. This is set on the command line.-->
  <xsl:param name="tablename"/>
  <!--Identity transform. Will basically output attributes/nodes without 
  change if not matched by a more specific template.-->
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>
  <!--Template matching the root element. I do this to narrow the scope of what's 
  being processed.-->
  <xsl:template match="/*">
    <!--Process tbody.-->
    <xsl:apply-templates select=".//*[@id='financial-data']/div/table/tbody"/>
  </xsl:template>
  <!--Match tr elements so we can add the new td with the table name.-->
  <xsl:template match="tr">
    <!--Output the tr element.-->
    <xsl:copy>
      <!--Process any attributes.-->
      <xsl:apply-templates select="@*"/>
      <!--Create new td element.-->
      <td><xsl:value-of select="$tablename"/></td>
      <!--Process any children of tr.-->
      <xsl:apply-templates/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

命令行

wget -O - "https://example.com/section-1/table-name/financial-data/" | 
xml tr --html stylesheet.xsl -p tablename="/html/body/div[3]/div/div[1]/div[3]/div[1]/h1"

我能够通过在本地HTML文件而不是wget上使用cat在本地测试。让我知道您是否要我将测试文件/结果添加到我的答案中。

此脚本有效,但效率低下;它需要一些编辑:

name_query="html/body/div[3]/div/div[1]/div[3]/div[1]/h1/text()"
# Use xargs to TRIM result.
header=$(wget -O - "https://example.com/section-1/name-1/financial-data/" |
    xmllint --html --xpath "$name_query" - 2>/dev/null |
    xargs)
wget -O - "https://example.com/section-1/name-1/financial-data/" |
    xmllint --html --xpath '//*[@id="financial-data"]/div/table/tbody' - 2>/dev/null |
    xmlstarlet ed --subnode "/tbody/tr" --type elem -n td -v "$header" >> /Applications/parser/output.txt

这提出了两个请求:

  1. 获取名称并将其传递到可变$header
  2. 获取表并附加子节点<td>$header</td>

因此,这将以下内容写入我的 output.txt 文件:

<tbody>
                    <tr class="text-right">
                      <td class="text-left">Sep 08, 2017</td>
                      <td>4605.16</td>     
                      <td>4661.00</td>
                      <td>4075.18</td>
                      <td>4228.75</td>
                      <td>2,700,890,000</td>
                      <td>76,220,200,000</td>
                      <td>Name 1</td>
                    </tr>
                    <tr class="text-right">
                      <td class="text-left">Sep 07, 2017</td>
                      <td>4589.14</td>     
                      <td>4655.04</td>
                      <td>4491.33</td>
                      <td>4599.88</td>
                      <td>1,844,620,000</td>
                      <td>75,945,000,000</td>
                      <td>Name 1</td>
                    </tr>
...
</tbody>

它相对较慢,因为这实际上只能使用一个请求完成,但我不知道如何。

最新更新