如何通过PHP获取网页的Open Graph协议?

28

PHP有一个简单的命令可以获取网页的元标签(get_meta_tags),但这只适用于带有name属性的元标签。然而,现在越来越流行使用Open Graph Protocol。最简单的方法是如何从网页中获取opg的值。例如:

<meta property="og:url" content=""> 
<meta property="og:title" content=""> 
<meta property="og:description" content=""> 
<meta property="og:type" content=""> 

我看到的基本方法是通过cURL获取页面,并使用正则表达式解析它。有什么想法吗?

8个回答

50

非常简单易用:

使用https://github.com/scottmac/opengraph

$graph = OpenGraph::fetch('http://www.avessotv.com.br/bastidores-pantene-institute-experience-pg.html');
print_r($graph);

将返回

OpenGraph对象

(
    [_values:OpenGraph:private] => Array
        (
            [type] => article
            [video] => http://www.avessotv.com.br/player/flowplayer/flowplayer-3.2.7.swf?config=%7B%27clip%27%3A%7B%27url%27%3A%27http%3A%2F%2Fwww.avessotv.com.br%2Fmedia%2Fprogramas%2Fpantene.flv%27%7D%7D
            [image] => /wp-content/thumbnails/9025.jpg
            [site_name] => Programa Avesso - Bastidores
            [title] => Bastidores “Pantene Institute Experience†P&G
            [url] => http://www.avessotv.com.br/bastidores-pantene-institute-experience-pg.html
            [description] => Confira os bastidores do Pantene Institute Experience, da Procter &#038; Gamble. www.pantene.com.br Mais imagens:
        )

    [_position:OpenGraph:private] => 0
)

太棒了!!我已经找了好几个小时了,它直接开箱即用! - Rob
2
Github用户scottmac似乎已经放弃了他的OpenGraph项目,但是目前(2016年初)有一个更新版本,在这里可以找到修复:https://github.com/AramZS/opengraph - JoLoCo
我喜欢这个软件包,但它不能处理重复的标签,我的意思是它只会获取最后一个重复的标签。例如,Youtube正在复制标签(我不知道为什么):<meta property="og:video:url" content="https://www.youtube.com/embed/C28onNQMXNU">...<meta property="og:video:url" content="http://www.youtube.com/v/C28onNQMXNU?version=3&autohide=1">,而最后一个标签(也就是这个插件获取的标签)会下载一个文件。那真糟糕,Youtube! - Miguel Peniche
有人知道为什么这个程序无法从某些URL(例如https://www.ajio.com/ajio-micro-print-spread-collar-shirt-/p/460292463_blue)中获取og:site_name吗? - chithra

31

当解析HTML数据时,你真的不应该使用正则表达式。看看DOMXPath Query函数

现在,实际的代码可能是:

[编辑] Stefan Gehrig提供了更好的XPath查询,因此代码可以缩短为:

libxml_use_internal_errors(true); // Yeah if you are so worried about using @ with warnings
$doc = new DomDocument();
$doc->loadHTML($html);
$xpath = new DOMXPath($doc);
$query = '//*/meta[starts-with(@property, \'og:\')]';
$metas = $xpath->query($query);
$rmetas = array();
foreach ($metas as $meta) {
    $property = $meta->getAttribute('property');
    $content = $meta->getAttribute('content');
    $rmetas[$property] = $content;
}
var_dump($rmetas);

改为:

$doc = new DomDocument();
@$doc->loadHTML($html);
$xpath = new DOMXPath($doc);
$query = '//*/meta';
$metas = $xpath->query($query);
$rmetas = array();
foreach ($metas as $meta) {
    $property = $meta->getAttribute('property');
    $content = $meta->getAttribute('content');
    if(!empty($property) && preg_match('#^og:#', $property)) {
        $rmetas[$property] = $content;
    }
}
var_dump($rmetas);

3
伙计,我们生活在一个非虚构的世界中,在这个世界中,HTML 并不是到处都适用的。请在 http://www.imdb.com/title/tt0120737/ 上检查您的代码。 - zerkms
1
“@”不是解决方案。不要假装没有警告,而是编写不会产生警告的代码。 - zerkms
1
这是为了举例目的,但现在我猜应该没问题了吧? - Tom
1
是的,libxml_use_internal_errors -- 这是完美的解决方案,+1 点赞 ;-) - zerkms
依然工作了五年... 迄今为止最简单、最有效和直接的解决方案。下面使用opengraph类的答案如果需要转换为json仍然有点复杂,因为它返回对象。 - Someone Special
显示剩余2条评论

4
如何:
preg_match_all('~<\s*meta\s+property="(og:[^"]+)"\s+content="([^"]*)~i', $str, $matches);

所以,是的,用任何方式抓取页面并用正则表达式解析。

谢谢,但我希望找到除 preg_match 之外的其他方法 :) - Googlebot
@zerkms 这个方法很不好,不可靠,而且在解析HTML时比DomDocument效率低得多。 - Tom
@Thomas Cantonnet:低效??在http://www.imdb.com/title/tt0120737/上,`preg_replace`比你的解决方案快大约100倍,并且它**不会抛出任何警告**,哈哈? - zerkms
1
@zerkms,好的,尝试使用以下代码:<meta property="test" content="none" /> 它有效吗?不是。 <meta content="none" property="test" /> 它有效吗?不是。 - Tom
1
嘿,大家好,你们两个都有一定的道理。preg_match很快但不可靠。DOM可靠但速度慢且消耗资源。我个人更喜欢preg_match,但结构上的微小变化可能会毁掉你的所有工作。 - Googlebot
显示剩余6条评论

3
这个函数可以独立完成任务,无需依赖和解析DOM:
function getOgTags($html)
{
    $pattern='/<\s*meta\s+property="og:([^"]+)"\s+content="([^"]*)/i';
    if(preg_match_all($pattern, $html, $out))
        return array_combine($out[1], $out[2]);
    return array();
}

测试代码:

$x=' <title>php - Using domDocument, and parsing info, I would like to get the &#39;href&#39; contents of an &#39;a&#39; tag - Stack Overflow</title>
        <link rel="shortcut icon" href="https://cdn.sstatic.net/Sites/stackoverflow/img/favicon.ico?v=4f32ecc8f43d">
        <link rel="apple-touch-icon image_src" href="https://cdn.sstatic.net/Sites/stackoverflow/img/apple-touch-icon.png?v=c78bd457575a">
        <link rel="search" type="application/opensearchdescription+xml" title="Stack Overflow" href="/opensearch.xml">
        <meta name="referrer" content="origin" />


        <meta property="og:type" content="website"/>
        <meta property="og:url" content="https://stackoverflow.com/questions/5278418/using-domdocument-and-parsing-info-i-would-like-to-get-the-href-contents-of"/>
        <meta property="og:image" itemprop="image primaryImageOfPage" content="https://cdn.sstatic.net/Sites/stackoverflow/img/apple-touch-icon@2.png?v=73d79a89bded" />
        <meta name="twitter:card" content="summary"/>
        <meta name="twitter:domain" content="stackoverflow.com"/>
        <meta name="twitter:title" property="og:title" itemprop="title name" content="Using domDocument, and parsing info, I would like to get the &#39;href&#39; contents of an &#39;a&#39; tag" />
        <meta name="twitter:description" property="og:description" itemprop="description" content="Possible Duplicate:
  Regular expression for grabbing the href attribute of an A element  
This displays the what is between the a tag, but I would like a way to get the href contents as well.

Is..." />';
echo '<pre>';
var_dump(getOgTags($x));

然后你会得到:

array(3) {
  ["type"]=>
  string(7) "website"
  ["url"]=>
  string(119) "https://stackoverflow.com/questions/5278418/using-domdocument-and-parsing-info-i-would-like-to-get-the-href-contents-of"
  ["image"]=>
  string(85) "https://cdn.sstatic.net/Sites/stackoverflow/img/apple-touch-icon@2.png?v=73d79a89bded"
}

它无法获取标题或描述,因此并不是很有用。您假设属性始终位于相同的位置。 - Panama Jack

2
根据这种方法,您将获得Facebook开放图谱标签的键值对数组。
 $url="http://fbcpictures.in";
 $site_html=  file_get_contents($url);
    $matches=null;
    preg_match_all('~<\s*meta\s+property="(og:[^"]+)"\s+content="([^"]*)~i',     $site_html,$matches);
    $ogtags=array();
    for($i=0;$i<count($matches[1]);$i++)
    {
        $ogtags[$matches[1][$i]]=$matches[2][$i];
    }

Output of facebook open graph tags


1
这是我用来提取Og标签的代码。
function get_og_tags($get_url = "", $ret = 0)
{

    if ($get_url != "") {
        $title = "";
        $description = "";
        $keywords = "";
        $og_title = "";
        $og_image = "";
        $og_url = "";
        $og_description = "";
        $full_link = "";
        $image_urls = array();
        $og_video_name = "";
        $youtube_video_url="";

        $get_url = $get_url;

        $ret_data = file_get_contents_curl($get_url);
        //$html = file_get_contents($get_url);

        $html = $ret_data['curlData'];
        $full_link = $ret_data['full_link'];

        $full_link = addhttp($full_link);


        //parsing begins here:
        $doc = new DOMDocument();
        @$doc->loadHTML($html);
        $nodes = $doc->getElementsByTagName('title');
        if ($nodes->length == 0) {
            $title = $get_url;
        } else {
            $title = $nodes->item(0)->nodeValue;
        }
        //get and display what you need:
        $metas = $doc->getElementsByTagName('meta');
        for ($i = 0; $i < $metas->length; $i++) {
            $meta = $metas->item($i);
            if ($meta->getAttribute('name') == 'description')
                $description = $meta->getAttribute('content');
            if ($meta->getAttribute('name') == 'keywords')
                $keywords = $meta->getAttribute('content');
        }
        $og = $doc->getElementsByTagName('og');
        for ($i = 0; $i < $metas->length; $i++) {
            $meta = $metas->item($i);
            if ($meta->getAttribute('property') == 'og:title')
                $og_title = $meta->getAttribute('content');

            if ($meta->getAttribute('property') == 'og:url')
                $og_url = $meta->getAttribute('content');

            if ($meta->getAttribute('property') == 'og:image')
                $og_image = $meta->getAttribute('content');

            if ($meta->getAttribute('property') == 'og:description')
                $og_description = $meta->getAttribute('content');

            // for sociotube video share 
            if ($meta->getAttribute('property') == 'og:video_name')
                $og_video_name = $meta->getAttribute('content'); 

            // for sociotube youtube video share 
            if ($meta->getAttribute('property') == 'og:youtube_video_url')
                $youtube_video_url = $meta->getAttribute('content');    

        }

        //if no image found grab images from body
        if ($og_image != "") {
            $image_urls[] = $og_image;
        } else {
            $xpath = new DOMXPath($doc);
            $nodelist = $xpath->query("//img"); // find your image
            $imgCount = 0;

            for ($i = 0; $i < $nodelist->length; $i++) {
                $node = $nodelist->item($i); // gets the 1st image
                if (isset($node->attributes->getNamedItem('src')->nodeValue)) {
                    $src = $node->attributes->getNamedItem('src')->nodeValue;
                }
                if (isset($node->attributes->getNamedItem('src')->value)) {
                    $src = $node->attributes->getNamedItem('src')->value;
                }
                if (isset($src)) {
                    if (!preg_match('/blank.(.*)/i', $src) && filter_var($src, FILTER_VALIDATE_URL)) {
                        $image_urls[] = $src;
                        if ($imgCount == 10) break;
                        $imgCount++;
                    }
                }
            }
        }

        $page_title = ($og_title == "") ? $title : $og_title;
        if(!empty($og_video_name)){
            // for sociotube video share 
            $page_body = $og_video_name;
        }else{
            // for post share 
           $page_body = ($og_description == "") ? $description : $og_description; 
        }

        $output = array('title' => $page_title, 'images' => $image_urls, 'content' => $page_body, 'link' => $full_link,'video_name'=>$og_video_name,'youtube_video_url'=>$youtube_video_url);
        if ($ret == 1) {
            return $output; //output JSON data
        }
        echo json_encode($output); //output JSON data

        die;
    } else {
        $data = array('error' => "Url not found");
        if ($ret == 1) {
            return $data; //output JSON data
        }
        echo json_encode($data);
        die;
    }
}

使用函数的方法

$url = "https://www.alectronics.com";
$tagsArray = get_og_tags($url);
print_r($tagsArray);

0

更加“XML”化的方式是使用XPath:

$xml = simplexml_load_file('http://ogp.me/');
$xml->registerXPathNamespace('h', 'http://www.w3.org/1999/xhtml');
$result = array();
foreach ($xml->xpath('//h:meta[starts-with(@property, \'og:\')]') as $meta) {
    $result[(string)$meta['property']]  = (string)$meta['content'];
}
print_r($result);

如果HTML文档在<html>标签中使用命名空间声明,则需要进行命名空间注册。


尝试在 http://www.imdb.com/title/tt0120737/ 上运行你的代码;-) 收到了一长串警告。 - zerkms
2
不要在这里引发某种争论... 我实际上会选择 preg_match 解决方案,但我只是想展示一种不同且更优雅的方法 - 不幸的是,在现实世界中它确实存在一些问题(最常见的原因是使用 HTML 实体或未转义的字符,如 <>& 等)。 - Stefan Gehrig
然后,它与不可预测的结果相关联;因为在HTML网页中使用了广泛的命名空间。但我欣赏你的思维方式! - Googlebot
你说得对。这种方法更适用于控制环境,在该环境下你了解自己的文档。命名空间问题可以通过检查声明的命名空间来解决,因此在我看来,更大的问题是野外的大多数HTML文档远未达到标准兼容性。 - Stefan Gehrig

-1

1
正如问题所述,“这仅适用于具有名称属性的元标记”,而Open Graph元标记没有名称属性,因此对所需目的完全无用。 - WebSmithery

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接