之前做网页采集一般使用正则匹配和字符串查找替换,很是麻烦,后来自从有了phpQuery类之后,采集方式变的和jquery一样简便。可并不是所有的html页面都能被phpQuery正确解析,有时候会出现解析失败的情况,例如很明显的一个ID标签对象,phpQuery却获取为空,在http://code.google.com/p/phpquery/上找到项目之后,发现没有更新的版本了,于是决定采用另一个html解析利器simple_html_dom作为候补,项目下载地址:http://sourceforge.net/projects/simplehtmldom/files/。
simple_html_dom主打find方法,如果find只有一个参数其查找到的元素一般以对象数组的方式进行遍历;如果添加第二个参数表示索引则返回一个元素,如下:
simple_html_dom的find方法参数一与jquery的模式基本一致,可以使用条件搜索,返回的变量e是一个对象,具有以下几类属性:
$e->tag 与原生js的tagName对应,jquery的$(e).attr('nodeName')对应
$e->outertext 与原生js的outerHTML对应,jquery的$(e).attr('outerHTML')对应
$e->innertext 与原生js的innerHTML对应,jquery的$(e).attr('innerHTML')或$(e).html()对应
$e->plaintext 与原生js的innerText对应,jquery的$(e).attr('innerText')或$(e).text()对应
常用的方法如下:
mixed $e->children(index) index为索引序号,返回第n个子节点对象
element $e->parent() 返回父节点对象
element $e->first_child() 返回第一个子节点对象
element $e->last_child() 返回最后一个子节点对象
element $e->next_sibling() 返回下一个邻节点对象
element $e->prev_sibling() 返回上一个邻节点对象
功能虽然发现的不多,但对应一般的html解析已经足够。如果需要解析大量html建议使用$html->clear()释放内存。
phpQuery的功能比simple_html_dom强大,使用方式上更接近于jquery,查看其源码可以知道其所支持的属性和方法。
phpQuery存在单文件版本和多文件版本,一般使用单文件足以。使用方法如下:
可以看出,phpQuery的pq相当于jquery的$符号,使用方式基本一致,查阅jquery手册即可。不过gbk网页需要注意编码问题,因为其解析为utf8编码,网上说可以使用phpQuery::$defaultCharset="GBK"进行申明,但不一定成功,建议在utf8环境下操作,解析获取到想要的内容之后再转码为所需的编码。
同样,如果需要解析大量html页面,建议使用phpQuery::$documents = array();方法释放内存。
simple_html_dom的常用操作:
include 'simple_html_dom.php';
//面向过程方式2种读取方式
$html = file_get_html('http://www.baidu.com');
$html = str_get_html(file_get_contents('http://www.baidu.com'));
//面向对象方式2种读取方式
$html = new simple_html_dom();
$html->load_file('http://www.baidu.com');
$html->load(file_get_contents('http://www.baidu.com'));
foreach($html->find('img') as $img)
{
print_r($img->src)."<br>";
}
simple_html_dom主打find方法,如果find只有一个参数其查找到的元素一般以对象数组的方式进行遍历;如果添加第二个参数表示索引则返回一个元素,如下:
echo "<pre>";
print_r($html->find('img', 0)->src);
echo "</pre>";
simple_html_dom的find方法参数一与jquery的模式基本一致,可以使用条件搜索,返回的变量e是一个对象,具有以下几类属性:
$e->tag 与原生js的tagName对应,jquery的$(e).attr('nodeName')对应
$e->outertext 与原生js的outerHTML对应,jquery的$(e).attr('outerHTML')对应
$e->innertext 与原生js的innerHTML对应,jquery的$(e).attr('innerHTML')或$(e).html()对应
$e->plaintext 与原生js的innerText对应,jquery的$(e).attr('innerText')或$(e).text()对应
常用的方法如下:
mixed $e->children(index) index为索引序号,返回第n个子节点对象
element $e->parent() 返回父节点对象
element $e->first_child() 返回第一个子节点对象
element $e->last_child() 返回最后一个子节点对象
element $e->next_sibling() 返回下一个邻节点对象
element $e->prev_sibling() 返回上一个邻节点对象
功能虽然发现的不多,但对应一般的html解析已经足够。如果需要解析大量html建议使用$html->clear()释放内存。
phpQuery的功能比simple_html_dom强大,使用方式上更接近于jquery,查看其源码可以知道其所支持的属性和方法。
phpQuery存在单文件版本和多文件版本,一般使用单文件足以。使用方法如下:
include 'phpQuery-onefile.php';
//面向对象方式2种读取方式
phpQuery::newDocumentFile('http://www.baidu.com');
$contents = get_html_contents($main_url);
phpQuery::newDocument($contents);
foreach(pq('img') as $img)
{
echo pq($img)->attr('src')."<br>";
}
可以看出,phpQuery的pq相当于jquery的$符号,使用方式基本一致,查阅jquery手册即可。不过gbk网页需要注意编码问题,因为其解析为utf8编码,网上说可以使用phpQuery::$defaultCharset="GBK"进行申明,但不一定成功,建议在utf8环境下操作,解析获取到想要的内容之后再转码为所需的编码。
同样,如果需要解析大量html页面,建议使用phpQuery::$documents = array();方法释放内存。
===========================================================================
虽然上面2种html解析方式已经能够满足node节点的操作,但是细化到字符串的操作,如js变量获取,只能获取到script标签的内容而无法获取变量内容,于是我写了下面这个字符串截取函数对获取的html元素进行截取:
/**
* 字符串边界截取函数
* @param string $haystack 待截取的字符串
* @param string $left 待查找的左侧字符串
* @param string $right 待查找的右侧字符串
* @return string 截取之后的字符串,不带$left和$right
* @author shuiguang
*/
function subByString($haystack, $left = '', $right = '')
{
$left_pos = false;
$right_pos = false;
if($left == '' || ($left_pos = strpos($haystack, $left)) === false)
{
$start_pos = 0;
}else{
$start_pos = $left_pos;
}
$right_data = substr($haystack, $start_pos + ($left_pos === false ? 0 : strlen($left)));
if($right == '' || ($right_pos = strpos($right_data, $right)) === false)
{
return substr($haystack, $left_pos+strlen($left));
}else{
$end_pos = $start_pos + $right_pos;
return substr($haystack, ($left_pos === false ? 0 : $start_pos+strlen($left)), $end_pos-$start_pos);
}
}
经过测试,发现上面的方法有bug,例如,查找的元素为'abcde',左侧元素为'f',右侧元素为'g',截取后的为bcde,少了一个字符,修复如下:
/**
* 用于字符串截取(从左往右读取),用于正向截取2个字符串之间的内容,不含边界
* @author Z
* @param String haystack 待截取的字符串
* @param String left 待查找的左侧字符串
* @param String right 待查找的右侧字符串
* @return String 截取之后的字符串,不带left和right
*/
function subByString($haystack, $left = '', $right = '') {
$left_pos = false;
$right_pos = false;
if($left == '' || ($left_pos = strpos($haystack, $left)) === false) {
$start_pos = 0;
}else{
$start_pos = $left_pos;
}
$right_data = substr($haystack, $start_pos + ($left_pos === false ? 0 : strlen($left)));
if($right == '' || ($right_pos = strpos($right_data, $right)) === false) {
return substr($haystack, $left_pos === false ? 0 : $left_pos+strlen($left));
}else{
return substr($haystack, $left_pos === false ? 0 : $left_pos+strlen($left), $right_pos);
}
}
/**
* 用于字符串逆向截取(从右往左读取),用于截取2个字符串之间的内容,不含边界
* @author Z
* @param String haystack 待截取的字符串
* @param String left 待查找的左侧字符串
* @param String right 待查找的右侧字符串
* @return String 截取之后的字符串,不带left和right
*/
function getMiddleReverse($haystack, $left = '', $right = '') {
$revhtml = strrev($haystack);
$revsuffix = strrev($right);
$revpreffix = strrev($left);
$middle = subByString($revhtml, $revsuffix, $revpreffix);
$result = strrev($middle);
return $result;
}
后来,在javascript中得到了相应的应用,需要注意strpos和indexOf的查找失败值不同,写法如下:
/**
* 用于字符串截取(从左往右读取),用于正向截取2个字符串之间的内容,不含边界
* author Z
* @param String haystack 待截取的字符串
* @param String left 待查找的左侧字符串
* @param String right 待查找的右侧字符串
* @return String 截取之后的字符串,不带left和right
*/
function subByString(haystack, left, right) {
var left_pos = 0;
var right_pos = 0;
var start_pos, leftIndex, rightIndex;
if(left == "" || (left_pos = haystack.indexOf(left)) == -1) {
start_pos = 0;
}else{
start_pos = left_pos;
}
leftIndex = start_pos + (left_pos == -1 ? 0 : left.length);
right_data = haystack.substring(leftIndex, haystack.length);
if(right == "" || (right_pos = right_data.indexOf(right)) == -1) {
rightIndex = left_pos == -1 ? 0 : left_pos+left.length;
return haystack.substring(rightIndex, haystack.length);
}else{
leftIndex = left_pos == -1 ? 0 : left_pos+left.length;
return haystack.substring(leftIndex, leftIndex+right_pos);
}
}
java语言版和javascript版几乎一致:
/**
* 用于字符串截取(从左往右读取),用于正向截取2个字符串之间的内容,不含边界
* @author Z
* @param String haystack 待截取的字符串
* @param String left 待查找的左侧字符串
* @param String right 待查找的右侧字符串
* @return String 截取之后的字符串,不带left和right
*/
public static String subByString(String haystack, String left, String right) {
int left_pos = 0;
int right_pos = 0;
int start_pos, leftIndex, rightIndex;
if (left.equals("") || (left_pos = haystack.indexOf(left)) == -1) {
start_pos = 0;
} else {
start_pos = left_pos;
}
leftIndex = start_pos + (left_pos == -1 ? 0 : left.length());
String right_data = haystack.substring(leftIndex, haystack.length());
if (right.equals("") || (right_pos = right_data.indexOf(right)) == -1) {
rightIndex = left_pos == -1 ? 0 : left_pos + left.length();
return haystack.substring(rightIndex, haystack.length());
} else {
leftIndex = left_pos == -1 ? 0 : left_pos + left.length();
return haystack.substring(leftIndex, leftIndex + right_pos);
}
}
/**
* 用于字符串逆向截取(从右往左读取),用于截取2个字符串之间的内容,不含边界
* author Z
* @param String haystack 待截取的字符串
* @param String left 待查找的左侧字符串
* @param String right 待查找的右侧字符串
* @return String 截取之后的字符串,不带left和right
*/
public static String getMiddleReverse(String haystack, String left, String right) {
StringBuffer sb = new StringBuffer(haystack);
StringBuffer lb = new StringBuffer(left);
StringBuffer rb = new StringBuffer(right);
StringBuffer revhtml = sb.reverse();
StringBuffer revsuffix = rb.reverse();
StringBuffer revpreffix = lb.reverse();
String middle = subByString(revhtml.toString(), revsuffix.toString(), revpreffix.toString());
StringBuffer result = new StringBuffer(middle).reverse();
return result.toString();
}
上面的2个php类和1个函数文件的下载地址:
simple_html_dom:http://download.csdn.net/detail/zhengshuiguang/8124219
phpQuery:http://download.csdn.net/detail/zhengshuiguang/8124243