bash - bash中的xpath解析表

标签 bash csv xpath xmllint

我有一个 html 表,我想用 bash 解析它
(注意:我已经使用 R 来执行此操作,但想尝试在 bash 中轻松地与另一个 shell 脚本集成)。

该表可从以下网址获取:
http://faostat.fao.org/site/384/default.aspx

通过查看源 - 特定表的 xpath 引用是:

//*[@id="ctl03_DesktopThreePanes1_ThreePanes_ctl01_MDlisting"]

如何直接从 bash 将此表解析为 csv 文件?

我尝试了以下方法:
curl "http://faostat.fao.org/site/384/default.aspx" | xpath '//*[@id="ctl03_DesktopThreePanes1_ThreePanes_ctl01_MDlisting"]' > test.txt

这只会为 test.txt 返回一个空白文本。

谁能帮我在 bash 中使用 xpath 解析有效的 html 表并创建它的 CSV 文件?

任何帮助表示赞赏。

最佳答案

//*[@id="ctl03_DesktopThreePanes1_ThreePanes_ctl01_MDlisting"]/tr (也就是说,将 /tr 附加到您在问题中的 XPath 表达式)将只抓取每一行,并跳过 table包装器(你不需要在你的输出中做任何事情)。

然后你还需要管道 xmllint --xpath通过sed输出或 perl或者其他的东西:

示例:perl 版本

wget -q -O - "http://faostat.fao.org/site/384/default.aspx" \
   | xmllint --html \
     --xpath '//*[@id="ctl03_DesktopThreePanes1_ThreePanes_ctl01_MDlisting"]/*' - \
     2>/dev/null \
   | perl -pe 's/<tr[^>]+>//' \
   | perl -pe 's/<\/tr>//' \
   | perl -pe 's/^\s+<t[dh][^>]*>//' \
   | perl -pe 's/<\/t[dh]><t[dh][^>]*>/|/g' \
   | perl -pe 's/<\/t[dh]>//' \
   | grep -v '^\s*$'

示例:sed 版本
wget -q -O - "http://faostat.fao.org/site/384/default.aspx" \
   | xmllint --html \
     --xpath '//*[@id="ctl03_DesktopThreePanes1_ThreePanes_ctl01_MDlisting"]/*' - \
     2>/dev/null \
   | sed -E 's/<tr[^>]+>//' \
   | sed -E 's/<\/tr>//' \
   | sed -E 's/^[[:space:]]+<t[dh][^>]*>//' \
   | sed -E 's/<\/t[dh]><t[dh][^>]*>/|/g' \
   | sed -E 's/<\/t[dh]>//' \
   | grep -v '^\s*$'

在这两种情况下,grep -v '^\s*$'是否只是为了删除空行。

它不是严格意义上的 CSV;它用 | 分隔字段/单元格(管道)字符,而不是逗号——因为一些(许多)字段本身也有逗号和引号。如果您真的是 CSV,请向下滚动并阅读 如何为这种情况生成真正的 CSV 以下。

改用 python 和 lxml

作为 xmllint --xpath 的替代品,您可以改用 Python 和 lxml.html图书馆:
wget -q -O - "http://faostat.fao.org/site/384/default.aspx" \
   | python -c "import lxml.html as html; import sys; \
       expr = sys.argv[1]; print '\n'.join([html.tostring(el) \
       for el in html.parse(sys.stdin).xpath(expr)])" \
       '//*[@id="ctl03_DesktopThreePanes1_ThreePanes_ctl01_MDlisting"]//tr' \
   | sed -E 's/<tr[^>]+>//' \
   | sed -E 's/<\/tr>//' \
   | sed -E 's/^[[:space:]]+<t[dh][^>]*>//' \
   | sed -E 's/<\/t[dh]><t[dh][^>]*>/|/g' \
   | sed -E 's/<\/t[dh]>//' \
   | grep -v '^\s*$'

使用 columncolrm格式化输出的命令

如果您希望在控制台中读取打印/格式化的列/ TableView 并滚动/翻页,请将输出进一步通过管道传输到 columncolrm命令,像这样:
wget -q -O - "http://faostat.fao.org/site/384/default.aspx" \
   | xmllint --html \
     --xpath '//*[@id="ctl03_DesktopThreePanes1_ThreePanes_ctl01_MDlisting"]/*' - \
     2>/dev/null \
   | sed -E 's/<tr[^>]+>//' \
   | sed -E 's/<\/tr>//' \
   | sed -E 's/^[[:space:]]+<t[dh][^>]*>//' \
   | sed -E 's/<\/t[dh]><t[dh][^>]*>/|/g' \
   | sed -E 's/<\/t[dh]>//' \
   | grep -v '^\s*$' \
   | column -t -s '|' \
   | colrm 14 21 | colrm 20 28 | colrm 63 95 | colrm 80

这将为您提供如下输出的结果:

使用 column 格式化的结果和 colrm
Group Name         Item FAO Code    Item HS+ Code    Item Name      Definition
Crops              800              5304_c           Agave fib      Including int
Crops              221              0802.11_a        Almonds,       Prunus amygda
Crops              711              0909             Anise, ba      Include: anis
Crops              515              0808.10_a        Apples         Malus pumila;
Crops              526              0809.10_a        Apricots       Prunus armeni
…

或者,您可以使用 cut命令而不是 colrm获得相同的格式。

如何生成真正的 CSV

如果不是像上面那样 pretty-print /格式化输出,你真的想要真正的 CSV,那么你还必须在字段周围发出引号,并 CSV 转义字段内的现有引号;像这样:

示例:真正的 CSV 输出
wget -q -O - "http://faostat.fao.org/site/384/default.aspx" \
   | xmllint --html \
     --xpath '//*[@id="ctl03_DesktopThreePanes1_ThreePanes_ctl01_MDlisting"]/tr' - \
   | sed -E 's/"/""/g' \ 
   | sed -E 's/<tr[^>]+>//' \
   | sed -E 's/<\/tr>//' \
   | sed -E 's/^[[:space:]]+<t[dh][^>]*>/"/' \
   | sed -E 's/<\/t[dh]><t[dh][^>]*>/","/g' \
   | sed -E 's/<\/t[dh]>/"/' \
   | grep -v '^\s*$'

使用 CSV 的工具显然希望将所有引号字符作为两个引号字符一起转义;例如,作为单词 ""fufu""在下面。
  "In West Africa they are consumed mainly as ""fufu"", a stiff glutinous dough."

所以sed -E 's/"/""/g'上面的代码片段的一部分就是这样做的。

上述示例的 CSV 输出
"Group Name","Item FAO Code","Item HS+ Code","Item Name ","Definition"
"Crops","800","5304_c","Agave fibres nes","Including inter alia: Haiti hemp…"
"Crops","221","0802.11_a","Almonds, with shell","Prunus amygdalus; P. communis…"
"Crops","711","0909","Anise, badian, fennel, coriander","Include: anise…"

免责声明:您应该避免对 HTML/XML 进行基于正则表达式的处理

(强制性免责声明) 综上所述,很多人会告诉你,基于正则表达式的 HTML/XML 处理很容易出错。确实如此,因此请谨慎使用上述方法(如果有的话)。

如果你有时间把它做好,你应该做的是:而是使用一个好的网络抓取库 , 或使用 Python+ lxml实际处理从评估 XPath 表达式返回的结果(而不是对结果进行字符串化),或使用 xsltproc 或其他一些 XSLT 引擎。

但是你只需要命令行中的一些快速n-dirty,上面的工作就完成了。
但是,它很脆弱,所以如果输出的某些部分以某种意想不到的方式损坏,请不要感到震惊。 如果您想要一些健壮的 HTML/XML,请不要使用基于正则表达式的方法 .

关于bash - bash中的xpath解析表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32428678/

相关文章:

bash - 如果Shell脚本中的错误不起作用,是否退出?

python - cygwin 路径使用。我就是想不明白

python-3.x - Pandas 在 csv 文件中写为列而不是行-Python

xml - 如何使用 Scala 获取与唯一节点相邻的节点?

xpath - EclipseLink Moxy unmarshall 和 getValueByXPath 给出 null

bash - Shell 重定向 i/o 顺序

bash - 在 bash 中将变量扩展为 "${var%%r*}"是什么意思?

excel - 从 CSV 读取并存储在 Excel 选项卡中

linq - 使用 C# .Net 4.0 LINQ 嵌入逗号的 CSV

python - xpath 获取表中的第三个 tr 及更多内容