按照那个悬赏任务的作者推荐,下载了那几个html parser,以及那个cJSON。
先写了一个workflow.h文件,里面填了几个函数声明:
// true(1) or false(0) int validate_args(int argc, char** argv); int validate_file(FILE* file); /* parse fileContent to page */ int parse_filecontent(FileContents* fileContent, PageTree* page); void clean_pagetree(PageTree* page); /* return true(1) or false(0) */ int check_page_type(PageTree* page); /* return true(1) or false(0). If return true, the json fragment is stored in argument (*json). */ int output_json(PageTree* page, jsonPtr* json);
这个已经很直观了,在main函数里依次调用这些函数,最终输出的json就是作者的任务要求,就是这么直接,main函数如下:
int main(int argc, char** argv) { int retcode; /*retcode = validate_args(argc, argv); if (!retcode) { report_msg(0, "Invalid arguments.\n"); return 1; }*/ const char* filename = "F:/迅雷下载/悬赏/html解析工具参考附件/html_samples/Barack Obama.html"; FILE* file = 0; fopen_s(&file, filename, "rb"); if (!file) { report_msg(0, "Open file failed.\n"); return 1; } retcode = validate_file(file); if (!retcode) { return 1; } FileContents fc; if (!load_file_to_memory(file, &fc)) { return 1; } PageTree page; if (!parse_filecontent(&fc, &page)) { return 1; } jsonPtr json; if (!output_json(&page, &json)) { return 1; } char* json_print_content = cJSON_Print(json); report_msg_with_newline(1, json_print_content); free(json_print_content); clean_pagetree(&page); unload_file_from_memory(&fc); fclose(file); return 0; }
里面的load_file_to_memory是因为gumbo那个解析库的parse函数只接收char* buffer,即只接收html格式的文本,不支持指定文件路径来parse的方式。而且那个库只支持utf8编码的文件。因此文件需要都是以utf8格式保存的才行。
用那个奥巴马的facebook页面测试了一下,最后控制台输出了正确的json串,算是跑通了流程。
剩下的就是如何判断页面类型,以及对不同类型页面运用不同函数来提取信息。
在chrome里查看页面源代码太杂乱,拷贝到visualstudio里把电脑卡死,看网上的推荐下载了一个webstorm,还挺好使。将html代码拷贝到webstorm里按Alt+F8格式化后,查找判断页面类型的蛛丝马迹。鼠标停留在html标签上时,编辑器头部会出现一个层次信息条,右击这个层次信息条的节点能在编辑器里选中包括该节点在内的节点内容。
针对facebook页面,发现html#id="facebook",据此可以确定一个页面是否facebook页面。同时发现head->noscript->meta#content是判断该facebook页面是列表页还是评论页的关键。我去你大爷的,gumbo解析noscript节点居然有个这么大的坑,noscript节点里的东西都被当做raw text了。先用下面的代码将就一下:
if (noscript = get_child_node(head, GUMBO_TAG_NOSCRIPT, 0, 0)) { const char* content = strstr(get_elem(noscript)->original_tag.data, "URL="); /*if (meta = get_child_node(noscript, GUMBO_TAG_META, 0, 0)) { GumboAttribPtr content = gumbo_get_attribute(&get_elem(meta)->attributes, "content"); if (content) { const char* p, *q; p = strchr(content->value, '/'); if (p && (q = strchr(content->value + 1, '/')) && (q > p + 1)) { if (*(q + 1) == '?') { page->page_type = eFacebookListPage; } else { page->page_type = eFacebookCommentPage; } int username_length = q - p - 1; page->user_name = malloc(username_length + 1); memcpy(page->user_name, p + 1, username_length); page->user_name[username_length] = 0; return 1; } } }*/ if (content) { const char* p, *q; p = strchr(content, '/'); if (p && (q = strchr(p+1, '?')) && (q > p + 1)) { if (isalpha(*(q - 1))) { page->page_type = eFacebookListPage; } else { page->page_type = eFacebookCommentPage; } int username_length = q - p - 1; page->user_name = malloc(username_length + 1); memcpy(page->user_name, p + 1, username_length); page->user_name[username_length] = 0; return 1; } } }
webstorm的导航区里选中文件,按shift+f6是重命名功能。
twitter的列表页与评论页的区分与facebook页面一致,并且这里也是判断该页面是否twitter页面的一个点。
youtube页面在head->link的rel="canonical"时,其href里有www.youtube.com字样,从这里也可以区分是列表页还是评论页。回去看了也twitter代码,发现twitter也如此。通过这个字段判断更容易。
区分开页面的具体类型后,剩下的最后一件事情就是从页面提取信息。
代码的下载编译:
git clone https://github.com/google/gumbo-parser.git
安装gumbo的过程:
$ ./autogen.sh $ ./configure $ make $ sudo make install
这过程中有可能提示g++和automake或autoconf没安装等等,有可能还需要安装pkg-config等,这些都是gumbo库需要的。
git clone https://git.oschina.net/zbjxb/jinmudaozhang.git
这是parser代码。
编译指令:
gcc -o parser parse_htmls.c cJSON.c zbj_utils.c zbj_workflow.c -I/usr/local/include /usr/local/lib/libgumbo.a -lm