@Emptyset
2015-07-29T19:33:59.000000Z
字数 5589
阅读 2000
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:
<?php
echo 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。