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

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

用C++开发一个图片爬虫 Ⅲ :URL预处理 【咕咕】
封面图片来自网络,如有侵权将删除。

前言

上一节说这节介绍WinHTTP,现在好像鸽子了。经过我再三思考后,还是先深入学习WinInet吧。

关于根据链接下载网页的各种方式在第一节已经介绍过了。这里再补充一下,还有一种方式是使用外部库libcurl,使用方便代码简洁,但我不把它当做我们主要学习的对象,因为它太过于简单。

这节可以说是个扩展篇,并不是这个项目优先考虑的内容。先介绍它主要是因为这部分东西更有意思,大家可能会更感兴趣。

目的

这个预处理是我自己勉强定义的,所以大家可能不理解。

通俗的说,URL预处理意为对一个URL链接本体进行处理,请大家不要误以为是对HTML网页内容进行分析。

再说清楚一些,获取链接所指向的内容前,程序并不知道这个内容是什么类型的。也就是说,它可能是一个HTML也可能是图片,又或者是一个CSS文件。所以我们要对URL进行一定的预处理,然后判断文件后缀之类的。

先给出本节流程图:

项目流程图

很多人可能不理解获得内容类型为什么需要先分解URL,那就接着往下看。

理论

从基础说起,大家都模糊地知道URL是什么,我给出它的定义:

URL是统一资源定位符(英语Uniform Resource Locator的缩写)也被称为网页地址,是因特网上标准的资源的地址。它最初是由蒂姆·伯纳斯·李发明用来作为万维网的地址。现在它已经被万维网联盟编制为互联网标准RFC1738了。

URL由多部分组成,详细说有9部分,我们一个一个介绍。有关资料部分来自于going_han的博客

一个URL的实例: scheme://user:password@host:port/path;params?query#frag

编号 名称 介绍
1 协议(protocol) 为进行网络中的数据交换而建立的规则、标准或约定。有时也叫scheme,常用的协议有http、https、ftp、file、mailto。
2 用户名(user) 一般用不到,为访问资源使用的用户名。
3 密码(password) 一般也用不到,为访问资源使用的密码。
4 域名(domain) 域名(英语:Domain Name),简称域名、网域,是由一串用点分隔的名字组成的Internet上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位(有时也指地理位置)。有时也叫host,可以使用IP地址作为域名使用。
5 端口(port) 在网络技术中,端口(Port)有好几种意思。我们这里所指的端口不是指物理意义上的端口,而是特指TCP/IP协议中的端口,是逻辑意义上的端口。端口不是一个必须部分,如果省略将采用协议默认使用的端口。
6 路径(path) 由零或多个“/”符号隔开的字符串,一般用来表示主机上的一个目录或文件地址。
7 参数(params) 这是用于指定特殊参数的可选项。
8 查询(query) 可选项。用于给动态网页传递参数,可有多个参数,用“&”符号隔开,每个参数的名和值用“=”符号隔开。
9 片段(frag) 主要用于对资源进行分类。例如一个网页中有多个名词解释,可使用fragment直接定位到某一名词解释。访问一个帮助文档时,文档的各个章节就可以表述为片段。

所以分解URL有什么用呢?我们再简单了解一下HTTP访问的步骤,HTTP访问时会进行多步操作,其中有两步我想重点介绍一下,分别是:发送请求头(Request Headers)和接受响应头(Response Headers)。

其实大家现在就可以直观的了解它,介绍一种方法。

随便找一个可用网页(比如这篇博客) -> 点击键盘上的F12,弹出开发人员调试工具 -> 进入’Network’选项页 -> 点击录制按钮(黑色的圆点),按钮变红色 -> 刷新页面。

然后会有很多信息,我们要找的一般在第一个位置,Name一般为URL最后一个’/‘后的字符串。如果是在这篇博客进行的上述操作,则Name为’03-spiking3/‘,点击会显示详细信息。

我使用的浏览器效果如下图,部分进行了打码处理,URL为localhost是因为我在本地测试。

开发人员调试工具截图

这里已经很直观了,我们需要的就是Response Headers中的’Content-Type:text/html’,这个可以用来判断内容的类型。大家可以再打开一个图片URL进行观察,比如上面那个截图,发现其中’Content-Type:image/png’。我们根据此信息来判断内容的类型。

其他信息大家可以自行了解,对项目作用不大,我就不作介绍了。

另外,本节含有大量string类的内容,但只要不是初学者都很熟悉了,因此不作理论上的介绍。

实战

这部分理论易理解,但代码部分比较长。先给出本节所有的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
string _trim(const string &str) {
size_t start= str.find_first_not_of(" \n\r\t");
size_t until= str.find_last_not_of(" \n\r\t");
string::const_iterator i= start == string::npos ? str.begin() : str.begin() + start;
string::const_iterator x= until == string::npos ? str.end() : str.begin() + until + 1;
return string(i, x);
}
void parse_url(const string &raw_url, string &port, string &domain, string &path) {
string x= _trim(raw_url), protocol, query;
int offset= 0;
size_t pos1, pos2, pos3, pos4;
offset= offset == 0 && x.compare(0, 8, "https://") == 0 ? 8 : offset;
offset= offset == 0 && x.compare(0, 7, "http://") == 0 ? 7 : offset;
pos1= x.find_first_of('/', offset + 1);
path= pos1 == string::npos ? "" : x.substr(pos1);
domain= string(x.begin() + offset, pos1 != string::npos ? x.begin() + pos1 : x.end());
path= (pos2= path.find("#")) != string::npos ? path.substr(0, pos2) : path;
port= (pos3= domain.find(":")) != string::npos ? domain.substr(pos3 + 1) : "0";
domain= domain.substr(0, pos3 != string::npos ? pos3 : domain.length());
protocol= offset > 0 ? x.substr(0, offset - 3) : "";
query= (pos4= path.find("?")) != string::npos ? path.substr(pos4 + 1) : "";
path= pos4 != string::npos ? path.substr(0, pos4) : path;
return;
}
void judgeUrlType(const char *Url, string &fileSuffix) {
static byte Temp[MAXBLOCKSIZE];
HINTERNET hSession= InternetOpenA("DownloadKit", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0), hConnect= 0, hRequest= 0;
if(hSession != NULL) {
INTERNET_PORT nPort= 0;
DWORD dwReadSize= MAXBLOCKSIZE;
string sPort, sServer, sObject;
parse_url(Url, sPort, sServer, sObject);
for(size_t i= 0; i < sPort.size(); i++) nPort= nPort * 10 + sPort[i] - '0';
hConnect= InternetConnectA(hSession, sServer.c_str(), nPort, NULL, NULL, INTERNET_SERVICE_HTTP, 0, NULL);
if(hConnect == NULL) goto fail;
hRequest= HttpOpenRequestA(
hConnect, "GET", sObject.c_str(), NULL, NULL, NULL, INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_AUTH | INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_NO_UI | INTERNET_FLAG_IGNORE_CERT_CN_INVALID | INTERNET_FLAG_RELOAD, NULL);
if(hRequest == NULL || !HttpSendRequest(hRequest, NULL, 0, NULL, 0)) goto fail;
if(!HttpQueryInfoA(hRequest, HTTP_QUERY_RAW_HEADERS, Temp, &dwReadSize, NULL)) goto fail;
Temp[dwReadSize]= '\0';

for(unsigned long i= 0; i < dwReadSize; i= i + strlen((const char *)Temp + i) + 1) {
if(string((const char *)Temp + i).compare(0, 13, "Content-Type:") == 0) {
printf("%s\n", Temp + i + 14);
fileSuffix= "";
fileSuffix= (string((const char *)Temp + i + 14).compare("text/html") == 0) ? "html" : fileSuffix;
fileSuffix= (string((const char *)Temp + i + 14).compare("image/png") == 0) ? "png" : fileSuffix;
fileSuffix= (string((const char *)Temp + i + 14).compare("image/jpeg") == 0) ? "jpg" : fileSuffix;
fileSuffix= (string((const char *)Temp + i + 14).compare("image/gif") == 0) ? "gif" : fileSuffix;
// Add what you want
break;
}
}
if(hRequest) InternetCloseHandle(hRequest);
if(hConnect) InternetCloseHandle(hConnect);
if(hSession) InternetCloseHandle(hSession);
return;
}
fail:
if(hRequest) InternetCloseHandle(hRequest);
if(hConnect) InternetCloseHandle(hConnect);
if(hSession) InternetCloseHandle(hSession);
printf("Internet Error\n");
return;
}

_trim函数作用为去除字符串的空白换行符。

parse_url函数作用为分解URL,分解为端口、域名和路径。

judgeUrlType函数作用为获得HTTP响应头并分析URL内容类型。

string

本节使用C++STL中的string类比较多,简单的大家都使用过,所以介绍一下几个比较陌生的函数。

find_first_not_of
1
2
3
4
size_t find_first_not_of (const string& str, size_t pos = 0) const;
size_t find_first_not_of (const char* s, size_t pos = 0) const;
size_t find_first_not_of (const char* s, size_t pos, size_t n) const;
size_t find_first_not_of (char c, size_t pos = 0) const;

Return Value即返回值,返回第一个不匹配字符的位置,如果没有不匹配字符则返回string::npos。若使用字符串作参数,则此处不匹配字符指参数中不包含的字符。

string::npos
1
static const size_t npos = -1;
find_last_not_of
1
2
3
4
size_t find_last_not_of (const string& str, size_t pos = npos) const;
size_t find_last_not_of (const char* s, size_t pos = npos) const;
size_t find_last_not_of (const char* s, size_t pos, size_t n) const;
size_t find_last_not_of (char c, size_t pos = npos) const;

Return Value即返回值,与find_first_not_of类似,返回最后一个不匹配字符的位置。

substr
1
string substr (size_t pos = 0, size_t len = npos) const;

Return Value即返回值,返回从pos开始长度为len的子串。

InternetConnectA

评论

Your browser is out-of-date!

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

×