bash中的xpath解析表

时间:2015-09-06 21:54:48

标签: 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文件吗?

任何帮助表示感谢。

1 个答案:

答案 0 :(得分:2)

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

然后您还需要通过xmllint --xpathsed或其他方式输出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命令格式化输出

如果您希望在控制台中读取结果的漂亮打印/格式化列/表视图并滚动/翻页,请将输出进一步输入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

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

使用columncolrm 进行格式设置的结果
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

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

示例:true 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处理

(强制性免责声明)以上所述,很多人会告诉你基于regexp的HTML / XML处理是kludgy +容易出错。它是,所以请谨慎使用上述方法(如果有的话)。

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

但是你只需要从命令行中快速弄脏,上面就可以完成工作了。 但是,它很脆弱,所以如果输出的某些部分以某种意想不到的方式被打破,不要感到震惊。 如果您想要HTML / XML的强大功能,请不要使用基于正则表达式的方法