项目日记 - 开发图片爬虫 Ⅱ

项目日记 - 开发图片爬虫 Ⅱ

用C++开发一个图片爬虫 Ⅱ :编码转换

前言

我们上一节学习了获取网页的代码,那么大家可能会以为接下来就是分析网页了。但事实上,我们还需要先转换编码。我们都知道,C++编程常用的是ASCII编码,而网页大部分使用的是UTF-8编码。

因此我们要先将获取后的网页转换成我们想要的编码,再去分析内容。

理论

我们写编码转换的代码,肯定要知道这些编码之间的关系。

ASCII编码我们再熟悉不过了,然而我相信很多人和我一样,分不清什么是Unicode,UTF-8,UTF-16,UTF-32。

我不想用长篇大论来解释这些东西,就给出它们各自的定义和我们需要知道的东西。如果想更深入的了解,可以自行搜索。

Unicode(统一码、万国码、单一码)是计算机科学领域里的一项业界标准,包括字符集、编码方案等。

至于它为什么诞生,我简单说明一下。因为我们熟知的ASCII码能表示的字符是非常有限的,根本无法承受那么多国家的字符。因此国际组织就又发明了一个新的编码,几乎收尽所有国家的字符,甚至包括特殊字符。

Unicode与其他编码类似,也是每一个字符都对应一个数字来表示它。但Unicode也仅仅如此,它没有规定这个二进制代码如何存储。

那么问题来了,在Unicode编码下,假设有一个3字节的二进制代码,计算机怎么知道表示的是一个字符还是两个字符或者是三个字符呢?因此UTF-8、UTF-16这一系列东西就诞生了。

先介绍UTF-8,这是开发网页首选的编码存储方式。

UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,又称万国码,由Ken Thompson于1992年创建。

如果略懂编码的话,可变长度是最大特点。但是为了实现可变长度,就需要一些二进制位来表示长度,我认为它的编码规则可以说是非常典型了。

字节数 二进制编码形式
1字节 0xxxxxxx
2字节 110xxxxx 10xxxxxx
3字节 1110xxxx 10xxxxxx 10xxxxxx
4字节 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
5字节 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
6字节 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

再简单说一下UTF-16,定义:

UTF-16是Unicode字符编码五层次模型的第三层:字符编码表(Character Encoding Form,也称为 “storage format”)的一种实现方式。

UTF-16虽然也可变长度,但不如UTF-8灵活。它有大尾序和小尾序两种储存形式,这个可以自行查阅。

UTF-32 (或 UCS-4)是一种将Unicode字符编码的协定,对每一个Unicode码位使用恰好32位元。其它的Unicode transformation formats则使用不定长度编码。

网上找到一个工具,Unicode和UTF编码转换,有兴趣可以试一下。

实战

我们为什么要了解上面那些东西,最终还是为了更容易理解代码。先给出一个将一个字符串从UTF-8转为ASCII的示例代码,从网上抄下来的。

1
2
3
4
5
6
7
8
9
10
11
12
const char *changeTxtEncoding(const char *szU8) {
int wcsLen= MultiByteToWideChar(CP_UTF8, NULL, szU8, strlen(szU8), NULL, 0);
wchar_t *wszString= new wchar_t[wcsLen + 1];
MultiByteToWideChar(CP_UTF8, NULL, szU8, strlen(szU8), wszString, wcsLen);
wszString[wcsLen]= '\0';
int ansiLen= WideCharToMultiByte(CP_ACP, NULL, wszString, wcslen(wszString), NULL, 0, NULL, NULL);
char *szAnsi= new char[ansiLen + 1];
WideCharToMultiByte(CP_ACP, NULL, wszString, wcslen(wszString), szAnsi, ansiLen, NULL, NULL);
szAnsi[ansiLen]= '\0';
delete[] wszString;
return szAnsi;
}

此函数是将一个字符串,先从UTF-8转为UTF-16,再转为ASCII。

MultiByteToWideChar

此函数可将字符串映射到UTF-16(宽字符)字符串。字符串不一定来自多字节字符集。在这里,字符集或代码页我们都可以理解为编码映射表。

1
2
3
4
5
6
7
8
int WINAPI MultiByteToWideChar(
_In_ UINT CodePage,
_In_ DWORD dwFlags,
_In_NLS_string_(cbMultiByte) LPCCH lpMultiByteStr,
_In_ int cbMultiByte,
_Out_writes_to_opt_(cchWideChar,return) LPWSTR lpWideCharStr,
_In_ int cchWideChar
);

定义简化如下:

1
2
3
4
5
6
7
8
int WINAPI MultiByteToWideChar(
UINT CodePage,
DWORD dwFlags,
LPCCH lpMultiByteStr,
int cbMultiByte,
LPWSTR lpWideCharStr,
int cchWideChar
);

CodePage为指定执行转换的字符集。如果原字符串为ASCII通常选择CP_ACP,关于CP_ACP与CP_THREAD_ACP的区别可以自己了解。我们这里原字符串为UTF-8,因此选择CP_UTF8。数值表:

数值 说明
CP_ACP Windows系统默认的ANSI代码页。
CP_MACCP Mac系统代码页。
CP_ACP OEM系统代码页。
CP_SYMBOL 符号字符集。
CP_THREAD_ACP 当前Windows线程的ANSI代码页。
CP_UTF7 使用UTF-7。
CP_UTF8 使用UTF-8。

dwFlags设定转换类型。缺省值为MB_PRECOMPOSED,对于UTF-8必须为0或MB_ERR_INVALID_CHARS,此参数作用较小,数值表就不给出了。

lpMultiByteStr指向一个要转换的字符串。

cbMultiByte指定长度,如果lpMultiByteStr指向的字符串为空结束,此参数可设为-1。如果设为-1,函数将处理包括终止空字符在内的所有字符。我们可以用strlen来自行获得长度。

lpWideCharStr为指向接收缓冲器的指针。

cchWideChar为lpWideCharStr指示的缓冲器的大小(以字符为单位)。如果此值为0不使用lpWideCharStr缓冲区。

Return Value即返回值,返回写入到缓冲器的字符数。如果cchWideChar为0则返回缓冲器所需大小(以字符为单位)。

我们第一次调用此函数时应将cchWideChar设为0,并记录函数返回值,第二次调用时将cchWideChar设为此值。

WideCharToMultiByte

与MultiByteToWideChar恰恰相反,但多两个参数,此函数可将UTF-16(宽字符)字符串映射到新字符串。

1
2
3
4
5
6
7
8
9
10
int WINAPI WideCharToMultiByte(
UINT CodePage,
DWORD dwFlags,
LPCWCH lpWideCharStr,
int cchWideChar,
LPSTR lpMultiByteStr,
int cbMultiByte,
LPCCH lpDefaultChar,
LPBOOL lpUsedDefaultChar
);

CodePage为指定执行转换的字符集。可选数值与MultiByteToWideChar相同,我们这里选择CP_ACP。

dwFlags设定转换类型。设为0即可。

lpWideCharStr指向一个要转换的字符串。

cchWideChar指定长度。对于宽字符,自行使用wcslen来获得。

lpMultiByteStr为指向接收缓冲器的指针。

cbMultiByte为lpMultiByteStr指示的缓冲器的大小(以字符为单位)。

lpDefaultChar为指向另一个接收缓冲器的指针,如果某个字符不能在指定的代码页中表示将使用此缓冲器。我们不需要,设为NULL即可。

lpUsedDefaultChar为一个指向BOOL变量的指针。如果使用了lpDefaultChar将被设为TRUE,否则为FALSE。我们也设为NULL即可。

Return Value即返回值,与MultiByteToWideChar相类比。

调用两次,步骤也与MultiByteToWideChar相似。

delete

网上给的代码没有释放内存的语句,可能会造成内存泄露。

delete语句使用过指针都知道,释放一个指针指向的内存。

delete[]语句释放一个数组指针指向的内存。

如果想深入了解其中的原理,可以自行查阅。

小结

这一节简单介绍了编码的知识和使用C++进行编码转换的代码。我们这个项目深入了解编码系统,所介绍的也不过是冰山一角。

现在大家已经可以自己写代码对网页进行简单分析了。下一节我打算介绍关于WinHTTP的内容和用途。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×