一個C++解析HTML的庫

HTTP協議使用廣泛,相應的,C++在這塊需求也開始增加。一個好的解析庫可以達到事半功倍的效果,在此貼出我的解析庫的代碼,方便新手朋友們使用。

hHttpParse.h

#ifndef __H_HTML_PARSE_H__
#define __H_HTML_PARSE_H__

#pragma once

#include <Windows.h>
#include <WinInet.h>
#include <string>
#include <cstdio>

class hHtmlParse {
	std::string data;
	int p;
public:
	//構造函數,傳入HTML代碼
	hHtmlParse (std::string& data);
	//獲取網頁的編碼方式
	bool GetCharset (std::string& s);
	//設置當前解析位置
	bool SetPos (const char* find);
	//設置當前解析位置(反向查找目標位置)
	bool SetPos_LastOf (const char* find);
	//查找是否存在目標位置,不會更新當前位置
	bool find (const char* find);
	//匹配一串字符串,使用sscanf_s獲取
	bool MatchString (const char* match, std::string& s);
	//獲取當前電腦IP地址
	static bool GetLocalIp (std::string& ip);
	//查詢某地址或某域名信息
	static bool GetAddrMessage (const wchar_t* addr, std::string& data);
	//關鍵函數,獲取lpURL指向的地址的HTML代碼,並存入data中
	static bool UrlGetHtml (LPCWSTR lpURL, std::string& data);
};

#endif //__H_HTML_PARSE_H__


hHttpParse.cpp

#include "hHtmlParse.h"

#pragma comment(lib, "WinInet.lib")

hHtmlParse::hHtmlParse (std::string& data) {
	this->data = data;
	this->p = 0;
}

bool hHtmlParse::GetCharset (std::string& s) {
	this->SetPos ("charset=");
	return this->MatchString ("%*[\"]%[^\"]", s);
}

bool hHtmlParse::SetPos (const char* find) {
	int t = this->data.find (find, p);
	if (-1 == t) return false;
	this->p = t + strlen (find);
	return true;
}

bool hHtmlParse::SetPos_LastOf (const char* find) {
	int t = this->data.rfind (find);
	if (-1 == t) return false;
	this->p = t + strlen (find);
	return true;
}

bool hHtmlParse::find (const char* find) {
	int t = this->data.find (find, p);
	return t != -1;
}

bool hHtmlParse::MatchString (const char* match, std::string& s) {
	return sscanf_s (&data.c_str () [p], match, const_cast<char*>(s.c_str ()), s.capacity ()) > 0;
}

bool hHtmlParse::GetLocalIp (std::string& ip) {
	std::string page, match;
	page.resize (512);
	match.resize(16);
	if (!hHtmlParse::UrlGetHtml (L"http://1111.ip138.com/ic.asp", page)) return false;
	hHtmlParse hp (page);
	hp.SetPos ("<center>");
	hp.MatchString ("%*[^0-9]%[0-9.]", match);
	ip.clear ();
	ip = match.c_str ();
	return true;
}

bool hHtmlParse::GetAddrMessage (const wchar_t* addr, std::string& data) {
	data.clear ();
	std::wstring link = L"http://www.ip138.com/ips138.asp?ip=";
	std::string page, match;
	link += addr;
	page.resize (16384);
	match.resize (64);
	if (!hHtmlParse::UrlGetHtml (link.c_str (), page)) return false;
	hHtmlParse hp (page);
	hp.SetPos_LastOf ("<table");
	hp.SetPos ("<td");
	hp.SetPos ("<td");
	bool b = true;
	if (hp.find (">>")) {
		b = false;
		hp.SetPos (">>");
		hp.MatchString ("%*[^0-9]%[0-9.]", match);
		data = match.c_str ();
	}
	hp.SetPos ("<ul");
	while (hp.find ("<li")) {
		hp.SetPos (":");
		hp.MatchString ("%[^<]", match);
		if (b) b = false; else data += "\n";
		data += match.c_str ();
	}
	return true;
}

bool hHtmlParse::UrlGetHtml (LPCWSTR lpURL, std::string& data) {
	HINTERNET hSession = InternetOpenW (L"EmotionSniffer", NULL, NULL, NULL, INTERNET_FLAG_NO_CACHE_WRITE);
	if (!hSession) return FALSE;
	HINTERNET hFile = InternetOpenUrlW (hSession, lpURL, NULL, NULL, INTERNET_FLAG_RELOAD, NULL);
	if (!hFile) {
		InternetCloseHandle (hSession); return FALSE;
	}
	DWORD dwW = 0, dwR = 0;
	int capacity = data.capacity ();
	do {
		dwW += dwR;
		if (dwW != 0 && dwR == 0) break;
		if (dwW + 1024 >= capacity) data.resize (capacity *= 2);
	} while (InternetReadFile (hFile, (LPVOID) (data.c_str () + dwW), 1024, &dwR));
	const_cast<char*>(data.c_str ()) [dwW] = '\0';
	InternetCloseHandle (hFile);
	InternetCloseHandle (hSession);
	return TRUE;
}

簡要說明下使用方法。首先是封裝的三個靜態函數, GetLocalIp 和 GetAddrMessage 這倆是通過調用 www.ip138.com 動態查詢獲取的結果,調用這個庫實現。使用這個庫時可以參照上面兩個函數的代碼; UrlGetHtml 是通過 Windows 的 Internet API 實現從 URL 指定的地址下載網頁。

重點說說這個庫的使用方法,我就說說 hHtmlParse::GetLocalIp 則函數的實現,方法大多類似。

1、字符串定義

std::string page, match;
page.resize (512);
match.resize (16);

其中 page 用作保存 HTML 代碼, match 用作保存匹配的字符串,也就是網頁中需要獲取的數據。由於訪問的數據不大,所以 page 設置 512 字節足夠。這兒也可以不用設置大小的, UrlGetHtml 實現的比較智能,可以自動擴展大小。設置一個大小隻是可以減少內存 I/O ,提高執行速度。另外這兒也給 match 設置一個大小。對於IP地址來說,16字節足夠了。

2、獲取網頁HTML代碼

if (!hHtmlParse::UrlGetHtml (L"http://1111.ip138.com/ic.asp", page)) return false;
這句話的意思就是下載網頁並將網頁代碼保存在 page 中,如果執行失敗則返回。

3、創建解析對象

hHtmlParse hp (page);
這句代碼用於創建一個解析對象,傳入的數據爲網頁HTML字符串。
這裏我們看看ip138的網頁代碼:

<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=gb2312">
<title> 您的IP地址 </title>
</head>
<body style="margin:0px"><center>您的IP是:[123.144.*.*] 來自:XX市 聯通</center></body></html>
地址和位置打碼了,大家看得懂就行了。我們先來分析分析,需要獲取地址的代碼前面有一個 <center> 是吧?那就把位置設置在這兒吧。。。
4、設置當前解析位置

hp.SetPos ("<center>");
在這個解析庫內部維護着一個字符指針,假如說網頁前面的都解析過了,需要解析後面的,那就在解析時給字符指針賦值,然後下次解析時從字符指針位置開始解析,既方便網頁處理,也提高解析速度,一舉兩得。這行代碼的意思就是將內部維護的地址放在 <center> 這兒,下次調用時直接從這兒開始了。

5、獲取匹配字符串,也就是IP地址

hp.MatchString ("%*[^0-9]%[0-9.]", match);
這兒就是獲取匹配字符串的代碼了 ,第一個參數爲sscanf函數需要的那個字符串,match返回匹配的結果。簡要說說這個字符串的意思,需要深究的自行bing。

%*[^0-9]   %*表示跳過,不匹配,[]表示匹配的內容,^表示非,0-9表示那十個數字。連起來的意思就是跳過所有不是0-9的字符。

%[0-9.]   %表示匹配,0-9.表示匹配的內容爲那十個數字和小數點,一直到非0-9或小數點爲止。

代碼到這兒就已經獲取了匹配的ip了,接下來的內容就是簡單的處理了。

6、簡單處理並返回

ip.clear ();
ip = match.c_str ();
return true;

由於匹配是通過 const_cast<char*>(s.c_str()) 賦值(這行代碼可以在 hHtmlParse::MatchString 函數中找到),所以,在執行步驟6這段代碼前,調用 match.length() 實際上返回的是不固定值,雖然不固定但是有數據。所以,假如非得在 const_cast<char*>(s.c_str()) 之後獲取字符串長度,只能用lstrlen。

這幾行代碼大家應該都懂,簡要說說第二行,意思是調用 std::string 的重載函數 operator=(char*) ,這樣可以刷新 std::string 的長度,執行第二行代碼之後,ip中的數據可以直接調用 length() 來獲取長度了。

使用步驟描述完畢,同學們如果需要解析其他網頁在相應地方修改就行了。另外, SetPos 和 MatchString 並不是只能調用一次的,只要找網頁方便,並且保證對象內部維護的指針還沒到末尾,就可以重複調用這些函數了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章