C++ 高性能爬虫

42 篇文章 0 订阅
18 篇文章 0 订阅
main.cpp
#include "stdafx.h"
#include "CNetCrawler.h"

#include"afxmt.h"
#include"DownloadData.h"
#include"MainThread.h"
#include"ProjectDlg.h"
#include"CNetCrawlerDlg.h"
#include<afxinet.h>           //向http服务器发送请求及网络相关操作的头文件

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

extern CCNetCrawlerDlg *pDlg; //主窗口的指针
extern bool ThreadPause;       //是否暂停线程
//全局变量

/
// MainThread

IMPLEMENT_DYNCREATE(MainThread, CWinThread)//在类声明中包含了DECLARE_DYNCREATE  允许CObject派生类对象在运行时自动建立
                                           //用户界面线程构造函数


MainThread::MainThread()
{                                   //用户界面线程构造函数
	m_bDone=false;                 //初始化线程未停止
}

MainThread::~MainThread(){
}

//函数功能:初始化
BOOL MainThread::InitInstance(){      //重写初始化函数
	// TODO:  perform and per-thread initialization here
	//生成一个新建工程对话框
     //设置共享数据
	m_DownData.SetPro(m_FileId,m_ThreadNum,m_LocalDir);//根据用户设定起始文件名称,最大线程数量,保存路径
	m_BeginURL.MakeLower();//起始地址的设置
	if(m_BeginURL.Find(_T("http://"))==-1) 
		str_BeginURL=_T("http://")+m_BeginURL;//若初始的URL地址并不是以http://开头,则加入
	else str_BeginURL=m_BeginURL;       //将初始URL地址赋值给工程起始网络地址变量str_BeginURL
	str_ProjectName=m_ProjectName;//工程名的设置
	
	CWnd *button;            //窗口类中的按钮
	button=pDlg->GetDlgItem(IDC_BUTTON_NEW);//通过子窗口IDC_BUTTON_NEW得到窗口指针
	button->EnableWindow(FALSE);//设置该指定的窗口禁止接受鼠标和键盘的输入
	
	Run(str_BeginURL);      //运行守护线程,启动工作者线程,下载网页
	return TRUE;
}
/*
int MainThread::ExitInstance(){
	CWnd *button;
	button=pDlg->GetDlgItem(IDC_BUTTON_NEW);
	button->EnableWindow(TRUE);         //线程结束,设置该指定的窗口允许鼠标和键盘的输入
	// TODO:  perform any per-thread cleanup here
	return CWinThread::ExitInstance();
}
*/

int MainThread::ExitInstance(){
	CWnd *button;
	button=pDlg->GetDlgItem(IDC_BUTTON_NEW);
	button->EnableWindow(TRUE);         //线程结束,设置该指定的窗口允许鼠标和键盘的输入
	
	ThreadPause=false;
	pDlg->m_active=true;//-置回初始值,以便新建下一个工程(7.6添加)	
	pDlg->m_pause.EnableWindow(false);//-禁用按钮“暂停/继续”,工作日志(四)中有资料。
	pDlg->m_stop.EnableWindow(false);//-禁用按钮“停止”,工作日志(四)中有资料。(7.6添加)
	// TODO:  perform any per-thread cleanup here
	return CWinThread::ExitInstance();
}

BEGIN_MESSAGE_MAP(MainThread, CWinThread) //使用宏BEGIN_MESSAGE_MAP实现消息映射
//{{AFX_MSG_MAP(MainThread)
// NOTE - the ClassWizard will add and remove mapping macros here.
//}}AFX_MSG_MAP
END_MESSAGE_MAP()                      //消息映射结束

/*===========================================全局函数===========================================================*/

/*-----------------------------------------------------------------------------------
函数功能:从网页中提取URL
调用之前的预备条件:网页已经从网络上下载到本地存为临时文件
返回后的处理:删除临时文件
入口参数:
CString s 临时文件的本地地址
MainThread *ptr 用于获得主控线程的共享数据区
抽象算法:
①只读方式打开本地文件
②查找连接,若未在共享数据区的URL任务队列中出现,则加入队列
③关闭文件
调用关系:被每一个工作者线程调用,来从网页中读取链接
工作者线程(worker thread)的传入函数不能为类中的成员函数,故声明为全局函数
-----------------------------------------------------------------------------------*/
void FindURL(CString s,	MainThread *ptr){
	CStdioFile fin;             //CStdioFile 对象代表一个用运行时函数fopen 打开的C 运行时流式文件
	if(!fin.Open(s,CFile::modeRead)) 
		return;                 //以只读模式打开文件 s是临时文件的本地地址
	CString str_BaseURL;
	if(!fin.ReadString(str_BaseURL))
		return;                //从文件读出的字符串为空
	CString mark="href=";      //-链接以"href="开始	
	int i=-1,j=-1,URL_end=-1;
	CString str_Line,str_URL;	
	bool exist=false;            //-标记网页是否被访问过的标签
	while(fin.ReadString(str_Line))
	{                             //-从指定文件中读取一行,读取成功时执行循环		
		if(ptr->m_DownData.IsFull())
			break;               //ptr用于控制主线程的数据区
		i=str_Line.Find(mark);
		if(i==-1)
			continue;             //如果本行无URL
/*------------------------否则(本行有URL),提取一个链接------------------------*/
//-处理形如"href =                "http://..."   "的URL
		str_Line=str_Line.Mid(i+4);           //-去掉herf		
		str_Line.TrimLeft();                  //-去掉当前字符串最左边的空格
		if(str_Line[0]=='=') 
			str_Line=str_Line.Mid(1);         //-去掉当前字符串起始的等号(若等号存在)
		str_Line.TrimLeft();               //-去掉当前字符串最左边的空格
		if(str_Line[0]=='\"')                //str_Line[0]是双引号
		{                                    //-处理" "中的URL
			URL_end=str_Line.Find(_T("\""),1);     //找到双引号之间的URL地址
			if(URL_end==-1 || URL_end==1)
				continue;                    //并未找到
			str_URL=str_Line.Mid(1,URL_end-1); //设置URL的值是从网页中获得链接"href=" (双引号)
		}
		else if(str_Line[0]=='\'')           //str_Line[0]是单引号
		{                                    //处理' '中的URL
			URL_end=str_Line.Find(_T("\'"),1);
			if(URL_end==-1 || URL_end==1) 
				continue;
			str_URL=str_Line.Mid(1,URL_end-1); //设置URL的值是从网页中获得链接"href=" (双引号)
		}
		else{
			i=str_Line.Find(_T(">"));
			j=str_Line.Find(_T(" "));
			if(i==-1) URL_end=j;              //若无>,截至空格
			else if(j==-1) URL_end=i;          //若无空格,截至>
			else if(i>j) URL_end=j;            //若都有,且>在空格前出现,截至空格
			else URL_end=i;                     //若都有,且>在空格后出现,截至>
			if(URL_end==-1) continue;           //i=-1&&j=-1时,进行下一次循环
			str_URL=str_Line.Left(URL_end);
		}			
		if(str_URL.Find(_T("mailto:"))!=-1 ) continue;          //忽略电子邮件地址		
		if(str_URL.Find(_T("#"))!=-1 ) continue;               //忽略含#的URL	
		if(str_URL.Find(_T(".asp"))==-1 && str_URL.Find(_T(".php"))==-1 && str_URL.Find(_T(".jsp"))==-1 &&
			str_URL.Find(_T(".aspx"))==-1 && str_URL.Find(_T(".htm"))==-1 && str_URL.Find(_T(".html"))==-1 &&
			str_URL.Find(_T(".shtml"))==-1 && str_URL.Find(_T(".shtml"))==-1 && str_URL[str_URL.GetLength()-1]!=_T('/')) continue;//忽略掉含以上字符串的URL	
		
/*------------------------用网页中获取的相对地址算出URL------------------------*/
		str_URL.TrimLeft();              //-去掉最左边的空格
		str_URL.TrimRight();             //-去掉最右边的空格
		if(str_URL==_T("")) continue;        //若为空,继续
		if(str_URL==_T("http://")) continue; //若为http://,继续
		if(str_URL.Find(_T("http:"))==-1){
			LPTSTR p=new TCHAR[200];
			unsigned long m=200;	
/*
BOOL InternetCombineUrl(
  __in     LPCTSTR lpszBaseUrl,
  __in     LPCTSTR lpszRelativeUrl,
  __out    LPTSTR lpszBuffer,
  __inout  LPDWORD lpdwBufferLength,
  __in     DWORD dwFlags
);
Value  
ICU_BROWSER_MODE 
Meaning
Does not encode or decode characters after "#" or "?", and does not remove trailing white space after "?". 
If this value is not specified, the entire URL is encoded and trailing white space is removed.
http://msdn.microsoft.com/en-us/library/aa384355(VS.85).aspx
//-工作日志(五)中有更多相关资料
*/
			if(!InternetCombineUrl(str_BaseURL,str_URL,p,&m,ICU_BROWSER_MODE)) continue;//-根据网页中获取的相对地址算出URL,失败则进行下一次循环
			str_URL=p;
			delete []p;
		}
		//	if(str_URL.Find(ptr->str_Confine)==-1)continue;
		if(!(ptr->m_DownData.IsExisted(str_URL))) ptr->m_DownData.AddURL(str_URL);//-若未在共享数据区的URL任务队列中出现,则加入队列
	}
	fin.Close();//-关闭
}

/*-------------------------------------------------------------------------------------
函数功能:
//	controlling function for the worker thread
//	从URL任务队列得到一个网址并尝试
调用之前的预备条件:网页已经从网络上下载到本地存为临时文件
返回后的处理:删除临时文件
入口参数:
LPVOID pParam	主控线程的指针,用于获取共享数据区
抽象算法:
①试图从URL队列中获取一个URL,若失败则返回(结束线程)
②根据地址向服务器发送请求,若请求失败则返回(结束线程)
③根据网页,提取主要内容,并存一个临时文件,用FindURL函数查找链接
④从共享数据区删除线程标签
⑤结束线程
工作者线程(worker thread)的传入函数不能为类中的成员函数,故声明为全局函数
-----------------------------------------------------------------------------------*/
UINT DownloadFile(LPVOID pParam){
	MainThread *ptr=(MainThread *)pParam;    //pParam	主控线程的指针,用于获取共享数据区
	CString URL;
	
	if(!(ptr->m_DownData.GetCurURL(URL))){   //试图获取一个URL
		ptr->m_DownData.DeleThread();
		return 0;
	}
//以下为建立网络发出请求
	//使用类CInternetSession 创建并初始化一个或多个同时的Internet 会话。如果需要,还可描述与代理服务器的连接。
	//如果Internet连接必须在应用过程中保持着,可创建一个类CWinApp的CInternetSession成员。
/*
CInternetSession(
   LPCTSTR pstrAgent = NULL,
   DWORD_PTR dwContext = 1,
   DWORD dwAccessType = PRE_CONFIG_INTERNET_ACCESS,
   LPCTSTR pstrProxyName = NULL,
   LPCTSTR pstrProxyBypass = NULL,
   DWORD dwFlags = 0 
);
INTERNET_OPEN_TYPE_DIRECT //Connect directly to Internet.
*/
	CInternetSession MyConnect(_T("Microsoft MFC APP"),1,INTERNET_OPEN_TYPE_DIRECT);
	CHttpConnection* pServer = NULL;            //为试图打开连接的应用打开一个HTTP服务器
	CHttpFile* pHttpFile=NULL;                  //CHttpFile提供向HTTP服务器中请求和读取的功能

	// check to see if this is a reasonable URL       http://210.48.16.168   /ss/aa.jpg
	CString strServerName;                     //210.48.16.168
	CString strObject;                         ///ss/aa.jpg
	INTERNET_PORT nPort;                       //端口
	DWORD dwServiceType;                       //URL的支持类型 如http

	try
	{      //如果成功地解析了URL,则返回非零值。如果URL为空或它不包含已知的Internet服务类型,则为0
		if(!AfxParseURL(URL, dwServiceType, strServerName, strObject, nPort) ||  //dwServiceType返回URL支持的类型
			dwServiceType != INTERNET_SERVICE_HTTP)  
		{                                                   //不是http站点
			THROW(new CInternetException(dwServiceType));  //除去异常 错误
		}                                                   //用CInternetSession实例来构造CHttpConnection对象
		pServer=MyConnect.GetHttpConnection(strServerName, nPort);//当前的URL向服务器请求,建立连接
		pHttpFile = pServer->OpenRequest(CHttpConnection::HTTP_VERB_GET,  //得到指向CHttpFile类的指针
			strObject, NULL, 1, NULL, NULL);
		pHttpFile->AddRequestHeaders(_T("Accept: text/*\r\nUser-Agent: MFC\r\n"));//添加发往HTTP服务器的请求头
		pHttpFile->SendRequest();                        //向HTTP服务器发送请求 
	
		DWORD StatusCode;
		pHttpFile->QueryInfoStatusCode(StatusCode);//获得HTTP请求相关联的状态号并将其放到所提供的StatusCode 参数中
                                                  //只有在SendRequest 被成功调用或者一个CHttpFile对象被 OpenURL成
                                                  //功创建后,才能使用该成员函数
		//file isn't there or is redirected
		/*200  URL定位,接着传输    400  不可理解的请求  404  所请求的URL未找到  405  服务器不支持所请求的方法 
		500  未知的服务器错误   503  已达到服务器容量  */
		if(StatusCode == HTTP_STATUS_MOVED ||StatusCode == HTTP_STATUS_REDIRECT ||   //是否需要重新定向
			StatusCode == HTTP_STATUS_REDIRECT_METHOD){
			CString strNewLocation;
			pHttpFile->QueryInfo(HTTP_QUERY_RAW_HEADERS_CRLF, strNewLocation); //返回HTTP请求中的回答或请求头

			int nPlace = strNewLocation.Find(_T("Location: ")); 
			if(nPlace == -1)                                //并未找到  说明站点地址改变
				THROW(new CInternetException(StatusCode));  //除去异常 错误
			strNewLocation = strNewLocation.Mid(nPlace + 10); //从'Location: '之后,删除'Location: ',存于变量
			nPlace = strNewLocation.Find('\n');              //寻找这一行的末尾
			if(nPlace > 0) strNewLocation = strNewLocation.Left(nPlace);//返回nPlace长度的字符串

			// close up the redirected site
			pHttpFile->Close();           //关闭CHttpFile 并释放其资源
			delete pHttpFile;
			pServer->Close();             //关闭CHttpConnection 并释放其资源
			delete pServer;
           
		    // 检查原来的位置
			if(!AfxParseURL(strNewLocation, dwServiceType,    //重定向的URL并未成功解析
				strServerName, strObject, nPort)) 
				THROW(new CInternetException(StatusCode));    //去除异常  错误
			if (dwServiceType != INTERNET_SERVICE_HTTP)       //重定向的URL不是一个HTTP资源
				THROW(new CInternetException(StatusCode));    //除去异常 错误
		    // 在新的位置尝试,并继续请求  获得HTTP请求相关联的状态号  同上
			pServer = MyConnect.GetHttpConnection(strServerName, nPort);
			pHttpFile = pServer->OpenRequest(CHttpConnection::HTTP_VERB_GET,
				strObject, NULL, 1, NULL, NULL);
			pHttpFile->AddRequestHeaders(_T("Accept: text/*\r\nUser-Agent: MFC\r\n"));
			pHttpFile->SendRequest();

			pHttpFile->QueryInfoStatusCode(StatusCode);
		}
		if (StatusCode != HTTP_STATUS_OK)     //http状态错误
			THROW(new CInternetException(StatusCode)); //除去异常 错误
	}
	catch(CInternetException *pEx)     //出错处理  并未关闭重定向站点
	{
		if(pServer!=NULL){
			pServer->Close();
			delete pServer;
		}
		if(pHttpFile!=NULL){
			pHttpFile->Close();
			delete pHttpFile;
		}
		pEx->Delete();
		MyConnect.Close();
		ptr->m_DownData.DeleThread(); //从共享数据区队列中删除该线程
		pDlg->Add(URL+"\r\n",0);
		return 0;
	}
	//message for "Connected"
	//if m_DownData is not full save the file
	bool isfull=!(ptr->m_DownData.IsFull());//若队列已满 isfull值为0,否则为非零值
	CString str_FileName;                     //内容提取后保存为本地文件
	ptr->m_DownData.GetFileName(str_FileName);//str_FileName为提取的内容在本地保存的文件名
	//内容提取后保存为本地文件
	CStdioFile LocalFile;
	//本地临时文件
	CStdioFile tempLocalFile;
	//提取变量
	CString dbTitle(""), dbUrl(""), dbText("");
	LocalFile.Open(str_FileName,CFile::modeCreate|
		CFile::modeWrite|CFile::typeText);       //以扩展名.txt文件形式打开
	if(isfull)                                   //若队列未满  打开一个windows的临时文件 .tmp为扩展名
		tempLocalFile.Open(str_FileName+".tmp",CFile::modeCreate|CFile::modeWrite|CFile::typeText);
	if(isfull)                                   //向str_FileName.tmp文件写入数据
		tempLocalFile.WriteString(URL+_T("\n"));
	LocalFile.WriteString(URL+_T("\n"));        //向str_FileName.txt文件写入数据
	dbUrl = URL;

	CString line;
	UINT w=0,k=0;
	int i=0,j=0;
	LPTSTR p;
	bool isUTF8 = false;	//记录网页是否UTF-8编码
	//找出本页的标题
	while(pHttpFile->ReadString(line))         //逐行读取文件中的数据
	{//每次读取一行
		if (line.Find(_T("charset=utf-8")) != -1 || line.Find(_T("charset=UTF-8")) != -1)
		{
			isUTF8 = true;
		}
		if(isfull)tempLocalFile.WriteString(line+_T("\n"));//若队列未满   下一行
		i=line.Find(_T("<TITLE>"));
		if(i==-1) i=line.Find(_T("<Title>"));      //i=-1,并未找到<TITLE>
		if(i==-1) i=line.Find(_T("<title>"));      //i=-1,并未找到<Title>
		if(i!=-1)                                  //找到<TITLE>、<Title>或<title>
		{
			i+=7;
			j=line.Find(_T("</"));            //从<title>之后找'</'
			if(j!=-1)LocalFile.WriteString(line.Mid(i,j-i)+_T("\n"));//找到'</'  将<title>和</title>之间的标题写入下一行
			//if(j!=-1)
			//{
			//	dbTitle = line.Mid(i,j-i);
			//}
			else{
				LocalFile.WriteString(line.Mid(i));//从第i个字符开始,将该行的标题写入文件
				//dbTitle.Append(line.Mid(i));
				while(pHttpFile->ReadString(line))
				{
					if(isfull)                  //队列未满
						tempLocalFile.WriteString(line+_T("\n"));//  从下一行开始
					j=line.Find(_T("</"));      //再次寻找'</'
					if(j==-1){
						LocalFile.WriteString(line);//并未找到 标题在该行未结束 继续下一行 continue
						//dbTitle.Append(line);
						continue;
					}
					LocalFile.WriteString(line.Left(j)+"\n");//将'</' 左侧的字符写入文件 得到完整的标题
					//dbTitle.Append(line.Left(j));
					break;         
				}
			}
			break;                  //找到完整的标题 直接退出循环
		}
		if(line.Find(_T("</HAED>"))!=-1 || line.Find(_T("</Head>"))!=-1 || line.Find(_T("</head>"))!=-1){
			LocalFile.WriteString(_T("Untitled Page\n"));//未找到<TITLE>、<Title>或<title> 
			                                             //但找到"</HAED>" "</Head>"或"</head>" 
			//dbTitle = _T("Untitled Page");
			break;                                       //说明该网页无标题
		}
	};
	bool body=false;
	while(pHttpFile->ReadString(line))
	{
		if(isfull)tempLocalFile.WriteString(line+_T("\n"));  //走向下一行
		if(line.Find(_T("<BODY"))!=-1 || line.Find(_T("<body"))!=-1 || line.Find(_T("<Body"))!=-1) 
			body=true;               //找到 "<BODY" "<body" 或 "<Body"  变量body值赋值为true
		if(body && line.Find('>')!=-1) break;
	}
	//对主体内容的过滤
	while(pHttpFile->ReadString(line))
	{
		if(isfull)tempLocalFile.WriteString(line+_T("\n"));//队列未满 从下一行开始

		p=line.GetBuffer(1024);            //重新获取其内部字符缓冲区的指针p
		ptr->TrimString((LPTSTR)p,w,k,TRUE);       //-设置主要保留中文
		line.ReleaseBuffer();             //将申请的1024个空间多余的释放掉  可与GetBuffer配合使用
		if(line!="")                       //如果tempLocalFile文件该行不为空
		{
			int f=0,g=0;
			
			while((g=line.Find(_T("&nbsp;"),f))!=-1)      //寻找&nbsp 即空格
			{//去除&nbsp;符号
				line.Delete(g,6);
				line.Insert(g,_T(" "));                       //将&nbsp替换为空格
				f=g;
			}
			while((g=line.Find(_T("&gt;"),f))!=-1)        //寻找&gt; 即'>'
			{//去除&gt;符号
				line.Delete(g,4);
				line.Insert(g,_T(">"));                       //将&gt;替换为'>'
				f=g;
			}
			while((g=line.Find(_T("&lt;"),f))!=-1)
			{//去除&lt;符号
				line.Delete(g,4);
				line.Insert(g,_T("<"));
				f=g;
			}
			line.TrimLeft();            //该行左边的空格除去
			line.TrimRight();           //该行右边的空格除去
			if(line!="")               //该行为空
				LocalFile.WriteString(line+_T(" "));//LocalFile文件相应为空
				//dbText.Append(line+_T(" "));
		}
	}
	LocalFile.Close();            //关闭LocalFile文件
	LocalFile.Open(str_FileName, CFile::modeRead|CFile::typeText);
	LocalFile.ReadString(dbUrl);
	LocalFile.ReadString(dbTitle);
	CString tempLine;
	while (LocalFile.ReadString(tempLine))
	{
		dbText.Append(tempLine);
		dbText.Append(_T("  "));
	}
	LocalFile.Close();
	if (isUTF8)
	{
		ptr->UTF8ToGB2312(dbTitle.GetBuffer(), dbTitle.GetLength(), dbTitle);
		ptr->UTF8ToGB2312(dbText.GetBuffer(), dbText.GetLength(), dbText);
	}
	int endPos = dbTitle.Find(_T("</"));
	if (endPos > 0)
	{
		dbTitle = dbTitle.Mid(0, endPos);
	}
	ptr->InsertDB(dbUrl, dbTitle, dbText);
	if(isfull) tempLocalFile.Close();
//查找临时文件中的链接
	if(isfull){
		FindURL(str_FileName+_T(".tmp"),ptr);//查找临时文件中的链接
	//	DeleteFile(str_FileName+_T(".tmp"));//删除临时文件
	}
	pHttpFile->Close();       //关闭CHttpFile 并释放其资源
	delete pHttpFile;
	pServer->Close();         //关闭CHttpConnection 并释放其资源
	delete pServer;
	MyConnect.Close();
//状态显示
	pDlg->Add(URL+_T("\r\n"),1);
	ptr->m_DownData.DeleThread(); //线程结束  从数据区删除一个线程记录
	return 1;
}


/*==========================================================================================================*/

/
// MainThread message handlers

void MainThread::Run(CString &str_Begin){	
	m_DownData.AddURL(str_Begin);//-向共享数据区URL队列加入根URL
	if(m_DownData.AddThread()) 
		AfxBeginThread(DownloadFile,this);//-访问根URL
	while(!m_bDone && !(m_DownData.IsEmpty() && m_DownData.GetCurThread()==0)){
		//依次启动工作者线程,根据共享数据区URL队列依次到指定URL下载
		Sleep(100);
		if(ThreadPause) continue;//判断全局变量ThreadPause,是否暂停线程
		if(m_DownData.AddThread()) AfxBeginThread(DownloadFile,this);
	}
	Sleep(1000);
	AfxMessageBox(_T("任务完成!"));
	ExitInstance();
}
/*=========================================================================================================
函数功能:过滤掉字符串中的html语言标签
入口参数:
LPTSTR pszBuffer 字符串指针指向被处理的字符串,以'\0'结尾
UINT &w	已经出现的"<"数目
UINT &K	已经出现的"{"数目
bool chinese 是否主要保留中文
函数的抽象算法: 
对于html代码,出现在{}中间的被视为函数体会被无条件的删除,出现在<>中间的代码会当作语言标签被删除。
如果是主要保留中文,为了更好的过滤,若一行中没有一个中文字符,则省略该行。
=========================================================================================================*/

bool MainThread::TrimString(LPTSTR pszBuffer,UINT &w,UINT &k,bool chinese){
	LPTSTR pszSource = pszBuffer;
	LPTSTR pszDest = pszBuffer;
	LPTSTR pszTemp = pszBuffer;
	bool ch=FALSE;
	bool mark=FALSE;
	
	while (*pszSource != '\0'){
		if(!ch && (*pszSource)<0) ch=TRUE;//本段字符中是否含有中文字符(汉字机内码以1开始,工作日志(四)有资料)
		if(*pszSource == '{')k++;
		if(k==0){//如果未被包含在{}中
			if(w!=0){//如果包含在<>中
				if(*pszSource == '>') w--;
				else if(*pszSource == '<') w++;
			}	
			else{//未包含在<>中
				if (*pszSource == '<'){
					w++;
					mark=TRUE;
				}
				else{
					if(mark){//说明是'>'后第一个字符;每段文字以空格分开
						*pszDest=' ';
						pszDest++;
						mark=FALSE;
					}
					*pszDest = *pszSource;
					pszDest++;
				}
			}
		}
		if(*pszSource == '}') k--;
		pszSource++;
	}
	//结束处理
	if(chinese){
		if(ch) *pszDest = '\0';
		else *pszTemp= '\0';//若一行中没有一个中文字符,则省略该行
	}
	else *pszDest = '\0';
	return true;
}

void MainThread::InsertDB(CString dbUrl, CString dbTitle, CString dbText)
{
	CoInitialize(NULL);
	_ConnectionPtr pConn(__uuidof(Connection));
	_RecordsetPtr pRst(__uuidof(Recordset));
	_CommandPtr pCmd(__uuidof(Command));
	_variant_t RecordsAffected;    //申请一个_variant_t类型的的变量

	pConn->ConnectionString="Provider=MIcrosoft.Jet.OLEDB.4.0;Data source=web.mdb";
	pConn->Open("","","",adConnectUnspecified);

	dbText.Replace(_T("'"), _T("''")); //单引号转义
	CString sql = "INSERT INTO crawler(Url,Title,Content) VALUES ('" + dbUrl + "','" + dbTitle + "','" + dbText + "')";
	//MessageBox(NULL, sql, NULL, NULL);
	try
	{
		pRst=pConn->Execute(sql.GetBuffer(sql.GetLength()), &RecordsAffected,adCmdText);
	}
	catch (...)
	{
		;
	}

	//pRst->Close();    //若有此句可以实现插入,但会产生runtime错误提示   
	pConn->Close();
	pCmd.Release();
	pRst.Release();
	pConn.Release();
	CoUninitialize();
}

void MainThread::UTF8ToGB2312(char *pText, int nLen, CString &strOutput) 
{ 
	if (nLen <= 0) 
	{ 
		return ; 
	} 
	char *PBuf = strOutput.GetBuffer(nLen); 
	char cTemp[4] = {0}; 
	int i = 0, j = 0; 

	// Jump "EF BB BF " if necessary. 
	if (memcmp(pText, "\xef\xbb\xbf ", 3) == 0) 
	{ 
		i = 3; 
	} 

	while(i < nLen) 
	{ 
		if(pText[i] > 0) 
		{ 
			PBuf[j++] = pText[i++]; 
		} 
		else 
		{ 
			WCHAR Wtemp; 
			char* uchar = (char *)&Wtemp; 

			uchar[1] = ((pText[i] & 0x0F) << 4) + ((pText[i+1] >> 2) & 0x0F); 
			uchar[0] = ((pText[i+1] & 0x03) << 6) + (pText[i+2] & 0x3F); 
			WideCharToMultiByte(CP_ACP, NULL, &Wtemp, 1, cTemp, sizeof(WCHAR), NULL, NULL); 
			PBuf[j] = cTemp[0]; 
			PBuf[j+1] = cTemp[1]; 

			i += 3; 
			j += 2; 
		} 
	} 
	PBuf[j] = '\0'; 
	strOutput.ReleaseBuffer(); 
} 


创作不易,小小的支持一下吧!

WEBCRAWLER 网络爬虫实训项目 1 WEBCRAWLER 网 络 爬 虫 实 训 项 目 文档版本: 1.0.0.1 编写单位: 达内IT培训集团 C++教学研发部 编写人员: 闵卫 定稿日期: 2015年11月20日 星期五WEBCRAWLER 网络爬虫实训项目 2 1. 项目概述 互联网产品形形色色,有产品导向的,有营销导向的,也有技术导向的,但是 以技术见长的互联网产品比例相对小些。搜索引擎是目前互联网产品中最具技 术含量的产品,如果不是唯一,至少也是其中之一。 经过十几年的发展,搜索引擎已经成为互联网的重要入口之一,Twitter联合创 始人埃文•威廉姆斯提出了“域名已死论”,好记的域名不再重要,因为人们会 通过搜索进入网站。搜索引擎排名对于中小网站流量来说至关重要。了解搜索 引擎简单界面背后的技术原理其实对每一个希望在互联网行业有所建树的信息 技术人员都很重要。 1.1. 搜索引擎 作为互联网应用中最具技术含量的应用之一,优秀的搜索引擎需要复杂的架构 和算法,以此来支撑对海量数据的获取、 存储,以及对用户查询的快速而准确 地响应。 从架构层面,搜索引擎需要能够对以百亿计的海量网页进行获取、 存 储、 处理的能力,同时要保证搜索结果的质量。 如何获取、 存储并计算如此海WEBCRAWLER 网络爬虫实训项目 3 量的数据?如何快速响应用户的查询?如何使得搜索结果尽可能满足用户对信 息的需求?这些都是搜索引擎的设计者不得不面对的技术挑战。 下图展示了一个通用搜索引擎的基本结构。商业级别的搜索引擎通常由很多相 互独立的模块组成,各个模块只负责搜索引擎的一部分功能,相互配合组成完 整的搜索引擎: 搜索引擎的信息源来自于互联网网页,通过“网络爬虫” 将整个“互联网” 的 信息获取到本地,因为互联网页面中有相当大比例的内容是完全相同或者近似 重复的,“网页去重”模块会对此做出检测,并去除重复内容。 在此之后,搜索引擎会对网页进行解析,抽取网页主体内容,以及页面中包含 的指向其它页面的所谓超链接。 为了加快用户查询的响应速度,网页内容通过 “倒排索引”这种高效查询数据结构来保存,而网页之间的链接关系也会予以 保存。之所以要保存链接关系,是因为这种关系在网页相关性排序阶段是可利 用的,通过“链接分析”可以判断页面的相对重要性,对于为用户提供准确的 搜索结果帮助很大。 由于网页数量太多,搜索引擎不仅需要保存网页的原始信息,还要保存一些中 间处理结果,使用单台或者少量的计算机明显是不现实的。 Google等商业搜索 引擎提供商,为此开发了一整套云存储与云计算平台,使用数以万计的普通PCWEBCRAWLER 网络爬虫实训项目 4 搭建了海量信息的可靠存储与计算架构,以此作为搜索引擎及其相关应用的基 础支撑。优秀的云存储与云计算平台已经成为大型商业搜索引擎的核心竞争 力。 以上所述是搜索引擎如何获取并存储海量的网页相关信息。这些功能因为不需 要实时计算,所以可以被看作是搜索引擎的后台计算系统。搜索引擎的首要目 标当然是为用户提供准确而全面的搜索结果,因此响应用户查询并实时提供准 确结果便构成了搜索引擎的前台计算系统。 当搜索引擎接收到用户的查询请求后,首先需要对查询词进行分析,通过与用 户信息的结合,正确推导出用户的真实搜索意图。 此后,先在“Cache系统” 所维护的缓存中查找。搜索引擎的缓存存储了不同的搜索意图及其相对应的搜 索结果。如果在缓存中找到满足用户需求的信息,则直接将搜索结果返回给用 户。这样既省掉了重复计算对资源的消耗,又加快了整个搜索过程的响应速 度。而如果在缓存中没有找到满足用户需求的信息,则需要通过“网页排 序”,根据用户的搜索意图,实时计算哪些网页是满足用户需求的,并排序输 出作为搜索结果。 而网页排序最重要的两个参考因素,一个是“内容相似 性”,即哪些网页是和用户的搜索意图密切相关的;一个是网页重要性,即哪 些网页是质量较好或相对重要的,而这往往可以从“链接分析”的结果中获 得。综合以上两种考虑,前台系统对网页进行排序,作为搜索的最终结果。 除了上述功能模块,搜索引擎的“反作弊”模块近年来越来越受到重视。搜索 引擎作为互联网用户上网的入口,对于网络流量的引导和分流至关重要,甚至 可以说起着决定性的作用。因此,各种“作弊”方式也逐渐流行起来,通过各 种手段将网页的搜索排名提前到与其网页质量不相称的位置,这会严重影响用 户的搜索体验。所以,如何自动发现作弊网页并对其给于相应的惩罚,就成了 搜索引擎非常重要的功能之一。 1.2. 网络爬虫 通用搜索引擎的处理对象是互联网网页,截至目前的网页数量数以百万计,所 以搜索引擎首先面临的问题就是如何能够设计出高效的下载系统,将如此海量 的网页数据传送到本地,在本地形成互联网网页的镜像备份。 网络爬虫即扮演 如此角色。 它是搜索引擎中及其关键的基础构件。WEBCRAWLER 网络爬虫实训项目 5 网络爬虫的一般工作原理如下图所示:  从互联网网页中选择部分网页的链接作为“种子URL”,放入“待抓取URL 队列”;  爬虫从“待抓取URL队列”中依次“读取URL”;  爬虫通过“DNS解析” 将读到的URL转换为网站服务器的IP地址;  爬虫将网站服务器的IP地址、通信端口、网页路径等信息交给“网页下载” 器;  “网页下载”器负责从“互联网”上下载网页内容;  对于已经下载到本地的网页内容,一方面将其存储到“下载页面库” 中,等 待建立索引等后续处理,另一方面将其URL放入“已抓取URL队列”,后者显 然是为了避免网页被重复抓取;  对于刚刚下载到本地的网页内容,还需要从中“抽取URL”;  在“已抓取URL队列”中检查所抽取的URL是否已被抓取过;  如果所抽取的URL没有被抓取过,则将其排入“待抓取URL队列” 末尾,在 之后的抓取调度中重复第步,下载这个URL所对应的网页。 如此这般,形成WEBCRAWLER 网络爬虫实训项目 6 循环,直到“待抓取URL队列”空,这表示爬虫已将所有能够被抓取的网页尽 数抓完,完成一轮完整的抓取过程。 以上所述仅仅是网络爬虫的一般性原理,具体实现过程中还可以有很多优化的 空间,比如将“网页下载”以多线索(进程或线程)并发的方式实现,甚至将 “DNS解析”也处理为并发的过程,以避免爬虫系统的I/O吞吐率受到网站服 务器和域名解析服务器的限制。而对于“已抓取URL队列”则可以采用布隆排 重表的方式加以优化,以降低其时间和空间复杂度。 2. 总体架构 本项目总体架构如下图所示: 配置器 Configurator 超文本传输协议响应 HttpResponse 日志 Log 主线程 main 多路输入输出 MultiIo 插件管理器 PluginMngr 套接字 Socket 字符串工具包 StrKit 统一资源定位符队列 UrlQueues 网络爬虫 WebCrawler 原始统一资源定位符 RawUrl 超文本传输协议响应包头 HttpHeader 域名解析线程 DnsThread 解析统一资源定位符 DnsUrl 接收线程 RecvThread 布隆过滤器 BloomFilter 哈希器 Hash 最大深度插件 MaxDepth 域名限制插件 DomainLimit 超文本传输协议响应包头过滤器插件 HeaderFilter 超文本标记语言文件存储插件 SaveHTMLToFile 图像文件存储插件 SaveImageToFile 发送线程 SendThreadWEBCRAWLER 网络爬虫实训项目 7 2.1. 基础设施 2.1.1. 字符串工具包(StrKit) 常用字符串处理函数。 2.1.2. 日志(Log) 分等级,带格式的日志文件打印。 2.1.3. 配置器(Configurator) 从指定的配置文件中加载配置信息。 2.1.4. 多路输入输出(MultiIo) 封装epoll多路I/O系统调用,提供增加、删除和等待操作接口。 2.1.5. 插件管理器(PluginMngr) 加载插件并接受其注册,维护插件对象容器并提供调用其处理函数的外部接 口。 2.2. 网络通信 2.2.1. 哈希器(Hash) 封装各种哈希算法函数。 2.2.2. 布隆过滤器(BloomFilter) 基于布隆算法,对欲加入队列的原始统一资源定位符进行过滤,以防止已被抓 取过的URL再次入队,降低冗余开销同时避免无限循环。 2.2.3. 原始统一资源定位符(RawUrl) 提供原始形态的统一资源定位符字符串的简单包装,以及规格化等辅助支持。 2.2.4. 解析统一资源定位符(DnsUrl) 将原始形态的统一资源定位符字符串,解析为服务器域名、资源路径、服务器 IP地址,乃至服务器通信端口等。WEBCRAWLER 网络爬虫实训项目 8 2.2.5. 统一资源定位符队列(UrlQueues) 封装原始统一资源定位符队列和解析统一资源定位符队列,提供线程安全的入 队、出队操作,通过统一资源定位符过滤器排重,同时支持基于正则表达式的 统一资源定位符抽取功能。 2.2.6. 套接字(Socket) 发送/接收超文本传输协议请求/响应,发送成功将套接字描述符加入多路I/O, 接收成功抽取统一资源定位符压入队列。 2.2.7. 超文本传输协议响应包头(HttpHeader) 状态码和内容类型等关键信息。 2.2.8. 超文本传输协议响应(HttpResponse) 服务器统一资源定位符和超文本传输协议包头、包体及长度的简单封装。 2.3. 流程控制 2.3.1. 域名解析线程(DnsThread) 从原始统一资源定位符队列中弹出RawUrl对象,借助域名解析系统(DNS)获 取服务器的IP地址,构造DnsUrl对象压入解析统一资源定位符队列。 2.3.2. 发送线程(SendThread) 通过WebCrawler对象启动新的抓取任务,从解析统一资源定位符队列中弹出 DnsUrl对象,向HTTP服务器发送HTTP请求,并将套接字描述符放入MultiIo 对象。 2.3.3. 接收线程(RecvThread) 由WebCrawler对象在从MultiIo对象中等到套接字描述符可读时动态创建,通 过Socket对象接收超文本传输协议响应。WEBCRAWLER 网络爬虫实训项目 9 2.3.4. 网络爬虫(WebCrawler) 代表整个应用程序的逻辑对象,构建并维护包括日志、配置器、多路I/O、插件 管理器、统一资源定位符队列、域名解析线程等在内的多个底层设施,提供诸 如初始化、执行多路输入输出循环、启动抓取任务等外部接口。 2.3.5. 主线程(main) 主函数,处理命令行参数,初始化应用程序对象,进入多路I/O循环。 2.4. 外围扩展 2.4.1. 最大深度插件(MaxDepth) 根据配置文件的MAX_DEPTH配置项,对被抓取超链接的最大递归深度进行限 制。 2.4.2. 域名限制插件(DomainLimit) 根据配置文件的INCLUDE_PREFIXES和EXCLUDE_PREFIXES配置项,对被抓取 超链接的前缀进行限制。 2.4.3. 超文本传输协议响应包头过滤器插件(HeaderFilter) 根据配置文件的ACCEPT_TYPE配置项,对超文本传输协议响应的内容类型进行 限制。 2.4.4. 超文本标记语言文件存储插件(SaveHTMLToFile) 将用超文本标记语言描述的页面内容保存到磁盘文件中。 2.4.5. 图像文件存储插件(SaveImageToFile) 将页面内容中引用的图像资源保存到磁盘文件中。 3. 工作流程 3.1. 主事件流 进程入口函数在进行必要的命令行参数处理和系统初始化以后,进入网络爬虫 的多路输入输出循环,一旦发现某个与服务器相连的套接字有数据可读,即创WEBCRAWLER 网络爬虫实训项目 10 建接收线程,后者负责抓取页面内容,而前者继续于多路输入输出循环中等待 其它套接字上的I/O事件。 3.2. 解析事件流 独立的域名解析线程实时监视原始统一资源定位符队列的变化,并将其中的每 一条新近加入的原始统一资源定位符,借助域名解析系统转换为解析统一资源 定位符,并压入解析统一资源定位符队列。 3.3. 发送事件流 不断从解析统一资源定位符队列弹出解析统一资源定位符,创建套接字,根据 服务器的IP地址和通信端口发起连接请求,建立TCP连接,发送超文本传输协 议请求包,并将套接字放入多路输入输出对象,由主事件流等待其数据到达事 件。 3.4. 接收事件流 每个超文本传输线程通过已明确有数据可读的套接字接收来自服务器的超文本 传输协议响应,并交由统一资源定位符队列进行超链接抽取和布隆排重过滤, 直至压入原始统一资源定位符队列。在压入原始统一资源定位符队列之前,以 及接收到超文本传输协议包头和包体之后,分别执行统一资源定位符插件、超 文本传输协议包头插件和超文本标记语言插件的处理过程。 以上四个事件流,需要平行且独立地并发运行,并在共享资源和执行步调上保 持适度的同步。 4. 目录结构 本项目的目录结构如下所示: WebCrawler/ ├── bin/ │ ├── WebCrawler │ ├── WebCrawler.cfg │ └── WebCrawler.scr ├── docs/ │ ├── 概要设计.pdfWEBCRAWLER 网络爬虫实训项目 11 │ └── 详细设计.pdf ├── download/ ├── plugins/ │ ├── DomainLimit.cpp │ ├── DomainLimit.h │ ├── DomainLimit.mak │ ├── DomainLimit.so │ ├── HeaderFilter.cpp │ ├── HeaderFilter.h │ ├── HeaderFilter.mak │ ├── HeaderFilter.so │ ├── MaxDepth.cpp │ ├── MaxDepth.h │ ├── MaxDepth.mak │ ├── MaxDepth.so │ ├── SaveHTMLToFile.cpp │ ├── SaveHTMLToFile.h │ ├── SaveHTMLToFile.mak │ ├── SaveHTMLToFile.so │ ├── SaveImageToFile.cpp │ ├── SaveImageToFile.h │ ├── SaveImageToFile.mak │ ├── SaveImageToFile.so │ └── mkall └── src/ ├── BloomFilter.cpp ├── BloomFilter.h ├── Configurator.cpp ├── Configurator.h ├── DnsThread.cpp ├── DnsThread.h ├── Hash.cpp ├── Hash.h ├── Http.h ├── Log.cpp ├── Log.h ├── Main.cpp ├── Makefile ├── MultiIo.cpp ├── MultiIo.h ├── Plugin.h ├── PluginMngr.cpp ├── PluginMngr.h ├── Precompile.h ├── RecvThread.cpp ├── RecvThread.h ├── SendThread.cpp ├── SendThread.h ├── Socket.cpp ├── Socket.hWEBCRAWLER 网络爬虫实训项目 12 ├── StrKit.cpp ├── StrKit.h ├── Thread.cpp ├── Thread.h ├── Url.cpp ├── Url.h ├── UrlFilter.h ├── UrlQueues.cpp ├── UrlQueues.h ├── WebCrawler.cpp └── WebCrawler.h 其中bin目录存放可执行程序文件、启动画面文件和配置文件,docs目录存放 项目文档,download目录存放爬虫下载的网页文件和图像文件,plugins目录 存放扩展插件的源代码和共享库文件,src目录存放项目主体部分的源代码文 件。 在教学环境下,以上目录结构可分别放在teacher和student两个子目录中。其 中teacher目录包含完整的程序源码和资料文档,以为学生开发时提供参考和借 鉴。 student目录中的源代码是不完整的,部分类或者函数的实现只给出了基 本框架,但代码中的注释和teacher目录下对应的部分完全相同,其中缺失的内 容,需要学生在理解整体设计思路和上下文逻辑的前提下予以补全。需要学生 参与补全的源代码文件详见开发计划。 5. 开发计划 本项目拟在四个工作日内完成: 工作日 模块 子模块 代码文件 第一天 基础设施 预编译头 Precompile Precompile.h 字符串工具包 StrKit StrKit.h StrKit.cpp 日志 Log Log.h Log.cpp 配置器 Configurator Configurator.h Configurator.cppWEBCRAWLER 网络爬虫实训项目 13 多路输入输出 MultiIo MultiIo.h MultiIo.cpp 插件接口 Plugin Plugin.h 插件管理器 PluginMngr PluginMngr.h PluginMngr.cpp 第二天 网络通信 哈希器 Hash Hash.h Hash.cpp 统一资源定位 符过滤器接口 UrlFilter UrlFilter.h 布隆过滤器 BloomFilter BloomFilter.h BloomFilter.cpp 原始统一资源定位符 RawUrl Url.h 解析统一资源定位符 DnsUrl Url.cpp 统一资源定位符队列 UrlQueues UrlQueues.h UrlQueues.cpp 套接字 Socket Socket.h Socket.cpp 超文本传输协 议响应包头 HttpHeader Http.h 超文本传输协议响应 HttpResponse 第三天 流程控制 线程 Thread Thread.h Thread.cpp 域名解析线程 DnsThread DnsThread.h DnsThread.cpp 发送线程 SendThread SendThread.h SendThread.cppWEBCRAWLER 网络爬虫实训项目 14 接收线程 RecvThread RecvThread.h RecvThread.cpp 网络爬虫 WebCrawler WebCrawler.h WebCrawler.cpp 主线程 main Main.cpp 构建脚本 Makefile Makefile 第四天 外围扩展 最大深度插件 MaxDepth MaxDepth.h MaxDepth.cpp MaxDepth.mak 域名限制插件 DomainLimit DomainLimit.h DomainLimit.cpp DomainLimit.mak 超文本传输协议响 应包头过滤器插件 HeaderFilter HeaderFilter.h HeaderFilter.cpp HeaderFilter.mak 超文本标记语言 文件存储插件 SaveHTMLToFile SaveHTMLToFile.h SaveHTMLToFile.cpp SaveHTMLToFile.mak 图像文件存储插件 SaveImageToFile SaveImageToFile.h SaveImageToFile.cpp SaveImageToFile.cpp 构建脚本 mkall mkall 其中被突出显示的代码文件中,包含需要学生添加的内容,注意源文件中形 如“// 此处添加代码”的注释。WEBCRAWLER 网络爬虫实训项目 15 6. 知识扩展 为了能在实训环节,进一步强化学生独立思考、独立解决问题的能力,本项目 有意涵盖了一些前期课程中不曾涉及或只作为一般性了解的知识和技巧。具体 包括:  预编译头文件  std::string  变长参数表  基于epoll的多路I/O  哈希算法和布隆表  URL、 DNS、 HTTP和HTML  正则表达式  线程封装  精灵进程和I/O重定向  Makefile 对于上述内容,建议项目指导教师根据学生的接受能力,结合项目中的具体应 用,在项目正式启动之前,先做概要性介绍,同时提供进一步详细学习和研究 的线索,包括man手册、参考书、网络链接或其它媒体资源,尽量让学生通过 自己的实践和探索找到解决问题的方法,这才是项目实训的意义所在!
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码力码力我爱你

创作不易,小小的支持一下吧!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值