PHP 挖掘 XML 和 HTML 数据

数据挖掘及其重要性

常用缩略词

  • API: 应用程序编程接口
  • CDATA: 字符数据
  • DOM: 文档对象模式
  • FTP: 文件传输协议
  • HTML: 超文本标记语言
  • HTTP: 超文本传输协议
  • REST: 具象状态传输
  • URL: 统一资源定位符
  • W3C: 万维网联盟
  • XML: 可扩展标记语言

Wikipedia 对 “数据挖掘” 的定义是 “数据挖掘是采用统计学方法和人工智能方法,结合数据库管理,从大型数据集提取模式的过程”。这是一个非常深入的定义,可能超出了大多数人的典型用例。很少有人使用人工智能;通常,数据挖掘只是搜索并汇集大型数据集,以查找有用的信息。

Internet 在飞速发展,并提供了海量信息,因此,能够收集大量数据并使数据变得有意义非常重要。收集个人无法读完的大型数据集并将它们提炼为有用的数据是一个很重要的目标。这种类型的数据挖掘是本文的焦点,本文将专门介绍如何收集和解析这种数据。

 

数据挖掘的实际应用

数据挖掘有很多实际应用。您可能假设这样一个场景:您想搜索一个网站,查找其中提供的信息(比如电影或音乐会的出席记录)。您可能需要检索更严肃的信息,比如投票者记录,并获得有用数据。或者,更常见的情况是,您可能需要检查社交网络数据,试图解析该数据,了解某种趋势,比如您的公司被提及的频率,以及这种提及是正面的还是负面的。

 

挖掘网站之前的注意事项

在继续介绍后面的内容之前,有一点需要说明:我们假定您将从另一个网站提取数据。如果您已经拥有要处理的数据,则又是完全不同的一种情况。当您从一个网站提取数据时,您需要确保遵守服务条款, 无论您是在做 Web 擦除(稍后将详细介绍)还是在使用一个 API。如果您在做擦除,还需要注意遵守该站点的 robots.txt 文件的规定,该文件描述了允许访问网站脚本的哪些部分。最后,确保不会妨碍站点的带宽。您编写的脚本不能按照脚本能够运行的速度随意访问站点的数据。否则,这不仅可能会导致托管问题,还有可能面临因为脚本太具 “侵略性” 而遭到被禁止或阻塞的风险。

 

理解 XML 数据结构

不管您采取何种方法提取数据,都有可能接收到 XML(或 HTML)格式的数据。当 XML 支持共享数据后,XML 就成为 Internet 的标准语言。在考虑提取数据的方法之前,重要的是要先研究 XML 结构和如何处理 XML 数据。

XML 文档的基本结构非常直观,特别是在您以前使用过 HTML 的情况下。XML 文档中的所有数据都用两种方法中的一种方法进行存储。存储数据的主要方法是将数据存储在嵌套标记中。下面举一个最简单的例子,假设您拥有一个地址,则该数据可以按如下方法存储在文档中:

<address>1234 Main Street, Baltimore, MD</address>

 

您可以嵌套这些 XML 数据点,以创建多个地址的列表。您可以将所有这些地址放到另一个标记中,在本例中这个标记为locations(请参见 清单 1)。


清单 1. XML 中的多个地址

				
<locations>
    <address>1234 Main Street, Baltimore, MD</address>
    <address>567 1st Street, San Jose, CA</address>
    <address>901 Washington Ave, Chicago, IL</address>
</locations>

 

要进一步扩展这种方法,可以将这些地址分解为各个组成部分:街道、城市和州,这使得数据处理变得更简单。这样,生成的 XML 文件就更典型,如 清单 2 所示。


清单 2. XML 中完全分解的地址

				
<locations>
    <address>
        <street>1234 Main Street</street>
        <city>Baltimore</city>
        <state>MD</state>
    </address>
    <address>
        <street>567 1st Street</street>
        <city>San Jose</city>
        <state>CA</state>
    </address>
    <address>
        <street>901 Washington Ave</street>
        <city>Chicago</city>
        <state>IL</state>
    </address>
</locations>

 

前面提到过,存储 XML 数据的方法有两种,我们刚才看到了其中一种。另一种方法是通过属性存储数据。可以为每个标记分配若干个属性。尽管这种方法不那么常用,但这可能是一种非常有用的工具。有时,它会提供额外的信息,比如一个唯一 ID 或一个事件日期。更常见的情况是添加元数据; 在您的地址示例中,可以使用一个 type 属性来指出该地址是家庭地址还是工作地址,如 清单 3 所示。


清单 3. 添加到 XML 的标记

				
<locations>
    <address type="home">
        <street>1234 Main Street</street>
        <city>Baltimore</city>
        <state>MD</state>
    </address>
    <address type="work">
        <street>567 1st Street</street>
        <city>San Jose</city>
        <state>CA</state>
    </address>
    <address type="work">
        <street>901 Washington Ave</street>
        <city>Chicago</city>
        <state>IL</state>
    </address>
</locations>

 

注意,XML 文档总是拥有一个父级根标记/节点,所有其他标记/节点都是该根标记/节点的子级标记/节点。XML 文档的开头部分还可以包含其他声明和定义,以及其他复杂内容(比如 CDATA 块)。强烈建议您参阅 参考资料 部分,以便进一步了解 XML。

 

解析 PHP 中的 XML 数据

您已经了解了 XML 的外观和结构,现在我们看看如何解析并以编程方式访问 PHP 中的 XML 数据。有几个针对 PHP 创建的库允许进行 XML 解析,每个库都有其优缺点。这些库包括 DOM、XMLReader/Writer、XML Parser、SimpleXML 等。对于本文的目的,我们主要关注的是 SimpleXML,这是因为它是一个最常用的库,同时也是我的最爱的库之一。

SimpleXML,顾名思义,旨在提供一个非常简单的接口来访问 XML。它可以将 XML 文档转换为内部 PHP 对象格式。访问数据点变得和访问对象变量一样简单。要使用 SimpleXML 解析一个 XML 文档,只需使用 simplexml_load_file() 函数(请参见 清单 4)。


清单 4. 使用 SimpleXML 解析文档

				
<?php
$xml = simplexml_load_file('listing4.xml');
?>

 

就这么简单!但是请注意,多亏了 PHP 的文件流集成,才可以在这里插入一个文件名或 URL,文件流集成会自动取回它。如果 XML 已经载入内存,还可以使用 simplexml_load_string()。如果您在 清单 3 中的 XML 上运行这个代码并使用 print_r() 来查看查看数据的大致结构,您将得到如 清单 5 所示的输出。


清单 5. 已解析的 XML 的输出

				
SimpleXMLElement Object 
(
     [address] => Array
         (
             [0] => SimpleXMLElement Object
                 (
                     [@attributes] => Array
                         (
                             [type] => home
                         )
                     [street] => 1234 Main Street
                     [city] => Baltimore
                     [state] => MD
                 )
              [1] => SimpleXMLElement Object
                 (
                     [@attributes] => Array
                         (
                             [type] => work
                         )
                     [street] => 567 1st Street
                     [city] => San Jose
                     [state] => CA
                 )
              [2] => SimpleXMLElement Object
                 (
                     [@attributes] => Array
                         (
                             [type] => work
                         ) 
                     [street] => 901 Washington Ave
                     [city] => Chicago
                     [state] => IL
                 )
          )
)

 

然后,您就可以使用标准的 PHP 对象访问和方法访问数据。例如,要回显某人居住过的每个州,可以通过迭代地址来实现(请参见清单 6)。


清单 6. 迭代地址

				
<?php
$xml = simplexml_load_file('listing4.xml');

foreach ($xml->address as $address) {
    echo $address->state, "<br \>\n";
}
?>

 

访问属性略有不同。与引用对象属性不同的是,您可以将属性作为数组值访问。您可以更改上面的代码样例来显示 type 属性,可以使用 清单 7 中所示代码实现上述操作。


清单 7. 添加属性

				
<?php
$xml = simplexml_load_file('listing4.xml');

foreach ($xml->address as $address) {
    echo $address->state, ': ', $address['type'], "<br \>\n";
}
?>

 

尽管上述所有示例都涉及迭代,但您也可以直接访问数据并使用您需要的特定信息片段,比如通过下面的代码提取第二个地址的街道地址:$xml->address[1]->street

现在,您应该有一些基本工具来开始处理 XML 数据。有关的详细信息,强烈建议您参阅 参考资料 中列出的 SimpleXML 文档和其他链接。

 

PHP 中的数据挖掘:可能的方法

如前所述,可以用多种方法访问数据,其中两种主要方法是 Web 擦除和 API 使用。

Web 擦除

Web 擦除(scraping)是以编程方式下载所有 Web 页面并从中提取数据的操作。有很多图书专门讨论了这个主题(请参阅 参考资料)。我只简单列举进行 Web 擦除所需的一些工具。首先,PHP 使得将一个 Web 页读入为一个字符串变得非常简单。有许多方法可以完成这个任务,包括通过某个 URL 使用 file_get_contents(),但在这里,您希望能以一种有意义的方式来解析 HTML。

因为 HTML 实质上是一种基于 XML 的语言,所以将 HTML 转换为一种 SimpleXML 结构很有用。但是不能只使用simplexml_load_file() 加载 HTML 页面,因为即使是有效的 HTML,它仍然不是 XML。一种好办法是使用 DOM 扩展,将 HTML 页面作为 DOM 文档载入,然后将其转换为 SimpleXML,如 清单 8 所示。


清单 8. 使用 DOM 方法来获取 Web 页面的 SimpleXML 版本

				
<?php
$dom = new DOMDocument();
$dom->loadHTMLFile('http://example.com/');
$xml = simplexml_import_dom($dom);
?>

 

现在,您可以像对其他 XML 文档一样遍历 HTML 页面。因此,您现在可以使用 $xml->head->title 来访问页面标题,或者使用 $xml->body->div[0]->div[0]->div[0]->h4[0] 这样的引用来深入页面。

但是,正如您从上一个示例中可以预料到的那样,试图从一个 HTML 页面中查找数据有时可能非常不便,这是因为 HTML 页面通常不像 XML 文件那样有良好的结构。上面那行代码查找三个嵌套的 div 中的第一个 h4;在每种情况下,它都会查找每个父 div 中的第一个 div。

幸运的是,如果您只想查找页面上的第一个 h4,或者其他 “直接数据”,那么 XPath 是一种更简单的实现方法。XPath 是一个非常强大的工具,可以将它作为整个文章系列的主题(请参阅 参考资料 中列示的部分文章)。简言之,可以使用 '/' 来描述层级关系;因此,可以将前面的引用重写为下面的 XPath 搜索(请参见 清单 9)。


清单 9. 直接使用 XPath

				
<?php
$h4 = $xml->xpath('/html/body/div/div/div/h4');
?>

 

或者,您可以只使用 '//' 选项和 XPath,这将搜索所有文档,查找您要查找的标记。因此,您可以找到所有 h4,生成一个数组,然后使用下面的 XPath 访问第一个 h4:
'//h4'

遍历 HTML 分层结构

讨论上述转换和 XPath 的主要原因是,进行 Web 擦除的常见必要任务之一就是自动查找 Web 页面上的其他链接并追索它们,这允许您 “遍历” 网站,找到尽量多的信息。

如果使用 XPath,这个任务将非常麻烦。清单 10 提供了一个由所有带有 “href” 属性的 <a> 链接组成的数组,并允许您处理它们。


清单 10. 结合使用多种技术查找页面上的所有链接

				
<?php
$dom = new DOMDocument();
$dom->loadHTMLFile('http://example.com/');
$xml = simplexml_import_dom($dom);
$links = $xml->xpath('//a[@href]');
foreach ($links as $l) {
    echo $l['href'], "<br />\n";
}
?>

 

现在,上面的代码找到了所有 <a href=""> 链接,但如果您打开您发现的每个可能链接,您就开始快速 “爬行” 整个 Web。因此,最好增强您的代码,确保只访问两类链接:一类是有效的 HTML 链接(不是 FTP 或 JavaScript);一类是只返回(通过完整或相对域链接)同一个网站的链接。

一种更简单的方法是使用 PHP 的内置 parse_url() 函数在这些链接上进行迭代,该函数为您处理大量资格审查,如 清单 11 所示。


清单 11. 一个更健壮的站点遍历程序

				
<?php
$dom = new DOMDocument();
$host = 'example.com';
$dom->loadHTMLFile("http://{$host}/");
$xml = simplexml_import_dom($dom);
$links = $xml->xpath('//a[@href]');
foreach ($links as $l) {
    $p = parse_url($l['href']);
    if (empty($p['scheme']) || in_array($p['scheme'], array('http', 'https'))) {
        if (empty($p['host']) || ($host == $p['host'])) {
            echo $l['href'], "<br />\n"; // Handle URL iteration here
        }
    }
}
?>

 

关于 HTML 解析的最后一点说明是,对于所有 XML 类语言的一个统一接口,您复习了如何只使用 DOM 扩展将 HTML 转换回 SimpleXML。注意,DOM 库本身非常强大,可以直接使用。如果您非常熟悉 JavaScript 和使用 getElementsByTagName 之类的工具来遍历 DOM 文档树,那么您完全可以只使用 DOM 库,不使用 SimpleXML。

您目前应该已经拥有了从 Web 页面上擦除数据所需的工具。一旦您熟悉了本文前面详细介绍的技术,就可以从 Web 页面上读取任何信息,而不仅限于可以关注的链接。我们希望您不必执行这个任务,因为已经存在 API 或其他数据源。

使用 XML API 和数据

此时,您已经掌握了一些基本技能,能够访问和使用 Internet 上的主要 XML 数据 API。它们通常基于 REST,因此只需一个简单的 HTTP 访问就可以检索数据,并使用前面介绍的结束来解析数据。

每个 API 在这方面的表现都是不同的。我们无法一一介绍如何访问每个 API,因此,我们只简要介绍一些基本的 XML API 示例。一个最常见且已经采用 XML 格式的数据源就是 RSS feed。RSS 代表 Really Simple Syndication,通常是一种标准化的格式,用于共享频繁更新的数据,比如博客帖子、新闻标题或播客。要详细了解 RSS 格式,请参见 参考资料。注意,RSS 是一个 XML 文件,有一个名为 <channel> 父标记,其中有若干 <item> 标记,每个标记都提供一组数据点。

下面举例说明。假设我们使用 SimpleXML 来读取纽约时报标题的 RSS feed(参见 参考资料 获取到该 RSS feed 的链接)并格式化一列标题,那些标题都将链接到相应的正文(请参见 清单 12)。


清单 12. 读取纽约时报的 RSS feed

				
<ul>
<?php
$xml = simplexml_load_file('http://www.washingtonpost.com/rss/homepage');
foreach ($xml->channel->item as $item) {
    echo "<li><a href=\"{$item->link}\">{$item->title}
</a></li>";
}
?>
</ul>

 

图 1 显示了来自纽约时报摘要的输出。


来自纽约时报摘要的输出
来自纽约时报摘要的输出的屏幕截图,列示 13 个标题 

现在,我们来探索一个功能更全的基于 REST 的 API。Flickr API 是一个好的起点,因为它提供大量数据,但不需要身份验证。许多 API 都需要身份验证(使用 Oauth 或其他机制),以代表一个 Web 用户进行操作。这个步骤可应用于整个 API,或者只应用于部分 API。参阅每个 API 文档,了解如何进行身份验证。

要了解如何对一个未经身份验证的请求使用 Flickr API,您可以使用它的搜索 API。作为一个示例,我们搜索 Flickr 的所有公共十字弓照片。尽管不必进行身份验证,但与许多 API 一样,仍然需要生成一个 API 密匙,以便在访问数据时使用。请直接参阅 Flickr 的 API 文档,了解如何完成这个任务。拥有一个 API 密匙后,就可以使用 Flickr API 的搜索功能进行探索,如 清单 13 所示。


清单 13. 使用 Flickr API 搜索 “crossbow”

				
<?php
// Store some basic information that you need to reference
$apiurl = 'http://api.flickr.com/services/rest/?';
$key = '9f275087e222ee395c92662437bf84a2'; // Replace with your own key

// Build an array of parameters that you want to request:
$params = array(
    'method' => 'flickr.photos.search',
    'api_key' => $key,
    'text' => 'crossbow', // Our search term
    'media' => 'photos',
    'per_page' => 20 // We only want 20 results
);

// Now make the request to Flickr:
$xml = simplexml_load_file($apiurl . http_build_query($params));

// From this, iterate over the list of photos & request more info:
foreach ($xml->photos->photo as $photo) {
    // Build a new request with this photo's ID
    $params = array(
        'method' => 'flickr.photos.getInfo',
        'api_key' => $key,
        'photo_id' => (string)$photo['id']
    );
    $info = simplexml_load_file($apiurl . http_build_query($params));
    
    // Now $info holds a vast amount of data about the image including
    //  owner, GPS, dates, description, tags, etc ... all to be used.

    // Let's also request "sizes" to get all of the image URLs:
    $params = array(
        'method' => 'flickr.photos.getSizes',
        'api_key' => $key,
        'photo_id' => (string)$photo['id']
    );
    $sizes = simplexml_load_file($apiurl . http_build_query($params));
    $small = $sizes->xpath("//size[@label='Small']");
    
    // For now, just going to create a simple display of the image,
    //  linked back to Flickr, with title, GPS info, and more shown:
    echo <<<EOHTML
<div>
  <a href="{$info->photo->urls->url[0]}">
    <img src="{$small[0]['source']}"
         width="{$small[0]['width']}" 
         height="{$small[0]['height']}" />
  </a>
  <ul>
    <li>Title: {$info->photo->title}</li>
    <li>User: {$info->photo->owner['realname']}</li>
    <li>Date Taken: {$info->photo->dates['taken']}</li>
    <li>Location: {$info->photo->location->locality}, 
        {$info->photo->location->county},
        {$info->photo->location->region}, 
        {$info->photo->location->country}
    </li>
  </ul>
</div>
EOHTML;
}
?>

 

图 2 显示了这个 Flickr 程序的输出。您的十字弓搜索结果包括一些照片和每张照片的相关信息(标题、用户、位置和摄影日期)。


图 2. 来自清单 13 的 Flickr 程序的示例输出
来自清单 13 的 Flickr 程序的示例输出的屏幕截图 

您已经看到,这样的 API 是多么强大,以及如何在同一个 API 中结合各种调用来获取所需的数据。通过这些基本技术,您可以挖掘任何网站和信息源的数据。

考虑一下,如何通过一个 API 或 Web 擦除获取对数据的编程访问。然后使用所展示的方法来访问并迭代所有目标数据。

 

存储和报告提取的数据

最后,存储和报告数据在很多方面都是最容易的部分,可能也是最有意思的部分。当您决定如何根据您的实际情况处理这个方面时,您可以任意发挥您的想象力。

通常,获得您收集的所有数据并将它们存储到某个数据库中。然后,设计您的数据结构,使其与您计划将来访问数据的方式相符。此时,尽量多存储一些信息,将来有可能会用到它们。尽管总是可以删除数据,但一旦数据太多,检索额外的信息可能是一个痛苦的过程。但最好还是在刚开始就多存储一些数据,毕竟,您永远也不会知道哪些数据可能会派上用场。

将数据存储到数据库或类似的数据存储器中之后,就可以创建报告了。创建报告可能很简单,只需针对数据库运行几个基本的 SQL 查询,查看某个数据出现的次数即可;或者,报告创建也可能是非常复杂的 Web 用户接口,旨在允许用户深入数据并发现数据之间的关系。

当您辛辛苦苦将所有数据分门别类后,便可以想象一些显示数据的创新方法。

 

结束语

通过本文,您了解了 XML 文档的基本结构,以及使用 SimpleXML 来解析 PHP 中的 XML 数据的一种简单方法。您还增加了以类似方式处理 HTML 的能力,并初步了解了如何遍历一个网站,获取没有以 XML 格式提供的数据。通过这些工具,结合本文提供的一些示例,现在您已经拥有了良好的知识基础,可以开始对某个网站进行数据挖掘了。关于这个主题还有许多知识需要学习,无法在一篇文章中对它们进行一一介绍。关于增加您的数据挖掘知识的其他方法,请参阅 参考资料

 

 

下载

描述名字大小下载方法
源代码 datamining_source.zip 10KB HTTP

关于下载方法的信息

posted @ 2013-03-17 22:15  方倍工作室  阅读(3429)  评论(0编辑  收藏  举报