【c語言】使用gumbo解析HTML


之前使用過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參數

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM