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 并不是只能调用一次的,只要找网页方便,并且保证对象内部维护的指针还没到末尾,就可以重复调用这些函数了。