【网络爬虫项目】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 // 爬虫抓取文件存放位置