运用the Simple HTML DOM Library实现HTML解析及内容抓取
如果你需要解析HTML,正则表达式不是一个太好的办法。在本教程中,将会向你讲述如何用一个开源的,简单易学的解析器从外部代码中来读取、修改及提取HTML。通过一个实例,你将学会如何获取并显示某个页面中已发布文章的列表。
第一步 准备工作
首先从sourceforge上下载 the simpleHTMLdom library。在已下载的文件中只有一个simple_html_dom.php 文件是我们所需要的,其余的都是例子跟文档。
第二步 解析的基础知识
the simpleHTMLdom library用起来比较容易,但是在进入实际操作前,先回顾一下基本知识。
载入HTML
$html = new simple_html_dom();
// Load from a string
$html->load('<html><body><p>Hello World!</p><p>We're here</p></body></html>');
// Load a file
$html->load_file('http://net.tutsplus.com/');
你可以通过一个字符串或者一个文件加载HTML来初始化一个对象。 加载文件可以通过URL或者本机路径来实现。
注意:load_file()方法把他的工作委派给了PHP的file_get_contents方法。如果在你的php.ini文件中没有将allow_url_fopen设置为true,将不能够打开一个远程的文件。这种情况下,需要调用CURL库来加载远程页面,并用load()方法读取。
访问信息
一旦建立了DOM对象,你就可以用find()方法开始工作并建立集合。一个集合是通过选择器得到的一组对象。语法与JQuery类似。
<html> <body> <p>Hello World!</p> <p>We're Here.</p> </body> </html>
在本例中,我们将看到如何访问第二个段落的信息,修改它并输出结果。
# create and load the HTML
include('simple_html_dom.php');
$html = new simple_html_dom();
$html->load("<html><body><p>Hello World!</p><p>We're here</p></body></html>");
# get an element representing the second paragraph
$element = $html->find("p");
# modify it
$element[1]->innertext .= " and we're here to stay.";
# output it!
echo $html->save();
第2-4行:加载HTML
第7行:查找HTML中所有的标签,并且将他们作为一个数组返回。第一个段落的索引是0,随后的段落索引依次递增。
第10行:进入我们的段落集合的第二项(索引是1),并且在它的innertext属性中添加一些内容。Innertext呈现的是标签之间的内容(不含有标签),而outertext呈现的是包括标签的内容(含有标签)。
我们可以通过outertext将标签彻底地替换。
$element[1]->class = "class_name"; echo $html->save();
保存后的结果是:
<html> <body> <p>Hello World!</p> <p>We're here and we're here to stay.</p> </body> </html>
其他的选择器
下面是一些其他的选择器。如果你曾经用过jQuery,看起来会觉得很熟悉。
# get the first occurrence of id="foo"
$single = $html->find('#foo', 0);
# get all elements with
$collection = $html->find('.foo');
# get all the anchor tags on a page
$collection = $html->find('a');
# get all anchor tags that are inside H1 tags
$collection = $html->find('h1 a');
# get all img tags with a title of 'himom'
$collection = $html->find('img[title=himom]');
第一个例子不是太直观——所有的查询即使是应该只返回一个单独结果的的ID查询,都默认地返回一个集合。但是,当指定第二个参数后,我们就可以说:只返回集合的第一项。
这意味着$single是一个单独的元素,而不是只有一个项目的元素数组。
文档
全部文档资料可以在项目文档页面中找到。
第三步 真实的例子
为了达到实践的目的,我们要写一个抓取Nettuts网站的脚本并且将文章的标题及摘要生成一个列表。这只是一个例子。如果没有得到授权,请不要随意抓取别人网站上的内容。
include('simple_html_dom.php');
$articles = array();
getArticles('http://net.tutsplus.com/page/76/');
首先引入库文件,然后调用getArticles方法,参数是要进行解析的页面。
定义一个全局数组来存放所有文章的信息。在开始解析之前,我们来看看Nettuts+中是如何描述文章摘要的。
<div> <!-- Post Taxonomies --> <div> ... </div> <!-- Post Title --> <h1><a>Title</a></h1> <!-- Post Meta --> <div> ... </div> <div><p>Description</p></div> </div>
以上代码描述的是页面上一篇文章的基本格式,包括对代码的注释。为什么注释很重要?因为解析器把它当作节点。
第4步 开始解析
首先声明一个全局变量并建立一个新的simple_html_dom对象,然后加载要解析的页面。这个函数会在稍后的代码中调用自己,所以我们设置了一个参数用于接收URL。
function getArticles($page) {
global $articles;
$html = new simple_html_dom();
$html->load_file($page);
// ... more ...
}
第五步 查找我们想要的信息
$items = $html->find('div[class=preview]');
foreach($items as $post) {
# remember comments count as nodes
$articles[] = array($post->children(3)->outertext,
$post->children(6)->first_child()->outertext);
}
我们来详细了解一下:
第1行:通过class创建一个div元素的数组。$items存放的是文章的集合。
第5行:$post引用了一个单独的div.如果我们去看原始的HTML代码,会发现第三个节点是H1元素包含的文章标题。我们获取到它并把它放到$articles[index][0]中。
记住:节点的索引都是从零开始的。
第6行:$post的第六个节点是<div class=”text”>。我们想要得到里面的文本,因此我们抓取第一个子节点的outertext——包含段落标签。文章中的一条单独的记录如下所示:
$articles[0][0] = "My Article Name Here"; $articles[0][1] = "This is my article description"
第六步 分页
首先我们要做的是如何查找下一页。在Nettuts+中,很容易找出URL,但是我们要假设不容易查找出,然后通过解析找出下一页的链接。
如果我们看看HTML代码,会看到如下所示:
<a href="http://net.tutsplus.com/page/2/">»</a>
如果有下一页(当然存在只有一页的情况),我们要找到class="nextpostslink"的锚。现在信息可以利用了。
if($next = $html->find('a[class=nextpostslink]', 0)) {
$URL = $next->href;
$html->clear();
unset($html);
getArticles($URL);
}
第一行代码是为了查找是否有class="nextpostslink"的锚。需要特别主意的是find()方法的第二个参数。它表示我们只想从被查找到的并返回的集合中获得第一个元素(index=0)。$next中保存的是一个单独的元素,而不是一个元素的集合。
接下来,我们把链接的href属性值放到变量$URL中。这一步很重要因为我们即将销毁HTML对象。由于php5的一个循环引用内存泄露问题,在生成一个新的simple_html_dom对象之前,必须要将simple_html_dom object 对象清空。否则,会吃掉所有可用的内存。
最后,我们调用getArticles方法,并传一个下一页链接的参数。当没有更多页面需要解析的时候递归就结束了。
第七步 输出结果
首先,设置一些基本的样式。只要符合你的期望,你可以随意设置样式。
main {
margin:80px auto;
width:500px;
}
h1 {
font:bold 40px/38px helvetica, verdana, sans-serif;
margin:0;
}
h1 a {
color:600;
text-decoration:none;
}
p {
background: ECECEC;
font:10px/14px verdana, sans-serif;
margin:8px 0 15px;
border: 1px CCC solid;
padding: 15px;
}
.item {
padding:10px;
}
接下来,我们用一小段Php代码来输出先前存储的信息。
<?php
foreach($articles as $item) {
echo "<div class='item'>";
echo $item[0];
echo $item[1];
echo "</div>";
}
?>
最后,得到的结果是一个单独的显示所有文章的HTML页面。
第八步 小结
如果你想要解析大量的页面(例如:整个站点),花费的时间也许会超出你的服务器所允许运行的时间限制。举个例子,从我本机上每抓取一个页面大约需要花费1秒钟。
在Nettuts这个站点上,有78篇教程,抓取所用的时间已经超出1分钟了。
有好多中方法来进行HTML的解析,从易学易用的角度上说,我觉得这个库是最好中的一个。最后,始终要记住在抓取某个站点之前要获得人家的许可,这是很重要的。感谢阅读。
