首先说明下, 当时主要是正好在用 gumbo 做爬虫,所以随手复用了下代码, 你也可以理解为"手里有个锤子的时候, 看世界万物都是钉子"。实际上, 大多数情况下 解析 xml 文档用 libxml 要好很多。

gumbo 是 google 发布的一个开源的 html5 解析器,使用的是Apache License.主要用于解析 html5 网页。代码只有几个C99文件,而且经过大量的测试,robust 做得很好,使用起来很是顺手。

这两天要解析一组Elsevier的 paper 信息,这些信息是用 xml 组织起来的。问题是,这货貌似使用的不是严格的 xml 标准,里面一些 escape 完全没做,使用 libxml 很是苦恼。当然,我也可以自己写个 kmp 或者 ac 自动机来实现自己需要的功能,但目标文档有 10 million, 时间也有点紧, 如果挂一下可承受不起这代价, 所以就把目光投向了 gumbo.

经过简单的一撸后,代码就能用了,然后试着跑了十万多个文件,内容大小7.7个G,花费 3~4 分钟, 差不多和硬盘处理速度相当了。然后做了点无关紧要的修改,几乎没啥调试,一个解析工具就完成了,效率高得我都有点不敢相信.代码可以从这儿看到.当然,这一切应该归功于 gumbo 的犀利。

gumbo 的使用很是简单。把一个 const char * 类型的字符串丢 gumbo_parser 中,它就会返回一个类似于 DOM tree 结构的 root, 类型是 GumboOutput*, 结构体的定义全在这个文件中,内容不长,很容易就可以看完。

我这个是读文件解析的,所以大致写成这个样子。

FILE * fp = fopen(filename,"rb");
if(fp == NULL) return false;
fseek(fp,0L,SEEK_END);
size_t sz = ftell(fp);
rewind(fp);
char * info = (char * ) malloc(sz * sizeof(char));
fread(info,sz,1,fp);
fclose(fp);

GumboOutput* output = gumbo_parse(info);
//handle the dom tree
gumbo_destroy_output(&kGumboDefaultOptions, output);
free(info);

在11行的时候,我们可以利用这个已有的结构去递归下,得到需要的结果,保存即可。简直是超顺手。

使用它解析XML的话,会缺少一些特别的设置,比如GumboTag把html中常见的tag都解析完毕,用enum保存在struct中,所以我们找body这个tag只要用

if(node->v.element.tag == GUMBO_TAG_BODY)

这样比较就可以,而我xml的解析中,只能定义了个宏

inline bool nsequal(const char * s1, const char * s2,int lenlim)
{
    int s2len = strlen(s2);
    return (s2len < lenlim) ? (strncmp(s1+1,s2,s2len-1) == 0 ): false;
}
#define NODE_NAME_IS(node,name) \
            (nsequal(node->v.element.original_tag.data,name,\
            (int)node->v.element.original_tag.length))

然后用 strncmp 的方法来比较了,效率低上了那么一些。

其它就和html一样解析了。

在使用的时候还要注意下,或许是出于效率的考虑,它的DOM tree中的内容并不是copy出某个node下的string,而是使用了一个指针,指向目标string的开头,另一个变量指明了该区域的length。所以获取其内容后,是不可以用strlen来取得node内容的长度的。


使用后补充说明

在实际解析的时候可以发现,内部某处实现使用的内存的复杂度似乎比较高,在监控中发现,当解析相对文件比较大的时候--比如30M多的样子--耗费的内存高了3~4个数量级。居然占了我40~50G的内存。所以它的实现大概主要是解析数百KB的小文件吧,当目标文件达到MB的数量级的时候,还请谨慎。

当然,这只是从观察现象说的话,具体占用内存处还待看下源码..

来自的你,很高兴你能看到这儿。若本文对你有所用处,或者内容有什么不足之处,敬请毫不犹豫给个回复。谢谢!