[关闭]
@Emptyset 2015-07-29T19:33:59.000000Z 字数 5589 阅读 1923

Baidusubmit——百度WordPress结构化数据插件有毒~(附插件代码分析与修正办法)

WordPress二次开发 SEO


百度官方的WordPress插件Baidusubmit存在一个bug,会导致生成递交的sitemap里出现大量404链接。对于新站来说大量递交的404显然是对SEO有毁灭性的打击的(会导致被降权k站之类……),笔者自己就遭遇了这样的悲剧。

本文仔(luo)细(suo)地叙述了事情的经过以及推理的过程,最终给出了插件代码改进建议。

故事的开始都是美好的

前两天帮朋友用WordPress搭了个企业网站(http://www.wxjinri.com/),三天时间百度收录首页,曾经输入企业名搜索结果可以正常出现在第一页。
站长工具的关键词记录

然后悲剧就开始了……

百度站长平台抓取频次
抓取频次直线下降,27号直接归零了……感觉就是被百度K了的节奏啊~

可是到底是哪里出了问题呢?百度站长平台能发现在22号和24号的时候都出现了大量的(虽然只有十几个,但是新站页面本来就不多)404抓取异常,即页面不存在,但是由于当时忘了开服务器访问日志,百度站长平台也没有提供抓取异常的链接具体位置。
抓取异常
其次,在某天凌晨我晕乎乎的关闭了SEO by Yoast的插件,导致description变空,第二天起床就发现百度搜不到排名了,不知道这是不是也是原因之一。

Baidusitemap……

还有一件诡异的事情就是Baidusubmit插件每天都有几十到好几百的递交量。
终于今天开启了BAE日志后发现了类似如下的记录——

  1. 123.125.71.49 www.wxjinri.com [2015-07-29 21:21:40] 200 1146 69648 2243 "GET /wp-content/plugins/baidusubmit/sitemap.php?m=sitemapall&start=1&p=c60af1349ced96b2 HTTP/1.1" "" "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"

也就是百度蜘蛛抓取baidusubmit插件生成的sitemap的记录,于是我加上域名访问了一下该链接(http://www.wxjinri.com/wp-content/plugins/baidusubmit/sitemap.php?m=sitemapall&start=1&p=c60af1349ced96b2),看看它到底生成了哪些sitemap……

点开以后我惊奇地发现里面有大量奇怪的链接,类似domain/2015/06/16/postname这样,其中postname是一些menu的名字,例如会有domain/2015/06/16/首页,这样明显是404的页面链接。
那么问题来了,这些链接到底是怎样通过插件生成出来的呢?那就去分析一下Baidusubmit插件的源代码吧。
根目录下有main.php是插件入口文件,这个文件末尾看到add_action('init', array('BaidusubmitGenerator', 'init'), 1000, 0);,说明插件钩子的位置是init,触发优先级为1000(优先级1是最先触发的意思),由于之前没有二次开发过WordPress,所以这次顺手查了一下init钩子。http://codex.wordpress.org.cn/Plugin_API/Action_Reference/init 解释说:

Runs after WordPress has finished loading but before any headers are sent. Typically used by plugins to initialize.

这是一个非常常用的钩子,因为它几乎可以挂载任何东西。
Baidusubmit插件文件目录——
- \sitemap.php:这是百度蜘蛛要来爬取的页面,正如刚才我们看到的sitemap.php?m=...&start=...&p=...,其中p是访问密码。这个页面收到了GET参数以后就根据数据库记录echo出来一个xml文件。
- \main.php:插件入口文件。
- \checksign.php:这个好像是插件用来判断是否被用户勾选“开启”自动递交选项的。
- \inc\sitemap.php:BaidusubmitSitemap类。里面写了许多重要的获取post,url相关的函数,待会儿要修改的部分就在这里
- \inc\schema.php:BaidusubmitSchemaPost类。这是xml生成文件的模板。

\inc\schema.php能看到输出xml的格式——

  1. return
  2. "<url>\n" .
  3. "<loc><![CDATA[{$this->_url}]]></loc>\n" .
  4. "<lastmod>{$this->_lastmod}</lastmod>\n" .
  5. "<data>\n" .
  6. "<blogposting>\n" .
  7. "<headline><![CDATA[{$this->_title}]]></headline>\n" .
  8. "<url><![CDATA[{$this->_url}]]></url>\n" .
  9. "<articleAuthor>\n" .
  10. "<articleAuthor>\n" .
  11. "<alias><![CDATA[{$this->_author}]]></alias>\n" .
  12. "</articleAuthor>\n" .
  13. "</articleAuthor>\n" .
  14. "<articleBody><![CDATA[{$this->_content}]]></articleBody>\n" .
  15. "<articleTime>{$this->_publishTime}</articleTime>\n" .
  16. "<articleModifiedTime>{$this->_lastmod}</articleModifiedTime>\n" .
  17. "{$keywords}" .
  18. "<articleSection><![CDATA[{$this->_term}]]></articleSection>\n" .
  19. "{$pics}\n" .
  20. "{$video}" .
  21. "{$audio}" .
  22. "{$comment}" .
  23. "<articleCommentCount>{$this->_commentCount}</articleCommentCount>\n" .
  24. "<articleLatestComment>{$this->_latestCommentTime}</articleLatestComment>\n" .
  25. "</blogposting>\n" .
  26. "</data>\n" .
  27. "</url>\n";

没错,我现在就盯着这儿呢<loc><![CDATA[{$this->_url}]]></loc>,毕竟问题就出在url上,我想知道它是在哪里生成的,外加上本类里有这样一个函数——

  1. public function setUrl($url)
  2. {
  3. $this->_url = trim($url);
  4. }

因此$url是从外部传入的了。接下来,在\inc\sitemap.php中有这样一个函数genSchemaByPostId,功能显然是通过PostID来生成xml,实例化了

  1. static function genSchemaByPostId($post_id, &$post=null)
  2. {
  3. require_once dirname(__FILE__).DIRECTORY_SEPARATOR.'schema.php';
  4. $post = get_post($post_id);
  5. $schema = new BaidusubmitSchemaPost();
  6. $schema->setTitle($post->post_title);
  7. $schema->setLastmod($post->post_modified);
  8. $schema->setCommentCount($post->comment_count);
  9. $schema->setPublishTime($post->post_date);
  10. $_user = WP_User::get_data_by('id', $post->post_author);
  11. $schema->setAuthor($_user->display_name);
  12. $_url = BaidusubmitSitemap::genPostUrl($post);
  13. $schema->setUrl($_url);
  14. $schema->setLoc($_url);

首先用WordPress函数get_post($post_id)来产生这个post对象,有兴趣的可以在模板里把它var_dump出来看一下。这里就很清楚了url的来源$_url = BaidusubmitSitemap::genPostUrl($post);,好,那我们再找到genPostUrl()函数——

  1. static function genPostUrl($post)
  2. {
  3. return get_permalink($post);
  4. }

起初我以为问题出在url生成方式上,结果发现它只是调用了WordPress的函数get_permalink()来生成的url,理论上也不可能出错。
这样一来我们得继续跟着url的线索往根源去找,既然url生成的方式没出错,那么就是这个$post的来源出了问题。既然函数是根据PostID来构造的post对象,那么我就得去找到这个PostID是哪来的。果然,在BaidusubmitSitemap类里发现了这样两个函数——

  1. static function getPostIdByIdRange($start_id, $end_id)
  2. {
  3. global $wpdb;
  4. $start_id = intval($start_id);
  5. $end_id = intval($end_id);
  6. $wpdb->query("SELECT ID FROM $wpdb->posts WHERE ID >= $start_id AND ID <= $end_id AND post_status = 'publish' AND post_password = ''");
  7. $ret = array();
  8. foreach ($wpdb->last_result as $val) {
  9. $ret[] = $val->ID;
  10. }
  11. return $ret;
  12. }
  13. static function getPostIdByTimeRange($start_time, $end_time, $limit)
  14. {
  15. global $wpdb;
  16. $start_time = date('Y-m-d H:i:s', $start_time);
  17. $end_time = date('Y-m-d H:i:s', $end_time);
  18. $limit = intval($limit);
  19. $sql = "SELECT ID FROM $wpdb->posts WHERE post_modified >= '$start_time' AND post_modified <= '$end_time' AND post_status = 'publish' LIMIT $limit";
  20. $wpdb->query($sql);
  21. $ret = array();
  22. foreach ($wpdb->last_result as $val) {
  23. $ret[] = $val->ID;
  24. }
  25. return $ret;
  26. }

我发现它是直接从wp_posts表中获取的$post_id。那么wp_posts表中到底有些什么样的数据呢?进入数据库查看后总算真相大白了。原来WordPress把许多东西(不止是文章)都叫做post,比如我们上传一个attachment,会有一个postid,我们发page也会有postid,制作的菜单也会有对应的postid。我尝试用菜单的id(4439)在模板中输出它的url:

  1. <?php
  2. echo get_permalink( 4439 );
  3. ?>

果然就输出了一个不存在的404 url……

解决办法

\inc\sitemap.php中找到getPostIdByIdRange函数,将

  1. $wpdb->query("SELECT ID FROM $wpdb->posts WHERE ID >= $start_id AND ID <= $end_id AND post_status = 'publish' AND post_password = ''");

改成

  1. $wpdb->query("SELECT ID FROM $wpdb->posts WHERE ID >= $start_id AND ID <= $end_id AND post_status = 'publish' AND post_password = '' AND (post_type = 'page' OR post_type = 'post')");

找到getPostIdByTimeRange函数,将

  1. $sql = "SELECT ID FROM $wpdb->posts WHERE post_modified >= '$start_time' AND post_modified <= '$end_time' AND post_status = 'publish' LIMIT $limit";

改成

  1. $sql = "SELECT ID FROM $wpdb->posts WHERE post_modified >= '$start_time' AND post_modified <= '$end_time' AND post_status = 'publish' AND (post_type = 'page' OR post_type = 'post') LIMIT $limit";

限制了插件只生成page页或者post页的sitemap。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注