当前位置: 首页 > 工具软件 > Ex-Crawler > 使用案例 >

【网络爬虫项目】实战知识点 - webcrawler

壤驷华辉
2023-12-01
【网络爬虫项目】webcrawler

<tips>
" grep" vi下透过文件的文本查找工具
$ grep -i template *.cpp //template 要查找的字符串

一、变长参数表
返回类型 函数名(参数类型1 形参1, 参数类型2 形参2, ...);
#include <stdarg.h>
va_list ap;
va_start(ap, 形参2); //ap, ...前最近的一个参数
va_arg(ap, 类型); //从ap链表获取下一个"类型"为参数,va_arg返回NULL停止
va_end(ap);
/** 代码演示 - 函数的变长参数表 **/
#include <stdio.h>
#include <stdarg.h>
int sum(int a, int b, ...) {
    int res = a + b;
    va_list ap; 
    va_start(ap, b); 
    int x;
    while((x = va_arg(ap, int)) != -1) 
        res += x;
    va_end(ap);
    return res;
}
void prints(char const * s1, ...) {
    puts(s1);
    va_list ap; 
    va_start(ap, s1);
    char const * sx; 
    while(sx = va_arg(ap, char const *))
        puts(sx);
    va_end(ap);
}
int main(void) {
    printf("%d\n", sum(1, 2, -1));
    printf("%d\n", sum(1, 2, 3, -1));
    printf("%d\n", sum(1, 2, 3, 4, -1));
    prints("a", NULL);
    prints("a", "bc", NULL);
    prints("a", "bc", "def", NULL);
    return 0;
}

二、字符串拆分
" strtok"(3)
#include <string.h>
char * strtok(char *str, char const * delim);
功能:拆分字符串
参数:
"str" 待拆分字符串
"delim" 分隔字符串
返回值:
成功 - 返回一个拆分所得字符串的指针,最后返回 NULL

char str[] = "abc,def.ghi tarena"; //不能是只读存储字符串
char const * delim = ",. ";
char *p = strtok(str, delim); //p->"abc"
abc\0def.ghi tarena
^
p = strtok(NULL, delim); //p->"def"
abc\0def\0ghi tarena
^
p = strtok(NULL, delim); //p->"ghi"
abc\0def\0ghi\0tarena
 ^
p = strtok(NULL, delim); //p->"tarena"
abc\0def\0ghi\0tarena
    ^
p = strtok(NULL, delim); //p->"NULL"
-----> "abc" "def" "ghi" "tarena"
/** 代码演示 - 字符串拆分 .c **/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void split(char const * s, char const * delim) {
	char * str = (char *)malloc(strlen(s)+1);
	strcpy(str, s);
	char * p;
	for(p = strtok(str, delim); p; p = strtok(NULL, delim))
		puts(p);
	free(str);
	str = NULL;
}
int main() {
	split("172.30.8.20", ".");	
	split("minwei@tarena.cn", ".@");
	split("HOME=/usr/tarena", "=");
	return 0;
}
/** 代码演示 - 字符串拆分 .cpp **/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <iostream>
using namespace std;
vector<string> split(char const * s, char const * delim) {
	vector<string> vs;
	char * str = (char *)malloc(strlen(s)+1);
	strcpy(str, s);
	char * p;
	for(p = strtok(str, delim); p; p = strtok(NULL, delim))
		vs.push_back(p);
	free(str);
	str = NULL;
	return vs;
}
int main() {
	vector<string> vs = split("172.30.8.20", ".");	
	vector<string>::const_iterator it;
	for(it = vs.begin(); it != vs.end(); ++it)
		cout << *it << endl;
	vs = split("minwei@tarena.cn", ".@");
	for(it = vs.begin(); it != vs.end(); ++it)
		cout << *it << endl;
	vs = split("HOME=/usr/tarena", "=");
	for(it = vs.begin(); it != vs.end(); ++it)
		cout << *it << endl;
	return 0;
}
"string"类成员函数:  //待补齐
QQword 文档。String类成员函数。

<tips>
g++ -c Precompile.h 
---> Precompile.h.gch
//后续可省区重复编译头文件的时间,提高效率

"【打印日志的通用格式】"
// 按格式来打印日志
void Log::printf (int level, char const* file, int line,char const* format, ...) const 
{
    if(level >= LEVEL_DBG)
    {
        // 格式化时间字符串
        char dateTime[32];
        time_t now = time (NULL);

        strftime(dateTime, sizeof(dateTime),"%Y-%m-%d %H:%M:%S", localtime (&now));
        fprintf (stdout, "[%s][%s][pid=%d][tid=%lu][%s:%d]\n", dateTime, s_levels[level], getpid (),
                 pthread_self (), file, line);

        va_list ap; 
        va_start (ap, format);
        vfprintf (stdout, format, ap);
        va_end (ap);

        fprintf (stdout, "\n\n");
    }   
    if(level >= LEVEL_ERR)
        exit (EXIT_FAILURE);
}

三、域名解析
 http://www.sina.com.cn:8080/web/index.html
|-协议-|---> 域 名 <---|端口|---> 路径 <---|
|-------> 统一资源定位符(URL) <-------|

http协议,默认的端口号:80

将字符串形式的主机域名,如www.sina.com.cn,转换为数字形势IP地址,如172.168.30.1,的过程叫:"域名解析"。

域名解析服务:DNS,Domain Name Service
域名解析服务器:提供域名解析服务的计算机

" gethostbyname"(3) //一个函数搞定域名解析
#include <netdb.h>
struct hostent *gethostbyname(const char *name); //hostent主机条目
功能:获得网络主机条目(解析字符串域名)
参数:"name" 字符串形式的主机域名
返回值:
成功 - 返回主机信息条目
失败 - 返回 NULL
struct hostent {
   char  *h_name;        /* official name of host */正式主机名
   char **h_aliases;     /* alias list */别名表(字符指针数组)
   int    h_addrtype;    /* host address type */地址类型IPv4
   int    h_length;      /* length of address */地址长度(字节)
   char **h_addr_list;   /* list of addresses */地址表
}

在IPv4的情况下,h_addr_list成员的实际类型为 struct in_addr**

/*
+----+   +---------+
h_addr_list --> | * | --> | in_addr |
+----+   +---------+
| * | --> ...
+----+
|NULL|  空指针作为遍历结束条件
+----+
*/

struct sockaddr_in addr;
addr.sin_family = AF_INET; //ipv4
addr.sin_port = htons(80); //端口
addr.sin_addr.s_addr = inet_addr("172.30.8.20"); //--->网络字节序
connect (sockfd, (sockaddr*)&addr, sizeof(addr)); //连接

struct in_addr {
unsigned int s_addr;
...
};

struct sockaddr_in {
...
struct in_addr sin_addr;
};

<tips>
一般返回指针的函数,以返回 NULL 代表失败;
一般返回整数的函数,以返回 0 或者 -1 代表失败。
/** 代码演示 - 域名解析过程 **/
#include <stdio.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <stdlib.h>
int main (int argc, char* argv[]) {
    if (argc < 2) {
        printf ("用法:%s <主机域名>\n", argv[0]);
        exit (EXIT_FAILURE);
    }   
    struct hostent* host = gethostbyname (argv[1]);
    if (! host) {
        perror ("gethostbyname");
        exit (EXIT_FAILURE);
    }   
    if (host->h_addrtype == AF_INET) {
        printf ("正式主机名:\n");
        printf ("\t%s\n", host->h_name);
        printf ("别名表:\n");
        char** ppaliases = host->h_aliases;
        while (*ppaliases)
            printf ("\t%s\n", *ppaliases++);
        printf ("地址表:\n");
        struct in_addr** ppaddr = 
            (struct in_addr**)host->h_addr_list;
        while (*ppaddr)
            printf ("\t%s\n", inet_ntoa (**ppaddr++));
    }   
    return 0;
}
$: dns www.sina.com.cn
正式主机名:
ara.sina.com.cn
别名表:
www.sina.com.cn
jupiter.sina.com.cn
地址表:
58.63.236.248
121.14.1.190

四、超文本传输协议(HTTP)

1. HTTP请求格式(流程)
GET /web/index.html HTTP/1.0<\r><\n> //获取访问目录
Host: www.sina.com.cn<\r><\n> //读取服务器主机
Accept: text/html<\r><\n> //接收请求-html文本格式
Connection: Keep-Alive<\r><\n> //对连接的约束-保持执行
User-Agent: Mozilla/5.0<\r><\n> //客户端代理程序:浏览器
Referer: www.sina.com.cn<\r><\n><\r><\n> //确定是否重定向
/** 代码参见 -  **/
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h> //Uc字符串处理头文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main (int argc, char* argv[]) {
	if (argc < 3) {
		printf ("用法:%s <主机IP地址>"
				"<主机域名> [<资源路径>]\n", argv[0]);
		exit (EXIT_FAILURE);
	}
	char const* ip = argv[1];
	char const* domain = argv[2];
	char const* path = argc < 4 ? "" : argv[3];
	int sockfd = socket (PF_INET, SOCK_STREAM, 0);
	if (sockfd == -1) {
		perror ("socket");
		exit (EXIT_FAILURE);
	}
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons (80);
	if (! inet_aton (ip, &addr.sin_addr)) { //还可以检查ip
		perror ("inet_aton");
		exit (EXIT_FAILURE);
	}
	if (connect (sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
		perror ("connect");
		exit (EXIT_FAILURE);
	}
	char request[1024];
	sprintf (request, 
			"GET &s HTTP/1.0\r\n"
			"Host: %s\r\n"
			"Accept: */*\r\n"
			"Connection: Keep-Alive\r\n"
			"User-Agent: Mozilla/5.0\r\n"
			"Referer: %s\r\n\r\n", path, domain, domain);
	if (send (sockfd, request, strlen(request), 0) == -1) {
		perror ("send");
		exit (EXIT_FAILURE);
	}
	for (;;) {
		char respond[1024] = {};
		ssize_t rlen = recv (sockfd, respond,
							 sizeof (respond) - 1, 0);
		if (rlen == -1) {
			perror ("recv");
			exit (EXIT_FAILURE);
		}
		if (rlen == 0)
			break;
		printf ("%s", respond);
	}
	printf("\n");
	close(sockfd);
	return 0;
}
$: http 58.51.150.41 www.tmooc.cn web/index_new.html?tedu > tmooc.html
//---> 输出内容到文件 tmooc.html :
HTTP/1.0 200 OK
Cache-Control: no-cache
Content-Length: 748
Content-Type: text/html
Date: Wed, 11 Jan 2017 12:08:32 GMT
Expires: 0

<html>
<!--
<?xml version="1.0" encoding="UTF-8"?>
  <WISPAccessGatewayParam
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:noNamespaceSchemaLocation="http://192.168.1.1/xml/WISPAccessGatewayParam.xsd">
    <Redirect>
<AccessProcedure>1.0</AccessProcedure>
<AccessLocation></AccessLocation>
<LocationName></LocationName>
<LoginURL>http://192.168.1.1/login?target=xml</LoginURL>
<MessageType>100</MessageType>
<ResponseCode>0</ResponseCode>
    </Redirect>
  </WISPAccessGatewayParam>
-->
<head>
<title>...</title>
<meta http-equiv="refresh" content="0; url=http://192.168.1.1/login?dst=http%3A%2F%2F%26s%2F">
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="expires" content="-1">
</head>
<body>
</body>
</html>

" strcasecmp" //忽略大小写的字符串比较

概念熟记:
// 统一资源定位符(URL)
// 超文本传输协议(HTTP)
// 超文本标记语言(HTML)

奇偶数算法:
int a;
a & 1 == 0; //为偶数
a & 1 == 1; //为奇数

乘法性能优化:
int a;
a * 33 <==> a * 32 + a <==> a << 5 + a;

五、正则表达式
- (此三个函数仅在UnixC才有,windouws没有)
HTML:
...   href="  http://www.ycty.org/about/news/78620.html \n" ...
^     ^  ^  ^  ^
|  |  |<--------------[1]子表达式------------->|
|rm_so|<----------------len---[0]主表达式---------------->|
|<------------------------------------------------------->|
rm_eo
html

href里面链接前后空白字符不确定。
href="\s*\([^ >"]*\)\s*"   //匹配各种href后链接地址的【正则表达式】

"href=\"\\s*\\([^ >\"]*\\)\\s*\""  //代码中格式,注意 \

\s
任意空白字符(空格、制表符、换页、换行符、回车符)
*
重复前一个匹配项任意次
[^ >"]
任意匹配不是空格、大于号、双引号的字符
\(
\)
表示子表达式的左右边界

#include <regex.h>  //regular expression 正则表达式
" regcomp"(3)
int regcomp(regex_t *preg, const char *regex, int cflags);
功能:编译正则表达式
参数:
"preg" 存储正则表达式的regex_t类型变量地址
"regex" 正则表达式字符串地址
"cflags" 0
返回值:
成功 - 返回 0
错误 - 返回错误码

" regexec"(3)
int regexec(const regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags);
功能:执行(匹配)正则表达式
参数:
"preg" 存储正则表达式的regex_t类型变量地址
"string" 指向读取到的html的代码形式字符串
"nmatch" regmatch_t定义的存储区数组个数,用于存储主表达式/子表达式
"pmatch[]" regmatch_t定义的存储区数组名
"eflags" 0
返回值:
成功 - 返回 0
错误 - 返回 REG_NOMATCH

typedef struct {
   regoff_t rm_so;  //从html开始到正则表达式首地址
   regoff_t rm_eo;  //从html开始到正则表达式尾地址
} regmatch_t;  // regmatch_t match[1]; 保存正则表达式的数组

" regfree"(3)
void regfree(regex_t *preg);
功能:释放正则表达式
参数:
"preg" 存储正则表达式的regex_t类型变量地址
返回值:
释放一般不会失败。

" regerror"(3)
size_t regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size);
功能:将错误解析为字符串,写入errbuf
参数:
"errcode" regcomp的返回值
"preg" 存储正则表达式的regex_t类型变量地址
"errbuf" 新定义的存放错误信息的数组的地址
"errbuf_size" sizeof (errbuf) 存放错误信息的数组长度
返回值:
成功 - 返回写入数组的错误信息的字符串长度
失败 - 暂无(无需验证失败)
/** 代码演示 - 利用正则表达式作为标记截取网页中的链接 **/
#include <stdio.h>
#include <regex.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char* argv[]) {
	if (argc < 2) {
		printf ("用法:%s <HTML文件>\n", argv[0]);
		exit (EXIT_FAILURE);
	}
	FILE* fp = fopen (argv[1], "r");
	if (! fp) {
		perror ("fopen");
		exit (EXIT_FAILURE);
	}
	if (fseek (fp, 0, SEEK_END) == -1) {
		perror ("fseek");
		exit (EXIT_FAILURE);
	}
	long size = ftell (fp);
	if (size == -1) {
		perror ("ftell");
		exit (EXIT_FAILURE);
	}
	char* buf = (char*)malloc (size + 1);
	if (! buf) {
		perror ("malloc");
		exit (EXIT_FAILURE);
	}
	if (fseek (fp, 0, SEEK_SET) == -1) {
		perror ("fseek");
		exit (EXIT_FAILURE);
	}
	if (fread (buf, 1, size, fp) != size) {
		perror ("fread");
		exit (EXIT_FAILURE);
	}
	buf[size] = '\0';
	fclose (fp);
	fp = NULL;
	// 创建正则匹配器
	regex_t ex;
	int error = regcomp (&ex, 
			"href=\"\\s*\\([^ >\"]*\\)\\s*\"", 0);
	if (error) {
		char errInfo[1024];
		regerror (error, &ex, errInfo, sizeof (errInfo));
		printf ("regcomp: %s\n", errInfo);
		exit (EXIT_FAILURE);
	}
	char const* html = buf;
	regmatch_t match[2]; //[0]主表达式 [1]子表达式
	// 匹配正则表达式
	while (regexec (&ex, html, 2, match, 0) != REG_NOMATCH) {
		html += match[1].rm_so;
		size_t len = match[1].rm_eo - match[1].rm_so; 
		char* url = (char*)malloc (len + 1);
		memcpy (url, html, len);
		url[len] = '\0';
		printf ("%s\n", url);
		free (url);
		// 打印一个链接后html位置要移动到子表达式的后面
		html += len + match[0].rm_eo - match[1].rm_eo;
	}
	// 销毁正则匹配器
	regfree (&ex);
	free (buf);
	buf = NULL;
	return 0;
}
$: regex tmooc.html 
../script/css/style_v2.css
http://www.ycty.org/about/news/78620.html
http://www.chuanke.com/v5189664-208498-1278072.html
http://www.chuanke.com/v5189664-202714-1223438.html
http://www.chuanke.com/v5189664-203119-1228927.html
http://www.ycty.org/about/news/78620.html
./course_v2.html

六、线程类的封装与继承
void* run (void* arg) { ... } //线程过程函数
pthread_creat (&tid, NULL, run, NULL); //线程tid,线程过程函数run

"线程类的封装":
class Thread { //基类
public:
	void start (void) {
		pthread_creat (&tid, NULL, run, this);
	}
private:
	static void* run (void* arg) {
		return ((Thread*)arg)->run ();
	}
	virtual void* run (void) = 0;
	pthread_t m_tid; //线程的属性:线程的tid
};
class MyThread : public Thread { //子类
private:
	void* run (void) {...} //没有this指针,不依赖对象有否
};
Thread thread;
thread.start ();
/** 代码演示 - 线程类的封装与继承 **/
#include <pthread.h>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
using namespace std;
class Thread {
public:
	virtual ~Thread (void) {}
	void start (void) {
		pthread_create (&m_tid, NULL, run, this);
	}
private:
	static void* run (void* arg) {
		return ((Thread*)arg)->run ();
	}
	virtual void* run (void) = 0;
	pthread_t m_tid;
};
class MyThread : public Thread {
public:
	MyThread (char ch, int ms) : m_ch (ch), m_ms (ms) {}
private:
	void* run (void) {
		for (;;) {
			cout << m_ch << flush;
			usleep (m_ms * 1000);
		}
		return NULL;
	}
	char m_ch;
	int m_ms;
};
int main(void) {
	MyThread t1 ('+', 500), t2 ('-', 100);
	t1.start ();
	t2.start ();
	getchar ();
	return 0;
}
$: g++ thread.cpp -o thread -lpthread
$: thread
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----...

"互斥锁,来避免线程并发冲突" //并发冲突的线程不安全

七、精灵(守护)进程
1. 孤儿进程
2. 独立会话进程组的首进程
3. 不拥有控制终端,三大标准I/O设备都是空设备
4. 永不终止 (随系统的启动而启动,结束而结束)
/** 代码演示 - 精灵(守护)进程 **/
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
int main (void) {
    pid_t pid = fork (); 
    if (pid == -1) {
        perror ("fork");
        exit (EXIT_FAILURE);
    }   
    if (pid)
        exit (EXIT_FAILURE);
    setsid (); // 设置会话id,独立会话进程组的首进程
    int fd = open ("/dev/null", O_RDWR, 0); 
    if (fd != -1) {
        dup2 (fd, STDIN_FILENO);
        dup2 (fd, STDOUT_FILENO);
        dup2 (fd, STDERR_FILENO);
    }   
    for (;;) {
        // ... 精灵进程做的事情
    }   
    return 0;
}
$: ps -eaf | grep daemon  //查进程

精灵进程未屏蔽信号的情况下,kill [pid]默认 (15)SIGTEAM 即可杀死。

八、多路I/O   " epoll"(7)
1. "创建"多路I/O对象
在系统内核中创建一个可以负责监视多个文件描述符上所发生I/O事件的对象。

" epoll_create1"(2)
#include <sys/epoll.h>
int epoll_create1(int flags);
功能:打开/创建一个多路文件描述符
参数:"flags" 取 0 ,不在函数调用时设置文件描述符个数(代码后可增)
返回值:
成功 - 返回一个多路I/O对象的文件描述符(非负整数)
失败 - 返回 -1,errno被设置

int epoll = epoll_create1 (0);
^
|
多路I/O对象的文件描述符

2. 将需要关注的文件描述符及其事件"添加"到多路I/O对象中
struct epoll_event ev; // 创建多路事件对象
ev.events = EPOLLIN; // "输入"事件。EPOLLIN | EPOLLOUT 输入输出事件
ev.data.fd = STDIN_FILENO; // "输入"文件描述符
epoll_ctl (epoll, EPOLL_CTL_ADD, STDIN_FILENO, &ev);// 关注对应事件
// epoll_ctl 返回-1代表错误

3. "等待"所关注的事件的发生
struct epoll_event events[10];
int fds = epoll_wait (epoll, events, 10, 5000); 
//5000毫秒,-1是无限时间。
将关注的文件描述符对应的事件填到events数组中,并返回该数组有效元素个数,如果没有事件发生,则最多等待5000毫秒。

4. "取消"对某个文件描述符及其事件的关注
struct epoll_event ev; // 创建多路事件对象
ev.events = EPOLLIN; // "输入"事件。EPOLLIN | EPOLLOUT 输入输出事件
epoll_ctl (epoll, EPOLL_CTL_DEL, STDIN_FILENO, &ev);// 取消关注事件
// epoll_ctl 返回-1代表错误

5. "关闭"多路I/O对象
close (epoll); //它也是个文件描述符,所以close通用
/** 代码验证 - 多路I/O对事件的关注和处理 **/
#include <stdio.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main (void) {
    // 创建多路I/O对象
    int epoll = epoll_create1 (0);
    if (epoll == -1) {
        perror ("epoll_create1");
        exit (EXIT_FAILURE);
    }   
    // 关注标准输入设备上的输入事件
    struct epoll_event ev; 
    ev.events = EPOLLIN;
    ev.data.fd = STDIN_FILENO;
    if (epoll_ctl (epoll, EPOLL_CTL_ADD, STDIN_FILENO, &ev) 
            == -1) {
        perror ("epoll_ctl");
        exit (EXIT_FAILURE);
    }   
    printf ("等待事件...\n"); // 调试信息
    struct epoll_event evts[10];
    int fds = epoll_wait (epoll, evts, 10, -1);
    if (fds == -1) {
        perror ("eopll_wait");
        exit (EXIT_FAILURE);
    }   
    printf ("fds = %d\n", fds);
    printf ("fd = %d\n", evts[0].data.fd);
    if (evts[0].events == EPOLLIN)
        printf ("发生了输入事件...\n");
    // 销毁多路I/O对象
    close (epoll);
    return 0;
}

/* ------------------------------------------------------------ */

"【Makefile】"
#【可复用Makefile】
# 1. 赋值给PROJ最终新生成的可执行文件名
PROJ   = WebCrawler
# 2. 赋值给OBJS可执行文件所依赖的所有.o文件
OBJS   = StrKit.o       \
         Log.o          \
         Configurator.o \
         MultiIo.o      \
         PluginMngr.o   \
         Hash.o         \
         BloomFilter.o  \
         Url.o          \
         UrlQueues.o    \
         Socket.o       \
         Thread.o       \
         DnsThread.o    \
         SendThread.o   \
         RecvThread.o   \
         WebCrawler.o   \
         Main.o
#赋值相当于重命名等号右侧内容
CXX    = g++
LINK   = g++
RM     = rm -rf
CFLAGS = -c -Wall -I. -D_DEBUG
LIBS   = -ldl -lpthread #-levent


#【链接】
# 以下内容替换规则等同于 宏(#define), $^ 代表依赖项 <==> $(OBJS)
$(PROJ): $(OBJS)
	$(LINK) $^ $(LIBS) -o ../bin/$@


#【编译】
# .o依赖于.cpp , 此处的 $^ 代表的是依赖文件所有的.cpp
.cpp.o:
	$(CXX) $(CFLAGS) $^


#【清理指令】$ make clean
clean:
	$(RM) ../bin/$(PROJ) $(OBJS) *.gch

/* ------------------------------------------------------------ */
"【.mak】"
# 【Makefile 编译动态链接共享库文件】
PROJ   = DomainLimit.so
OBJS   = DomainLimit.o
CXX    = g++
LINK   = g++
RM     = rm -rf
CFLAGS = -c -fpic -Wall -I. -I../src -D_DEBUG


$(PROJ): $(OBJS)
	$(LINK) -shared $^ -o $@


.cpp.o:
	$(CXX) $(CFLAGS) $^


clean:
	$(RM) $(PROJ) $(OBJS) *.gch

/* ------------------------------------------------------------ */
"【mkall】"
#!/bin/bash


make -f MaxDepth.mak clean
make -f MaxDepth.mak


make -f DomainLimit.mak clean
make -f DomainLimit.mak


make -f HeaderFilter.mak clean
make -f HeaderFilter.mak


make -f SaveHTMLToFile.mak clean
make -f SaveHTMLToFile.mak


make -f SaveImageToFile.mak clean
make -f SaveImageToFile.mak


exit 0

/* ------------------------------------------------------------ */
"【构建脚本】"
$: make clean
$: make


"【构建插件】"
$: mkall


"【运行】"
$: cd ../bin
$: WebCrawler -d    // 精灵模式运行
$: cd ../download   // 爬虫抓取文件存放位置

 类似资料: