存档

2009年10月 的存档

php采集类

2009年10月29日 没有评论

一个自己写的php采集类,充分利用正则的强大字串处理能力,使用简单,功能也比较简单,能满足一般应用,功能也在不断完善中,使用过程:设置一个初始url,添加导航规则,添加采集字段和规则,保存输出即可

使用代码如下

$spider  = new spider();
$spider->addStartUrl(‘http://www.onlinedown.net/hits/week_{2,3}.htm’);
$spider->addLayer(0,’list’,’../soft/{*}.htm’);
$spider->addField(‘title’,'<title>{title}</title>’,array(‘华军软件园’,'安风信息网’));
$spider->run();
$spider->output();

上面采集的是华军软件的一周软件排行
源码如下

set_time_limit(0);
header("Content-type: text/html; charset=utf-8");
/**
* 采集程序类
* @author shooting
* @version 1.0.0
*/
class spider
{

/**
* 采集的终端页地址
*
* @var array
*/
var $pages = array();
/**
* 采集结果
*
* @var array
*/
var $result = array();
/**
* 第一层链接页面
*
* @var array
*/
var $startUrls = array();
/**
* 超时时间
*
* @var integer
*/
var $timeout;
/**
* 正在处理的文件内容
*
* @var string
*/
var $httpContent;
/**
* 正在处理的文件头
*
* @var array
*/
var $httpHead=array();
/**
* 自定义的head数组
*
* @var array
*/
var $putHead = array();
/**
* 采集字段与规则数组
*
* @var array
*/
var $field_arr = array();
/**
* 采集层次数
*
* @var interger
*/
var $deep;
/**
* 采集层次结构
*
* @var array
*/
var $layout_arr = array();
/**
* 采集限制条数
*
* @var integer
*/
var $limit = 0;

/**
* 程序运行时间
*
* @var float
*/
var $runtime = 0;

/**
* 被采集页面编码
*
* @var string
*/
var $charset = 'UTF-8';
/**
* 页面引用地址
*
* @var string
*/
var $httpreferer;

var $pagelimit = 0;

var $filepath = './';

function spider()
{
$this->timeout = 30;
}
/**
* 运行采集
*
* @return array
*/
function run()
{
$begintime = $this->microtime_float();
$cnt = 1;
foreach ($this->startUrls as $starturl){
/**
* 解析出起始地址中的页码区间
*/
if(preg_match(“~\{(\d+),(\d+)\}~”,$starturl,$pagenum)){
$pagebegin = intval($pagenum[1]);
$pageend = intval($pagenum[2]);
for(;$pagebegin<=$pageend;$pagebegin++){
$starturl = str_replace($pagenum[0],$pagebegin,$starturl);
$urllists = $this->getLists($this->layout_arr[0]['pattern'],$this->getContent($starturl));
foreach ($urllists as $url){
if(($this->limit > 0 && $cnt <= $this->limit)||$this->limit == 0)
{
$this->filterContent($this->getContent($url,$starturl));
$cnt++;
}
}
}
}else{
$urllists = $this->getLists($this->layout_arr[0]['pattern'],$this->getContent($starturl));
foreach ($urllists as $url){
if(($this->limit > 0 && $cnt <= $this->limit)||$this->limit == 0)
{
$this->filterContent($this->getContent($url,$starturl));
$cnt++;
}
}
}
}
$this->runtime = $this->microtime_float()-$begintime;
return $this->result;
}

/**
* 从文字段中根据规则提取出url列表
*
* @param string $pattern
* @param string $content
* @return Array
*/
function getLists($pattern=”,$content=”)
{
if(strpos($pattern,’{*}’) === false)return array($pattern);
$pattern = preg_quote($pattern);
$pattern = str_replace(‘\{\*\}’,'([^\'\">]*)’,$pattern);
$pattern = “~”.$pattern.”~is”;
preg_match_all($pattern,$content,$preg_rs);
return array_unique($preg_rs[0]);
}

/**
* 获取指定url的html内容包括头
*
* @param string $url
* @return string
*/
function getContent($url,$referer = ”)
{
$url = $this->urlRtoA($url,$referer);
preg_match(“/(http:\/\/)([^:\/]*):?(\d*)(\/?.*)/i”,$url,$preg_rs);
$host = $preg_rs[2];
$port = empty($preg_rs[3])?80:$preg_rs[3];
$innerUrl = $preg_rs[4];

$fsp = fsockopen($host,$port,$errno,$errstr,$this->timeout);
if(!$fsp)$this->log($errstr.’(‘.$errno.’)');
$output = “GET $url HTTP/1.0\r\nHost: $host\r\n”;
if(!isset($this->putHead['Accept']))$this->putHead['Accept']= “*/*”;
if(!isset($this->putHead['User-Agent']))$this->putHead['User-Agent']=’Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2)’;
if(!isset($this->putHead['Refer'])){
$this->putHead['Refer'] = ($referer == ”)?’http://’.$host:$referer;
}
foreach ($this->putHead as $headname => $headvalue){
$output .= trim($headname).’: ‘.trim($headvalue).”\r\n”;
}

$output .= “Connection: close\r\n\r\n”;
fwrite($fsp,$output);

$content = ”;
while (!feof($fsp)) {
$content .= fgets($fsp,256);
}
fclose($fsp);

$this->getHead($content);
$this->httpContent = $content;
if(strtoupper($this->charset) != ‘UTF-8′){
$content = iconv($this->charset,’utf-8′,$content);
}else if(!empty($this->httpHead['charset']) && $this->httpHead['charset']!=’UTF-8′)
{
$content = iconv($this->httpHead['charset'],’utf-8′,$content);
}
$this->httpreferer = $referer;
return $content;
}

/**
* 按照规则从内容提取所有字段
* @param Array
* @return Array
*/
function filterContent($content=”)
{
$rs = array();
foreach ($this->field_arr as $field => $fieldinfo){
$rs[$field] = $this->getPregField($fieldinfo,$content);
}
$this->result[] = $rs;
}
/**
* 相对路径转化为绝对路径
*
* @param string $relative
* @param string $referer
* @return string
*/
function urlRtoA($relative,$referer)
{
/**
* 去除#后面的部分
*/
$pos = strpos($relative,’#');
if($pos >0)$relative = substr($relative,0,$pos);
/**
* 检测路径如果是绝对地址直接返回
*/
if(preg_match(“~^(http|ftp)://~i”,$relative))
return $relative;
/**
* 解析引用地址,获得协议,主机等信息
*/
preg_match(“~((http|ftp)://([^/]*)(.*/))([^/#]*)~i”, $referer, $preg_rs);
$parentdir = $preg_rs[1];
$petrol = $preg_rs[2].’://’;
$host = $preg_rs[3];
/**
* 如果以/开头的情况
*/
if(preg_match(“~^/~i”,$relative))
return $petrol.$host.$relative;
return $parentdir.$relative;
}

/**
* 根据规则提取一个字段
*
* @param string $pattern
* @param string $content
* @return string
*/
function getPregField($fieldinfo,$content)
{
/**
* 规则为固定值的情况,直接返回固定值
*/
if(strpos($fieldinfo['pattern'],’{‘.$fieldinfo['field'].’}') === false)
return $fieldinfo['pattern'];
if($fieldinfo['isregular'] == ‘true’){
$pattern = $fieldinfo['pattern'];
$pattern = str_replace(‘{‘.$fieldinfo['field'].’}',’(?P<'.$fieldinfo['field'].'>.*?)’,$pattern);
}else{
$pattern = preg_quote($fieldinfo['pattern']);
$pattern = str_replace(‘\{‘.$fieldinfo['field'].’\}’,'(?P<'.$fieldinfo['field'].'>.*?)’,$pattern);
}
$pattern = “~”.$pattern.”~is”;
preg_match($pattern,$content,$preg_rs);
$fieldresult = $preg_rs[$fieldinfo['field']];
/**
* 去掉换行符
*/
$fieldresult = preg_replace(“~[\r\n]*~is”,”,$fieldresult);
/**
* 对采集到的结果根据规则再进行二次替换处理
*/
$replace_arr = $fieldinfo['replace'];
if(is_array($replace_arr)){
$replace_arr[0] = “~”.$replace_arr[0].”~s”;
$fieldresult = preg_replace($replace_arr[0],$replace_arr[1],$fieldresult);
}
/**
* 针对有下一页的字段递归采集
*/
if($this->pagelimit == 0){
if($fieldinfo['nextpage'] != ”){
$pattern = $fieldinfo['nextpage'];
$pattern = str_replace(‘{nextpage}’,'(?P[^\'\">]*?)’,$pattern);
$pattern = “~”.$pattern.”~is”;
if(preg_match($pattern,$content,$preg_rs) && $preg_rs['nextpage'] != ”){
$fieldresult .= $this->getPregField($fieldinfo,$this->getContent($preg_rs['nextpage'],$this->httpreferer));
}
}
}
if(!empty($fieldinfo['callback']))$fieldresult = $fieldinfo['callback']($fieldresult);
return $fieldresult;
}
/**
* 添加一个采集字段和规则
*
* @param string $field
* @param string $pattern
*/
function addField($field,$pattern,$replace_arr=”,$isregular=’false’,$nextpage = ”,$callback=”)
{
$rs = array(
‘field’ => $field,
‘pattern’ => $pattern,
‘replace’ => $replace_arr,
‘isregular’ => $isregular,
‘nextpage’ => $nextpage,
‘callback’=>$callback
);
$this->field_arr[$field] =$rs;
}
/**
* 输出
*
*/
function output()
{
echo “The result is:
“;
echo “runtime :$this->runtime S

";
		print_r($this->result);
		echo "

“;
}
/**
* 输出到XLS文件
*
* @param string $file
*/
function saveXls($file = ‘spider_result.xls’)
{
$fp = fopen($file,’w');
if($fp){
foreach ($this->result as $result)
{
$line = implode(“\t”,$result).”\n”;
fputs($fp,$line);
}
}
fclose($fp);
echo ‘The result has been saved to ‘.$file.’.
Cost time:’.$this->runtime;
}

function saveSql($table = ‘spider_result’,$file = ‘spider_result.sql’)
{
$fp = fopen($file,’w');
if($fp){

foreach($this->field_arr as $fieldinfo){
$sql_key .= ‘, `’.$fieldinfo['field'].’`';
}
$sql_key = substr($sql_key,1);
foreach ($this->result as $result)
{
$sql_value = array();
foreach ($result as $key => $value){
$sql_value[] = “‘”.$this->addslash($value).”‘”;
}
$line =”INSERT INTO `$table` ( $sql_key ) VALUES (“.join(‘, ‘,$sql_value).”);\r\n”;
fputs($fp,$line);
}
}
fclose($fp);
echo ‘The result has been saved to ‘.$file.’.
Cost time:’.$this->runtime;
}
/**
* 取得响应内容的头部信息
*
* @param string $content
* @return array
*/
function getHead($content)
{
$head = explode(“\r\n\r\n”,$content);
$head = $head[0];
// echo $head;
if(!preg_match(“~charset\=(.*)\r\n~i”,$head,$preg_rs))
preg_match(‘~charset=([^\"\']*)~i’,$content,$preg_rs);
$this->httpHead['charset'] = strtoupper(trim($preg_rs[1]));
// preg_match(“~charset\=(.*)~i”,$head,$preg_rs);
return $this->httpHead;
}
/**
* 设置采集页面的编码
* 在程序不能自动识别的情况下采集前要手动调用此函数
*
* @param string $charset
*/
function setCharset($charset){
$this->charset = strtoupper($charset);
}

/**
* 设置第一层链接页面地址
*
* @param array $url_arr
*/
function setStartUrls($url_arr)
{
$this->startUrls = $url_arr;
}

/**
* 增加一个第一层链接页面地址
*
* @param string $url
*/
function addStartUrl($url)
{
$this->startUrls[] = $url;
}
/**
* 添加一个采集层次
*
* @param integer $deep
* @param string $layout
* @param boolean $isSimple
* @param boolean $isPageBreak
* @param string $pattern
*/
function addLayer($deep,$layout,$pattern = ”,$isSimple = ‘false’,$isPageBreak = ‘false’)
{
$this->layout_arr[$deep] = array(
‘layout’=>$layout,
‘isSimple’=>$isSimple,
‘isPageBreak’=>$isPageBreak,
‘pattern’=>$pattern );

}

/**
* 自定义head
* @param string $namespace
* @param string $value
*/
function setHead($name,$value)
{
$this->putHead[$name] = $value;
}

/**
* 清除html代码
* @param string $content;
* @param string $cleartags
* @return string
*/
function clearHtml($content,$cleartags = ‘div’)
{
$cleartags_arr = explode(‘|’,$cleartags);

foreach ($cleartags_arr as $cleartag){
$pattern = ‘~<\/?'.$cleartag.'[^>]*>~is’;
$content = preg_replace($pattern,”,$content);
}
return $content;
}
/**
* 日志
*
*/
function log($str)
{
echo $str.”
\n”;
}
/**
* 获取采集运行时间
*
* @return float
*/
function getRuntime()
{
return $this->runtime;
}

function microtime_float()
{
list($usec, $sec) = explode(” “, microtime());
return ((float)$usec + (float)$sec);
}

function addslash($string)
{
return addslashes($string);
}
}

$spider = new spider();
$spider->addStartUrl(‘http://hi.baidu.com/shuntian/blog/index/{0,5}’);
$spider->setCharset(‘gb2312′);
$spider->addLayer(0,’list’,'/shuntian/blog/item/{*}.html’);
$spider->addField(‘title’,’‘,array(‘_顺者的天空-shooting's sky ‘,”));
$spider->addField(‘body’,’

{body}

‘);
$spider->addField(‘author’,'shooting’);
$spider->run();
$spider->saveSql();

php变量类型?内存指针?

2009年10月28日 没有评论

这个问题在C/C++语言中是个常见的问题,然而在php这种弱类型的语言中却并不被关注,个人觉得在php中探讨这个问题有点空中楼阁的感觉,因为php本身就是一种运行在Zend engine解释器基础上的一种语言,如果非要探讨这个问题,需要了解下php在zend引擎上的运行原理。
我们都知道C是强类型的语言,定义了的变量,在它的生命周期类就无法改变的,php这种弱类型的语言怎么能在C中完美运行呢?实际上php中的变量,在C中都是通过一个结构体来表示的,此结构体的定义是:

typedef struct _zval_struct {
zvalue_value value;
zend_uint refcount;
zend_uchar type;
zend_uchar is_ref;
} zval;

其中zvalue_value是真正保存数据的关键部分zvalue_value又是个联合体(union),结构如下

typedef union _zvalue_value {
long lval;
double dval;
struct {
char *val;
int len;
} str;
HashTable *ht;
zend_object_value obj;
} zvalue_value;

这个联合体中保存着一个弱类型的值常用的多种类型
zend engine根据 struct _zval_struct中的type来取union _zvalue_value中相应的单元,这就实现了php的多种类型间的自动转化。

总结,在php中探讨内存指针的问题没有意义,这应该是C/C++爱好者需要深入研究的问题!
深入内容参考 http://www.laruence.com/2008/08/22/412.html

我的wordpress图形验证码插件Wp-validcode

2009年10月25日 8 条评论

今天一天收到了65个垃圾评论,于是在前一个php验证码的基础上做出了自己的第一个wordpress插件,case见本站,下载地址在下面

wp-validcode

解压上传到wp-content/plugins目录,从后台安装即可!

已经调整为不区分大小写,验证码背景也进行了更换,更简单,但更具干扰性

mysql group by排序问题【转】

2009年10月18日 1 条评论

类如 有一个 帖子的回复表,posts( id , tid , subject , message ,  dateline ) ,

id为 自动增长字段, tid为该回复的主题帖子的id(外键关联),  subject 为回复标题, message 为回复内容, dateline 为回复时间,用UNIX 时间戳表示,

现在要求 选出 前十个来自不同主题的最新回复

SELECT * FROM posts GROUP BY  tid  LIMIT 10

这样一个sql语句选出来的并非你想要的 最新的回复,而是最早的回复,实际上是某篇主题的第一条回复记录!

也就是说 GROUP BY 语句没有排序,那么怎么才能让 GROUP 按照 dateline 倒序排列呢?加上 order by 子句?

看下面:

SELECT * FROM posts GROUP BY  tid  ORDER BY dateline DESC LIMIT 10

这条语句选出来的结果和上面的完全一样,不过把结果倒序排列了,而选择出来的每一条记录仍然是上面的记录,原因是 group by 会比 order by 先执行,这样也就没有办法将 group by 之前,也就是在分组之前进行排序了, 有网友会写出下面的sql 语句:

SELECT * FROM posts GROUP BY  tid DESC ORDER BY dateline DESC LIMIT 10

也就是说 在 GROUP BY 的字段 tid 后面加上递减顺序,这样不就可以取得分组时的最后回复了吗?这个语句执行结果会和上面的一模一样,这里加上 DESC 和ASC对执行结果没有任何影响!其实这是一个错误的语句,原因是GROUP BY 之前并没有排序功能,mysql 手册上面说,GROUP BY 时是按照某种顺序排序的,某种顺序到底是什么顺序?其实根本没有顺序,因为按照tid分组,其实也就是说,把tid相等的归纳到一个组,这样想的话,GROUP BY tid DESC 可以认为是在按照 tid 分组的时候,按照tid进行倒序排列,这不扯吗,既然是按照tid分组,当然是tid相等的归到一组,而这时候按照tid倒叙还是升序有个P用!

于是有网友发明下面的语句:

SELECT * FROM posts GROUP BY  tid , dateline DESC ORDER BY dateline DESC LIMIT 10

心想这样我就可以在分组前按照  dateline 倒序排列了,其实这个语句并没有起到按照tid分组的作用,原因还是上面的,在group by 字段后加 desc 还是 asc 是错误的写法,而这种写法 网友本意是想 按照 tid 分组,并且在分组的时候按照 dateline排倒序!而实际这句相当于下面的写法:(去掉 GROUP BY 字段后面的 DESC)

SELECT * FROM posts GROUP BY  tid , dateline ORDER BY dateline DESC LIMIT 10

也就是说,按照 tid 和 dateline 联合分组,只有在记录tid和dateline 同时相等的时候才归纳到一组,这显然不可能, 因为 dateline 时间线基本上是唯一的!

有人写出下面的语句:

SELECT *,max(dateline) as max_line FROM posts GROUP BY  tid ORDER BY dateline DESC LIMIT 10

这条语句的没错是选出了最大发布时间,但是你可以对比一下 dateline 和 max_dateline 并不相等!(可能有相当的情况,就是分组的目标记录只有一条的时候!)

为什么呢?原因很简单,这条语句相当于是 在group by 以后选出 本组的最大的 发布时间!对分组没有起到任何影响!因为SELECT子句是最后执行的!

后来更有网友发明了下面的写法!

SELECT *,max(dateline) as max_line FROM posts GROUP BY  tid HAVING dateline=max(dateline)

ORDER BY dateline DESC LIMIT 10

这条语句的预期结果和想象中的并不相同!因为你会发现,分组的结果中大量的记录没有了!为什么?因为 HAVING 是在分组的时候执行的,也就说:在分组的时候加上一个这样的条件:选择出来的 dateline 要和 本组最大的dateline 相等,执行的结果和下面的语句相同:

SELECT *,max(dateline) as max_line FROM posts GROUP BY  tid HAVING count(*)=1

ORDER BY dateline DESC LIMIT 10

看了这条sql语句是不是明白了呢?

dateline=max(dateline) 只有在分组中的记录只有一条的时候才成立,原因很明白吧!只有一条他才会和本组的最大发布时间相等阿,(默认dateline为不重复的值)

原因还是因为 group by 并没有排序功能,所有的这些排序功能只是错觉,所以你最终选出的 dateline 和max(dateline) 永远不可能相等,除非本组的记录只有一条!GROUP BY 在分组的时候,可能是一个一个来找的,发现有相等的tid,去掉,保留第一个发现的那一条记录,所以找出来的 记录永远只是按照默认索引顺序排列的!

那么说了这么多,到底有没有办法让 group by 执行前分组阿?有的 ,子查询阿!

最简单的 :

SELECT * FROM (SELECT * FROM posts ORDER BY dateline DESC) AS p GROUP BY  tid ORDER BY dateline DESC LIMIT 10

也有网友利用自连接实现的 ,这样的效率应该比上面的子查询效率高,不过,为了简单明了,就只用这样一种了,GROUP BY没有排序功能,可能是mysql弱智的地方,也许是我还没有发现.