@Emptyset
        
        2015-07-29T19:33:59.000000Z
        字数 5589
        阅读 2122
    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变空,第二天起床就发现百度搜不到排名了,不知道这是不是也是原因之一。
还有一件诡异的事情就是Baidusubmit插件每天都有几十到好几百的递交量。 
终于今天开启了BAE日志后发现了类似如下的记录——
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的格式——
return"<url>\n" ."<loc><![CDATA[{$this->_url}]]></loc>\n" ."<lastmod>{$this->_lastmod}</lastmod>\n" ."<data>\n" ."<blogposting>\n" ."<headline><![CDATA[{$this->_title}]]></headline>\n" ."<url><![CDATA[{$this->_url}]]></url>\n" ."<articleAuthor>\n" ."<articleAuthor>\n" ."<alias><![CDATA[{$this->_author}]]></alias>\n" ."</articleAuthor>\n" ."</articleAuthor>\n" ."<articleBody><![CDATA[{$this->_content}]]></articleBody>\n" ."<articleTime>{$this->_publishTime}</articleTime>\n" ."<articleModifiedTime>{$this->_lastmod}</articleModifiedTime>\n" ."{$keywords}" ."<articleSection><![CDATA[{$this->_term}]]></articleSection>\n" ."{$pics}\n" ."{$video}" ."{$audio}" ."{$comment}" ."<articleCommentCount>{$this->_commentCount}</articleCommentCount>\n" ."<articleLatestComment>{$this->_latestCommentTime}</articleLatestComment>\n" ."</blogposting>\n" ."</data>\n" ."</url>\n";
没错,我现在就盯着这儿呢<loc><![CDATA[{$this->_url}]]></loc>,毕竟问题就出在url上,我想知道它是在哪里生成的,外加上本类里有这样一个函数——
public function setUrl($url){$this->_url = trim($url);}
因此$url是从外部传入的了。接下来,在\inc\sitemap.php中有这样一个函数genSchemaByPostId,功能显然是通过PostID来生成xml,实例化了
static function genSchemaByPostId($post_id, &$post=null){require_once dirname(__FILE__).DIRECTORY_SEPARATOR.'schema.php';$post = get_post($post_id);$schema = new BaidusubmitSchemaPost();$schema->setTitle($post->post_title);$schema->setLastmod($post->post_modified);$schema->setCommentCount($post->comment_count);$schema->setPublishTime($post->post_date);$_user = WP_User::get_data_by('id', $post->post_author);$schema->setAuthor($_user->display_name);$_url = BaidusubmitSitemap::genPostUrl($post);$schema->setUrl($_url);$schema->setLoc($_url);
首先用WordPress函数get_post($post_id)来产生这个post对象,有兴趣的可以在模板里把它var_dump出来看一下。这里就很清楚了url的来源$_url = BaidusubmitSitemap::genPostUrl($post);,好,那我们再找到genPostUrl()函数——
static function genPostUrl($post){return get_permalink($post);}
起初我以为问题出在url生成方式上,结果发现它只是调用了WordPress的函数get_permalink()来生成的url,理论上也不可能出错。 
这样一来我们得继续跟着url的线索往根源去找,既然url生成的方式没出错,那么就是这个$post的来源出了问题。既然函数是根据PostID来构造的post对象,那么我就得去找到这个PostID是哪来的。果然,在BaidusubmitSitemap类里发现了这样两个函数——
static function getPostIdByIdRange($start_id, $end_id){global $wpdb;$start_id = intval($start_id);$end_id = intval($end_id);$wpdb->query("SELECT ID FROM $wpdb->posts WHERE ID >= $start_id AND ID <= $end_id AND post_status = 'publish' AND post_password = ''");$ret = array();foreach ($wpdb->last_result as $val) {$ret[] = $val->ID;}return $ret;}static function getPostIdByTimeRange($start_time, $end_time, $limit){global $wpdb;$start_time = date('Y-m-d H:i:s', $start_time);$end_time = date('Y-m-d H:i:s', $end_time);$limit = intval($limit);$sql = "SELECT ID FROM $wpdb->posts WHERE post_modified >= '$start_time' AND post_modified <= '$end_time' AND post_status = 'publish' LIMIT $limit";$wpdb->query($sql);$ret = array();foreach ($wpdb->last_result as $val) {$ret[] = $val->ID;}return $ret;}
我发现它是直接从wp_posts表中获取的$post_id。那么wp_posts表中到底有些什么样的数据呢?进入数据库查看后总算真相大白了。原来WordPress把许多东西(不止是文章)都叫做post,比如我们上传一个attachment,会有一个postid,我们发page也会有postid,制作的菜单也会有对应的postid。我尝试用菜单的id(4439)在模板中输出它的url:
<?phpecho get_permalink( 4439 );?>
果然就输出了一个不存在的404 url……
\inc\sitemap.php中找到getPostIdByIdRange函数,将
$wpdb->query("SELECT ID FROM $wpdb->posts WHERE ID >= $start_id AND ID <= $end_id AND post_status = 'publish' AND post_password = ''");
改成
$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函数,将
$sql = "SELECT ID FROM $wpdb->posts WHERE post_modified >= '$start_time' AND post_modified <= '$end_time' AND post_status = 'publish' LIMIT $limit";
改成
$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。