您的位置:首页 > 其它

自己动手编写CSDN博客备份工具-blogspider之源码分析(3)

2012-02-02 11:18 585 查看
作者:gzshun. 原创作品,转载请标明出处!

来源:
http://blog.csdn.net/gzshun

周星驰:剪头发不应该看别人怎么剪就发神经跟流行,要配合啊!你看你的发型,完全不配合你的脸型脸型又不配合身型,身型又和发型完全不搭,而且极度不配合啊!!欢哥!你究竟要怎么样啊? 《算死草》

在开篇,先happy下,新年到,开开心心过好年!

已经写了几篇文章,把代码贡献给有需要的人,这里列出前几篇文章,需要的马上跳转,麻利的。。

自己动手编写CSDN博客备份工具-blogspider

自己动手编写CSDN博客备份工具-blogspider之源码分析(1)

自己动手编写CSDN博客备份工具-blogspider之源码分析(2)

本文是blogspider最重要的部分,开始要下载并分析CSDN博客,把博文的URL分析出来,添加进链表,GO!

一.先下载博客主页到本地的index.html

下载网页到本地的步骤:

建立连接 -> 连接网站服务器 -> 发送请求 -> 接收响应 -> 保存到本地

connect_web -> send_request -> recv_response

源码说话:

[cpp] view
plaincopy

/*****************************************************************

下载个人的博客主页

*****************************************************************/

static int download_index(blog_spider * spider_head)

{

int ret;

ret = connect_web(spider_head);

if (ret < 0) {

goto fail_download_index;

}

ret = send_request(spider_head);

if (ret < 0) {

goto fail_download_index;

}

ret = recv_response(spider_head);

if (ret < 0) {

goto fail_download_index;

}

close(spider_head->blog->b_sockfd);

return 0;

fail_download_index:

close(spider_head->blog->b_sockfd);

return -1;

}

二.建立连接,并连接网站服务器

先从"blog.csdn.net"主机名获取到IP地址,如下:

[cpp] view
plaincopy

/**********************************************************

根据主机名获取到主机信息,主要是获取到IP地址.

**********************************************************/

static int get_web_host(const char * hostname)

{

/*get host ip*/

web_host = gethostbyname(hostname);

if (NULL == web_host) {

#ifdef SPIDER_DEBUG

fprintf(stderr, "gethostbyname: %s\n", strerror(errno));

#endif

return -1;

}

#ifdef SPIDER_DEBUG

printf("IP: %s\n", inet_ntoa(*((struct in_addr *)web_host->h_addr_list[0])));

#endif

return 0;

}

开始初始化套接字,连接网站服务器:

[cpp] view
plaincopy

/**********************************************************

初始化SOCKET,并连接到网站服务器

**********************************************************/

static int connect_web(const blog_spider * spider)

{

int ret;

struct sockaddr_in server_addr;

/*init socket*/

spider->blog->b_sockfd = socket(AF_INET, SOCK_STREAM, 0);

if (spider->blog->b_sockfd < 0) {

#ifdef SPIDER_DEBUG

fprintf(stderr, "socket: %s\n", strerror(errno));

#endif

return -1;

}

memset(&server_addr, 0, sizeof(server_addr));

server_addr.sin_family = AF_INET;

server_addr.sin_port = htons(spider->blog->b_port);

server_addr.sin_addr = *((struct in_addr *)web_host->h_addr_list[0]);

ret = connect(spider->blog->b_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));

if (ret < 0) {

#ifdef SPIDER_DEBUG

fprintf(stderr, "connect: %s\n", strerror(errno));

#endif

return -1;

}

return 0;

}

三.发送请求到网站服务器

HTTP协议里面比较重要的有俩方法:GETPOST

向网站服务器发送请求:

GET %s HTTP/1.1\r\n

Accept: */*\r\n

Accept-Language: zh-cn\r\n

User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)\r\n

Host: %s:%d\r\n

Connection: Close\r\n\r\n

GET后面跟的是请求的文件,剩下的是一些基本信息,该协议头的结束标志是一个空行,所以程序可以通过判断"\r\n\r\n"为结束标志。具体HTTP协议可以上网搜索一些资料,这里不做介绍。

源码说话:

[cpp] view
plaincopy

/**********************************************************

向网站服务器发送请求

**********************************************************/

static int send_request(const blog_spider * spider)

{

int ret;

char request[BUFSIZE];

memset(request, 0, sizeof(request));

sprintf(request,

"GET %s HTTP/1.1\r\n"

"Accept: */*\r\n"

"Accept-Language: zh-cn\r\n"

"User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)\r\n"

"Host: %s:%d\r\n"

"Connection: Close\r\n"

"\r\n", spider->blog->b_page_file, spider->blog->b_host, spider->blog->b_port);

ret = send(spider->blog->b_sockfd, request, sizeof(request), 0);

if (ret < 0) {

#ifdef SPIDER_DEBUG

fprintf(stderr, "send: %s\n", strerror(errno));

#endif

return -1;

}

#ifdef SPIDER_DEBUG

printf("request:\n%s\n", request);

#endif

return 0;

}

周星驰:扫地只不过是我的表面工作,我真正地身份是一位研究僧(生)。《少林足球》

轻松一下,继续。。。


四.接收响应消息

向网站服务器发送了请求,当然必须在本地开始接收。由于可能是网速慢的原因,接收响应消息与消息正体速度有点慢。这里使用了select函数与FD_SET集合来处理,当监听到socket可读,才开始读取消息并保存到本地。

[cpp] view
plaincopy

/***************************************************************************************

接受网站服务器的反馈信息,得到请求的文件内容

向服务器发送请求信息或者服务器的响应消息,以空行结束,所以可以用"\r\n\r\n"来判断结束标志

select:

int select (int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval * timeout);

>0: 正确

-1: 出错

0 : 超时

void FD_ZERO(fd_set *fdset); // clear all bits in fdset

void FD_SET(int fd, fd_set *fdset); // turn on the bit for fd in fdset

void FD_CLR(int fd, fd_set *fdset); // turn off the bit for fd in fdset

int FD_ISSET(int fd, fd_set *fdset); // is the bit for fd on in fdset

***************************************************************************************/

static int recv_response(const blog_spider * spider)

{

int ret, end, recvsize, count;

char recvbuf[BUFSIZE];

fd_set read_fds;

struct timeval timeout;

FILE *fp;

/*建议时间要长点, select失败可能的原因是收到网站的响应消息超时*/

timeout.tv_sec = 30;

timeout.tv_usec = 0;

while (1) {

FD_ZERO(&read_fds);

FD_SET(spider->blog->b_sockfd, &read_fds);

ret = select(spider->blog->b_sockfd+1, &read_fds, NULL, NULL, &timeout);

if (-1 == ret) {

/*出错,直接返回错误*/

#ifdef SPIDER_DEBUG

fprintf(stderr, "select: %s\n", strerror(errno));

#endif

return -1;

}

else if (0 == ret) {

/*超时, 继续轮询*/

#ifdef SPIDER_DEBUG

fprintf(stderr, "select timeout: %s\n", spider->blog->b_title);

#endif

goto fail_recv_response;

}

/*接受到数据*/

if (FD_ISSET(spider->blog->b_sockfd, &read_fds)) {

end = 0;

count = 0;

/*这里出错可能是文件名不规则,比如"3/5",'/'在Linux是代表目录*/

fp = fopen(spider->blog->b_local_file, "w+");

if (NULL == fp) {

goto fail_recv_response;

}

spider->blog->b_download = BLOG_DOWNLOAD;

while (read(spider->blog->b_sockfd, recvbuf, 1) == 1) {

if(end< 4) {

if(recvbuf[0] == '\r' || recvbuf[0] == '\n') {

end++;

}

else {

end = 0;

}

/*这里是http服务器反馈的消息头,若需要,则可以保存下来*/

}

else {

fputc(recvbuf[0], fp);

count++;

if (1024 == count) {

fflush(fp);

}

}

}

fclose(fp);

break;

}

}

return 0;

fail_recv_response:

spider->blog->b_download = BLOG_UNDOWNLOAD;

return -1;

}

五.获取CSDN博客的URL,与博客的发表日期,阅读次数,评论次数,并添加进爬虫链表

[cpp] view
plaincopy

/*****************************************************************

分析个人的博客主页, 获取所有文章的URL, 将博客信息添加到爬虫链表中.

*****************************************************************/

static int analyse_index(blog_spider *spider_head)

{

FILE *fp;

int ret;

int len;

int reads, comments;

char *posA, *posB, *posC, *posD;

char line[BUFSIZE*4] = {0};

char tmpbuf[BUFSIZE] = {0};

char tmpbuf2[BUFSIZE] = {0};

char page_file[BUFSIZE] = {0};

char url[BUFSIZE] = {0};

char title[BUFSIZE] = {0};

char date[BUFSIZE] = {0};

fp = fopen(spider_head->blog->b_local_file, "r");

if (fp == NULL) {

#ifdef SPIDER_DEBUG

fprintf(stderr, "fopen: %s\n", strerror(errno));

#endif

return -1;

}

while (1) {

if (feof(fp)) {

break;

}

/*查找博客*/

while (fgets(line, sizeof(line), fp)) {

posA = strstr(line, HTML_ARTICLE);

if (posA) {

/*查找博客网址*/

posA += strlen(HTML_ARTICLE) + strlen(BLOG_HREF);

posB = strchr(posA, '"');

*posB = 0;

memset(page_file, 0, sizeof(page_file));

memset(url, 0, sizeof(url));

strcpy(page_file, posA);

sprintf(url, "%s%s", CSDN_BLOG_URL, posA);

/*查找博客标题*/

posB += 1;

posC = strstr(posB, BLOG_TITLE);

/*与博客地址处在同一行*/

posC += strlen(BLOG_TITLE);

posD = strstr(posC, "\">");

*posD = 0;

memset(title, 0, sizeof(title));

strcpy(title, posC);

/*查找博客发表日期*/

while (fgets(line, sizeof(line), fp)) {

posA = strstr(line, BLOG_DATE);

if (posA) {

posA += strlen(BLOG_DATE);

posB = strstr(posA, BLOG_SPAN_END);

*posB = 0;

memset(date, 0, sizeof(date));

strcpy(date, posA);

break;

}

}

/*查找博客阅读次数*/

while (fgets(line, sizeof(line), fp)) {

posA = strstr(line, BLOG_READ);

if (posA) {

posA += strlen(BLOG_READ);

posB = strchr(posA, '(') + 1;

posC = strchr(posB, ')');

*posC = 0;

reads = atoi(posB);

break;

}

}

/*查找博客评论次数*/

while (fgets(line, sizeof(line), fp)) {

posA = strstr(line, BLOG_COMMENT);

if (posA) {

posA += strlen(BLOG_COMMENT);

posB = strchr(posA, '(') + 1;

posC = strchr(posB, ')');

*posC = 0;

comments = atoi(posB);

break;

}

}

spider_head->blog->b_download = BLOG_DOWNLOAD;

blog_spider *spider;

ret = init_spider(&spider);

if (ret < 0) {

return -1;

}

spider->blog->b_page_file = strdup(page_file);

spider->blog->b_url = strdup(url);

spider->blog->b_date = strdup(date);

spider->blog->b_reads = reads;

spider->blog->b_comments = comments;

spider->blog->b_seq_num = ++g_seq_num;

memset(tmpbuf, 0, sizeof(tmpbuf));

sprintf(tmpbuf, "%d.%s", spider->blog->b_seq_num, title);

spider->blog->b_title = strdup(tmpbuf);

memset(tmpbuf, 0, sizeof(tmpbuf));

memset(tmpbuf2, 0, sizeof(tmpbuf2));

strcpy(tmpbuf2, spider->blog->b_title);

strfchr(tmpbuf2);

sprintf(tmpbuf, "%s/%s.html", csdn_id, tmpbuf2);

spider->blog->b_local_file = strdup(tmpbuf);

/*将博客插入博客爬虫链表*/

insert_spider(spider_head, spider);

fputc('.', stdout);

}

}

}

fclose(fp);

#ifdef SPIDER_DEBUG

printf("\nspider size = %d\n", spider_size(spider_head));

#endif

return 0;

}

代码本身已经注释得很清楚了,看注释就够了。HTTP协议涉及到很多知识点,有空可以写写程序来练练手,blogspider效率上还是不够高,有空添加线程处理,同时下载多个博客,这样才能提高效率。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: