linux网络爬虫,Linux下网络爬虫程序

网络爬虫是搜索引擎最主要的组成部分,网络爬虫通过互联网获取网页,并将其存储在本地。然后通过对下载的网页的进一步分析,获取网页中的URL从而下载更多的资源。本文通过借助于curl库以及C++中的STL库来编写一个简单的网络爬虫程序。程序需要输入一个索引网页(例如www.baidu.com),然后程序会下载解析该网页,并将其中的url按照优先级进行保存。

1、网页下载:

网页下载主要是借助于现有的curl库进行,curl的具体使用方法可以参考man手册,在这里我们仅用到其-o(保存下载的网页)和-m(设置下载时间)选项。下载文件我们通过执行脚本程序来完成。

# !/bin/bash

# Progran:

# This program is going to down url from web by curl

# Para:

#$1:the url will be download$2: the new name of the url

# History:

#2014.11.06By Gao Fan, SEUfirst release

PATH=/bin:/sbin:/usr/bin:/usr/sbin:/local/bin:/local/sbin:~/bin

export PATH

curl -m 30 $1 -o $2 && exit 0

exit 1

脚本程序传入两个参数,其中$1为要下载网页的URL,$2表示保存的文件名。为了方便,我们将url中的‘/’替换为‘_’来作为该url的本地保存文件名。其中我们设置连接时间最长为30s以防止一直试图下载一个存在但不能下载的网页。

以上是采用curl进行网页下载的,该方法只能将网页下载完后对其解析。当然也可以自己写TCP程序请求服务器获得网页内容,这样就可以边下载边解析。

然后我们定义一个URL类,类名为URLclass,该类主要是解析URL,将其主机名和所请求的资源名分开,本程序由于采用curl下载网页,所以用不到该类,但如果想要通过自己写TCP来从主机直接获得网页资源,运用该类会非常方便。

#ifndef URL_H__

#define URL_H__

#include "webspider.h"

#includeusing namespace std;

class URLclass

{

private:

string url; //提供的URL

string hostURL; //主机名

string file; //文件名(包含参数)

int paraseURL();

public:

URLclass()

{}

int setURL(string murl);

string getHostURL();

string getFile();

};

#endif // URL_H__

string URLclass::getHostURL()

{

return hostURL;

}

string URLclass::getFile()

{

return file;

}

int URLclass::paraseURL()

{

string s1;

if(url.find("//") == string::npos)

{

return 0;

}

s1 = url.substr(url.find("//") + 2, url.size());

if(s1.find('/', 0) == string::npos)

{

hostURL = s1;

file = "/";

}

else

{

hostURL = s1.substr(0, s1.find("/"));

file = s1.substr(s1.find("/"), s1.size());

}

return 1;

}

int URLclass::setURL(string murl)

{

url = murl;

int flag = paraseURL();

return flag;

}

我们可以通过声明URLclass对象,并使用setURL来对其赋予url,通过getHostUrl和getFIle方法获得url中的主机名和请求资源。

当我们下载完一个资源后,将该资源的本地文件名放到消息队列里,供解析文件程序解析。我们可以定义消息队列消息为如下结构:

struct mymesg

{

long mtype;

int len;

char pathName[512];

};下载URL的完整程序如下:

int Download(const char *url, char *pathName)

{

char *cmd;

cmd = (char*)malloc(1024);

memset(cmd, '\0', 1024);

sprintf(cmd, "/home/web/Spiderscript/downURL %s %s", url, pathName); //downURL是上述脚本程序

int flag = system(cmd);

if(flag != 0)

{

return 0;

}

mymesg *MyMsg;

MyMsg = (mymesg*)malloc(sizeof(mymesg));

memset(MyMsg, '\0', sizeof(mymesg));

MyMsg->mtype = 1;

memcpy(MyMsg->pathName, pathName, strlen(pathName));

MyMsg->len = sizeof(int) + strlen(MyMsg->pathName);

msgsnd(msgID,(char *)MyMsg, MyMsg->len, IPC_NOWAIT);

free(MyMsg);

free(cmd);

return 1;

}

为了使程序执行较快,我们将定义一个线程专门下载网页。

int tranName(const char *str, char *name)

{

memcpy(name, str, strlen(str));

int i;

for(i = 0; i < strlen(name); i++)

{

if(name[i] == '/')

{

name[i] = '_';

}

}

return 1;

}

void* Down(void* ptr)

{

char name[200];

while(1)

{

if(URLMap.empty())

{

sleep(10);

}

else

{

if(URLDeque.empty())

{

pthread_mutex_lock(&DequeMutex); //实现线程同步

mapSortByValue(URLMap, URLDeque);

pthread_mutex_unlock(&DequeMutex);

}

string url = URLDeque.front(); //获取队列中第一个URL

VisitedURL.insert(url); //存入已访问set中。

URLDeque.pop_front();

cout << "visit:" << url << endl;

memset(name, '\0', 200);

tranName(url.c_str(), name);

Download(url.c_str(), name);

}

sleep(1);

}

}

其中tranName函数为将url中的'/'替换为'_'的函数

2、网页解析

网页解析主要是解析网页内容并提取其中的URL,然后对URL进行优先级分类。我们利用pcre库提供的正则表达式来解析网页并提取其中的url。pcre的安装参考http://blog.csdn.net/lanxinglan/article/details/40798933。正则表达式的使用参考http://blog.chinaunix.net/uid-26575352-id-3517146.html。

为了解析出rul,我们定义如下的正则表达式:const char pattern[] = "此表达式可以提取出

类似的URL,其中提取到的URL为

http://blog.csdn.net/microzone/article/details/6684436这样我们就可以根据此URL来下载相应的网页了。

接下来的问题就是如何对URL进行优先级分级,对网页解析一般分为广度优先遍历算法,PageRank算法和OPIC算法。

其中广度优先遍历算法最简单,其将每一个网页中的url按照解析的顺序插入到一个队列的尾部,然后从队列头部取url进行下载解析。离root(index)越近的url会优先得到解析。

PageRank算法是将所有网页抓到本地,然后对其url解析,此算法认为当所有网页中某个url数量最多时其最重要,优先级也就最高。然而在现实中无法获取所有网页后再对其解析,因此我们采用非完全的PageRank算法。解析一定数量网页后取出数量最多的url再进行下载。

OPIC是将一个网页的权值平均分给该网页中所有URL的一种算法。

我们采用非完全的PageRank算法,改程序我们声明一个mapURLMap、一个dequeURLDeque、一个setVisitedURL来完成此算法。其中URLMap中存放解析出来的url和该url目前为止出现的次数。URLDeque中存放URLMap中value值最大的前DequeSize个url。Visit而URL中存放已经访问过的url。

由于map结构无法使其按照value值进行排序,所以我们需要定义一个函数来获得map中value值最大的url。该函数定义如下:

bool cmp(const PAIR &val1, const PAIR &val2)

{

return val1.second > val2.second;

}

void mapSortByValue(map&myMap, deque&myDeque)

{

vectortmpVec(myMap.begin(), myMap.end()); //利用vector作为中介对其进行排序

sort(tmpVec.begin(), tmpVec.end(), cmp);

vector:: iterator iter = tmpVec.begin();

int i = 0;

while(i != DequeSize && iter != tmpVec.end())

{

myDeque.push_back(iter->first);

cout << "\n" ;

cout << iter->first << " " << iter->second << "\n";

myMap.erase(iter->first);

++i;

++iter;

}

}

我们通过读取本地网页来对网页进行解析,每次读入一行,然后调用正则表达式获取此行中的url并保存起来。具体函数如下:

/*

* 函数名:GetURL

* 函数描述:读取消息队列中第一条消息,得到要解析URL的文件名,对其进行解析,将解析到的

* URL放到优先队列URLDeququ中,此函数只得到带.html的URL

* 参数:*ptr(由于在用g++编译时必须指定参数,所以在此指定参数,该参数没有什么实际运用)

*/

void* GetURL(void *ptr)

{

mymesg *MyMsg;

MyMsg = (mymesg*)malloc(sizeof(mymesg));

memset(MyMsg, '\0', sizeof(mymesg));

char buf[MAXLINE];

int fd;

rio_t rp;

string str1, str2;

pcre *re;

const char *error;

int erroffset;

int ovector[OVECCOUNT];

int rc, i;

while(1)

{

memset(MyMsg, '\0', sizeof(mymesg));

int flag = msgrcv(msgID, (char*)MyMsg, sizeof(mymesg) - sizeof(long), 1, 0);//获取消息队列里消息

if(flag == -1)

{

continue;

}

else

{

fd = open(MyMsg->pathName, O_RDONLY);

if(fd < 0)

{

continue;

}

rio_readinitb(&rp, fd);

re = pcre_compile(pattern, // pattern, 输入参数,将要被编译的字符串形式的正则表达式

0, // options, 输入参数,用来指定编译时的一些选项

&error, // errptr, 输出参数,用来输出错误信息

&erroffset, // erroffset, 输出参数,pattern中出错位置的偏移量

NULL);

if (re == NULL)

{ //如果编译失败,返回错误信息

printf("PCRE compilation failed at offset %d: %s\n", erroffset, error);

return (void*)0;

}

while(rio_readnb(&rp, buf, MAXLINE) > 0)

{

int t = 0;

char *ptr = buf;

do{

rc = pcre_exec(re, // code, 输入参数,用pcre_compile编译好的正则表达结构的指针

NULL, // extra, 输入参数,用来向pcre_exec传一些额外的数据信息的结构的指针

ptr, // subject, 输入参数,要被用来匹配的字符串

strlen(ptr), // length, 输入参数, 要被用来匹配的字符串的指针

0, // startoffset, 输入参数,用来指定subject从什么位置开始被匹配的偏移量

0, // options, 输入参数, 用来指定匹配过程中的一些选项

ovector, // ovector, 输出参数,用来返回匹配位置偏移量的数组

OVECCOUNT);

if(rc <= 0)

{

break;

}

char str[1024];

memset(str, '\0', 1024);

char *substring_start = ptr + ovector[2];

int substring_length = ovector[3] - ovector[2];

memcpy(str, substring_start,substring_length);

string cstr = str;

if(cstr.find("http") != string::npos) //某些URL中省略主机名,所以需要保存一个主机名。

{

memset(host, '\0', 50);

strcpy(host, "http://");

URLclass url;

url.setURL(cstr);

strcpy(host, url.getHostURL().c_str());

}

else if(cstr.find("http") == string::npos)//一般省略的主机名为本网页中的主机名

{

char tmp[40];

strcpy(tmp, host);

strcat(tmp, str);

memset(str, '\0', strlen(str));

memcpy(str, tmp, strlen(tmp));

}

if(string(str).find(keyWord) == string::npos) //keyWord为url限制

{

}

else if(VisitedURL.count(str) == 0)

{

pthread_mutex_lock(&MapMutex); //多线程操作需对其同步

if(URLMap.count(str) == 0)

{

URLMap[str] = 1;

}

else

{

++URLMap[str];

}

pthread_mutex_unlock(&MapMutex);

}

memset(str, '\0', 1024);

t = ovector[1];

ptr += t;

memset(ovector, '\0', sizeof(ovector));

}while(rc > 0); //如果一行中包含多个url,需要进行循环获得

}

close(fd);

}

sleep(1);

}

free(MyMsg);

return (void*)0;

}

我们运用到的读函数为rio_readnb,此函数鲁棒性较强,详细说明请参考《深入理解计算机操作系统》一书。另外此函数声明为void*,因为此函数为一个单独的线程函数。在C++中定义线程函数时必须包含参数。

3、main函数

我们的main函数主要是负责初始化和创建主线程。其中第二个参数argv[1]表示初始网页(index)。第三个参数argv[2]表示网页中需要包含的字符串。具体实现如下:

int main(int argc, char *argv[])

{

if(argc < 2)

{

cout << "please input the index html for start" << endl;

return 0;

}

int key = ftok(dirPath, 2);

while((msgID = msgget(key, IPC_CREAT | 0660)) < 0)

{

cout << "Create msgID failed..." << endl;

sleep(1);

}

Download(argv[1], "index.html");

strcpy(host, argv[1]);

strcpy(keyWord, argv[2]);

pthread_t downhtml, geturl;

pthread_create(&geturl, NULL, GetURL, NULL);

pthread_create(&downhtml, NULL, Down, NULL);

while(1)

{

sleep(10);

}

return 0;

}

微信扫码订阅
UP更新不错过~
关注
  • 0
    点赞
  • 0
    收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值