一只c++爬虫
一、原理
爬虫:用于在网页上抓取数据,用队列的思想,进行BFS~(将源URL放入队列,从队头取出一个URL进行遍历,并将其页面上的所有未爬过的URL放入队列中,直到队列为空。
二、实现
代码及解析如下:
int main()
{
startupWSA();
Go("music.163.com", 200);
cleanupWSA();
system("pause");
return 0;
}
void startupWSA()
{
WSADATA wsadata;//版本信息
WSAStartup( MAKEWORD(2,0), &wsadata);
}
解析:为了在应用程序当中调用任何一个Winsock API函数,首先第一件事情就是必须通过WSAStartup函数完成对Winsock服务的初始化,因此需要调用WSAStartup函数。使用Socket的程序在使用Socket之前必须调用WSAStartup函数。该函数的第一个参数指明程序请求使用的Socket版本,其中高位字节指明副版本、低位字节指明主版本;操作系统利用第二个参数返回请求的Socket的版本信息。当一个应用程序调用WSAStartup函数时,操作系统根据请求的Socket版本来搜索相应的Socket库,然后绑定找到的Socket库到该应用程序中。以后应用程序就可以调用所请求的Socket库中的其它Socket函数了。
void Go(const string &url, int count)
{
queue<string> urls;//用于放置所有的url
urls.push(url);
for (auto i = 0; i != count; ++i)
{
if ( !urls.empty() )
{
auto &url = urls.front();
auto pair = binaryString( url, "/" );
auto sock = connect(pair.first);//链接到拿出的URL
if ( sock && sendRequest(sock, pair.first, "/" + pair.second) )
{
auto buffer = move( recvRequest(sock) );
extUrl(buffer, urls);
}
closesocket(sock);
cout << url << ": count=> " << urls.size() << endl;
urls.pop();
}
}
}//
inline pair<string, string> binaryString(const string &str, const string &dilme)
{
//这个函数用于将字符串从‘/’处进行分割,分开URL和主机号,所以这只能做简单的识别,识别出第一个‘/’
pair<string, string> result(str, "");
auto pos = str.find(dilme);
if ( pos != string::npos )
{
result.first = str.substr(0, pos);
result.second = str.substr(pos + dilme.size());
}
return result;
}
inline SOCKET connect(const string &hostName)
{
auto ip = getIpByHostName(hostName);
if ( ip.empty() )
return 0;
auto sock = socket(AF_INET, SOCK_STREAM, 0);
if ( sock == INVALID_SOCKET )
return 0;
SOCKADDR_IN addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(80);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
if ( connect(sock, (const sockaddr *)&addr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR )
return 0;
return sock;
}
inline string getIpByHostName(const string &hostName)
{
//gethostbyname()返回对应于给定主机名的包含主机名字和地址信息的hostent结构指针。结构的//声明与gethostbyaddr()中一致。
hostent* phost = gethostbyname( hostName.c_str() );
return phost? inet_ntoa(*(in_addr *)phost->h_addr_list[0]): "";//得到对应的IP
}
inline bool sendRequest(SOCKET sock, const string &host, const string &get)
{
//用于
string http
= "GET " + get + " HTTP/1.1\r\n"
+ "HOST: " + host + "\r\n"
+ "Connection: close\r\n\r\n";
return http.size() == send(sock, &http[0], http.size(), 0);//send函数返回发送的字节//数是否一致
}
inline string recvRequest(SOCKET sock)
{
static timeval wait = {2, 0};
static auto buffer = string(2048 * 100, '\0');
auto len = 0, reclen = 0;
do {
fd_set fd = {0};
FD_SET(sock, &fd);
reclen = 0;
if ( select(0, &fd, nullptr, nullptr, &wait) > 0 )
{
reclen = recv(sock, &buffer[0] + len, 2048 * 100 - len, 0);//接收返回的数据//流
if (reclen > 0)
len += reclen;
}
FD_ZERO(&fd);
} while (reclen > 0);
return len > 11
? buffer[9] == '2' && buffer[10] == '0' && buffer[11] == '0'
? buffer.substr(0, len)
: ""
: "";
}
inline void extUrl(const string &buffer, queue<string> &urlQueue)
{
if (buffer.empty())
{
return ;
}
smatch result;
auto curIter = buffer.begin();
auto endIter = buffer.end();
while ( regex_search(curIter, endIter, result, regex("href=\"(https?:)?//\\S+\"") ) )
{//正则匹配找到超链接
urlQueue.push(regex_replace(
result[0].str(),
regex("href=\"(https?:)?//(\\S+)\""),
"$2") );
curIter = result[0].second;
}
}
本程序的流程为:启动Winsocket服务——将给的URL加入队列——取出队头元素——进行划分找出URL和主机——连接Socket——接收到返回的数据——判断其中是否有URL——将满足条件的URL加入队列——关闭Socket.