从v0.15.0版本开始,在编译时,可以添加curl模块,从而可以执行抓取数据相关的操作。只要在make命令后面加入USE_CURL=yes即可。
页面导航:
zenglServer源代码的相关地址:https://github.com/zenglong/zenglServer 当前版本对应的tag标签为:v0.15.1
zenglServer v0.15.0 - v0.15.1:
从v0.15.0版本开始,在编译时,可以添加curl模块,从而可以执行抓取数据相关的操作。只要在make命令后面加入USE_CURL=yes即可。
当然,要使用curl模块,前提是系统中安装了底层的curl开发库。
如果是ubuntu系统,可以通过 sudo apt-get install curl libcurl3 libcurl3-dev 来安装curl相关的库和开发头文件等:
zengl@zengl-ubuntu:~$ sudo apt-get install curl libcurl3 libcurl3-dev
如果是centos系统,则可以通过 yum install curl curl-devel 来安装相关的底层库:
[root@localhost ~]# yum install curl curl-devel
要同时使用mysql,magick,pcre,以及curl模块,可以使用 make USE_MYSQL=yes USE_MAGICK=6 USE_PCRE=yes USE_CURL=yes 命令:
[parallels@localhost zenglServer]$ make USE_MYSQL=yes USE_MAGICK=6 USE_PCRE=yes USE_CURL=yes ................................................... gcc -g3 -ggdb -O0 -std=c99 main.c http_parser.c module_request.c module_builtin.c module_session.c dynamic_string.c multipart_parser.c resources.c client_socket_list.c json.c randutils.c md5.c debug.c main.h http_parser.h common_header.h module_request.h module_builtin.h module_session.h dynamic_string.h multipart_parser.h resources.h client_socket_list.h json.h randutils.h md5.h debug.h module_mysql.c module_mysql.h module_magick.c module_magick.h module_pcre.c module_pcre.h module_curl.c module_curl.h zengl/linux/zengl_exportfuns.h -o zenglServer zengl/linux/libzengl.a crustache/libcrustache.a -lpthread -lm -DUSE_MYSQL `mysql_config --cflags --libs` -D USE_MAGICK=6 `pkg-config --cflags --libs Wand` -DUSE_PCRE `pcre-config --cflags --libs` -DUSE_CURL `curl-config --cflags --libs` mysql module is enabled!!! magick module is enabled!!! pcre module is enabled!!! curl module is enabled!!! [parallels@localhost zenglServer]$
和curl模块相关的C源码位于module_curl.c文件中:
/* * module_curl.c * * Created on: Nov 20, 2018 * Author: zengl */ ............................................................................ /** * curlEasyInit模块函数,执行curl初始化,并返回一个用于执行curl操作的指针, * 返回的指针是my_curl_handle_struct类型的,该类型对应的结构体中又封装了一个CURL类型的指针(用于执行实际的底层的curl操作的), * 该模块函数在创建了my_curl_handle_struct类型的指针后,还会将其存储到资源列表中, * 这样其他的curl模块函数在接收到该类型的指针后,就可以根据资源列表来判断是否是有效的指针了, * 同时,如果在脚本中忘了手动清理该指针的话,在脚本结束时,也会自动根据资源列表来清理掉指针。 * 该模块函数相关的示例代码,请参考curlEasyPerform模块函数的注释部分 * * 此模块函数在底层,会先通过curl_global_init库函数来进行curl的初始化操作,如果在同一脚本中已经初始化过了,则会跳过去,不会重复进行初始化, * 接着会通过curl_easy_init的库函数来创建一个CURL类型的指针,并将该指针封装到自定义的my_curl_handle_struct类型的结构中, * 最后将my_curl_handle_struct类型的指针返回,之所以做一个封装,是为了兼容旧的curl库版本而做的兼容处理。 * curl_global_init库函数的官方地址为:https://curl.haxx.se/libcurl/c/curl_global_init.html * curl_easy_init库函数的官方地址为:https://curl.haxx.se/libcurl/c/curl_easy_init.html */ ZL_EXP_VOID module_curl_easy_init(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount) { st_curl_global_init(); CURL * curl_handle = curl_easy_init(); write_to_server_log_pipe(WRITE_TO_PIPE, "[debug] curl_easy_init: %x\n", curl_handle); // debug my_curl_handle_struct * my_curl_handle = zenglApi_AllocMem(VM_ARG, sizeof(my_curl_handle_struct)); memset(my_curl_handle, 0, sizeof(my_curl_handle_struct)); my_curl_handle->curl_handle = curl_handle; zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_INT, ZL_EXP_NULL, (ZL_EXP_LONG)my_curl_handle, 0); MAIN_DATA * my_data = zenglApi_GetExtraData(VM_ARG, "my_data"); int ret_code = resource_list_set_member(&(my_data->resource_list), my_curl_handle, st_curl_easy_cleanup_callback); if(ret_code != 0) { zenglApi_Exit(VM_ARG, "curlEasyInit add resource to resource_list failed, resource_list_set_member error code:%d", ret_code); } } /** * curlEasyCleanup模块函数,清理和my_curl_handle_struct指针相关的资源 * 该模块函数的第一个参数必须是有效的my_curl_handle_struct指针,该指针由curlEasyInit模块函数返回, * 当不需要执行curl相关的抓取操作时,可以手动通过该模块函数将指针相关的资源给清理掉, * 如果没有手动清理的话,在脚本退出时也会自动进行指针的清理操作。 * 该模块函数的示例代码,请参考curlEasyPerform模块函数的注释部分 * * my_curl_handle_struct指针指向的结构中,又封装了一个CURL类型的指针,该指针是用于执行实际的curl操作的, * 该模块函数在底层,会通过curl_easy_cleanup库函数将CURL类型指针相关的资源给清理掉, * 和curl_easy_cleanup库函数相关的官方地址为:https://curl.haxx.se/libcurl/c/curl_easy_cleanup.html */ ZL_EXP_VOID module_curl_easy_cleanup(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount) { ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}}; if(argcount < 1) zenglApi_Exit(VM_ARG,"usage: curlEasyCleanup(curl_handle): integer"); ............................................................................ } /** * curlEasySetopt模块函数,设置curl抓取相关的选项,例如:抓取的目标地址,需要使用的用户代理等 * 该模块函数的第一个参数必须是有效的my_curl_handle_struct指针,该指针由curlEasyInit模块函数返回, * 第二个参数是字符串类型的选项名称,暂时只支持以下几个选项: * 'URL':表示需要抓取的目标地址 * 'USERAGENT':需要设置的用户代理 * 'FOLLOWLOCATION':当抓取到重定向页面时,是否进行重定向操作 * 'SSL_VERIFYPEER':是否校验SSL证书 * 'TIMEOUT': 设置超时时间 * 第三个参数是需要设置的具体的选项值,当第二个参数是'URL','USERAGENT'时,选项值必须是字符串类型,表示需要设置的url地址,用户代理等, * 当第二个参数是'FOLLOWLOCATION'时,选项值必须是整数类型,表示是否进行重定向操作,默认是0,即不进行重定向,需要进行重定向的,可以将选项值设置为1 * 当第二个参数是'SSL_VERIFYPEER'时,选项值必须是整数类型,表示是否校验SSL证书,默认是1,即需要进行校验,如果不需要校验,可以将选项值设置为0 * 当第二个参数是'TIMEOUT'时,选项值必须是整数类型,表示需要设置的超时时间 * 具体的例子,请参考curlEasyPerform模块函数的注释部分 * * 该模块函数最终会通过curl_easy_setopt库函数去执行具体的操作, * 该库函数的官方地址为:https://curl.haxx.se/libcurl/c/curl_easy_setopt.html */ ZL_EXP_VOID module_curl_easy_setopt(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount) { ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}}; if(argcount < 3) zenglApi_Exit(VM_ARG,"usage: curlEasySetopt(curl_handle, option_name, option_value): integer"); ............................................................................ } /** * curlEasyPerform模块函数,它会使用curl库执行具体的抓取操作 * 该模块函数的第一个参数必须是有效的my_curl_handle_struct指针,该指针由curlEasyInit模块函数返回, * 第二个参数content必须是引用类型,用于存储抓取到的具体数据, * 第三个参数size也必须是引用类型,用于存储抓取到的数据的字节大小,该参数是可选的, * 该模块函数如果执行成功,会返回0,如果执行失败,则返回相应的错误码,可以使用curlEasyStrError模块函数,来获取错误码对应的字符串类型的错误描述, * 例如: use builtin, curl, request; def TRUE 1; def FALSE 0; rqtSetResponseHeader("Content-Type: text/html; charset=utf-8"); curl_handle = curlEasyInit(); curlEasySetopt(curl_handle, 'URL', 'https://www.example.com/'); curlEasySetopt(curl_handle, 'USERAGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0'); curlEasySetopt(curl_handle, 'FOLLOWLOCATION', TRUE); curlEasySetopt(curl_handle, 'SSL_VERIFYPEER', FALSE); curlEasySetopt(curl_handle, 'TIMEOUT', 30); ret = curlEasyPerform(curl_handle, &content, &size); if(ret == 0) print 'size: ' + size; print 'content: ' + content; else print 'error: ' + curlEasyStrError(ret); endif curlEasyCleanup(curl_handle); 上面脚本中会先通过curlEasyInit模块函数获取my_curl_handle_struct类型的指针, 接着,通过curlEasySetopt模块函数来设置需要抓取的目标地址,以及设置useragent用户代理等, 在设置好后,最后通过curlEasyPerform模块函数去执行抓取操作,如果返回的ret为0,则content变量将包含抓取的数据,size变量则会包含抓取数据的字节大小, 如果返回的ret不为0,则会将返回值传递给curlEasyStrError模块函数,以获取具体的错误描述信息。 该模块函数在底层最终会通过curl_easy_perform库函数去执行具体的抓取操作, 该库函数的官方地址为:https://curl.haxx.se/libcurl/c/curl_easy_perform.html */ ZL_EXP_VOID module_curl_easy_perform(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount) { ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}}; if(argcount < 2) zenglApi_Exit(VM_ARG,"usage: curlEasyPerform(curl_handle, &content[, &size]): integer"); ............................................................................ } /** * curlEasyStrError模块函数,根据其他模块函数返回的整数类型的错误码,返回相应的错误描述 * 该模块函数的第一个参数是整数类型的错误码,返回的结果是字符串类型的错误信息 * 该模块函数底层会通过curl_easy_strerror库函数来获取具体的错误信息 * curl_easy_strerror库函数的官方地址:https://curl.haxx.se/libcurl/c/curl_easy_strerror.html */ ZL_EXP_VOID module_curl_easy_strerror(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount) { ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}}; if(argcount < 1) zenglApi_Exit(VM_ARG,"usage: curlEasyStrError(errornum): string"); ............................................................................ } /** * curlVersion模块函数,用于获取当前所使用的curl库的版本信息 * 该模块函数除了会返回curl库的版本信息,还会将与其相关的组件的版本信息也显示出来(例如:OpenSSL等) * 该模块函数返回的结果,类似如下: * libcurl/7.29.0 NSS/3.34 zlib/1.2.11 libidn/1.28 libssh2/1.4.3 * 该模块函数底层会通过curl_version的库函数去执行具体的操作 * curl_version库函数的官方地址:https://curl.haxx.se/libcurl/c/curl_version.html */ ZL_EXP_VOID module_curl_version(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount) { zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_STR, (char *)curl_version(), 0, 0); } /** * curl模块的初始化函数,里面设置了与该模块相关的各个模块函数及其相关的处理句柄(对应的C函数) */ ZL_EXP_VOID module_curl_init(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT moduleID) { zenglApi_SetModFunHandle(VM_ARG,moduleID,"curlEasyInit",module_curl_easy_init); zenglApi_SetModFunHandle(VM_ARG,moduleID,"curlEasyCleanup",module_curl_easy_cleanup); zenglApi_SetModFunHandle(VM_ARG,moduleID,"curlEasySetopt",module_curl_easy_setopt); zenglApi_SetModFunHandle(VM_ARG,moduleID,"curlEasyPerform",module_curl_easy_perform); zenglApi_SetModFunHandle(VM_ARG,moduleID,"curlEasyStrError",module_curl_easy_strerror); zenglApi_SetModFunHandle(VM_ARG,moduleID,"curlVersion",module_curl_version); }
在my_webroot的v0_15_0目录中,增加了两个测试脚本:test.zl以及test_with_pcre.zl,其中,test.zl脚本主要利用上面这些curl模块函数来抓取并显示网页内容。
而test_with_pcre.zl脚本则是在test.zl脚本代码的基础上,在抓取到网页数据后,结合pcre正则表达式模块,将网页中的title标题给提取了出来,从而实现了一个简单的采集操作。
test.zl脚本的代码如下:
use builtin, curl, request; def TRUE 1; def FALSE 0; rqtSetResponseHeader("Content-Type: text/html; charset=utf-8"); print 'curl version: ' + curlVersion(); curl_handle = curlEasyInit(); curlEasySetopt(curl_handle, 'URL', 'https://www.example.com/'); // curlEasySetopt(curl_handle, 'URL', 'https://www.baidu.com/'); curlEasySetopt(curl_handle, 'USERAGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0'); curlEasySetopt(curl_handle, 'FOLLOWLOCATION', TRUE); curlEasySetopt(curl_handle, 'SSL_VERIFYPEER', FALSE); curlEasySetopt(curl_handle, 'TIMEOUT', 30); ret = curlEasyPerform(curl_handle, &content, &size); if(ret == 0) print 'size: ' + size; print 'content: ' + content; // print 'content: ' + bltHtmlEscape(content); else print 'error: ' + curlEasyStrError(ret); endif curlEasyCleanup(curl_handle);
上面脚本中,先通过curlEasyInit模块函数返回了一个用于执行curl操作的指针,后面的curlEasySetopt,curlEasyPerform,curlEasyCleanup这些模块函数都需要接收这个指针作为第一个参数。
接着,脚本中使用curlEasySetopt模块函数设置了curl抓取相关的选项。
其中,URL选项设置了需要访问的url地址,上面代码中,设置的地址为:https://www.example.com/
USERAGENT选项设置了用户代理信息,上面代码中,设置的用户代理为:Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0
FOLLOWLOCATION选项设置了当遇到重定向页面时,需要执行重定向操作,代码中的第三个参数为TRUE即整数1,表示需要重定向。
SSL_VERIFYPEER选项设置了不需要校验SSL证书,代码中的第三个参数为FALSE即整数0,表示不校验证书。
TIMEOUT选项设置了超时时间,代码中设置的超时时间为30秒。
在设置了curl抓取相关的选项后,代码中就通过curlEasyPerform模块函数去执行具体的抓取操作,当该模块函数的返回值为0时,就表示执行成功,抓取的数据会存储在content变量中,抓取到的数据的字节大小会存储在size变量中。
curlEasyPerform模块函数的第三个size参数是可选的,也就是说,没提供size变量作为参数的话,模块函数就不会去设置抓取的数据大小。此外,该模块函数的第二个和第三个参数都必须是引用类型,才能获取到抓取的数据信息,所以代码中就将content和size变量的引用传给了模块函数。
如果curlEasyPerform模块函数的返回值不为0,则表示抓取失败,此时,返回值会是一个错误码,可以通过curlEasyStrError模块函数将错误码对应的具体错误信息获取出来。
在curl抓取完毕后,代码最后通过curlEasyCleanup模块函数手动清理掉了curl_handle这个用于curl操作的指针。如果没有手动清理的话,zenglServer在脚本执行结束时,也会自动执行指针的清理操作。
test.zl脚本的执行结果如下:
test.zl脚本在浏览器中的执行结果
test_with_pcre.zl脚本的内容如下:
use builtin, curl, request, pcre; def TRUE 1; def FALSE 0; rqtSetResponseHeader("Content-Type: text/html; charset=utf-8"); print 'curl version: ' + curlVersion(); curl_handle = curlEasyInit(); curlEasySetopt(curl_handle, 'URL', 'https://www.example.com/'); // curlEasySetopt(curl_handle, 'URL', 'https://www.baidu.com/'); curlEasySetopt(curl_handle, 'USERAGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0'); curlEasySetopt(curl_handle, 'FOLLOWLOCATION', TRUE); curlEasySetopt(curl_handle, 'SSL_VERIFYPEER', FALSE); curlEasySetopt(curl_handle, 'TIMEOUT', 30); ret = curlEasyPerform(curl_handle, &content, &size); if(ret == 0) print 'size: ' + size + '<br/>'; ret = pcreMatch('<title>(.*?)</title>', content, &results, 'is'); if(!ret) print 'no match'; else print 'title: ' + results[1]; endif else print 'error: ' + curlEasyStrError(ret); endif curlEasyCleanup(curl_handle);
上面脚本中使用curl抓取网页数据的代码,和test.zl是一致的,在抓取到网页数据后,接着就通过pcre正则表达式模块里的pcreMatch模块函数,将title标签里的标题信息给提取并显示了出来。
test_with_pcre.zl脚本的执行结果如下:
test_with_pcre.zl脚本在浏览器中的执行结果
以上就是当前版本的相关内容,就到这里,休息,休息一下。
只要还在路上,就要坚持创业的精神
—— 《创业时代》