传智课程学习笔记。
搜索引擎后台的第一个子系统,
爬虫,机器人,软件形式的,
暗网,尽管连在互联网上,但是是私有的,
内部网,
整站下载工具,就是一种爬虫,
索引系统,从抓到的网站上提取关键字,建立索引,
用户产生的数据我们暂时不管,
互联网产生的数据,
大数据,
内容很多,没有归类,
首先是抓取,
微博推荐名人啊,
背后是什么,数据挖掘系统,
微博官方提供的有一些api,但是有限制,比如访问请求速度,
第三方通过http开发的接口,限制就相对少很多,其背后还是爬虫,
爬虫简单说就是一个网页下载器,
灌水机,刷票系统,也是爬虫在背后不停地抓取,
还有淘宝秒杀,
项目,
需求,设计,编码,测试,集成,
设计:业务设计和技术设计,
比如客户去银行存取钱,
对银行工作人员来说,就是要办一套 业务,
对软件工程师来说,我们的系统要完成这项工作,要经历 哪些步骤,
技术设计,关于框架的流程,
程序员,
往技术方向,要关注框架,
往管理方向,是项目管理,
需求一变,业务流程就会变,进而底层的实现也会跟着变,
需求设计的时间大于代码实现的实现,
测试的时间和代码实现差不多,
测试,系统可靠性的一个检测,
当要 支持新的特性或者功能的时候,
冷更新,机器关掉,重新编译源代码,重新启动,
动态更新,通过插件的方式,
软件开发思想,
模块思想,
渐进式开发,好比房地产的一期,二期,
需求,
爬虫,自动抓取网络资源,太广泛了,
具体呢?
1,资源?网页,图片,音乐,视频等,
2,自动?一旦运行,我们就不用管了,
生成需求说明文档,
对需求中的不明确或不完善的方面进行解释,
mysql,oracle,这种数据库,不适合保存网页,
种子,最初爬取的url,
下载种子url中的资源,
解析资源,
从已经下载的资源中获取更多url,
对页面做持久化操作,
根据提取的url再进行下载操作,
重复上面的操作,继续下载,
google,百度,属于广域的搜索引擎,
垂直类型的,比如只抓取新闻,
系统结构图,
控制器的设计,因为它是核心,
输入是配置文件,输出是指令,指令用于控制其它模块,
控制器由三个模块组成:
1、 配置文件处理模块:从配置文件中读取配置项,提供配置项的提取接口
2、 URL维护模块:负责维护URL库,提供如下功能
a) 输入新的URL
b) 输出一个未被抓取的URL
c) 负责维护URL的抓取状态
3、 任务调度模块
a) 负责协调控制器的流程
b) 负责调用其他系统模块完成工作
4、 维护URL列表数据结构
下载器,
网页是数据来源,url才是输入,
下载url的工具有很多,在ubuntu中的wget命令,curl命令,
使用wget XX,在当前目录下会生成一个index.html的文件,
按理说我们可以通过复用的方式,直接使用现成的工具,
但是由于我们还要实现一个其它功能的支持,所以我们还是得自己来写,
但是还是可以参考这两个命令的实现,
http,
get,post指令,
解析器,
提取页面中的url,
html解析模块,
有的是用xml来写的,考虑到扩展性,所以单独写出来,
持久化器,输入是数据流,输出是文件,
页面保存,涉及到编码,
uft-8,
GB2312,
GBK,
图片保存模块,
视频流保存模块,
音乐流保存模块,
总之编码的种类非常多,
设计好业务流程,
每一个部分,对应一个对象,用于进行相关处理,
说道对象,你有没有想到当初学习C++的时候,企业对第三方产品的支持问题,比如不同厂商的加密产品,
哈哈,连起来了,
关于测试,
专门的测试团队,是测试系统集成以后,
代码的质量,还是靠程序员自己,小模块的测试,还是程序员自己来做,
控制器设计,
配置文件解析模块,
配置文件是以文件的形式保存程序运行时必要的参数,
并发任务数,
url种子,
抓取深度,
允许抓取资源类型,
。。。
配置文件的值,
我们需要读取到程序内部,
具体怎么做呢?
构造函数自动做了,
class ConfigParser
{
public:
ConfigParser();
load();
getValue( char *key);
}
C++向下兼容C,
但是混合写的时候,容易产生一些麻烦,
我们需要做一些类的接口导出,
用C++引用C的模块,不是很麻烦,
用C引用C++的模块,就比较麻烦,
所以怎么解决呢?
避免调用,,我感觉这不算解决。。
用传参数的方式,
单例模式,
1,构造函数私有,
2,需要一个方法得到这个类的实例,
3,需要一个类变量,指向这个类实例本身,
读取配置文件,
需要操作字符串,
需要分隔符作为读取的标志,用于生成键值对,
按行处理,
过滤掉注释,
job_num=10,如果等号前后有空格,我们也需要考虑到,做法就是过来掉空格,
技术实现,(先想,有没有现成的函数已经实现)
1,fgets,读取整行,
2,分割字符串,
3,消除注释,
4,消除空格
技术点讲了之后,具体实现自己去敲,
框架代码老师带着敲,
URL维护模块,
URL格式,http://192.168.0.22/docs/linuxdex.html
http://,是协议名称,还有可以有ftp等,
域名或ip地址,路径,文件名,
设计URL的数据结构,
完整的URL,
协议类型,
域名,
资源路径,
文件名,
当前url处理状态,
当前url深度,
资源类型,
页面抓取处理流程,
1,得到url,
2,url进入抓取队列等待抓取,
3,从队列中得到一个url,把其分配给一个下载器实例,
4,得到下载器的处理状态,
5,得到当前页面中存在的下一级URL列表,
URL维护模块操作(对外接口):
1、添加新URL
2、使URL进入抓取队列
3、从抓取队列中移除一个URL
4、修改URL库中某一个URL的值
5、添加新URL的列表
class Url
{
public:
//初始化
//set…
//get…
private:
//
url
protocal
Sitename
Path
Filename
state
Deep
Filetype
}
class UrlManager
{
public: //外部接口
addUrl();
addUrlList();
getUrlForQuque();
removeUrlForQuque();
//setUrlValue();
private: //内部接口
findUrl();
private:
list<Url> Urls;
quque<Url*>Urlquque;
}
http协议请求页面时的流程,
1,输入网址,
2,向DNS发送解析请求,
3,DNS返回给我们一个对应的ip地址,
4,通过ip地址向资源所在的主机发送请求,
5,如果资源存在,主机返回200状态,同时返回数据部分,
6,本地http客户端接收数据,
7,得到资源,
任务调度模块,
尽可能把可拆分的功能封装成独立函数进行调用。
定义主程序框架的处理流程:
1程序运行时先处理命令行参数,根据参数跳转到相应分支或调用对应的函数
2检测是否按照守护进程模式运行(控制选项从命令行参数中得到)
3初始化环境
a读取配置文件,提取配置文件中的参数
b根据守护进程模式的标记将当前进程转变为守护进程
c载入程序模块的动态库
4开始程序的主处理流程
a检测种子是否存在,把种子交给URL管理器
b分析种子,得到种子url的IP地址(DNS解析)
c根据种子URL得到第一个页面
d对页面进行处理(复杂流程,由其他模块实现细节)
e从URL管理器中取出一个URL
f启动一个处理任务(先检测是否达到最大任务数量,功能封装到一个独立函数中)
g监控任务处理数量,如果任务维护池中有空闲任务,那么重复e步骤
h回收资源,准备结束程序或当前任务
多路复用框架:
1、为什么不用select而是用epoll
具体的笔记,如果想看,去看文档,
日志工具,
方便调试,以及代码维护,
日志输出信息设计,
日期时间+调试信息,
日志输出等级设计
0,调试,debug,仅用于调试,
1,普通信息,可以让使用者了解一些信息,
2,警告信息,意味着程序中出现了错误,但是并不严重,
3,错误信息,意味着程序中发生了严重错误,根据实际情况可选择使程序继续运行或终止,
4,程序崩溃,程序无法继续运行了,
日志调用接口设计,
SPIDER_LOG(日志等级标记,日志输出信息);
注意,配置文件中的日志输出登等级段和接口中的日志等级标记不是一个概念
日志等级标记,纯粹是一个标记,体现在输出的日志字符串中
配置文件中的日志输出等级字段用来控制那些日志被输出
接口内部的处理流程:
1得到控制日志输出等级的标记,用来控制当前日志是否要输出
2得到调用日志接口的时间
3得到日志输出信息并进行日志字符串的拼接
4把日志信息输出到指定的设备
底层通过socket,
使用epoll,只能管理io相关的,
PPC 或TPC除了上面,还能管理数据包处理
静态文件,是通过.h文件,将编译好的代码嵌入到我们的代码中,
动态的.so,也就是下面的模块,插件,
模块,插件,
设计入口函数指针原型,
int (*handle)(void *);
设计初始化函数指针原型,
int (*int)(Module *);
模块管理器设计,
1载入模块的操作
Int Load (char* path, char*name);
Module* getModule(char* name);
载入模块操作的处理流程:
1通过路径找到模块文件(.so)
2调用dlopen打开动态库(.so)
3使用动态库
4关闭动态库文件
下载器设计,
下载器模块分为socket功能封装与http功能模块,
这里有大篇幅的http处理的介绍,
页面解析器设计,
html文档,标记语言,
对页面解析,得到页面中存在的下级url,存在于 a标签中,
但是这里a标签是在太多了,用字符串提取不太方便,
因此用正则表达式,
页面解析的处理流程,
1,得到下载的页面,
2,得到页面对应的url结构体,
3,使用正则表达式得到页面中所有url列表,
4,处理url中的相对路径,
5,一个细节:把当前页面深度加一,生成并填充URL结构体6把得到的URL列表回写到URL管理器中(生成列表数据,以返回值形式回传给上层代码)一个细节:把当前页面深度加一,生成并填充URL结构体6把得到的URL列表回写到URL管理器中(生成列表数据,以返回值形式回传给上层代码)
持久化器设计,
处理流程,
1,得到页面的数据流或在内存缓冲区的数据,
2,得到当前页面的url描述结构体,
3,生成保存目录,
4,把文件按照指定模式写入磁盘系统,
5,向主处理器流程发送一个反馈,表示当前页面处理的进度,
系统的核心代码:
1、 系统主处理框架
2、 Epoll框架的调用
3、 插件框架的完整实现
4、 Soket功能封装
5、 http协议头解析
6、 html解析并提取URL列表
7、 URL管理器实现
后续工作:如何进行单元测试 cunit。
集成测试。
项目中用到的一个开源库,
Libevent,
虽然都是it,但是像一些领域,比如通信,http,它里面的一些细节是非常的复杂,
不是专门去做的,不是那么容易理解,
但是开源库使用也要注意,
因为有些库可能编译不了,或者使用不稳定,
开源库有很多版本,怎么选择呢?
release版本是成熟的稳定的,一般采用它,
beta版,测试版,一般不采用,
RC版,比beta高,一版也不采用,
最后一次提交版本,非常不稳定,
DNS查询使用的就是这个库中的,
这个库,需要在源代码级别进行安装,
tar -zxf xxxxx
z代表格式,
x代表解压缩,
f代表按照后面的目录结构来解压,
v可以看到解压过程,
动态库,需要加载到动态库的配置文件中,
有点像path,
系统中可以被开源库替换的部分,
下载器,wget,curl,
用wget,使用fork+exec,但是无法使用epoll,因为得不到句柄,
用curl,可以使用,因为这个有库,
对代码进行单元测试,
1,代码产生bug的原因,
低级错误,编译器可以帮我们检查大部分,
一些编译器检查不到的错误, if( var == 1) 写成 if( var = 1),
逻辑上的错误,
容错,比如打开文件应该判断是否成功打开,
测试用例,
事先做好的一个
正常的输入,预期输出是什么,
非正常的输入,预期输出是什么,
然后编写测试程序,
方法,
1,断言,#include<assert.h>,
断言是调试工具,不能有程序处理流程,
因为程序发布的时候,断言是要屏蔽掉的,因此里面若有业务处理流程,那么业务流程岂不是少了,
2,使用单元测试工具CUNIT,
得有测试用例,,编写测试代码,
大系统测试,
业务逻辑,处理流程,压力测试,
对程序员来说,测试是保证代码质量的,
一定要掌握容错,否则后果非常严重,
断言,知道一点就行,
而像CUNIT,高级的功能,就属于专门的测试人员的范畴了,我们不必那么深入,
分析代码的方法,
1,找到程序的入口,
找与程序名称相同的,相似的,
找main()函数,
查说明文件,
2,弄清楚主处理流程,不要果早陷入到细节中去,
一边分析,一边加注释,
ps:我们这个项目主要关注的是,网络资源的抓取,所以一些数据结构直接使用STL模板,