本文约 4,074 字,预计阅读 11 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:付费墙不是”拦一段文字”那么简单
最常见的做法是”前 30% 免费,后面藏起来”。但真正上线就会遇到一堆细节:搜索引擎要不要抓到付费内容?免费用户看到的截断位置怎么定?付费用户在微信里打开要不要放行?给老用户的赠阅怎么处理?付费墙的本质是一套”内容可见性规则”,它由三个变量决定:谁在读、读的是什么、在什么入口读。把这三个变量想清楚,代码才写得对。
原理分析:三种付费墙的适用边界
| 类型 | 实现方式 | 优点 | 风险 |
|---|---|---|---|
| 硬付费墙 | 整页拦截,未登录跳转 | 实现最简单 | SEO 拿不到内容,转化率低 |
| 计量付费墙 | 按篇数/天数放行 | 利于拉新 | 需记录计数,逻辑复杂 |
| 分段试读 | 正文按比例截断 | 转化最好 | 需处理节选与全文的 SEO 关系 |
对中文知识付费场景,推荐”分段试读 + 计量限制”的组合:注册用户每月免费读 3 篇,未注册用户每篇只看前 30%。这样既有拉新钩子,又不至于被白嫖。
关键点在于:给搜索引擎和付费用户返回的必须是完整正文,否则会出现”付费后仍看不到全文”或者”收录的是截断内容”这两个典型事故。做法是——判断可见性时先排除爬虫,再判断用户权限。
保姆级实操
步骤 1:确定试读的切分单位。 不要按字符数切,会切在句子中间。按段落切:取正文的前 N 个 <p>,末尾补一句引导。中文场景 N 取 3–5 段比较合适。
步骤 2:写核心的可见性判断函数。 先判爬虫(is_feed() 与常见爬虫 UA),再判是否已付费。
function mimangfei_is_crawler() {
if ( is_feed() ) {
return true;
}
$ua = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '';
if ( preg_match( '/bot|crawl|spider|slurp|bingpreview|facebookexternalhit/i', $ua ) ) {
return true;
}
return false;
}
function mimangfei_can_read_full( $post_id, $user_id = 0 ) {
$post_id = (int) $post_id;
if ( ! get_post_meta( $post_id, '_is_paid', true ) ) {
return true; // 免费内容
}
if ( mimangfei_is_crawler() ) {
return true; // 让爬虫抓到全文,利于收录
}
$user_id = $user_id ? $user_id : get_current_user_id();
if ( ! $user_id ) {
return false;
}
// 已购买:订单记录在用户 meta 中
$owned = (array) get_user_meta( $user_id, '_owned_posts', true );
if ( in_array( $post_id, $owned, true ) ) {
return true;
}
// 计量:本月剩余免费额度
$quota = (int) get_user_meta( $user_id, '_monthly_quota', true );
return $quota > 0;
}
步骤 3:在 the_content 上做截断。 优先级设高一点,避免被其他插件的内容追加逻辑覆盖。
add_filter( 'the_content', 'mimangfei_paywall_content', 5 );
function mimangfei_paywall_content( $content ) {
if ( ! is_singular( 'post' ) || ! in_the_loop() || ! is_main_query() ) {
return $content;
}
$post_id = get_the_ID();
if ( mimangfei_can_read_full( $post_id ) ) {
return $content;
}
$paragraphs = preg_split( '/(?<=<\/p>)/', $content, -1, PREG_SPLIT_NO_EMPTY );
$free = implode( '', array_slice( $paragraphs, 0, 4 ) );
$notice = '<div class="paywall-box">';
$notice .= '<p>本文为付费内容,已读至试读部分,订阅后可继续阅读。</p>';
$notice .= '<p><a class="paywall-btn" href="' . esc_url( home_url( '/pricing/' ) ) . '">立即订阅</a></p>';
$notice .= '</div>';
return $free . $notice;
}
步骤 4:扣减计量额度。 用 wp_footer 或 REST 请求触发,避免缓存把计数器弄乱。计数的 key 里带年月,天然实现按月重置。
function mimangfei_consume_quota( $user_id, $post_id ) {
$month = current_time( 'Ym' );
$key = '_quota_' . $month;
$used = (int) get_user_meta( $user_id, $key, true );
update_user_meta( $user_id, $key, $used + 1 );
update_user_meta( $user_id, '_monthly_quota', max( 0, 3 - $used - 1 ) );
}
步骤 5:给付费内容加结构化标记。 用 schema.org 的 isAccessibleForFree 标注,让搜索引擎理解哪些是付费内容,避免被判为”隐藏真实内容”。
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "NewsArticle",
"headline": "示例文章标题",
"isAccessibleForFree": false,
"isPartOf": {
"@type": "Product",
"name": "年度会员",
"offers": {
"@type": "Offer",
"price": "299.00",
"priceCurrency": "CNY"
}
}
}
</script>
步骤 6:处理赠阅与体验卡。 给 _owned_posts 追加 ID 即可,并在用户中心显示有效期。
步骤 7:把付费页排除出页面缓存。 同一 URL 对不同用户返回不同内容,页面缓存必须按登录态区分,否则未登录用户会看到别人的全文。
常见踩坑
- 付费用户看不到全文。 典型是页面缓存命中了未登录版本,按 Cookie 区分缓存即可。
- 收录的全是截断内容。 忘了给爬虫放行,或者用了 JS 前端截断。
the_content优先级太低。 被其他插件的”文末加版权”逻辑抢先,导致付费提示出现在全文后面。- 案例: 某付费专栏把截断逻辑写在前端 JS 里,正文完整输出到 HTML。第二天内容就被整站采集走了。教训:截断必须在服务端完成。
- 计量额度被刷新页面刷掉。 计数写在模板里而不是独立接口,缓存层没命中时就重复计。
- RSS 全文泄露。
is_feed()判断缺失,付费内容通过订阅源全量流出。 - 忘了标注
isAccessibleForFree。 容易被搜索引擎判定为诱导点击。
总结
付费墙的合格标准是三条:爬虫能抓全文、付费用户永远能看到全文、未付费用户在服务端就已被截断。做到这三条,再加结构化标记与按登录态区分的缓存,就是一套能长期跑的方案。更细的会员等级与权限划分见付费会员社区权限方案;订阅到期后的自动降级处理见会员续费与权限回收。
