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

来源:互联网 发布:flexpaper flash.js 编辑:程序博客网 时间:2024/06/05 19:07
【网络爬虫项目】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)) { //还可以检查ipperror ("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 -rfCFLAGS = -c -Wall -I. -D_DEBUGLIBS   = -ldl -lpthread #-levent#【链接】# 以下内容替换规则等同于 宏(#define), $^ 代表依赖项 <==> $(OBJS)$(PROJ): $(OBJS)$(LINK) $^ $(LIBS) -o ../bin/$@#【编译】# .o依赖于.cpp , 此处的 $^ 代表的是依赖文件所有的.cpp.cpp.o:$(CXX) $(CFLAGS) $^#【清理指令】$ make cleanclean:$(RM) ../bin/$(PROJ) $(OBJS) *.gch

/* ------------------------------------------------------------ */
"【.mak】"# 【Makefile 编译动态链接共享库文件】PROJ   = DomainLimit.soOBJS   = DomainLimit.oCXX    = g++LINK   = g++RM     = rm -rfCFLAGS = -c -fpic -Wall -I. -I../src -D_DEBUG$(PROJ): $(OBJS)$(LINK) -shared $^ -o $@.cpp.o:$(CXX) $(CFLAGS) $^clean:$(RM) $(PROJ) $(OBJS) *.gch

/* ------------------------------------------------------------ */
"【mkall】"#!/bin/bashmake -f MaxDepth.mak cleanmake -f MaxDepth.makmake -f DomainLimit.mak cleanmake -f DomainLimit.makmake -f HeaderFilter.mak cleanmake -f HeaderFilter.makmake -f SaveHTMLToFile.mak cleanmake -f SaveHTMLToFile.makmake -f SaveImageToFile.mak cleanmake -f SaveImageToFile.makexit 0

/* ------------------------------------------------------------ */
"【构建脚本】"$: make clean$: make"【构建插件】"$: mkall"【运行】"$: cd ../bin$: WebCrawler -d    // 精灵模式运行$: cd ../download   // 爬虫抓取文件存放位置

0 0