From 0261fe2f22d5a40907b3712a9a98468a233cb5bd Mon Sep 17 00:00:00 2001 From: JAE Date: Thu, 12 Jun 2014 14:58:02 +0800 Subject: [PATCH] OK --- QueryList.class.php | 122 ++++++++++++++++++++++++++++++++-------- README.md | 2 +- demo/Searcher.class.php | 2 +- demo/demo.php | 4 +- demo/thanks.png | Bin 0 -> 8815 bytes 5 files changed, 102 insertions(+), 28 deletions(-) create mode 100644 demo/thanks.png diff --git a/QueryList.class.php b/QueryList.class.php index 01dd960..fc92215 100644 --- a/QueryList.class.php +++ b/QueryList.class.php @@ -7,42 +7,93 @@ * @author Jaeger * @email 734708094@qq.com * @link http://git.oschina.net/jae/QueryList - * @version 1.6.1 + * @version 2.0.0 + * + * @example + * + //获取CSDN移动开发栏目下的文章列表标题 +$hj = QueryList::Query('http://mobile.csdn.net/',array("title"=>array('.unit h1','text'))); +print_r($hj->jsonArr); + +//获取CSDN文章页下面的文章标题和内容 +$url = 'http://www.csdn.net/article/2014-06-05/2820091-build-or-buy-a-mobile-game-backend'; +$reg = array( + 'title'=>array('h1','text'), //获取纯文本格式的标题 + 'summary'=>array('.summary','text','input strong'), //获取纯文本的文章摘要,但保留input和strong标签 + 'content'=>array('.news_content','html','div a') //获取html格式的文章内容,但过滤掉div和a标签 + ); +$rang = '.left'; +$hj = QueryList::Query($url,$reg,$rang,'curl'); +print_r($hj->jsonArr); + +//继续获取右边相关热门文章列表的标题以及链接地址 +$hj->setQuery(array('title'=>array('','text'),'url'=>array('a','href')),'#con_two_2 li'); +//输出json数据 +echo $hj->getJson(); */ require 'phpQuery/phpQuery.php'; class QueryList { - private $pageURL; private $regArr; public $jsonArr; private $regRange; private $html; private $outputEncoding; private $htmlEncoding; + private static $ql; /** - * 构造函数 + * 静态方法,访问入口 * @param string $page 要抓取的网页URL地址(支持https);或者是html源代码 - * @param array $regArr 【选择器数组】说明:格式array("名称"=>array("选择器","类型"),.......),【类型】说明:值 "text" ,"html" ,"属性" + * @param array $regArr 【选择器数组】说明:格式array("名称"=>array("选择器","类型"[,"标签列表"]),.......),【类型】说明:值 "text" ,"html" ,"属性" ,【标签列表】:可选,当【类型】值为text时表示需要保留的HTML标签,为html时表示要过滤掉的HTML标签 * @param string $regRange 【块选择器】:指 先按照规则 选出 几个大块 ,然后再分别再在块里面 进行相关的选择 * @param string $getHtmlWay 【源码获取方式】指是通过curl抓取源码,还是通过file_get_contents抓取源码 * @param string $outputEncoding【输出编码格式】指要以什么编码输出(UTF-8,GB2312,.....),防止出现乱码,如果设置为 假值 则不改变原字符串编码 */ - public function __construct($page, $regArr, $regRange = '', $getHtmlWay = 'curl', $outputEncoding = false) + public static function Query($page, $regArr, $regRange = '', $getHtmlWay = 'curl', $outputEncoding = false) { + if(!(self::$ql instanceof self)) + { + self::$ql = new self(); + } + self::$ql->_query($page, $regArr, $regRange, $getHtmlWay, $outputEncoding); + return self::$ql; + } + /** + * 重新设置选择器 + * @param array $regArr 选择器数组 + * @param string $regRange 块选择器 + */ + public function setQuery($regArr, $regRange = '') + { + $this->jsonArr = array(); + $this->regArr = $regArr; + $this->regRange = $regRange; + $this->_getList(); + } + /** + * 得到JSON结构的结果 + * @return string + */ + public function getJSON() + { + return json_encode($this->jsonArr); + } + private function _query($page, $regArr, $regRange, $getHtmlWay, $outputEncoding) + { + $this->jsonArr = array(); $this->outputEncoding = $outputEncoding; if ($this->_isURL($page)) { - $this->pageURL = $page; if ($getHtmlWay == 'curl') { //为了能获取https:// $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $this->pageURL); + curl_setopt($ch, CURLOPT_URL, $page); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $this->html = curl_exec($ch); curl_close($ch); } else { - $this->html = file_get_contents($this->pageURL); + $this->html = file_get_contents($page); } } else { $this->html = $page; @@ -55,13 +106,6 @@ class QueryList $this->_getList(); } } - public function setQuery($regArr, $regRange = '') - { - $this->jsonArr = array(); - $this->regArr = $regArr; - $this->regRange = $regRange; - $this->_getList(); - } private function _getList() { $hobj = phpQuery::newDocumentHTML($this->html); @@ -70,13 +114,14 @@ class QueryList $i = 0; foreach ($robj as $item) { while (list($key, $reg_value) = each($this->regArr)) { + $tags = isset($reg_value[2])?$reg_value[2]:''; $iobj = pq($item)->find($reg_value[0]); switch ($reg_value[1]) { case 'text': - $this->jsonArr[$i][$key] = trim(pq($iobj)->text()); + $this->jsonArr[$i][$key] = $this->_allowTags(pq($iobj)->html(),$tags); break; case 'html': - $this->jsonArr[$i][$key] = trim(pq($iobj)->html()); + $this->jsonArr[$i][$key] = $this->_stripTags(pq($iobj)->html(),$tags); break; default: $this->jsonArr[$i][$key] = pq($iobj)->attr($reg_value[1]); @@ -89,15 +134,16 @@ class QueryList } } else { while (list($key, $reg_value) = each($this->regArr)) { + $tags = isset($reg_value[2])?$reg_value[2]:''; $lobj = pq($hobj)->find($reg_value[0]); $i = 0; foreach ($lobj as $item) { switch ($reg_value[1]) { case 'text': - $this->jsonArr[$i++][$key] = trim(pq($item)->text()); + $this->jsonArr[$i++][$key] = $this->_allowTags(pq($item)->html(),$tags); break; case 'html': - $this->jsonArr[$i++][$key] = trim(pq($item)->html()); + $this->jsonArr[$i++][$key] = $this->_stripTags(pq($item)->html(),$tags); break; default: $this->jsonArr[$i++][$key] = pq($item)->attr($reg_value[1]); @@ -111,10 +157,6 @@ class QueryList $this->jsonArr = $this->_arrayConvertEncoding($this->jsonArr, $this->outputEncoding, $this->htmlEncoding); } } - public function getJSON() - { - return json_encode($this->jsonArr); - } /** * 获取文件编码 * @param $string @@ -157,4 +199,36 @@ class QueryList } return false; } -} \ No newline at end of file + /** + * 去除特定的html标签 + * @param string $html + * @param string $tags 多个标签名之间用空格隔开 + * @return string + */ + private function _stripTags($html,$tags) + { + $tagsArr = preg_split("/\s+/",$tags,-1,PREG_SPLIT_NO_EMPTY); + $p = array(); + foreach ($tagsArr as $tag) { + $p[]="/(<(?:\/".$tag."|".$tag.")[^>]*>)/i"; + } + $html = preg_replace($p,"",trim($html)); + return $html; + } + /** + * 保留特定的html标签 + * @param string $html + * @param string $tags 多个标签名之间用空格隔开 + * @return string + */ + private function _allowTags($html,$tags) + { + $tagsArr = preg_split("/\s+/",$tags,-1,PREG_SPLIT_NO_EMPTY); + $allow = ''; + foreach ($tagsArr as $tag) { + $allow .= "<$tag> "; + } + return strip_tags(trim($html),$allow); + } +} + diff --git a/README.md b/README.md index 8119635..56655e8 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ - #QueryList交流QQ群:123266961 ╰☆邪恶 魔方☆ #QueryList简介 *** QueryList是一个基于phpQuery的通用列表采集类,是一个简单、 灵活、强大的采集工具,采集任何复杂的页面 基本上就一句话就能搞定了。 #QueryList 使用 ```php //实例化一个采集对象 $hj = new QueryList('http://www.baidu.com/s?wd=jaekj',array('title'=>array('h3','text'))); //输出结果:二维关联数组 print_r($hj->jsonArr); //输出结果:JSON数据 echo $hj->getJSON(); ``` 上面的代码实现的功能是采集百度搜索结果页面的所有搜索结果的标题,然后分别以数组和JSON格式输出。 ###QueryList 构造函数原型: >***QueryList***($page,$regArr,$regRange='',$getHtmlWay="curl",$output_encoding=false) 一共有五个参数,后面三个参数是可选的 * *$page* 要抓取的网页URL地址(默认支持https);或者是html源代码 * *$regArr* 【选择器数组】说明:格式array("名称"=>array("选择器","类型"),.......),【类型】说明:值 "text" ,"html" ,"属性" * *$regRange* 【块选择器】:指 先按照规则 选出 几个大块 ,然后再分别再在块里面 进行相关的选择 * *$getHtmlWay* 【源码获取方式】指是通过curl抓取源码,还是通过file\_get_contents抓取源码,当$page参数为URL时此参数才有效 * *$output_encoding*【输出编码格式】指要以什么编码输出(UTF-8,GB2312,.....),防止出现乱码,如果设置为 假值 则不改变原字符串编码 ###QueryList 属性 * **得到多维数组格式的采集结果** >***jsonArr*** ###QueryList 方法 * **重新设置选择器** >void ***setQuery***($regArr,$regRange='') 一共两个参数,第二个参数是可选的,参数意义同构造函数。 * **得到JSON格式的采集结果** > string ***getJSON***() 无参,返回JSON字符串。 ##QueryList 依赖库 ``` phpQuery ``` phpQuery项目主页:[https://code.google.com/p/phpquery/](https://code.google.com/p/phpquery/) ##其它说明 QueryList 内置的只是简单的源码抓取方法,遇到更复杂的抓取情况,如:需要登陆 身份验证 时,请配合其它的PHP的HTTP类来使用,通过将辅助的HTTP类抓取到的网页源码传给QueryList即可。 ##DEMO站 微动态:[http://querylist.jaekj.com/](http://querylist.jaekj.com/) * thinkphp版本:V3.1.2 * QueryList版本:V1.6 * 后台地址: /admin * 后台账号密码: guest guest 这个demo站实现的功能相当于一个轻量级的微博站,内容全自动采集更新,可以自定义时间间隔采集任意站点的信息,自动更新到这个站点来,只需要在后台规则库简单的添加一条规则就可以实现全自动采集了,大家可以自行进入后台进行尝试,体验QueryList的魅力! ##作者信息 ``` Author : Jaeger Email : hj.q@qq.com 交流QQ群:123266961 ``` \ No newline at end of file + #QueryList交流QQ群:123266961 ╰☆邪恶 魔方☆ #QueryList简介 *** QueryList是一个基于phpQuery的通用列表采集类,是一个简单、 灵活、强大的采集工具,采集任何复杂的页面 基本上就一句话就能搞定了。 #QueryList 使用 ```php //获取采集对象 $hj = QueryList::Query('http://www.baidu.com/s?wd=jaekj',array('title'=>array('h3','text'))); //输出结果:二维关联数组 print_r($hj->jsonArr); //输出结果:JSON数据 echo $hj->getJSON(); ``` 上面的代码实现的功能是采集百度搜索结果页面的所有搜索结果的标题,然后分别以数组和JSON格式输出。 ###QueryList 静态方法Query原型: >***Query***($page,$regArr,$regRange='',$getHtmlWay="curl",$output_encoding=false) 一共有五个参数,后面三个参数是可选的 * *$page* 要抓取的网页URL地址(默认支持https);或者是html源代码 * *$regArr* 【选择器数组】说明:格式array("名称"=>array("选择器","类型"[,"标签列表"]),.......),【类型】说明:值 "text" ,"html" ,"属性" ,【标签列表】:可选,当【类型】值为text时表示需要保留的HTML标签,为html时表示要过滤掉的HTML标签 * *$regRange* 【块选择器】:指 先按照规则 选出 几个大块 ,然后再分别再在块里面 进行相关的选择 * *$getHtmlWay* 【源码获取方式】指是通过curl抓取源码,还是通过file\_get_contents抓取源码,当$page参数为URL时此参数才有效 * *$output_encoding*【输出编码格式】指要以什么编码输出(UTF-8,GB2312,.....),防止出现乱码,如果设置为 假值 则不改变原字符串编码 ###QueryList 属性 * **得到多维数组格式的采集结果** >***jsonArr*** ###QueryList 方法 * **重新设置选择器** >void ***setQuery***($regArr,$regRange='') 一共两个参数,第二个参数是可选的,参数意义同构造函数。 * **得到JSON格式的采集结果** > string ***getJSON***() 无参,返回JSON字符串。 ##QueryList 依赖库 ``` phpQuery ``` phpQuery项目主页:[https://code.google.com/p/phpquery/](https://code.google.com/p/phpquery/) ##其它说明 QueryList 内置的只是简单的源码抓取方法,遇到更复杂的抓取情况,如:需要登陆 身份验证 时,请配合其它的PHP的HTTP类来使用,通过将辅助的HTTP类抓取到的网页源码传给QueryList即可。 ##DEMO站 微动态:[http://querylist.jaekj.com/](http://querylist.jaekj.com/) * thinkphp版本:V3.1.2 * QueryList版本:V1.6 * 后台地址: /admin * 后台账号密码: guest guest 这个demo站实现的功能相当于一个轻量级的微博站,内容全自动采集更新,可以自定义时间间隔采集任意站点的信息,自动更新到这个站点来,只需要在后台规则库简单的添加一条规则就可以实现全自动采集了,大家可以自行进入后台进行尝试,体验QueryList的魅力! ##作者信息 ``` Author : Jaeger Email : hj.q@qq.com 交流QQ群:123266961 ``` \ No newline at end of file diff --git a/demo/Searcher.class.php b/demo/Searcher.class.php index 442a102..4d52815 100644 --- a/demo/Searcher.class.php +++ b/demo/Searcher.class.php @@ -48,7 +48,7 @@ require '../QueryList.class.php'; $reg_znum='/([\d,]+) result(s)?/'; $getHtmlWay = 'curl'; } - $searcherObj = new QueryList($url,$this->regArr,$this->regRange,$getHtmlWay,false); + $searcherObj = QueryList::Query($url,$this->regArr,$this->regRange,$getHtmlWay,false); for($i=0;$ijsonArr);$i++) { if($this->searcher=='baidu') diff --git a/demo/demo.php b/demo/demo.php index 7b6a119..63eb10b 100644 --- a/demo/demo.php +++ b/demo/demo.php @@ -6,7 +6,7 @@ $url = "http://www.oschina.net/code/list"; $reg = array("title"=>array(".code_title a:eq(0)","text"),"url"=>array(".code_title a:eq(0)","href"),"author"=>array("img","title")); $rang = ".code_list li"; //使用curl抓取源码并以GB2312编码格式输出 -$hj = new QueryList($url,$reg,$rang,'curl','GB2312'); +$hj = QueryList::Query($url,$reg,$rang,'curl','GB2312'); $arr = $hj->jsonArr; echo "
";
 print_r($arr);
@@ -21,7 +21,7 @@ echo $json . "
"; //采OSC内容页内容 $url = "http://www.oschina.net/code/snippet_186288_23816"; $reg = array("title"=>array(".QTitle h1","text"),"con"=>array(".Content","html")); -$hj = new QueryList($url,$reg); +$hj = QueryList::Query($url,$reg); $arr = $hj->jsonArr; echo "
";
 print_r($arr);
diff --git a/demo/thanks.png b/demo/thanks.png
new file mode 100644
index 0000000000000000000000000000000000000000..b5c72a72746ec64a0ff730644f853994e38f63d3
GIT binary patch
literal 8815
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