后台看到安装的蜘蛛日志一个多月产生了 20 万的日志,看着网站的访问量也没怎么增加,但分布的文章都会有一定的阅读数,且随时间迁移,越积越多。好奇 Typecho 的 Initial 主题阅读次数统计是否会受到蜘蛛爬虫的影响,让 chatgpt 帮我分析找到了返回阅读数显示的方法:

# functions.php
function Postviews($archive) {
    $db = Typecho_Db::get();
    $cid = $archive->cid;
    if (!array_key_exists('views', $db->fetchRow($db->select()->from('table.contents')))) {
        $db->query('ALTER TABLE `'.$db->getPrefix().'contents` ADD `views` INT(10) DEFAULT 0;');
    }
    $exist = $db->fetchRow($db->select('views')->from('table.contents')->where('cid = ?', $cid))['views'];
    if ($archive->is('single')) {
        $cookie = Typecho_Cookie::get('contents_views');
        $cookie = $cookie ? explode(',', $cookie) : array();
        if (!in_array($cid, $cookie)) {
            $db->query($db->update('table.contents')
                ->rows(array('views' => (int)$exist+1))
                ->where('cid = ?', $cid));
            $exist = (int)$exist+1;
            array_push($cookie, $cid);
            $cookie = implode(',', $cookie);
            Typecho_Cookie::set('contents_views', $cookie);
        }
    }
    echo $exist == 0 ? '暂无阅读' : $exist.' 次阅读';
}

可以看到统计主要是通过将文章的 cid 写入到 cookie 中的,这样下次再次阅读的时候可以排除,不会重复统计。但显然,没有排除爬虫的访问。

一直很好奇谷歌和百度这样的搜索引擎,是如何精确统计 PV、UV 的。问了 chatgpt,主要还是一些参数做成的“综合指纹”,并且因为大部分爬虫都只爬页面结构,js 等文件是排除在外的。这就让搜索引擎的统计,只有在浏览器之类的客户端加载进来才会发出。

并且这里针对是文章 cid,而搜索引擎的统计是针对的所有收录的页面,根据访问路径再细分请求。各种参数都会对统计结果有印象,比如清一下缓存,或者切一下 VPN,所以结果只能是尽量精确。

  • Cookie
  • Session
  • IP + UA
  • Visitor ID

最后是 php 增加排除蜘蛛爬虫方法:

function isBot() {
    $bots = [
        'bot',
        'spider',
        'crawl',
        'slurp',
        'Baiduspider',
        'Googlebot',
        'bingbot',
        'YandexBot',
        'Bytespider',
        'Sogou',
        '360Spider'
    ];
    return preg_match(
        '/'.implode('|', $bots).'/i',
        $_SERVER['HTTP_USER_AGENT'] ?? ''
    );
}

function Postviews($archive) {
    $db = Typecho_Db::get();
    $cid = $archive->cid;
    // if (!array_key_exists('views', $db->fetchRow($db->select()->from('table.contents')))) {
    //     $db->query('ALTER TABLE `'.$db->getPrefix().'contents` ADD `views` INT(10) DEFAULT 0;');
    // }
    $exist = $db->fetchRow($db->select('views')->from('table.contents')->where('cid = ?', $cid))['views'];
    if ($archive->is('single') && !isBot()) {
        $cookie = Typecho_Cookie::get('contents_views');
        $cookie = $cookie ? explode(',', $cookie) : array();
        if (!in_array($cid, $cookie)) {
            $db->query($db->update('table.contents')
                ->rows(array('views' => (int)$exist+1))
                ->where('cid = ?', $cid));
            $exist = (int)$exist+1;
            array_push($cookie, $cid);
            $cookie = implode(',', $cookie);
            Typecho_Cookie::set('contents_views', $cookie);
        }
    }
    echo $exist == 0 ? '暂无阅读' : $exist.' 次阅读';
}

扩展阅读:typecho 后台添加文章、页面阅读数量统计功能 & 新版本 1.3 下的更新