目前我们运营一家综合B2B平台,虽然行业广,但深度不够。

2017年为了补短板,我们需要深入了解一些垂直行业,期望依靠深耕细作,提升网站的单位流量价值。

对于互联网公司来说,多数都是搞产品和技术的互联网人,没有行业经验背景。想要深挖垂直行业,第一步只能靠自己摸索,最简单有效的方法就是分析这个行业的垂直网站。

对应的工作简单说就两条:

        
  1. 找到这个行业里的优秀垂直网站
  2.     
  3. 庖丁解牛”式的初步分析。

如何找到高质量的垂直网站?

中国互联网发展也有很多年了,相对大的行业,一般都有自己的行业平台级网站,如何找到这些网站?

方法一
最简单的方法就是百度搜:“行业名+网”,以皮革行业为例,那就是搜“皮革网”,然后把百度搜索结果前三页的独立网站、B2B平台的子域名站都收集过来。

如果使用Chrome浏览器,推荐安装TabCopy这个插件,点击一下就可以复制网站的Title和URL到剪贴板里,方便粘贴到其他应用里处理。

除了百度外,还可以结合谷歌、360等其他搜索引擎,避免漏掉一些网站。

方法二

从某个垂直网站的友情链接里找出其他垂直网站。行业做久了,一般都是互相认识或有朋友关系的,友情链接里不只可以找到同类网站,还可以找到一些行业上下游关系,周边产业。

通过以上两种方法以找到很多垂直网站,但是否都是高质量呢?

除了行业词的百度排名,然后就要靠数据来说话了。

比如这个网站的百度权重、百度流量如何?百度收录页面数量多少等等。

推荐一个工具网站:买链帮手_最好用的网站批量查询工具

这个网站一般是做站群买外链的人用,可以查询域名的一些信息。

借助这个工具可以查询出各个垂直网站的百度收录情况、百度权重、搜狗权重、反链等信息,而且可以导出生成Excel文件。

还可以查询一下爱站网,获取网站的百度流量预估数据,然后也添加到Excel中。

理论上讲:一个网站的百度收录、权重、流量越高越好,这些数据可以证明它是这个行业最好的网站。

(如果有时间、精力的话,其实可以自己开发一套分析工具,把以上工作尽可能的自动化。)

需要看什么数据?

经过简单的分析,我们有了一个垂直行业网站的排名表。

那下一步就要靠人工去分析各个网站了,机器就很难替代这部分工作。

重点看什么部分?

        
  1. 网站栏目都有哪些?
  2.     
  3. 网站分类层级和维度
  4.     
  5. 网站特色是什么
  6.     
  7. 哪些内容有价值?

栏目对比分析

做一个对比表格,看看对于这个垂直领域来说,哪些功能和服务是必须提供的。

比如皮革网中,除了B2B常见的供求、企业名录、资讯、展会等等模块外,多数都提供了“价格指数”栏目,里面多是一些分析文章和趋势图,跟农产品和化工网站的价格行情展现形式略微不同。

还有皮革因为除了人工合成,很多是用动物皮毛加工,所以行业就表现出明显的养殖地、产地聚集现象。

如果要做一个专业的皮革网站,就要考虑这些情况。

网站分类层级和维度

分类是个头疼的问题,多数行业没有一个统一的标准。

理论上分类要按照MECE原则(相互独立、完全穷尽)去划分,但实际情况又很复杂,做网站的人也不一定完全了解行业。

所以分类时使用的维度可能不同,比如皮革分类,有的按处理工艺分类、有的按质地分类、有的按表层品质分类。感兴趣的可以读这篇:皮革档次如何分类? - 宋世泊的回答 - 知乎

更粗暴的直接分为“天然皮革”和“代用革”两类,然后再细分。

涉及到不同的垂直领域,要具体问题具体分析,但使用归纳法找出共性,一直是很有效的方法。

当你不懂时,最好的学习方法就是“模仿“、”从众”。

另一个计划尝试的方法:

借助5118网或战神等工具,获取这些垂直网站的百度搜索流量数据,然后分析关键词和着陆页面,根据搜索内容进行提炼总结,反向定义出网站分类和栏目

吸收特色内容和高价值内容

对于B2B行业来说,供求信息、企业名录一般都是有价值的内容。

除此之外,要看看资讯、展会、行情等模块,是否存在某个网站做的非常好,是否值得“偷”过来。

当做完以上分析工作后,借助分析过程中收集整理的网址和数据,可以迅速做出一个非常专业的垂直网址导航,对想学习了解这个行业的人来说是有价值的。

而且对于网站营销推广来说,拥有一个垂直专业网址导航,也可以成为以后资源互换的基础。

总结

以上只是一些粗浅的经验分享,因为工作刚刚展开,需要不断的积累形成套路,以后发现什么好的工具、方法会陆续跟大家分享交流。

虽然讲的是垂直行业的分析方法,但这个思路还是很通用的,对于做产品的人来说,其实就是竞争对手分析,而且只是其中的一部分。

作者:乔向阳

http://httpd.apache.org/docs/2.4/upgrading.html

http://httpd.apache.org/docs/2.4/new_features_2_4.html



/*2012.12.25 補充*/

apache 2.4.3 版有對 window 版的 ssl bug 做修正 ,bug 說明 http://www.apachelounge.com/viewtopic.php?p=22306

這個 bug 簡而言之就是會讓 443 的頁面連線掛點...
Tags:

Apache2.4开启GZIP功能 不指定

jed , 2017-2-8 23:50 , 服务器技术 , 评论(0) , 阅读(48743) , Via 本站原创
      HTTP协议上的GZIP编码是一种用来改进WEB应用程序性能的技术。大流量的WEB站点常常使用GZIP压缩技术来让用户感受更快的速度。这一般是指WWW服务器中安装的一个功能,当有人来访问这个服务器中的网站时,服务器中的这个功能就将网页内容压缩后传输到来访的电脑浏览器中显示出来.一般对纯文本内容可压缩到原大小的40%.这样传输就快了,效果就是你点击网址后会很快的显示出来.当然这也会增加服务器的负载. 一般服务器中都安装有这个功能模块的。
HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。

Let's Encrypt,是2016年4月12日成立的一家证书授权中心,提供免费的传输层安全(TLS)X.509证书,通过自动化的过程消除目前安全网站证书需要手工创建,加密,签名,安装以及更新的复杂性。
Tags: ,
  应用场景
  
  某些情况下,我们需要持续的周期性的提供一些服务,比如监控内存或cpu的运行状况,这些应用与客户端是没有关系的,不是说客户端(如web界面,手机app等)关闭了,我们就不监控内存或cpu了,为了应对这种业务场景,后台守护进程就可以派上用场了。
Tags: ,

php写守护进程(Daemon) 不指定

jed , 2017-1-12 14:22 , 服务器技术 , 评论(0) , 阅读(49552) , Via 本站原创
  守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。php也可以实现守护进程的功能。
  
  1、基本概念
  
  进程
  
  每个进程都有一个父进程,子进程退出,父进程能得到子进程退出的状态。
Tags: ,
PHP curl实现多进程并发抓取数据我们经常的用到了,今天我们来看一篇关于PHP curl实现多进程并发高效率采集爬虫的例子,具体的细节如下。



主要封装函数

multi_process();
根据参数,创建指点数目的子进程。
亮点功能1:子进程各种异常退出,比如segment fault, Allowed memory size exhausted等,中断一个子进程后,父进程会重新fork一个新进程顶上去,保持子进程数量。如果子进程里完成任务(比如判断tid达到10000),可以在子进程里exit(9),父进程会收到这个退出状态(9),然后等待所有子进程退出,最后退出自身进程。
亮点功能2:与curl封装函数一起实现了一统计功能,在程序关闭后会显示出一些主要的统计信息(图2的底部)。
mp_counter();
在父进程以及所有子进程之间通信,负责协调分配各子进程的任务,使用了锁机制。可以设置’init’参数重置计数,可以设置每次更新计数的值。
curl_get();
对curl相关函数的封装,加入了大量的错误机制,支持POST,GET,Cookie,Proxy,下载。
mp_msg();
实现规范之一就是,每条任务处理完,只输出一行信息。
亮点功能:这个函数会判断终端的高度和宽度,实现每一屏内容会显示一条统计信息(图1的紫色行),便于观察程序的执行情况,控制每一行输出的长度,保持一条信息不会超过一行。
rand_exit();
众所周知,PHP存在内在泄露的问题,所以每一个子进程里执行一定次数的任务后就退出,由multi_process()负责自动建立新的子进程(如图1中的绿色行)。

curl.lib.php

<?php

// 命令行颜色输出
$colors['red']         = "\33[31m";
$colors['green']       = "\33[32m";
$colors['yellow']      = "\33[33m";
$colors['end']         = "\33[0m";
$colors['reverse']     = "\33[7m";
$colors['purple']      = "\33[35m";

/*
默认参数设置
*/
$curl_default_config['ua'] = 'Mozilla/5.0 (compatible; Baiduspider/2.0; http://www.baidu.com/search/spider.html)';
$curl_default_config['referer'] = '';
$curl_default_config['retry'] = 5;
$curl_default_config['conntimeout'] = 30;
$curl_default_config['fetchtimeout'] = 30;
$curl_default_config['downtimeout'] = 60;

/*
针对指定域名设置referer(通常是用于下载图片),优先于$curl_default_config
默认使用空referer,一般不会有问题
eg: $referer_config = array(
          'img_domain'=>'web_domain',
          'e.hiphotos.baidu.com'=>'http://hi.baidu.com/');
*/
$referer_config = array('img1.51cto.com'=>'blog.51cto.com',
    '360doc.com'=>'www.360doc.com');

/*
针对指定域名设置User-agent,优先于$curl_default_config
默认使用百度蜘蛛的UA,拒绝百度UA的网站极少
eg: $useragent_config = array(
          'web_domain'=>'user agent',
          'www.xxx.com'=>'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)');
*/
$useragent_config = array('hiphotos.baidu.com'=>'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)';

/*
* 如果机器有多个IP地址,可以改变默认的出口IP,每次调用会在数组中随机选择一个。考虑到可能会有需要排除的IP,所以这里不自动配置为所有的IP。
* eg: $curl_ip_config = array('11.11.11.11', '22.22.22.22');
*/
$local_ip_config = array();

// cookie和临时文件目录
if((@file_exists('/dev/shm/') && @is_writable('/dev/shm/'))){
$cookie_dir = $tmpfile_dir = '/dev/shm/';
}else{
$cookie_dir = $tmpfile_dir = '/tmp/';
}

// 清除过期的cookie文件和下载临时文件
if(php_sapi_name() == 'cli'){
clear_curl_file();
}

/**
* GET方式抓取网页
*
* @param string $url    网页URL地址
* @param string $encode 返回的页面编码,默认为GBK,设置为空值则不转换
* @return string        网页HTML内容
*/
function curl_get($url, $encode='gbk'){
    return curl_func($url, 'GET', null, null, null, $encode);
}

/**
* POST方式请求网页
*
* @param string $url       请求的URL地址
* @param array  $data      发送的POST数据
* @param string $encode    返回的页面编码,默认为GBK,设置为空值则不转换
* @return bool
*/
function curl_post($url, $data, $encode='gbk'){
    return curl_func($url, 'POST', $data, null, null, $encode);
}

/**
* 获取页面的HEADER信息
*
* HTTP状态码并不是以“名称:值”的形式返回,这里以http_code作为它的名称,其他的值都有固定的名称,并且转成小写
*
* @param string $url URL地址
* @return array      返回HEADER数组
*/
function curl_header($url, $follow=true){
    $header_text = curl_func($url, 'HEADER');

    if(!$header_text){
        // 获取HTTP头失败
        return FALSE;
    }
    $header_array =explode("\r\n\r\n", trim($header_text));
if($follow){
  $last_header = array_pop($header_array);
}else{
  $last_header = array_shift($header_array);
}
  
    $lines = explode("\n", trim($last_header));

    // 处理状态码
    $status_line = trim(array_shift($lines));
    preg_match("/(\d\d\d)/", $status_line, $preg);
    if(!empty($preg[1])){
        $header['http_code'] = $preg[1];
    }else{
        $header['http_code'] = 0;
    }
    foreach ($lines as $line) {
        list($key, $val) = explode(':', $line, 2);
        $key = str_replace('-', '_', strtolower(trim($key)));
        $header[$key] = trim($val);
    }
    return $header;
}

/**
* 下载文件
*
* @param $url      文件地址
* @param $path     保存到的本地路径
* @return bool     下载是否成功
*/
function curl_down($url, $path, $data=null, $proxy=null){
    if(empty($data)){
        $method = 'GET';
    }else{
        $method = 'POST';
    }

    return curl_func($url, $method, $data, $path, $proxy);
}

/**
* 使用代理发起GET请求
*
* @param string $url       请求的URL地址
* @param string $proxy     代理地址
* @param string $encode    返回编码
*
* @return string           网页内容
*/
function curl_get_by_proxy($url, $proxy, $encode='gbk'){
    return curl_func($url, 'GET', null, null, $proxy, $encode);
}


/**
* 使用代理发起POST请求
*
* @param string $url       请求的URL地址
* @param string $proxy     代理地址
* @param string $encode    返回编码
*
* @return string           网页内容
*/
function curl_post_by_proxy($url, $data, $proxy, $encode='gbk'){
    return curl_func($url, 'POST', $data, null, $proxy, $encode);
}

/**
* @param string $url       请求的URL地址
* @param string $encode    返回编码
*
* @return string           网页内容
*/

function img_down($url, $path_pre){
    $img_tmp = '/tmp/curl_imgtmp_pid_'.getmypid();
    $res = curl_down($url, $img_tmp);
    if(empty($res)){
        return $res;
    }
    $ext = get_img_ext($img_tmp);
    if(empty($ext)){
        return NULL;
    }
    $path = "{$path_pre}.{$ext}";
    @mkdir(dirname($path), 0777, TRUE);
    // 转移临时的文件路径
    rename($img_tmp, $path);
    return $path;
}

function get_img_ext($path){
    $types = array(
        1 => 'gif',
        2 => 'jpg',
        3 => 'png',
        6 => 'bmp'
    );
    $info = @getimagesize($path);
    if(isset($types[$info[2]])){
        $ext = $info['type'] = $types[$info[2]];
        $ext == 'jpeg' && $ext = 'jpg';
    } else{
        $ext = FALSE;
    }
    return $ext;
}

/**
* 获取文件类型
*
* @param string $filepath 文件路径
* @return array           返回数组,格式为array($type, $ext)
*/
function get_file_type($filepath){

}

/**
* 返回文件的大小,用于下载文件后判断与本地文件大小是否相同
* curl_getinfo()方式获得的size_download并不一定是文件的真实大小
*
* @param  string $url    URL地址
* @return string         网络文件的大小
*/
function get_file_size($url){
    $header = curl_header($url);
    if(!empty($header['content_length'])){
        return $header['content_length'];
    }else{
        return FALSE;
    }
}

/**
* 获取状态码
*
* @param  string $url URL地址
* @return string      状态码
*/
function get_http_code($url, $follow=true){
    $header = curl_header($url, $follow);
    if(!empty($header['http_code'])){
        return $header['http_code'];
    }else{
        return FALSE;
    }
}

/**
* 获取URL文件后缀
*
* @param  string $url URL地址
* @return array      文件类型的后缀
*/
function curl_get_ext($url){
    $header = curl_header($url);
    if(!empty($header['content_type'])){
        @list($type, $ext) = @explode('/', $header['content_type']);
        if(!empty($type) && !empty($ext)){
            return array($type, $ext);
        }else{
            return array('', '');
        }
    }else{
        return array('', '');
    }
}

/**
* 封装curl操作
*
* @param string $url            请求的URL地址
* @param string $method         请求的方法(POST, GET, HEADER, DOWN)
* @param mix    $arg            POST方式为POST数据,DOWN方式时为下载保存的路径
* @param string $return_encode  网页返回的编码
* @param string $proxy          代理
* @return mix                   返回内容。4xx序列错误和空白页面会返回NULL,curl抓取错误返回False。结果正常则返回页面内容。
*/
// 待改进,下载到临时文件,下载成功后再转移(已经有文件则覆盖),下载失败则删除。
// 待改进,参数形式改成curl_func($url, $method, $data=null, savepath=null, $proxy=null, $return_encode='gbk')
function curl_func($url, $method, $data=null, $savepath=null, $proxy=null, $return_encode=null){
    global $colors, $cookie_dir, $tmpfile_dir, $referer_config, $useragent_config, $local_ip_config, $curl_config;

    // 控制台输出颜色
    extract($colors);

    // 去除URL中的/../
    $url = get_absolute_path($url);

    // 去除实体转码
    $url = htmlspecialchars_decode($url);

    // 统计数据
    if(function_exists('mp_counter')){
        if(!empty($savepath)){
            mp_counter('down_total');   // 下载次数计数
        }elseif($method == 'HEADER'){
            mp_counter('header_total'); // 抓取HTTP头次数计数
        }else{
            mp_counter('fetch_total');  // 抓取网页次数计数
        }
    }

    for($i = 0; $i < curl_config_get('retry'); $i ){

        // 初始化
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);

        // 设置超时
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, curl_config_get('conntimeout'));      // 连接超时
        if(empty($savepath)){
            curl_setopt($ch, CURLOPT_TIMEOUT, curl_config_get('fetchtimeout'));           // 抓取网页(包括HEADER)超时
        }else{
            curl_setopt($ch, CURLOPT_TIMEOUT, curl_config_get('downtimeout'));        // 下载文件超时
        }

        // 接收网页内容到变量
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);

        // 忽略SSL验证
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);

        // 设置referer, 在文件里配置的优先级最高
        foreach($referer_config as $domain=>$ref){
            if(stripos($url, $domain) !== FALSE){
                $referer = $ref;
                break;
            }
        }
  // 检查是否有通过curl_set_referer()设置referer
        if(empty($referer) && !empty($curl_config[getmypid()]['referer'])){
            $referer = $curl_config[getmypid()]['referer'];
        }
  if(!empty($referer)){
   curl_setopt($ch, CURLOPT_REFERER, $referer);
  }
  

        // 设置HTTP请求标识,在文件里配置的优先级最高
        foreach($useragent_config as $domain=>$ua){
            if(stripos($url, $domain) !== FALSE){
                $useragent = $ua;
                break;
            }
        }
  // 检查是否有通过curl_set_ua()设置useragent
        if(empty($useragent)){
            $useragent = curl_config_get('ua');
        }

        curl_setopt($ch, CURLOPT_USERAGENT, $useragent);

        // 出口IP
        if(!empty($local_ip_config)){
            curl_setopt($ch, CURLOPT_INTERFACE, $local_ip_config[array_rand($local_ip_config)]);
        }

        // 设置代理
        if(!empty($proxy)){
            curl_setopt($ch, CURLOPT_PROXY, $proxy);
            curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
        }

        // 设置允许接收gzip压缩数据,以及解压,抓取HEADER时不使用(获取不到正确的文件大小,影响判断下载成功)
        if($method != 'HEADER') {
            curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept-Encoding: gzip, deflate'));
            curl_setopt($ch, CURLOPT_ENCODING, "");
        }

        // 遇到301和302转向自动跳转继续抓取,如果用于WEB程序并且设置了open_basedir,这个选项无效
        @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
        // 最大转向次数,避免进入到死循环
        curl_setopt($ch, CURLOPT_MAXREDIRS, 5);

        // 启用cookie
        $cookie_path = $cookie_dir . 'curl_cookie_pid_' . get_ppid();
        curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_path);
        curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_path);

        // 设置post参数内容
        if($method == 'POST'){
            curl_setopt($ch, CURLOPT_HEADER, 0);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        }

        // 设置用于下载的参数
        if(!empty($savepath)){
            $tmpfile = $tmpfile_dir . '/curl_tmpfile_pid_'.getmypid();
            file_exists($tmpfile) && unlink($tmpfile);
            $fp = fopen($tmpfile, 'w');
            curl_setopt($ch, CURLOPT_FILE, $fp);
        }

        // 仅获取header
        if($method == 'HEADER'){
            curl_setopt($ch, CURLOPT_NOBODY, TRUE);
            curl_setopt($ch, CURLOPT_HEADER, TRUE);
        }

        // 抓取结果
        $curl_res = curl_exec($ch);
        // curl info
        $info = curl_getinfo($ch);

        // 调试curl时间,记录连接时间,等待时间,传输时间,总时间。
        // 测试方法,任何输出前设置sleep,输出中间设置sleep
        /*
        foreach($info as $key=>$val){
            echo "$key:$val\n";
        }
        exit(9);
        */
        // 错误信息
        $error_msg = curl_error($ch);
        $error_no = curl_errno($ch);

        // 关闭CURL句柄
        curl_close($ch);

        // 如果CURL有错误信息则判断为抓取失败,重试
        if(!empty($error_no) || !empty($error_msg)){
            $error_msg = "{$error_msg}($error_no)";
            curl_msg($error_msg, $method, $url, 'yellow');
            continue;
        }

        // 统计流量
        if(function_exists('mp_counter')){
            if(!empty($info['size_download']) && $info['size_download'] > 0){
                mp_counter('download_total', $info['size_download']);
            }
        }

        // 对结果进行处理
        if($method == 'HEADER'){
            // 返回header信息
            return $curl_res;
        }else{
            // 最终的状态码
            $status_code = $info['http_code'];

            if(in_array($status_code, array_merge(range(400, 417), array(500, 444)))){
                // 非服务器故障性的错误,直接退出,返回NULL
                $error_msg = $status_code;
                if(!empty($savepath)){
                    $method = "{$method}|DOWN";
                }
                curl_msg($error_msg, $method, $url, 'red');
                return NULL;
            }if($status_code != 200){
                // 防止网站502等临时错误,排除了上面的情况后,非200就重试。这一条规则需要后续根据情况来改进。
                // curl执行过程中会自动跳转,这里不会出现301和302,除非跳转次数超过CURLOPT_MAXREDIRS的值
                $error_msg = $status_code;
                curl_msg($error_msg, $method, $url, 'yellow');
                continue;
            }

            if(empty($savepath)){
                // 抓取页面
                if(empty($curl_res)){
                    // 空白页面
                    $error_msg = "blank page";
                    // 返回NULL值,调用处注意判断
                    return NULL;
                }else{
                    // 默认将页面以GBK编码返回

                    // 分析页面编码
                    preg_match_all("/<meta.*?charset=(\"|'?)(.*?)(;|\"|'|\s)/is", $curl_res, $matches);

                    // 转码条件:1)匹配到编码, 2)返回编码不为空, 3)匹配到的编码和返回编码不相同
                    if(!empty($matches[2][0]) && !empty($return_encode)
                        && str_replace('-', '', strtolower($matches[2][0]))
                        != str_replace('-', '', strtolower($return_encode))){
                        $curl_res = @iconv($matches[2][0], "{$return_encode}//IGNORE", $curl_res);
                        // 替换网页标明的编码
                        $curl_res = str_ireplace($matches[2][0], $return_encode, $curl_res);
                    }

                    // iconv如果失败则返回空白页
                    if(empty($curl_res)){
                        return NULL;
                    }else{
                        // 将相对路径转换为绝对路径
                        $curl_res = relative_to_absolute($curl_res, $url);
                        return $curl_res;
                    }
                }
            }else{
                // 下载文件
                if(@filesize($tmpfile) == 0){
                    $error_msg = 'Emtpy Content';
                    continue;
                }

                // 统计下载文件量
                if(function_exists('mp_counter')){
                    mp_counter('download_size', filesize($tmpfile));
                }
                // 创建目录
                @mkdir(dirname($savepath), 0777, TRUE);
                // 转移临时的文件路径
                rename($tmpfile, $savepath);

                return TRUE;
            }
        }
    }

    // 如果是下载或者抓取header,并且错误代码为6(域名无法解析),则不输出错误。失效的图片引用太多了。
    // 域名不合法的时候也无法输出错误了,需要改进,在前面判断URL的合法性
    if(!(($method == 'HEADER' || !empty($savepath)) && !empty($error_no) && $error_no == 6)){
        if(!empty($savepath)){
            $method = "{$method}|DOWN";
        }
        curl_msg($error_msg, $method, $url, 'red');
    }

    // 统计数据
    if(function_exists('mp_counter')){
        if(!empty($savepath)){
            mp_counter('down_failed');
        }elseif($method == 'HEADER'){
            mp_counter('header_failed');
        }else{
            mp_counter('fetch_failed');
        }
    }

    return FALSE;
}

/**
* 输出错误信息
*
* @param string $msg     错误信息
* @param string $method  请求方式
* @param string $url     URL地址
* @param string $color   颜色
*/
function curl_msg($msg, $method, $url, $color){
    global $colors;
    extract($colors);

    // 多并发下建议关闭黄色错误输出
    //$available_msg[] = 'yellow';
    $available_msg[] = 'red';

    if(php_sapi_name() != 'cli'){
        return;
    }

    if(!in_array($color, $available_msg)){
        return;
    }

    echo "{$reverse}".$colors[$color]."({$method})[cURL ERROR: {$msg}] {$url}{$end}\n";
}

/**
* 将URL地址转换为绝对路径
* URL地址有可能会遇到包含'/../'构成的相对路径,curl不会自动转换
* echo get_absolute_path("http://www.a.com/a/../b/../c/../././index.php");
* 结果为:http://www.a.com/index.php
*
* @param  string $path 需要处理的URL
* @return string       返回URL的绝对路径
*/
function get_absolute_path($path) {
    $parts = array_filter(explode('/', $path), 'strlen');
    $absolutes = array();
    foreach ($parts as $part) {
        if ('.' == $part) continue;
        if ('..' == $part) {
            array_pop($absolutes);
        } else {
            $absolutes[] = $part;
        }
    }
    return str_replace(':/', '://', implode('/', $absolutes));
}

/**
* 使用图片URL的md5值作为路径,并且分级目录
* 深度为e时,伪静态规则为rewrite ^/(.)(.)(.)(.*)$ /$1/$2/$3/$4 break;
* 平均1篇文章1张图片,三千万文章,三千万图片,3级目录最终4096子目录,平均每目录7324个图片
*
* @param string $str 原图片地址
* @param int $deep   目录深度
* @return string     返回分级目录
*/
function md5_path($str, $deep = 3){
    $md5 = substr(md5($str), 0, 16);
    preg_match_all('/./', $md5, $preg);
    $res = '';
    for($i = 0; $i < count($preg[0]); $i ){
        $res .= $preg[0][$i];
        if($i < $deep){
            $res .= '/';
        }
    }
    return $res;
}

function relative_to_absolute($content, $url) {
    $content = preg_replace("/src\s*=\s*\"\s*/", 'src="', $content);
    $content = preg_replace("/href\s*=\s*\"\s*/", 'href="', $content);

    preg_match("/(http|https|ftp):\/\/[^\/]*/", $url, $preg_base);
    if(!empty($preg_base[0])){
        // $preg_base[0]内容如http://www.yundaiwei.com
        // 这里处理掉以/开头的链接,也就是相对于网站根目录的路径
        $content = preg_replace('/href=\s*"\//i', 'href="'.$preg_base[0].'/', $content);
        $content = preg_replace('/src=\s*"\//ims', 'src="'.$preg_base[0].'/', $content);
    }

    preg_match("/(http|https|ftp):\/\/.*\//", $url, $preg_full);
    if(!empty($preg_full[0])){
        // 这里处理掉相对于目录的路径,如src="../../images/jobs/lippman.gif"
        // 排除掉file://开头的本地文件链接,排除掉data:image方式的BASE64图片
        $content = preg_replace('/href=\s*"\s*(?!http|file:\/\/|data:image|javascript)/i', 'href="'.$preg_full[0], $content);
        $content = preg_replace('/src=\s*"\s*(?!http|file:\/\/|data:image|javascript)/i', 'src="'.$preg_full[0], $content);
    }

    return $content;
}

/**
* 清除过期的cookie文件和下载临时文件
*/
function clear_curl_file(){
    global $cookie_dir;

    $cookie_files = glob("{$cookie_dir}curl_*_pid_*");
    $tmp_files = glob("/tmp/curl_*_pid_*");
    $files = array_merge($cookie_files, $tmp_files);

    foreach($files as $file){
        preg_match("/pid_(\d*)/", $file, $preg);
        $pid = $preg[1];
        $exe_path = "/proc/{$pid}/exe";
        // 如果文件不存在则说明进程不存在,判断是否为PHP进程,排除php-fpm进程
        if(!file_exists($exe_path)
            || stripos(readlink($exe_path), 'php') === FALSE
            || stripos(readlink($exe_path), 'php-fpm') === TRUE){
   $sem = @sem_get(@ftok($file, 'a'));
   if($sem){
    @sem_remove($sem);
   }
            unlink($file);
        }
    }
}


/**
* 如果是在子进程中,获取父进程PID,否则获取自身PID
* @return int
*/
if(!function_exists('get_ppid')){
    function get_ppid(){
  
  if(php_sapi_name() != 'cli'){
   // 如果是web方式调用,返回PHP执行进程PID,如APACHE或者PHP-FPM
   getmypid();
  }else{
   // 命令行执行进入到这里
   // 这里需要识别出是在子进程中调用还是在父进程中调用,不同的形式,保存的变量内容的文件位置需要保持一致
   $ppid = posix_getppid();
   // 理论上,这种判断方式可能会出坑。但在实际使用中,除了fork出的子进程外,不太可能让PHP进程的父进程的程序名中出现php字样。
   if(strpos(readlink("/proc/{$ppid}/exe"), 'php') === FALSE){
    $pid = getmypid();
   }else{
    $pid = $ppid;
   }
   return $pid;
  }

    }
}


// UTF-8转GBK
if(!function_exists('u2g')){
    function u2g($string){
        return @iconv("UTF-8", "GBK//IGNORE", $string);
    }
}


// GBK转UTF-8
if(!function_exists('g2u')) {
    function g2u($string)
    {
        return @iconv("GBK", "UTF-8//IGNORE", $string);
    }
}

function curl_rand_ua_pc(){
    $ua = 'Mozilla/5.0 (compatible; MSIE '.rand(7, 9).
        '.0; Windows NT 6.1; WOW64; Trident/'.rand(4, 5).'.0)';
    return $ua;
}

function curl_rand_ua_mobile(){
    $op = 'Mozilla/5.0 (Linux; U; Android '.rand(4,5).'.'.rand(1,5).'.'.rand(1,5).'; zh-cn; MI '.rand(3, 5).');';
    $browser = 'AppleWebKit/'.rand(500, 700).'.'.rand(1,100).'.'.rand(1,100)
        .' (KHTML, like Gecko) Version/'.rand(5,10)
        .'.0 Mobile Safari/537.36 XiaoMi/MiuiBrowser/'.rand(1,5).'.'.rand(1,5).'.'.rand(1,5);
    return $op.$browser;
}

function curl_config_get($key){
global $curl_config, $curl_default_config;

if(!empty($curl_config[getmypid()][$key])){
  return $curl_config[getmypid()][$key];
}elseif(!empty($curl_default_config[$key])){
  return $curl_default_config[$key];
}else{
  echo '$curl_default_config'."[$key] Not Found!\n";
  exit(9);
}
}

function curl_config_set($key, $val){
global $curl_config;
$curl_config[getmypid()][$key] = $val;
}

function curl_set_ua($ua){
curl_config_set('ua', $ua);
}

function curl_set_referer($referer){
curl_config_set('referer', $referer);
}

function curl_set_retry($retry){
curl_config_set('retry', $retry);
}

function curl_set_conntimeout($conntimeout){
curl_config_set('conntimeout', $conntimeout);
}

function curl_set_fetchtimeout($fetchtimeout){
curl_config_set('fetchtimeout', $fetchtimeout);
}

function curl_set_downtimeout($downtimeout){
curl_config_set('downtimeout', $downtimeout);
}


process.lib.php

<?php
if(php_sapi_name() != 'cli'){
return;
}

declare(ticks = 1);

// 中断信号
$signals = array(
    SIGINT  => "SIGINT",
    SIGHUP  => "SIGHUP",
    SIGQUIT => "SIGQUIT"
);

// 命令行颜色输出
$colors['red']         = "\33[31m";
$colors['green']       = "\33[32m";
$colors['yellow']      = "\33[33m";
$colors['end']         = "\33[0m";
$colors['reverse']     = "\33[7m";
$colors['purple']      = "\33[35m";
$colors['cyan']        = "\33[36m";

// 程序开始运行时间
$start_time = time();

// 父进程PID
$fpid = getmypid();

// 文件保存目录,/dev/shm/是内存空间映射到硬盘上,IO速度快。
// 有些环境上可能会没有这个目录,比如OpenVZ的VPS,这个路径实际是在硬盘上
if(file_exists('/dev/shm/') && is_dir('/dev/shm/')){
    $process_file_dir = '/dev/shm/';
}else{
    $process_file_dir = '/tmp/';
}

// 清理过期资源(文件和SEM信号锁),每次程序执行都需要调用,清除掉之前执行时的残留文件。
clear_process_resource();

// 判断是否在子进程中
function is_subprocess(){
global $fpid;
if(getmypid() != $fpid){
  return true;
}else{
  return false;
}
}

/**
* 多进程计数
*
* 1,用于多进程运行时的任务分配与计数,比如要采集某DZ论坛的帖子,则可以将计数器用于/thread-tid-1-1.html中
* 的tid,实现进程间的协调工作
* 2,由于shm_*系列函数的操作不够灵活,所以这里主要用于/proc/和/dev/shm/这二个目录来实现数据的读写(内存操
* 作,不受硬盘IO性能影响),用semaphore信号来实现锁定和互斥机制
* 3,编译PHP时需要使用参数--enable-sysvmsg安装所需的模块
*
* @param   string  $countername    计数器名称
* @param   mix     $update         计数器的更新值,如果是'init',计数器则被初始化为0
* @return int                      返回计数
*/
function mp_counter($countername, $update=1){
    global $process_file_dir;
    $time = date('Y-m-d H:i:s');

    // 父进程PID或者自身PID
    $top_pid = get_ppid();

    // 系统启动时间
    $sysuptime = get_sysuptime();

    // 进程启动时间
    $ppuptime = get_ppuptime($top_pid);

    // 由父进程ID确定变量文件路径前缀
    $path_pre = "{$process_file_dir}mp_counter_{$countername}_pid_{$top_pid}_";

    // 由于系统启动时间和当前父进程启动时间(jiffies格式)确定计数使用的文件
    $cur_path = "{$path_pre}btime_{$sysuptime}_ptime_{$ppuptime}";

    // 更新计数,先锁定
    $lock = sem_lock();

    if(!file_exists($cur_path)){
        // 调试代码。个别系统上启动时间会变化,造成文件路径跟随变化,最终导致计数归0。
        // $log = "[{$time}] - {$countername}($cur_path) - init\n";
        // file_put_contents('/tmp/process.log', $log, FILE_APPEND);

        $counter = 0;
    }else{
        // 理论上在这里,文件是一定存在的
        $counter = file_get_contents($cur_path);
    }

    // 更新记数, 继续研究下判断init不能用==
    if($update === 'init'){
        // 如果接收到更新值为init,或者变量文件不存在,则将计数初始化为0。
        $new_counter = 0;
    }else{
        $new_counter = $counter $update;
    }

    // 写入计数,解锁
    file_put_contents($cur_path, $new_counter);
    sem_unlock($lock);

    return $new_counter;
}

/**
* 创建多进程
*
* 1,通过mp_counter()函数实现进程间的任务协调
* 2,由于PHP进程可能会由于异常而退出(主要是segment fault),并且由于处理内存泄露的问题需要子进程主动退出,本函数可以实现自动建立
* 新的进程,使子进程数量始终保持在$num的数量
* 3,编译PHP时需要使用参数--enable-pcntl安装所需的模块
* 4,如果在子进程中调用了exit(9),那么主进程和所有子进程都将退出
*
* @param int       $num            进程数量
* @param bool      $stat           结束后是否输出统计信息
*/
function multi_process($num, $stat=FALSE){
    global $colors, $signals;
    extract($colors);

    if(empty($num)){
        $num = 1;
    }

    // 记录进程数量,统计用
mp_counter('process_num', 'init');
    mp_counter('process_num', $num);

    // 子进程数量
    $child = 0;

    // 任务完成标识
    $task_finish = FALSE;

    while(TRUE) {

        // 清空子进程退出状态
        unset($status);

        // 如果任务未完成,并且子进程数量没有达到最高,则创建
        if ($task_finish == FALSE && $child < $num) {
            $pid = pcntl_fork();
            if ($pid) {
                // 有PID,这里是父进程
                $child ;

                // 注册父进程的信号处理函数
                if($stat){
                    foreach ($signals as $signal => $name) {
                        if (!pcntl_signal($signal, "signal_handler")) {
                            die("Install signal handler for {$name} failed");

PHP实现敏感词过滤系统 不指定

jed , 2016-11-6 22:44 , 服务器技术 , 评论(0) , 阅读(60745) , Via 本站原创
安装说明

安装PHP扩展 trie_filter,安装教程 http://blog.41ms.com/post/39.html
安装PHP扩展 swoole,安装教程 http://www.swoole.com/
安装 libdatrie , 需要 libdatrie-0.2.4 或更新的版本

它依赖 libiconv .
安装:
================================

wget http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.14.tar.gz
tar -zxf libiconv-1.14.tar.gz
cd libiconv-1.14
./configure
make && make install

libdatrie 下载地址:
http://linux.thai.net/~thep/datrie/datrie.html#Download

安装:
================================

wget ftp://linux.thai.net/pub/ThaiLinux/software/libthai/libdatrie-0.2.4.tar.gz
tar -zxf libdatrie-0.2.4.tar.gz
cd libdatrie-0.2.4

./configure --prefix=/usr/local/libdatrie/
make ICONV_LIBS='/usr/local/lib/libiconv.so'
make install

安装 PHP 扩展
================================

wget https://github.com/wulijun/php-ext-trie-filter/archive/master.zip
unzip master.zip
cd php-ext-trie-filter-master/
/usr/local/php/bin/phpize
./configure --with-php-config=/usr/local/php/bin/php-config --with-trie_filter=/usr/local/libdatrie/
make && make install

将生成的 trie_filter.so 文件复制到 php 扩展目录,并在 php.ini 中添加该文件


https://github.com/wulijun/php-ext-trie-filter
  先写个结论,sphinx本身是可以支持中文搜索的,只是不支持中文分词,需要安装中文分词插件,coreseek就是一个打包了mmseg中文分词插件和sphinx源码的安装包。
  
  概念讲解
  
  Sphinx全文搜索引擎
  
  一般用作大数据量的搜索支持
  
  Sphinx特性
  
  1:Sphinx支持高速建立索引(可达10MB/秒,而Lucene建立索引的速度是1.8MB/秒)
分页: 9/77 第一页 上页 4 5 6 7 8 9 10 11 12 13 下页 最后页 [ 显示模式: 摘要 | 列表 ]