来源:http://blog.csdn.net/gzshun
网络爬虫(又被称为网页蜘蛛,网络机器人),是一种按照一定的规则,自动的抓取万维网信息的程序或者脚本。另外一些不常使用的名字还有蚂蚁,自动索引,模拟程序或者蠕虫。
网络爬虫最重要的任务,就是从互联网搜索出需要的信息,将网页抓取下来并分析,很多搜索引擎,比如百度,谷歌,后台都有一只很强悍的网络爬虫,用来访问互联网上的网页,图片,视频等内容,并建立索引数据库,使用户能在百度搜索引擎中搜索到您网站的网页、图片、视频等内容。
我们常见的几个大型搜索引擎公司的爬虫名称:
1.谷歌(Google) -> Googlebot
2.百度(Baidu)爬虫名称:Baiduspider
3.雅虎(Yahoo) -> Yahoo! Slurp
4.有道(Yodao) -> YodaoBot
5.搜狗(sogou) -> Sogou spider
6.MSN -> msmbot
7.腾讯搜搜 -> Sosospider
最近我突然想自己动手写一只小型的博客爬虫,将自己在CSDN博客网站写的文章给抓取下来,想做个博客备份工具。当了解到网络爬虫的用途后,就来动手实现一个应用,用来备份自己在CSDN的博客,这样即使没有网络,或者文章丢失了,我手头都有一个备份。记得上次在微博看过CSDN创始人蒋涛先生说的一句话,他想做一个CSDN博客生成PDF文档的工具,其实那也相当于对自己博客的备份,这样就能很方便的浏览自己的写的文章。
我写的这个"blogspider"程序,将会把自己博客信息提取出来,并将所有的文章下载到本地。这里只是简单的下载网页而已,里面的图片我没有下载,那得涉及到太多的东西。如果电脑有网络,将会很容易的看到博客里面的图片,如果没有网络,图片将无法显示。
blogspider程序由C语言编写的,基于Linux平台,我编写该程序的环境如下:
- gzshun@ubuntu:~$ uname -a
- Linux ubuntu 2.6.32-24-generic-pae #39-Ubuntu SMP Wed Jul 28 07:39:26 UTC 2010 i686 GNU/Linux
- gzshun@ubuntu:~$ gcc -v
- Using built-in specs.
- Target: i486-linux-gnu
- Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.4.3-4ubuntu5' --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 --program-suffix=-4.4 --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-plugin --enable-objc-gc --enable-targets=all --disable-werror --with-arch-32=i486 --with-tune=generic --enable-checking=release --build=i486-linux-gnu --host=i486-linux-gnu --target=i486-linux-gnu
- Thread model: posix
- gcc version 4.4.3 (Ubuntu 4.4.3-4ubuntu5)
本人在putty终端测试程序,可以正确的显示中文,要设置为UTF-8,或者GB2312,如果显示乱码,切换一下字符集试试。
1.获取博客的基本信息:
博客标题
博客访问量
博客积分
博客排名
博客原创文章数量
博客转载文章数量
博客译文文章数量
博客评论数量
2.下载博客到本地:
博客主题
博客发表日期
博客阅读次数
博客评论次数
二.blogspider涉及到的知识点:
1.文件I/O
2.网络编程socket
3.数据结构-链表
4.内存分配
三.blogspider程序执行流程:
以我的博客为例:
1.将"http://blog.csdn.net/gzshun"主页下载到本地
2.分析该主页,获取到博客的URL
3.将博客的URL添加到爬虫链表
4.遍历爬虫链表,将博客下载到本地
5.将下载日志保存在gzshun.log
四.blogspider程序的重要部分:
1.爬虫链表的结构体
- typedef struct tag_blog_info {
- char *b_url; /*网址*/
- char *b_host; /*网站服务器主机名*/
- char *b_page_file; /*页面文件名称*/
- char *b_local_file; /*本地保存的文件名称*/
- char *b_title; /*博客主题*/
- char *b_date; /*博客发表日期*/
- int b_port; /*网址端口号*/
- int b_sockfd; /*网络套接字*/
- int b_reads; /*阅读次数*/
- int b_comments; /*评论次数*/
- int b_download; /*下载状态*/
- int b_lock; /*处理锁*/
- int b_seq_num; /*序号*/
- }blog_info;
- typedef struct tag_blog_spider {
- blog_info *blog;
- struct tag_blog_spider *next;
- }blog_spider;
2.博客基本信息结构体
- typedef struct tag_blog_rank {
- int b_page_total; /*博客总页数*/
- char *b_title; /*博客标题*/
- char *b_page_view; /*博客访问量*/
- char *b_integral; /*博客积分*/
- char *b_ranking; /*博客排名*/
- char *b_original; /*博客原创文章数量*/
- char *b_reship; /*博客转载文章数量*/
- char *b_translation; /*博客译文文章数量*/
- char *b_comments; /*博客评论数量*/
- }blog_rank;
3.定义的函数
- static char *strrstr(const char *s1, const char *s2);
- static char *strfchr(char *s);
- static int init_spider(blog_spider **spider);
- static int init_rank(blog_rank **rank);
- static void insert_spider(blog_spider *spider_head, blog_spider *spider);
- static int spider_size(blog_spider *spider_head);
- static void print_spider(blog_spider *spider_head);
- static void print_rank(blog_rank *rank);
- static void free_spider(blog_spider *spider_head);
- static void free_rank(blog_rank *rank);
- static int get_blog_info(blog_spider *spider_head, blog_rank *rank);
- static int analyse_index(blog_spider *spider_head);
- static int download_index(blog_spider *spider_head);
- static int download_blog(blog_spider *spider);
- static int get_web_host(const char *hostname);
- static int connect_web(const blog_spider *spider);
- static int send_request(const blog_spider * spider);
- static int recv_response(const blog_spider * spider);
4.strrstr是自己实现的,C库没有提供
1.strrstr函数:从一个字符串中查找指定字符串,返回最后一次出现的地址
程序如下:
- /**************************************************************
- strrstr : 查找指定字符串, 返回最后一次出现的地址, 自己实现
- ***************************************************************/
- static char *strrstr(const char *s1, const char *s2)
- {
- int len2;
- char *ps1;
- if (!(len2 = strlen(s2))) {
- return (char *)s1;
- }
- ps1 = (char *)s1 + strlen(s1) - 1;
- ps1 = ps1 - len2 + 1;
- while (ps1 >= s1) {
- if ((*ps1 == *s2) && (strncmp(ps1, s2, len2) == 0)) {
- return (char *)ps1;
- }
- ps1--;
- }
- return NULL;
- }
5.初始化爬虫链表
- /*********************************************************
- 初始化博客爬虫的链表节点, 申请空间并赋空值
- *********************************************************/
- static int init_spider(blog_spider * * spider)
- {
- *spider = (blog_spider *)malloc(sizeof(blog_spider));
- if (NULL == *spider) {
- #ifdef SPIDER_DEBUG
- fprintf(stderr, "malloc: %s\n", strerror(errno));
- #endif
- return -1;
- }
- (*spider)->blog = (blog_info *)malloc(sizeof(blog_info));
- if (NULL == (*spider)->blog) {
- #ifdef SPIDER_DEBUG
- fprintf(stderr, "malloc: %s\n", strerror(errno));
- #endif
- free(*spider);
- return -1;
- }
- (*spider)->blog->b_url = NULL;
- (*spider)->blog->b_host = strdup(CSDN_BLOG_HOST);
- (*spider)->blog->b_page_file = NULL;
- (*spider)->blog->b_local_file = NULL;
- (*spider)->blog->b_title = NULL;
- (*spider)->blog->b_date = NULL;
- (*spider)->blog->b_port = CSDN_BLOG_PORT;
- (*spider)->blog->b_sockfd = 0;
- (*spider)->blog->b_reads = 0;
- (*spider)->blog->b_comments = 0;
- (*spider)->blog->b_download = BLOG_UNDOWNLOAD;
- (*spider)->blog->b_lock = BLOG_UNLOCK;
- (*spider)->blog->b_seq_num = 0;
- (*spider)->next = NULL;
- return 0;
- }
6.初始化博客基本信息结构体
- /*********************************************************
- 初始化博客基本信息结构体,包含以下几个变量:
- 1.博客页面总页数
- 2.博客标题
- 3.博客访问量
- 4.博客积分
- 5.博客排名
- 6.博客原创文章数量
- 7.博客转载文章数量
- 8.博客译文文章数量
- 9.博客评论数量
- *********************************************************/
- static int init_rank(blog_rank **rank)
- {
- *rank = (blog_rank *)malloc(sizeof(blog_rank));
- if (NULL == *rank) {
- #ifdef SPIDER_DEBUG
- fprintf(stderr, "malloc: %s\n", strerror(errno));
- #endif
- return -1;
- }
- (*rank)->b_page_total = 0;
- (*rank)->b_title = NULL;
- (*rank)->b_page_view = NULL;
- (*rank)->b_integral = NULL;
- (*rank)->b_ranking = NULL;
- (*rank)->b_original = NULL;
- (*rank)->b_reship = NULL;
- (*rank)->b_translation = NULL;
- (*rank)->b_comments = NULL;
- return 0;
- }
五.blogspider遇到的问题:
1.博客标题如果有'/','?',或者其他不规则的符号,文件将会创建失败。
解决方案:将不规则的符号赋空,并在后面连接"xxx"字符串,表示省略;
2.在接受网站服务器响应的时候,要将select函数的时间设置长点,有时候因为网络差的问题,将会超时导致退出程序。在blogspider里面,将timeout设置30s。
3.本程序在考虑加入多线程遍历爬虫链表,经过尝试,连接网站服务器会出现竞争问题,将导致连接延时,影响程序效率,暂时不考虑。
六.blogspider运行截图:
使用blogspider:
这里以下载我的博客为例,我CSDN的ID是:gzshun, 网址是:http://blog.csdn.net/gzshun
title : 博客标题
url : 博客网址
date : 博客发表日期
reads : 博客阅读次数
comments : 博客评论次数
download : 博客下载状态
以下这张图片是在windows查看的,通过samba连接到ubuntu服务器。我博客上面的所有文章已经成功地下载到本地。
打开下载在本地的html文件,此时有网络。
![](http://hi.csdn.net/attachment/201201/15/0_1326616749eLdS.gif)
若需要blogspider的源程序,请留下您的E-mail(注意要写成我后面的那种形式,否则会被非法网络爬虫抓取),或者直接联系我的E-mail:gzshuns#163.com (#->@).
前一篇博文《自己动手编写CSDN博客备份工具-blogspider》介绍了blogspider的使用,使用方法很简单,blogspider可以将自己的CSDN博客下载到本地,这里也只提供最基本的功能。这两天有很多哥们儿给我发邮件,想要blogspider的源码,该程序是开源的,有需要的可以留下联系方式。
今天就介绍下blogspider的源代码,其实这里面比较核心的东西就是如何向网站服务器申请我们需要的网页文件。在Java语言,有提供一些网络包,已经将HTTP协议的东西都集成在了包里面,那实现起来就比较简单。最近由于春运期间,大家都在12306网站购票,于是网上就出现了一款抢票的软件,那是用Java写的,是一个谷歌插件。其实那个软件是我一个同事以前的同事写出来的,我们都从这里受益,也买到了回家过年的票,在这里感谢那位牛人。
向Java程序员了解了一下,那个软件的实现原理很简单,步骤如下:
1.访问网站获取网站信息
2.接受到网站服务器的响应消息
3.根据用户选择(硬座,硬卧)的消息再提交到网站服务器
4.得到网站的结果
主要是2个操作:一个是GET方法,一个是POST方法。
GET方法: 从网站服务器下载网页消息,比如网页浏览器可以浏览CSDN网站的新闻与图片,这些都是从网站服务器GET下载到本地;
POST方法:从本地将资料提交到网站服务器,比如在CSDN博客写完文章要点击发表博客,这时候是将一篇文章的所有信息给POST到CSDN服务器。
blogspider的主要目的,就是下载功能,这里使用的是GET方法,用C语言写的都比较低级,这些最基本的都需要自己来实现,等有空看看面向对象编程语言的实现。
废话少说,源码说话:
一.贴出代码中的调试宏,汗,太儿戏了
- /*Debug program macro*/
- #if 0
- #define SPIDER_DEBUG
- #endif
二.贴出代码中的一些宏定义,这些涉及到HTML文件的语法,但本代码不需要会html,只需要最基本的字符串处理:
- #define BUFSIZE 1024
- #define HTML_ARTICLE ("<span class=\"link_title\">")
- #define HTML_MULPAGE ("class=\"pagelist\"")
- #define BLOG_NEXT_LIST ("article/list")
- #define BLOG_TITLE ("title=\"")
- #define BLOG_HREF ("<a href=\"")
- #define BLOG_DATE ("<span class=\"link_postdate\">")
- #define BLOG_READ ("<span class=\"link_view\"")
- #define BLOG_COMMENT ("<span class=\"link_comments\"")
- #define BLOG_SPAN_HEAD ("<span>")
- #define BLOG_SPAN_END ("</span>")
- #define BLOG_RANK ("blog_rank")
- #define BLOG_LI ("<li>")
- #define BLOG_INDEX ("index.html")
- #define CSDN_BLOG_URL ("http://blog.csdn.net")
- #define CSDN_BLOG_HOST ("blog.csdn.net")
- #define CSDN_BLOG_PORT (80)
- #define BLOG_LOCK (10)
- #define BLOG_UNLOCK (11)
- #define BLOG_DOWNLOAD (20)
- #define BLOG_UNDOWNLOAD (21)
上面的BLOG_LOCK,BLOG_UNLOCK是爬虫链表的处理锁,这是扩展预留的,现在还没用。本来要用多线程来处理链表,但经过测试,会产生竞争,导致connect超时,这等过完年再试试。
三.这里再给出爬虫链表的结构体与博客存放基本信息的结构体,里面有多一些变量,但没真正的使用,有些只是预留而已:
- typedef struct tag_blog_info {
- char *b_url; /*网址*/
- char *b_host; /*网站服务器主机名*/
- char *b_page_file; /*页面文件名称*/
- char *b_local_file; /*本地保存的文件名称*/
- char *b_title; /*博客主题*/
- char *b_date; /*博客发表日期*/
- int b_port; /*网址端口号*/
- int b_sockfd; /*网络套接字*/
- int b_reads; /*阅读次数*/
- int b_comments; /*评论次数*/
- int b_download; /*下载状态*/
- int b_lock; /*处理锁*/
- int b_seq_num; /*序号*/
- }blog_info;
- typedef struct tag_blog_spider {
- blog_info *blog;
- struct tag_blog_spider *next;
- }blog_spider;
- typedef struct tag_blog_rank {
- int b_page_total; /*博客总页数*/
- char *b_title; /*博客标题*/
- char *b_page_view; /*博客访问量*/
- char *b_integral; /*博客积分*/
- char *b_ranking; /*博客排名*/
- char *b_original; /*博客原创文章数量*/
- char *b_reship; /*博客转载文章数量*/
- char *b_translation; /*博客译文文章数量*/
- char *b_comments; /*博客评论数量*/
- }blog_rank;
四.在一个程序中,使用全局变量不是最好的方法,但都有优缺点:
使用全局变量:
1.优点:操作简单,不用提供太多的函数形参;
2.缺点:不好维护,代码可读性差;所以该程序只使用了3个全局变量。
- /*global variable*/
- static int g_seq_num = 0;
- static char csdn_id[255];
- static struct hostent *web_host;
web_host变量用来保存"blog.csdn.net"主机信息,在初始化socket的使用会使用到里面的IP地址, web_host->h_addr_list[0];
五.程序中定义了很多函数,如下:
- static char *strrstr(const char *s1, const char *s2);//从s1字符串中查找s2字符串,返回最后一次出现的地址
- static char *strfchr(char *s);//过滤掉s字符串中不规则的字符
- static int init_spider(blog_spider **spider);//初始化博客爬虫节点,必须使用指针的指针,否则达不到预期效果
- static int init_rank(blog_rank **rank);//初始化博客存放基本信息的结构体
- static void insert_spider(blog_spider *spider_head, blog_spider *spider);//将博客插入爬虫链表
- static int spider_size(blog_spider *spider_head);//计算爬虫链表的长度
- static void print_spider(blog_spider *spider_head);//打印爬虫链表,保存到当前目录的*.log文件
- static void print_rank(blog_rank *rank);//打印博客基本信息
- static void free_spider(blog_spider *spider_head);//释放爬虫链表的空间
- static void free_rank(blog_rank *rank);//释放博客基本信息的空间
- static int get_blog_info(blog_spider *spider_head, blog_rank *rank);//从博客主页获取博客标题,博客文章的总页数,积分,排名等信息
- static int analyse_index(blog_spider *spider_head);分析每一页博客的信息,并添加进爬虫链表
- static int download_index(blog_spider *spider_head);//下载博客主页
- static int download_blog(blog_spider *spider);//下载每一篇博客
- static int get_web_host(const char *hostname);//得到"blog.csdn.net"网站的主机信息
- static int connect_web(const blog_spider *spider);//初始化socket,并连接网站服务器
- static int send_request(const blog_spider * spider);//给网站服务器发送请求
- static int recv_response(const blog_spider * spider);//接受网站服务器的响应消息
六.先给出上述2个字符串处理函数,这家伙,有点罗嗦
- /**************************************************************
- strrstr : 查找指定字符串, 返回最后一次出现的地址, 自己实现
- ***************************************************************/
- static char *strrstr(const char *s1, const char *s2)
- {
- int len2;
- char *ps1;
- if (!(len2 = strlen(s2))) {
- return (char *)s1;
- }
- ps1 = (char *)s1 + strlen(s1) - 1;
- ps1 = ps1 - len2 + 1;
- while (ps1 >= s1) {
- if ((*ps1 == *s2) && (strncmp(ps1, s2, len2) == 0)) {
- return (char *)ps1;
- }
- ps1--;
- }
- return NULL;
- }
- /*********************************************************
- strfchr : 查找指定字符串中不规则的字符, 并赋空
- 若没有删除这些不规则的字符,则创建文件的时候将会出错
- *********************************************************/
- static char *strfchr(char *s)
- {
- char *p = s;
- while (*p) {
- if (('/' == *p) || ('?' == *p)) {
- *p = 0;
- strcat(s, "xxx");
- return p;
- }
- p++;
- }
- return NULL;
- }
引用星爷的一句话:" 功夫其实绝对是适合男女老幼的,打打杀杀只是大家对它的误解。功夫更加是一种艺术,一种不屈的精神。所以,一直以来我都在找方法想将功夫重新包装起来,使得你们这些升斗小民对功夫能够有更深一层的了解。".
轻松一下,继续:
七.初始化爬虫链表,我把很多处理都给独立到函数了,这样可以增加程序的可读性,不能将所有功能都在main函数实现.
- /*********************************************************
- 初始化博客爬虫的链表节点, 申请空间并赋空值
- *********************************************************/
- static int init_spider(blog_spider * * spider)
- {
- *spider = (blog_spider *)malloc(sizeof(blog_spider));
- if (NULL == *spider) {
- #ifdef SPIDER_DEBUG
- fprintf(stderr, "malloc: %s\n", strerror(errno));
- #endif
- return -1;
- }
- (*spider)->blog = (blog_info *)malloc(sizeof(blog_info));
- if (NULL == (*spider)->blog) {
- #ifdef SPIDER_DEBUG
- fprintf(stderr, "malloc: %s\n", strerror(errno));
- #endif
- free(*spider);
- return -1;
- }
- (*spider)->blog->b_url = NULL;
- (*spider)->blog->b_host = strdup(CSDN_BLOG_HOST);
- (*spider)->blog->b_page_file = NULL;
- (*spider)->blog->b_local_file = NULL;
- (*spider)->blog->b_title = NULL;
- (*spider)->blog->b_date = NULL;
- (*spider)->blog->b_port = CSDN_BLOG_PORT;
- (*spider)->blog->b_sockfd = 0;
- (*spider)->blog->b_reads = 0;
- (*spider)->blog->b_comments = 0;
- (*spider)->blog->b_download = BLOG_UNDOWNLOAD;
- (*spider)->blog->b_lock = BLOG_UNLOCK;
- (*spider)->blog->b_seq_num = 0;
- (*spider)->next = NULL;
- return 0;
- }
- /*********************************************************
- 初始化博客基本信息结构体,包含以下几个变量:
- 1.博客页面总页数
- 2.博客标题
- 3.博客访问量
- 4.博客积分
- 5.博客排名
- 6.博客原创文章数量
- 7.博客转载文章数量
- 8.博客译文文章数量
- 9.博客评论数量
- *********************************************************/
- static int init_rank(blog_rank **rank)
- {
- *rank = (blog_rank *)malloc(sizeof(blog_rank));
- if (NULL == *rank) {
- #ifdef SPIDER_DEBUG
- fprintf(stderr, "malloc: %s\n", strerror(errno));
- #endif
- return -1;
- }
- (*rank)->b_page_total = 0;
- (*rank)->b_title = NULL;
- (*rank)->b_page_view = NULL;
- (*rank)->b_integral = NULL;
- (*rank)->b_ranking = NULL;
- (*rank)->b_original = NULL;
- (*rank)->b_reship = NULL;
- (*rank)->b_translation = NULL;
- (*rank)->b_comments = NULL;
- return 0;
- }
八.爬虫链表的一些处理,这些都比较简单,就都贴出来吧
- /*********************************************************
- 将博客爬虫节点插入爬虫链表
- *********************************************************/
- static void insert_spider(blog_spider * spider_head, blog_spider * spider)
- {
- blog_spider *pspider;
- pspider = spider_head;
- while (pspider->next) {
- pspider = pspider->next;
- }
- pspider->next = spider;
- }
- /*********************************************************
- 返回爬虫链表长度
- *********************************************************/
- static int spider_size(blog_spider * spider_head)
- {
- int count = 0;
- blog_spider *pspider;
- pspider = spider_head;
- while (pspider->next) {
- pspider = pspider->next;
- count++;
- }
- return count;
- }
篇幅有点长,待下篇文章...
周星驰:你来这里干什么?
赵薇:我想帮你们比赛。
周星驰:你怎么帮?你快点回火星吧,地球是很危险的。
唐僧:你想要啊?悟空,你要是想要的话你就说话嘛,你不说我怎么知道你想要呢,虽然你很有诚意地看着我,可是你还是要跟我说你想要的。你真的想要吗?那你就拿去吧!你不是真的想要吧?难道你真的想要吗?……
悟空:我Kao!
在开篇,先happy下,有个好心情,才能天天向上,奋发图强,自强不息。
继《自己动手编写CSDN博客备份工具-blogspider》与《自己动手编写CSDN博客备份工具-blogspider之源码分析(1)》博文后,继续贴出处理的一些函数,原理很简单。
一.在博客的下载过程中,打印了一些信息到屏幕,也保存到了*.log文件
- /*********************************************************
- 将爬虫链表的内容打印到log文件,格式为"csdn_id.log",比如
- 我的博客的地址为: "gzshun.log"
- *********************************************************/
- static void print_spider(blog_spider *spider_head)
- {
- char file[BUFSIZE] = {0};
- char tmpbuf[BUFSIZE] = {0};
- blog_spider *spider;
- FILE *fp;
- sprintf(file, "%s.log", csdn_id);
- fp = fopen(file, "a+");
- if (NULL == fp) {
- #ifdef SPIDER_DEBUG
- fprintf(stderr, "fopen: %s\n", strerror(errno));
- #endif
- return;
- }
- setvbuf(fp, NULL, _IONBF, 0);
- fseek(fp, 0, SEEK_END);
- spider = spider_head->next;
- while (spider) {
- fprintf(fp, "%d:\n"
- "%-15s : %s\n"
- "%-15s : %s\n"
- "%-15s : %s\n"
- "%-15s : %d\n"
- "%-15s : %d\n"
- "%-15s : %s\n\n",
- spider->blog->b_seq_num,
- "title", spider->blog->b_title,
- "url", spider->blog->b_url,
- "date", spider->blog->b_date,
- "reads", spider->blog->b_reads,
- "comments", spider->blog->b_comments,
- "download",
- (spider->blog->b_download == BLOG_DOWNLOAD) ? "Download" : "UnDownload");
- spider = spider->next;
- }
- fclose(fp);
- }
- /*********************************************************
- 将博客的基本信息打印到标准输出
- *********************************************************/
- static void print_rank(blog_rank *rank)
- {
- char file[BUFSIZE] = {0};
- FILE *fp;
- sprintf(file, "%s.log", csdn_id);
- fp = fopen(file, "w+");
- if (NULL == fp) {
- #ifdef SPIDER_DEBUG
- fprintf(stderr, "fopen: %s\n", strerror(errno));
- #endif
- return;
- }
- setvbuf(stdout, NULL, _IONBF, 0);
- fprintf(stdout, "CSDN ID : %s\n"
- "TITLE : %s\n"
- "URL : %s/%s\n"
- "%s\n"
- "%s\n"
- "%s\n"
- "%s\n"
- "%s\n"
- "%s\n"
- "%s\n",
- csdn_id,
- rank->b_title,
- CSDN_BLOG_URL,
- csdn_id,
- rank->b_page_view,
- rank->b_integral,
- rank->b_ranking,
- rank->b_original,
- rank->b_reship,
- rank->b_translation,
- rank->b_comments);
- fprintf(fp, "CSDN ID : %s\n"
- "TITLE : %s\n"
- "URL : %s/%s\n"
- "%s\n"
- "%s\n"
- "%s\n"
- "%s\n"
- "%s\n"
- "%s\n"
- "%s\n",
- csdn_id,
- rank->b_title,
- CSDN_BLOG_URL,
- csdn_id,
- rank->b_page_view,
- rank->b_integral,
- rank->b_ranking,
- rank->b_original,
- rank->b_reship,
- rank->b_translation,
- rank->b_comments);
- fclose(fp);
- }
唐僧:喂喂喂!大家不要生气,生气会犯了嗔戒的!悟空你也太调皮了,我跟你说过叫你不要乱扔东西,你怎么又…你看我还没说完你又把棍子给扔掉了!月光宝盒是宝物,你把他扔掉会污染花草草也是不对的!
二.申请了空间,在程序结束后必须释放,要不内存泄露了,污染到内存,污染到花花草草也是不对的.
- /*********************************************************
- 释放爬虫链表的空间
- *********************************************************/
- static void free_spider(blog_spider * spider_head)
- {
- blog_spider *pspider;
- blog_spider *tmp;
- pspider = spider_head;
- while (pspider) {
- if (pspider->blog->b_url) {
- free(pspider->blog->b_url);
- }
- if (pspider->blog->b_host) {
- free(pspider->blog->b_host);
- }
- if (pspider->blog->b_page_file) {
- free(pspider->blog->b_page_file);
- }
- if (pspider->blog->b_local_file) {
- free(pspider->blog->b_local_file);
- }
- if (pspider->blog->b_title) {
- free(pspider->blog->b_title);
- }
- if (pspider->blog->b_date) {
- free(pspider->blog->b_date);
- }
- free(pspider->blog);
- tmp = pspider->next; /*保存下一个节点地址*/
- free(pspider);
- pspider = tmp;
- }
- }
- /*********************************************************
- 释放博客基本信息结构体空间
- *********************************************************/
- static void free_rank(blog_rank *rank)
- {
- if (rank->b_title) {
- free(rank->b_title);
- }
- if (rank->b_page_view) {
- free(rank->b_page_view);
- }
- if (rank->b_integral) {
- free(rank->b_integral);
- }
- if (rank->b_ranking) {
- free(rank->b_ranking);
- }
- if (rank->b_original) {
- free(rank->b_original);
- }
- if (rank->b_reship) {
- free(rank->b_reship);
- }
- if (rank->b_translation) {
- free(rank->b_translation);
- }
- if (rank->b_comments) {
- free(rank->b_comments);
- }
- free(rank);
- }
三.下载个人博客的主页,并分析出必要的信息,比如下载:http://blog.csdn.net/gzshun主页,程序将该文件保存到了本地的"index.html"文件中,先贴出一点html文件的源码,这样就更加清晰的了解代码的字符串解析:
博客标题:
- <div class="header">
- <div id="blog_title">
- <h1>
- <a href="/gzshun">Open Linux C/C++专栏</a></h1>
- <h2></h2>
- <div class="clear">
- </div>
- </div>
博客的总页数:
- <!--显示分页-->
- <div id="papelist" class="pagelist">
- <span> 35条数据 共2页</span><strong>1</strong> <a href="/gzshun/article/list/2">2</a> <a href="/gzshun/article/list/2">下一页</a> <a href="/gzshun/article/list/2">尾页</a>
- 只需要获取到"尾页"前面的数字即可.
博客的排名,积分信息:
- <span>gzshun</span>
- </div>
- <div id="blog_medal">
- </div>
- <ul id="blog_rank">
- <li>访问:<span>54524次</span></li>
- <li>积分:<span>1070分</span></li>
- <li>排名:<span>第5615名</span></li>
- </ul>
- <ul id="blog_statistics">
- <li>原创:<span>29篇</span></li>
- <li>转载:<span>6篇</span></li>
- <li>译文:<span>0篇</span></li>
- <li>评论:<span>209条</span></li>
- </ul>
贴出源码,这几个字符串解析函数没必要看,自己看下html的规则就能解析出来了:
- /**********************************************************
- 获取博客的基本信息,包括以下几点(以下是按照页面的顺序,
- 若不按照该顺序,每次查找必须重设偏移量到开头rewind(fp)):
- 这里获取很多信息, 具体参考blog_spider与blog_rank结构体
- **********************************************************/
- static int get_blog_info(blog_spider * spider_head, blog_rank * rank)
- {
- FILE *fp;
- int count;
- char *posA, *posB, *posC, *posD, *posE;
- char tmpbuf[BUFSIZE] = {0};
- char tmpbuf2[BUFSIZE] = {0};
- char line[BUFSIZE] = {0};
- char *rank_info_addr[7];
- fp = fopen(spider_head->blog->b_local_file, "r");
- if (NULL == fp) {
- fprintf(stderr, "fopen: %s\n", strerror(errno));
- return -1;
- }
- /*查找博客的标题*/
- sprintf(tmpbuf, "<a href=\"/%s\">", csdn_id);
- while (fgets(line, sizeof(line), fp)) {
- posA = strstr(line, tmpbuf);
- if (posA) {
- posA += strlen(tmpbuf);
- posB = strstr(posA, "</a>");
- *posB = 0;
- /*设置爬虫头结点的标题*/
- spider_head->blog->b_title = strdup(posA);
- rank->b_title = strdup(posA);
- #ifdef SPIDER_DEBUG
- printf("%s\n", spider_head->blog->b_title);
- #endif
- break;
- }
- }
- /*查找博客文章的总页数*/
- while (fgets(line, sizeof(line), fp)) {
- posA = strstr(line, HTML_MULPAGE);
- if (posA) {
- fgets(line, sizeof(line), fp);
- posB = strrstr(line, BLOG_HREF);
- /* /gzshun/article/list/N, N就是总页数 */
- memset(tmpbuf, 0, sizeof(tmpbuf));
- sprintf(tmpbuf, "/%s/%s/", csdn_id, BLOG_NEXT_LIST);
- posB += strlen(BLOG_HREF) + strlen(tmpbuf);
- posC = strchr(posB, '"');
- *posC = 0;
- rank->b_page_total = atoi(posB);
- spider_head->blog->b_seq_num = rank->b_page_total;
- #ifdef SPIDER_DEBUG
- printf("b_page_total = %d\n", rank->b_page_total);
- #endif
- break;
- }
- }
- /*总共有7条信息: 访问 积分 排名 原创 转载 译文 评论*/
- while (fgets(line, sizeof(line), fp)) {
- posA = strstr(line, BLOG_RANK);
- if (posA) {
- count = 0;
- while (fgets(line, sizeof(line), fp)) {
- posB = strstr(line, BLOG_LI);
- if (posB) {
- if (7 == count) {
- break;
- }
- posB += strlen(BLOG_LI);
- posC = strstr(posB, BLOG_SPAN_HEAD);
- posD = posC + strlen(BLOG_SPAN_HEAD);
- posE = strstr(posD, BLOG_SPAN_END);
- *posC = 0;
- *posE = 0;
- memset(tmpbuf, 0, sizeof(tmpbuf));
- memset(tmpbuf2, 0, sizeof(tmpbuf2));
- strcpy(tmpbuf, posB);
- strcpy(tmpbuf2, posD);
- strcat(tmpbuf, tmpbuf2);
- rank_info_addr[count++] = strdup(tmpbuf);
- }
- }
- rank->b_page_view = rank_info_addr[0];
- rank->b_integral = rank_info_addr[1];
- rank->b_ranking = rank_info_addr[2];
- rank->b_original = rank_info_addr[3];
- rank->b_reship = rank_info_addr[4];
- rank->b_translation = rank_info_addr[5];
- rank->b_comments = rank_info_addr[6];
- break;
- }
- }
- fclose(fp);
- return 0;
- }
以上使用了rank_info_addr数组,是为了在while (fgets(line, sizeof(line), fp)) 循环里面方便赋值。
博客里面可能有很多页,必须我的博客就有2页,这时候网址是这样:
http://blog.csdn.net/gzshun/article/list/1
http://blog.csdn.net/gzshun/article/list/2
所以循环下载blog.csdn.net对应自己的博客列表就行,网页文件的名称如:/gzshun/article/list/1 把gzshun改为自己的csdn的id就是了。
先来杯咖啡,待下一篇文章,前几天奔波在火车上,辛苦啊,今天及时赶到,马上发表,持之以恒。。
周星驰:剪头发不应该看别人怎么剪就发神经跟流行,要配合啊!你看你的发型,完全不配合你的脸型脸型又不配合身型,身型又和发型完全不搭,而且极度不配合啊!!欢哥!你究竟要怎么样啊? 《算死草》
在开篇,先happy下,新年到,开开心心过好年!
已经写了几篇文章,把代码贡献给有需要的人,这里列出前几篇文章,需要的马上跳转,麻利的。。
《自己动手编写CSDN博客备份工具-blogspider》
《自己动手编写CSDN博客备份工具-blogspider之源码分析(1)》
《自己动手编写CSDN博客备份工具-blogspider之源码分析(2)》
本文是blogspider最重要的部分,开始要下载并分析CSDN博客,把博文的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.csdn.net"主机名获取到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协议里面比较重要的有俩方法:GET和POST
向网站服务器发送请求:
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);
- 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,与博客的发表日期,阅读次数,评论次数,并添加进爬虫链表
- /*****************************************************************
- 分析个人的博客主页, 获取所有文章的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效率上还是不够高,有空添加线程处理,同时下载多个博客,这样才能提高效率。