下面是一篇很有启发性的文章先分享给大家。
信息论的发展https://zhuanlan.zhihu.com/p/20841617
我们通过上一篇了解了爬虫具体要实现的工作之后,我们分析得出的网络爬虫的基本工作流程如下:
1.首先选取一部分精心挑选的种子URL;
2.将这些URL放入待抓取URL队列;
3.从待抓取URL队列中取出待抓取在URL,解析DNS,并且得到主机的ip,并将URL对应的网页下载下来,存储进已下载网页库中。此外,将这些URL放进已抓取URL队列。
4.分析已抓取URL队列中的URL,分析其中的其他URL,并且将URL放入待抓取URL队列,从而进入下一个循环。
一、爬虫设计
从爬虫的角度对互联网进行划分,可以将互联网的所有页面分为五个部分:
1.已下载未过期网页
2.已下载已过期网页:抓取到的网页实际上是互联网内容的一个镜像与备份,互联网是动态变化的,一部分互联网上的内容已经发生了变化,这时,这部分抓取到的网页就已经过期了。
3.待下载网页:也就是待抓取URL队列中的那些页面
4.可知网页:还没有抓取下来,也没有在待抓取URL队列中,但是可以通过对已抓取页面或者待抓取URL对应页面进行分析获取到的URL,认为是可知网页。
5.还有一部分网页,爬虫是无法直接抓取下载的。称为不可知网页。
我们爬虫项目的主事件流程大致如下:
1.获取命令行参数,执行相应操作
2.读取配置文件,解析得到各种设置
3.载入各种模块
4.种子入队,开启DNS解析线程(原始队列不为空时解析)
5.创建epoll,开启任务,发起请求等等,关注事件
6.while大循环中使用epoll_wait返回活跃的事件,每个事件开启一个线程处理(线程中主要是解析页面,保存页面,url处理等),在线程结束的时候可能会开启新的任务。(或者程序初始化时创建线程池,在没任务时阻塞线程,通过pthread_cond_signal来唤醒睡眠队列中的线程,但是一个页面分析出的有效url会很多,这时候我们甚至需要在队列满时阻塞分析页面获得url的线程,使用线程池的优点就是减少了线程创建和销毁的系统开销)
在爬虫系统中,待抓取URL队列是很重要的一部分。待抓取URL队列中的URL以什么样的顺序排列也是一个很重要的问题,因为这涉及到先抓取那个页面,后抓取哪个页面。而决定这些URL排列顺序的方法,叫做抓取策略。下面重点介绍几种常见的抓取策略(关于爬取策略优劣分析的建议大家读一下吴军先生的《数学之美》的第九章和第十章):
1.深度优先遍历策略
深度优先遍历策略是指网络爬虫会从起始页开始,一个链接一个链接跟踪下去,处理完这条线路之后再转入下一个起始页,继续跟踪链接。
2.广度优先遍历策略
广度优先遍历策略的基本思路是,将新下载网页中发现的链接直接插入待抓取URL队列的末尾。也就是指网络爬虫会先抓取起始网页中链接的所有网页,然后再选择其中的一个链接网页,继续抓取在此网页中链接的所有网页。还是以上面的图为例:
3.反向链接数策略
反向链接数是指一个网页被其他网页链接指向的数量。反向链接数表示的是一个网页的内容受到其他人的推荐的程度。因此,很多时候搜索引擎的抓取系统会使用这个指标来评价网页的重要程度,从而决定不同网页的抓取先后顺序。
在真实的网络环境中,由于广告链接、作弊链接的存在,反向链接数不能完全等他我那个也的重要程度。因此,搜索引擎往往考虑一些可靠的反向链接数。
4.Partial PageRank策略
Partial PageRank算法借鉴了PageRank算法的思想:对于已经下载的网页,连同待抓取URL队列中的URL,形成网页集合,计算每个页面的PageRank值,计算完之后,将待抓取URL队列中的URL按照PageRank值的大小排列,并按照该顺序抓取页面。
如果每次抓取一个页面,就重新计算PageRank值,一种折中方案是:每抓取K个页面后,重新计算一次PageRank值。但是这种情况还会有一个问题:对于已经下载下来的页面中分析出的链接,也就是我们之前提到的未知网页那一部分,暂时是没有PageRank值的。为了解决这个问题,会给这些页面一个临时的PageRank值(比如1):将这个网页所有入链传递进来的PageRank值进行汇总(其实就是反复进行大矩阵运算,未知站的PageRank值会逐渐收敛到他应有的值,说实话收敛是我个人认为这个算法最美的地方),这样就形成了该未知页面的PageRank值,从而参与排序。
5.OPIC策略策略
该算法实际上也是对页面进行一个重要性打分。在算法开始前,给所有页面一个相同的初始现金(cash)。当下载了某个页面P之后,将P的现金分摊给所有从P中分析出的链接,并且将P的现金清空。对于待抓取URL队列中的所有页面按照现金数进行排序。
6.大站优先策略
对于待抓取URL队列中的所有网页,根据所属的网站进行分类。对于待下载页面数多的网站,优先下载。这个策略也因此叫做大站优先策略。
下面
二、通过一个http请求抓取网页的html存储到文件的简单实现
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
#include<sys/socket.h>
#include<netdb.h>
#include<unistd.h>
#include<fcntl.h> #define MAX_URL_LEN 1024
#define MAX_FILE_NAME_LEN 64
#define MAX_REUQEST_LEN 1024
#define BUFF_MAX_SIZE 512
#define PAGE_MAX_SIZE 4096*128
void parseURL(char * url,char* host,char* path)
{
//char tmp[MAX_URL_LEN] = {0};
char*ptmp = NULL;
strcpy(host,url);
if((ptmp = strstr(url,"http://")) != NULL)//https format
{
ptmp = ptmp + ;
strcpy(host,ptmp);
}else if(ptmp = NULL,(ptmp = strstr(url,"https://")) != NULL)//http format
{
ptmp = ptmp + ;
strcpy(host,ptmp);
}
ptmp = NULL;
if((ptmp = strpbrk(host,"/")) != NULL)
{
strcpy(path,ptmp);
ptmp[] = '\0';
} }
void getPage(char* host,char* path,char* file)
{
struct hostent *phost;
if( == (phost = gethostbyname(host)))
{
printf("host err\n");
exit();
} struct sockaddr_in pin;
int port = ;
bzero(&pin,sizeof(pin));
pin.sin_family=AF_INET;
pin.sin_port=htons(port);
pin.sin_addr.s_addr=((struct in_addr*)(phost->h_addr))->s_addr;
int isock;
if((isock = socket(AF_INET,SOCK_STREAM,)) == -)
{
printf("socket err\n");
exit();
}
char requestHeader[MAX_REUQEST_LEN] = "GET ";
strcat(requestHeader,path);
strcat(requestHeader," HTTP/1.0\r\nHost: ");
strcat(requestHeader,host);
strcat(requestHeader,"\r\nAccept: */*\r\n");
strcat(requestHeader,"User-Agent: Mozilla/4.0(compatible)\r\n");
strcat(requestHeader,"Connection: Keep-Alive\r\n");
strcat(requestHeader,"\r\n"); if(connect(isock,(struct sockaddr*)&pin,sizeof(pin)) == -)
{
printf("connect err\n");
exit();
} if(send(isock,requestHeader,strlen(requestHeader),) == -)
{
printf("send err\n");
exit();
}
//struct timeval timeout={1,0};
//setsockopt(isock,SOL_SOCKET,SO_RCVTIMEO,(char *)&timeout,sizeof(struct timeval));
char buffer[BUFF_MAX_SIZE];
char page[PAGE_MAX_SIZE];
int len;
printf("Start fetch\n");
int fd = open("file",O_RDWR|O_CREAT,);
int flag = ;
char tmpch;
//while(recv(isock,&tmpch,sizeof(char))>)
//{
// if(tmpch == '\r')
// {
// 如何读到一个http请求头的末尾?
// http://www.runoob.com/http/http-messages.html
// }
//}
while((len = recv(isock,buffer,BUFF_MAX_SIZE-,))>)
{
buffer[len]='\0'; write(fd,buffer,strlen(buffer)+); }
close(isock);
close(fd);
} int main()
{
char url[MAX_URL_LEN] = "http://www.runoob.com/http/http-intro.html";
//char url[MAX_URL_LEN] = "https://www.runoob.com/http/http-intro.html";
char host[MAX_URL_LEN] = {};
char path[MAX_URL_LEN] = {};
char file[MAX_FILE_NAME_LEN] = "file"; //parse url to get host and page path
parseURL(url,host,path);
//puts(host);
//puts(path);
//connect and sv the page into a file
getPage(host,path,file); }
下一篇我们将分析这个小程序所做的事情,虽然他很小,却把包含了一个爬虫所要做的大部分内容。