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

本文详细介绍了如何使用blogspider备份优快云博客,包括建立连接、发送请求、接收响应以及解析博客URL和元数据。作者分享了源码并讨论了HTTP协议的关键点,建议增加线程以提升下载效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

作者:gzshun. 原创作品,转载请标明出处!
来源:
http://blog.youkuaiyun.com/gzshun

 

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

在开篇,先happy下,新年到,开开心心过好年!
已经写了几篇文章,把代码贡献给有需要的人,这里列出前几篇文章,需要的马上跳转,麻利的。。
自己动手编写优快云博客备份工具-blogspider

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

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

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

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

下载网页到本地的步骤:
建立连接 -> 连接网站服务器 -> 发送请求 -> 接收响应 -> 保存到本地
connect_web -> send_request -> recv_response
源码说话:

/*****************************************************************
下载个人的博客主页
*****************************************************************/
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.youkuaiyun.com"主机名获取到IP地址,如下:

/**********************************************************
根据主机名获取到主机信息,主要是获取到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;
}


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

/**********************************************************
初始化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协议可以上网搜索一些资料,这里不做介绍。

源码说话:

/**********************************************************
向网站服务器发送请求
**********************************************************/
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可读,才开始读取消息并保存到本地。

/***************************************************************************************
接受网站服务器的反馈信息,得到请求的文件内容
向服务器发送请求信息或者服务器的响应消息,以空行结束,所以可以用"\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);
		timeout.tv_sec  = 30;
		timeout.tv_usec = 0;
		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;
}


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

/*****************************************************************
分析个人的博客主页, 获取所有文章的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", 优快云_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效率上还是不够高,有空添加线程处理,同时下载多个博客,这样才能提高效率。

需要blogspider的源代码,可以留下E-mail

评论 36
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值