你是否曾經遇到過需要大量獲取網頁上的數據,但手動復制粘貼又太過費時費力?那么這篇文章就是為你而寫。今天我們將會詳細討論如何使用C語言實現自動抓取網頁上的數據。本文將會從以下8個方面進行逐步分析討論。
1. HTTP協議的基本原理
在開始之前,我們需要了解HTTP協議的基本原理。HTTP是一種客戶端和服務器端之間傳輸數據的協議,它使用TCP/IP協議作為傳輸層。當客戶端需要訪問某個服務器時,它會向服務器發送一個HTTP請求。服務器在接收到請求后,會返回一個HTTP響應。HTTP請求和響應都是由一個頭部和一個可選的消息體組成。
2.使用C語言發送HTTP請求
在C語言中,我們可以使用libcurl庫來發送HTTP請求。libcurl提供了一系列函數來處理網絡請求,并且支持各種常見的網絡協議,包括HTTP、FTP、SMTP等等。下面是一個簡單的例子:
#include <stdio.h>#include <curl/curl.h>int main(void){ CURL *curl; CURLcode res; curl = curl_easy_init(); if(curl){ curl_easy_setopt(curl, CURLOPT_URL,";); res = curl_easy_perform(curl); if(res != CURLE_OK) fprintf(stderr,"curl_easy_perform() failed:%s\n", curl_easy_strerror(res)); curl_easy_cleanup(curl); } return 0;}
這個例子中,我們使用了curl_easy_init函數來初始化一個curl對象。然后,我們使用curl_easy_setopt函數來設置請求的URL。最后,我們使用curl_easy_perform函數來執行請求,并將返回結果存儲在res變量中。
3.使用正則表達式解析HTML
當我們從網頁上獲取到數據后,我們需要對其進行解析。HTML是一種標記語言,因此我們可以使用正則表達式來進行解析。下面是一個簡單的例子:
#include <stdio.h>#include <regex.h>int main(void){ regex_t regex; int reti; char msgbuf[100]; const char *pattern ="<title>(.*)</title>"; char *data ="<html><head><title>Example</title></head><body><p>Hello World!</p></body></html>"; reti = regcomp(®ex, pattern, REG_EXTENDED); if (reti){ fprintf(stderr,"Could not compile regex\n"); return 1; } reti = regexec(®ex, data,0, NULL,0); if (!reti){ puts("Match"); regmatch_t matches[2]; reti = regexec(®ex, data,2, matches,0); if (!reti){ printf("Match:%.*s\n",(int)(matches[1].rm_eo - matches[1].rm_so),&data[matches[1].rm_so]); } } else if (reti == REG_NOMATCH){ puts("No match"); } else { regerror(reti,®ex, msgbuf, sizeof(msgbuf)); fprintf(stderr,"Regex match failed:%s\n", msgbuf); return 1; } regfree(®ex); return 0;}
這個例子中,我們使用了正則表達式來匹配網頁中的標題。首先,我們使用regcomp函數來編譯正則表達式。然后,我們使用regexec函數來執行匹配操作,并將結果存儲在matches數組中。
4.使用XPath解析HTML
除了正則表達式外,我們還可以使用XPath來解析HTML。XPath是一種用于在XML文檔中進行導航的語言,它也可以用于HTML文檔的解析。下面是一個簡單的例子:
#include <stdio.h>#include <libxml/xpath.h>#include <libxml/HTMLparser.h>int main(void){ char *data ="<html><head><title>Example</title></head><body><p>Hello World!</p></body></html>"; htmlDocPtr doc = htmlReadMemory(data, strlen(data), NULL, NULL, HTML_PARSE_NOWARNING | HTML_PARSE_NOERROR); if (doc == NULL){ fprintf(stderr,"Failed to parse document\n"); return 1; } xmlXPathContextPtr context = xmlXPathNewContext(doc); if (context == NULL){ fprintf(stderr,"Failed to create XPath context\n"); xmlFreeDoc(doc); return 1; } xmlXPathObjectPtr result = xmlXPathEvalExpression((const xmlChar*)"//title/text()", context); if (result == NULL){ fprintf(stderr,"Failed to evaluate XPath expression\n"); xmlXPathFreeContext(context); xmlFreeDoc(doc); return 1; } if (xmlXPathNodeSetIsEmpty(result->nodesetval)){ fprintf(stderr,"No match found\n"); xmlXPathFreeObject(result); xmlXPathFreeContext(context); xmlFreeDoc(doc); return 1; } printf("Match:%s\n", result->nodesetval->nodeTab[0]->content); xmlXPathFreeObject(result); xmlXPathFreeContext(context); xmlFreeDoc(doc); return 0;}
這個例子中,我們使用了libxml2庫來解析HTML。首先,我們使用htmlReadMemory函數將HTML文檔讀入內存,并解析成一個DOM樹。然后,我們使用xmlXPathNewContext函數創建一個XPath上下文。接著,我們使用xmlXPathEvalExpression函數來執行XPath表達式,并將結果存儲在result對象中。
5.使用JSON解析數據
當我們從網頁上獲取到數據時,它有可能是JSON格式的。JSON是一種輕量級的數據交換格式,易于閱讀和編寫。我們可以使用cJSON庫來解析JSON數據。下面是一個簡單的例子:
#include <stdio.h>#include <stdlib.h>#include <string.h>#include "cJSON.h"int main(void){ char *data ="{\"name\":\"John Smith\",\"age\":30,\"hobbies\":[\"reading\",\"swimming\"]}"; cJSON *root = cJSON_Parse(data); if (root == NULL){ fprintf(stderr,"Failed to parse JSON data\n"); return 1; } cJSON *name = cJSON_GetObjectItem(root,"name"); if (name == NULL){ fprintf(stderr,"Failed to get name\n"); cJSON_Delete(root); return 1; } printf("Name:%s\n", name->valuestring); cJSON *age = cJSON_GetObjectItem(root,"age"); if (age == NULL){ fprintf(stderr,"Failed to get age\n"); cJSON_Delete(root); return 1; } printf("Age:%d\n", age->valueint); cJSON *hobbies = cJSON_GetObjectItem(root,"hobbies"); if (hobbies == NULL){ fprintf(stderr,"Failed to get hobbies\n"); cJSON_Delete(root); return 1; } int i; for (i=0; i < cJSON_GetArraySize(hobbies);i++){ cJSON *hobby = cJSON_GetArrayItem(hobbies,i); printf("Hobby %d:%s\n", i +1, hobby->valuestring); } cJSON_Delete(root); return 0;} 這個例子中,我們使用了cJSON庫來解析JSON數據。首先,我們使用cJSON_Parse函數將JSON數據解析成一個cJSON對象。然后,我們使用cJSON_GetObjectItem函數來獲取對象中的屬性。最后,我們使用cJSON_GetArrayItem函數來獲取數組中的元素。6.使用數據庫存儲數據 當我們從網頁上獲取到數據時,我們可以將其存儲到數據庫中。在C語言中,我們可以使用SQLite庫來操作數據庫。下面是一個簡單的例子: #include <stdio.h>#include <sqlite3.h>static int callback(void *NotUsed, int argc, char **argv, char **azColName){ int i; for (i=0; i < argc;i++){ printf("%s=%s\n", azColName[i], argv[i]? argv[i]:"NULL"); } printf("\n"); return 0;}int main(void){ sqlite3 *db; char *zErrMsg =0; int rc; rc = sqlite3_open("test.db",&db); if (rc){ fprintf(stderr,"Can't open database:%s\n", sqlite3_errmsg(db)); sqlite3_close(db); return 1; } const char *sql ="CREATE TABLE IF NOT EXISTS users (" "id INTEGER PRIMARY KEY," "name TEXT NOT NULL," "age INTEGER NOT NULL)"; rc = sqlite3_exec(db, sql, NULL,0,&zErrMsg); if (rc != SQLITE_OK){ fprintf(stderr,"SQL error:%s\n", zErrMsg); sqlite3_free(zErrMsg); sqlite3_close(db); return 1; } sql ="INSERT INTO users (name, age) VALUES ('John Smith', 30)"; rc = sqlite3_exec(db, sql, NULL,0,&zErrMsg); if (rc != SQLITE_OK){ fprintf(stderr,"SQL error:%s\n", zErrMsg); sqlite3_free(zErrMsg); sqlite3_close(db); return 1; } sql ="SELECT * FROM users"; rc = sqlite3_exec(db, sql, callback,0,&zErrMsg); if (rc != SQLITE_OK){ fprintf(stderr,"SQL error:%s\n", zErrMsg); sqlite3_free(zErrMsg); sqlite3_close(db); return 1; } sqlite3_close(db); return 0;} 這個例子中,我們使用了SQLite庫來操作數據庫。首先,我們使用sqlite3_open函數打開一個數據庫連接。然后,我們使用sqlite3_exec函數執行SQL語句。最后,我們使用回調函數來處理查詢結果。 7.使用多線程提高效率 當我們需要從多個網頁上獲取數據時,我們可以使用多線程來提高效率。在C語言中,我們可以使用pthread庫來創建和管理線程。下面是一個簡單的例子: #include <stdio.h>#include <pthread.h>void *thread_func(void *arg){ int i; for (i=0; i < 10;i++){ printf("Thread %d:%d\n",*((int*)arg),i); } return NULL;}int main(void){ pthread_t threads[4]; int thread_args[4]; int i; for (i=0; i <4;i++){ thread_args[i]= i +1; pthread_create(&threads[i], NULL, thread_func,&thread_args[i]); } for (i=0; i <4;i++){ pthread_join(threads[i], NULL); } return 0;}
這個例子中,我們使用了pthread庫來創建和管理線程。首先,我們使用pthread_create函數創建一個新的線程,并將thread_args數組中的元素傳遞給線程函數。然后,我們使用pthread_join函數等待線程結束。
8.使用定時器實現定時抓取
當我們需要定時抓取網頁上的數據時,我們可以使用定時器來實現。在C語言中,我們可以使用timer_create函數來創建一個定時器。下面是一個簡單的例子:
#include <stdio.h>#include <signal.h>#include <time.h>void handler(int sig){ printf("Timer expired\n");}int main(void){ struct sigevent sev; timer_t timerid; struct itimerspec its; sev.sigev_notify = SIGEV_SIGNAL; sev.sigev_signo = SIGUSR1; sev.sigev_value.sival_ptr =&timerid; timer_create(CLOCK_REALTIME,&sev,&timerid); signal(SIGUSR1, handler); _sec =5; _nsec =0; _sec =5; _nsec =0; timer_settime(timerid,0,&its, NULL); while (1){f56ac3d0fc4809ae1c100a6b745ccf4b// do something } return 0;}
9.舉例說明:通過C語言進行封裝接口獲取淘寶商品詳情返回值說明
9.1公共參數
名稱 | 類型 | 必須 | 描述 |
---|---|---|---|
key | String | 是 | 調用key(必須以GET方式拼接在URL中,演示demo地址) |
secret | String | 是 | 調用密鑰 |
api_name | String | 是 | API接口名稱(包括在請求地址中)[item_search,item_get,item_search_shop等] |
cache | String | 否 | [yes,no]默認yes,將調用緩存的數據,速度比較快 |
result_type | String | 否 | [json,jsonu,xml,serialize,var_export]返回數據格式,默認為json,jsonu輸出的內容中文可以直接閱讀 |
lang | String | 否 | [cn,en,ru]翻譯語言,默認cn簡體中文 |
version | String | 否 | API版本 (復制Taobaoapi2014獲取API SDK文件) |
9.2 請求示例(C語言)
#include<stdio.h>
#include <stdlib.h>
#include<string.h>
#include<curl/curl.h>int main(){CURL *curl; CURLcode res; struct curl_slist *headers=NULL; char url[] = "https://api.xxxxx.cn/taobao/item_get/?key=<您自己的apiKey>&secret=<您自己的apiSecret>&num_iid=商品ID&is_promotion=1";curl_global_init(CURL_GLOBAL_ALL); curl = curl_easy_init(); if(curl) {curl_easy_setopt(curl, CURLOPT_URL,url);headers = curl_slist_append(headers, "Content-Type: application/json"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); res = curl_easy_perform(curl);if(res != CURLE_OK){printf("curl_easy_perform(): %s\n",curl_easy_strerror(res)); }curl_easy_cleanup(curl); }curl_global_cleanup();return 0;
}
通過上面的代碼,我們可以得到一個簡單版的爬蟲程序,它可以從目標網站上抓取內涵段子,并提取出來打印輸出。
注意事項及高級技巧
在使用PHP編寫爬蟲程序時,需要注意以下事項:
遵循目標網站的robots.txt協議,不要濫用爬蟲而導致網站崩潰;
使用cURL等工具時,需要設置User-Agent、Referer等頭部信息,模擬瀏覽器行為;
對獲取的HTML數據進行適當的編碼處理,防止亂碼問題;
避免頻繁訪問目標網站,操作過于頻繁可能會被網站封禁IP地址;
如需獲取驗證碼等需要人工干預的內容,需要使用圖像識別技術等高級技巧。
通過以上這些注意事項和高級技巧,我們可以更好地應對不同的爬蟲需求,實現更加高效、穩定的數據采集。