之前使用過PHP的Simple HTML DOM簡單地解析HTML但PHP終非我所熟悉的語言,雖然我並不對語言抱有絕對的執着= =(什么你不相信,好吧,不管你信不信,反正我是信了= =)。雖然可以簡單地使用正則表達式來解析HTML但我不是希望能夠找到一個合適的HTML解析庫,網上搜索了下關於c語言解析HTML的庫,好像不是挻多的樣子,我搜索到了google的gumbo, gumbo是開源的,可以從這里得到它 https://github.com/google/gumbo-parser 我們需要下載回來手動編譯安裝,這里以linux debian為例 git clone https://github.com/google/gumbo-parser cd gumbo-parser ./autogen.sh ./configure 這些一般都會非常順利,沒什么好說的,接下來就是 make 我要執行make后發現有一個錯誤導致無法編譯通過,不知道各位是什么情況,給出的錯誤提示是benchmarks/benchmark.cc 文件中使用了未定義的函數clock_gettime man了一下,該函數需要包含time.h頭文件,打開benchmark.cc文件查看的確已經包含了time.h頭文件,很苦惱,突然一下子就懵了,不過還好我反應還算快,看到manpages中寫到 Link with -lrt (only for glibc versions before 2.17). 於是猜測沒有鏈接庫,使用vim打開Makefile文件,這個文件內容太多= =,要分析的話有些費勁,不過機智的我還是很快地通過benchmark關鍵字定位到了benchmark_LDADD這個變量,然后在后面加上 -lrt 注意有空格 再次make,果然成功了。。。。。。。。。。。 編譯完成之后就可以使用make install進行安裝了,你可能需要使用root用戶權限,因為默認的安裝目錄在/usr/local/下 gumbo的源碼提供了幾個示例程序,一個c語言寫的獲取標題的源碼和另外三個使用c++編寫的代碼,我全都看了(你看,我說過我不是絕對的語言執着者吧,很不幸,這些程序我都看懂了= =) 簡單地說gumbo的使用很簡單,使用gumbo_parse或者gumbo_parse_with_options就可以得到一個GumboOutput數據結構,我們就可以從該結構中尋找我們想要的東西了。 我們先來看一個簡單的例子,就拿獲取title來說吧,我決定用自己寫的解析代碼而不是gumbo源碼提供的好個示例,因為我發現該程序無法解析出我使用的示例HTML文本文件= =,所以我就自己寫個吧。。。。。。 #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <stdlib.h> #include <string.h> /* 包含頭文件 */ #include <gumbo.h> void get_title(GumboNode *node) { GumboVector *children; int i; /* 如果當前節點不是一個元素的話直接返回 */ if(node->type != GUMBO_NODE_ELEMENT) return; /* 獲取該節點的所有子元素節點 */ children=&node->v.element.children; /* 檢查當前節點的標簽是否為TITLE(title) * 如果是則輸出該節點下第一個節點的文本內容 */ if(node->v.element.tag == GUMBO_TAG_TITLE) printf("%s\n",((GumboNode *)children->data[0])->v.text.text); /* 遞歸該節點下的所有子節點 */ for(i=0;i < children->length;++i) get_title(children->data[i]); } int main(int argc,char **argv) { struct stat buf; GumboOutput *output; FILE *fp; char *data; /* 讀取HTML文本文件 */ if(!(fp=fopen(argv[1],"rb"))) return -1; stat(argv[1],&buf); data=malloc(sizeof(char)*(buf.st_size+1)); fread(data,sizeof(char),buf.st_size,fp); fclose(fp); data[buf.st_size]=0; /* 解析HTML文本文件 */ output=gumbo_parse(data); /* 獲取TITLE */ get_title(output->root); /* 銷毀,釋放內存 */ gumbo_destroy_output(&kGumboDefaultOptions,output); free(data); return 0; } 注釋已經寫的很清楚了,首先我們的節奏是這個樣子的: 第一步加載HTML文本文件,我們把它讀到一個buf中, 第二步我們進行解析出GumboOutput數據結構 第三步在GumboOptout這個數據結構中找出title標簽 最后我們輸出內容,gumbo的步驟基本上就是這個樣子的了,使用gcc編譯的時候需要加上 -lgumbo 下面再說一個例子,該例子中的HTML文件內容是各國DNS的IP地址以及物理地址,大概的格式是 <dt><dd class="ipstart">開始ip地址</dd><dd class="ipend">結束ip地址</dd><dd class="address">物理地址</dd></dt> 我們的解析步驟是獲取所有dt標簽再獲取所有dd標簽,然后分別輸出dd標簽中class屬性為ipstart、ipend、address的內 容,下面放代碼,由於原HTML文本文件內容放多,我不便放上來,這里就使用在線抓取的方式獲取HTML文本,所以這里給出的是HTML文本的url地 址,至目前寫代碼這一刻該程序還是完全能夠正常工作的,日后會該網頁是否會因該網頁做調整等原因解析出錯就不得而知了。 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <oauth.h> /* 包含頭文件 */ #include <gumbo.h> #define URL "http://ip.yqie.com/dns_usa.htm" void print_dns(GumboNode *node,GumboAttribute *attr) { /* 獲取子節點 */ GumboNode *ip=(GumboNode *)(&node->v.element.children)->data[0]; /* 根據class屬性的值打印結果 */ if(strcmp(attr->value,"ipstart") == 0) { if(ip->type == GUMBO_NODE_TEXT) printf("開始IP:%s ",ip->v.text.text); } else if(strcmp(attr->value,"ipend") == 0) { if(ip->type == GUMBO_NODE_TEXT) printf("結束IP:%s ",ip->v.text.text); } else if(strcmp(attr->value,"address") == 0) { if(ip->type == GUMBO_NODE_TEXT) printf("物理地址:%s\n",ip->v.text.text); } } void get_dns(GumboNode *node,GumboTag tag) { GumboVector *children; GumboAttribute *attr; int i; if(node->type != GUMBO_NODE_ELEMENT) return; /* 獲取當前節點class屬性 */ if(attr=gumbo_get_attribute(&node->v.element.attributes,"class")) print_dns(node,attr); /* 當前節點子節點 */ children=&node->v.element.children; /* 如果當前節點標簽為td我們就查找dd標簽 */ if(node->v.element.tag == GUMBO_TAG_DT) for(i=0;i < children->length;++i) get_dns(children->data[i],GUMBO_TAG_DD); /* 查找所有<dt>標簽 */ for(i=0;i < children->length;++i) get_dns(children->data[i],GUMBO_TAG_DT); } int main(void) { GumboOutput *output; char *buf; /* 下載HTML文本文件 */ buf=oauth_http_get(URL,NULL); if(!buf) return-1; /* 解析 */ output=gumbo_parse(buf); if(!output) { free(buf); return -1; } /* 獲取我們想要的內容 <dt>*/ get_dns(output->root,GUMBO_TAG_DT); /* 釋放資源 */ gumbo_destroy_output(&kGumboDefaultOptions,output); free(buf); return 0; } 由於使用了oauth所以使用gcc編譯時需要加上-loauth參數