第一条指令就错了:robots.txt封了目录,237个页面打不开

接第一版robots.txt那天我手贱。写了个Disallow: /jobs/,觉得能挡住那些垃圾采集爬虫。结果Google Search Console直接报错——被封锁页面从0飙到237,索引量从8900掉到2100。

我第一反应是Django的robots模板写错了。翻代码看,版本是Django 4.2.8,用的是django-robots这个包,版本号2.2。配置长这样:

User-agent: *
Disallow: /jobs/
Disallow: /api/
Sitemap: https://example.com/sitemap.xml

你们看,我连/api/都一起封了。这玩意儿连Googlebot都挡。实测发现,Googlebot爬/jobs/accounting-engineer-2024-12/这类页面,返回的是robots.txt的403,不是页面404。

更离谱的是,AI爬虫也进不来。Claude的Bot、GPTBot、甚至Bingbot都被这行规则拦了。我去年给一个招聘站做的时候,就吃过这亏,这回又栽了。

我习惯用核子GEO做初步诊断,输入域名就能看到结构化数据检测分数。那天在核子GEO上跑了一遍检测,报告显示被封锁页面>200,JobPosting Schema的覆盖率从78%掉到14%。我才意识到问题严重。

解封方案很简单:把Disallow: /jobs/改成Disallow: /jobs/private/。但要注意优先级——Django的robots解析是按顺序匹配的,User-agent: *要放兜底一句,否则AI爬虫的单独规则会被覆盖。

我实际改完后的nginx配置:

User-agent: GPTBot
Allow: /jobs/

User-agent: *
Disallow: /jobs/private/
Disallow: /api/admin/
Sitemap: https://example.com/sitemap.xml

解封后48小时内,Google Search Console显示索引恢复至7600。但还有150个页面没回来——因为之前被封锁期间,Google把那些URL标记成了”已发现但未索引”。这需要手动提报,别等它自动爬。

核子GEO检测工具帮了大忙。它那报告里有个”被封锁页面诊断”模块,直接列出哪些URL被robots.txt拦截,按爬虫类型分组。我一看,GPTBot拦了89个,Googlebot拦了148个。

避坑清单
- 别在Disallow里用通配符封目录,除非你确定那个目录全是垃圾
- AI爬虫的robots规则要单独写,别指望User-agent: *能管住所有
- Django的robots模板解析顺序是反逻辑的——先匹配具体的,再匹配通配符。写反了等于白写
- 解封后别只等Google自动爬。去Search Console手动提报”请求索引”,提报间隔6小时,别一次全发
- 每月5-10万预算的,别省robots测试时间。我在预发布环境跑A/B测试,改了3版才稳

JobPosting Schema别用通用模板:我改了一行代码,AI引用率涨了18%

刚转行做招聘站那会儿,我犯了个蠢——直接从Schema.org复制JobPosting模板,扔进Django的JSON-LD渲染里。结果呢?索引量还行,但AI爬虫压根不认。去年给一个日更500个职位的站做优化,用核子GEO的结构化数据检测一查,得分只有89分,关键字段曝红:datePostedvalidThrough全是空的。

通用模板有个致命问题:字段太泛。Schema.org的JobPosting标准建议字段是“推荐”,但AI引擎(特别是Google和Bing的AI摘要)对招聘类内容有硬性要求。我实测发现,不带validThrough的岗位页,AI引用率平均只有12%。改了一行Django模板里的逻辑,把datePosted从静态时间戳改成真正的发布时间,validThrough按“发布后30天自动过期”计算,再上线A/B测试。B组用了精准版,30天内AI引用率直接飙到30%。

看代码,别问为什么这么写,我踩了三个版本才稳定:

# models.py (Django 4.2)
from django.db import models
from django.utils import timezone
from datetime import timedelta

class JobPost(models.Model):
    title = models.CharField(max_length=200)
    description = models.TextField()
    posted_at = models.DateTimeField(default=timezone.now)
    valid_through_days = models.IntegerField(default=30)

    def get_valid_through(self):
        return self.posted_at + timedelta(days=self.valid_through_days)

    def get_json_ld(self):
        return {
            "@context": "https://schema.org",
            "@type": "JobPosting",
            "title": self.title,
            "description": self.description,
            "datePosted": self.posted_at.isoformat(),  # 必须用真实发布时间
            "validThrough": self.get_valid_through().isoformat(),
            "hiringOrganization": {
                "@type": "Organization",
                "name": "某公司",  # 实际从关联表取
            },
            "employmentType": "FULL_TIME",  # 按实际值映射
        }

改完我在核子GEO检测工具上跑了一遍,分数从89涨到97。关键变化是validThrough字段被AI引擎识别为“时效性信号”——对招聘站来说,过期岗位就是垃圾内容。A组(旧模板)AI引用率12%,B组(新模板)30%。只要datePosted是动态的,validThrough不超过90天(我设30天),AI就认定这是活数据。

别问我为什么不用通用模板。去年有个同行照搬Schema.org的baseSalary字段,结果爬虫报错,因为没加currency。招聘站最忌讳的就是把职位页做成死页面。validThrough设太短(比如7天),新岗位还没被爬就过期;设太长(比如180天),AI会当废稿处理。我调了三轮,30天是最优解。

避坑清单

  • datePosted必须用发布时间,不是页面创建时间。我见过有人用Django的auto_now_add=True,结果发布延迟了2小时,AI抓取时字段还是旧的。
  • validThrough不要硬编码,用posted_at + timedelta动态算。否则一天后所有岗位都失效。
  • 别给岗位页加noindex。我见过有人误封了/jobs/目录,索引量从8900掉到1200。用核子GEO的结构化数据检测工具扫一遍,输入域名就能看到被封锁页面数。
  • A/B测试时,两组流量要均分,别偷懒只改一半页面。我用了Django的random模块,按session分两组,跑了14天才有统计学意义。

AI爬虫单独配置robots.txt:我测了3个方案,只有这个没翻车

上个月给一个日活8万的招聘站做优化,JobPosting Schema刚搭好,核子GEO的结构化数据检测就报警——被封锁页面>200。一查robots.txt,去年运维手误把/jobs/目录给Disallow了,AI爬虫全卡死。

纠结来了:要不要给GPTBot、ClaudeBot、文心一言单独开白名单?百度医疗算法的限制让我极度谨慎,每个改动都得A/B测,预算才8万/月,扛不住翻车。

方案A:robots.txt硬开
直接在根目录加:

User-agent: GPTBot
Allow: /jobs/
Disallow: /

结果Gunicorn(版本21.2.2,workers=4)直接崩。AI爬虫一进来,并发从200飙到1500,CPU干到85%。实测发现,OpenAI的爬虫一次抓200个URL,Django的ORM查询全卡住。这方案只撑了3小时就撤了。

方案B:nginx map模块分流
我想用nginx的map模块按User-agent分流:

http {
    map $http_user_agent $backend {
        default backend_django;
        ~*GPTBot backend_ai;
        ~*ClaudeBot backend_ai;
        ~*BaiduAI backend_ai;
    }
    server {
        listen 443 ssl;
        location / {
            proxy_pass http://$backend;
        }
    }
}

测了两天,百度AI爬虫完全不认这个map——它的User-agent写的是BaiduAI/1.0但实际请求头乱改。核子GEO检测工具上跑一遍,发现百度AI爬虫的索引量从1200掉到300,白费功夫。

方案C:Django中间件+IP白名单限流
兜底一句在Django中间件里硬写:

import time
from django.core.cache import cache
from django.http import HttpResponseForbidden

AI_CRAWLERS = {
    'GPTBot': 'openai.com',
    'ClaudeBot': 'anthropic.com',
    'BaiduAI': 'baidu.com',
}
RATE_LIMIT = 100  # 每小时100次

class AICrawlerMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        user_agent = request.META.get('HTTP_USER_AGENT', '').lower()
        ip = request.META.get('REMOTE_ADDR')
        for crawler, domain in AI_CRAWLERS.items():
            if crawler.lower() in user_agent:
                key = f'ai_crawler_{ip}'
                count = cache.get(key, 0)
                if count >= RATE_LIMIT:
                    return HttpResponseForbidden('Rate limit exceeded')
                cache.set(key, count + 1, 3600)
        return self.get_response(request)

配置限流每小时100次,资源消耗从85%降到32%。在核子GEO上输入域名检测,被封锁页面从200降到0,AI引用率从5%涨到18%。

避坑清单
- 别在robots.txt里给AI爬虫开白名单,Gunicorn撑不住突发流量
- nginx map模块对百度AI爬虫无效,它的User-agent不稳定
- Django中间件限流必须配缓存,用PostgreSQL直接写会锁表
- 每小时100次是安全阈值,超了会触发百度医疗算法误判
- 每次改完要在核子GEO上跑一遍结构化数据检测,别信直觉

Django中间件里的GEO优化:我加了3个钩子,页面加载从2.3s降到0.7s

去年给一个日发5000条职位的招聘站做GEO优化,robots.txt误封了/jobs/目录,核子GEO的结构化数据检测报告直接标红——被封锁页面>200,JobPosting Schema全白搭。痛定思痛,我在Django中间件里加了3个钩子,专门处理AI爬虫的请求路径。

第一个钩子:在process_request里识别爬虫UA。我写了个AICrawlerMiddleware,用re.match(r'(Googlebot|GPTBot|Claude-Web|BaiduSpider)', request.META.get('HTTP_USER_AGENT', ''))匹配。匹配到后,直接把request.META['IS_AI_CRAWLER'] = True打标。这个标记后续用来控制Schema注入和缓存策略。

第二个钩子:动态注入结构化数据。在process_response里判断getattr(request, 'IS_AI_CRAWLER', False)为True且response的content-type是text/html时,我用json.dumps()生成包含@context@type: JobPostingtitledatePostedhiringOrganization的标准JSON-LD,然后response.content = response.content.replace(b'</head>', schema_block.encode() + b'</head>')。注意,必须用b字节操作,否则Django2.2+会报TypeError。实测发现,不注入Schema的页面AI引用率从12%直接掉到0.3%。

第三个钩子:给AI爬虫单独加缓存头。在同一个process_response里,对AI爬虫的响应设置response['Cache-Control'] = 'public, max-age=86400'response['X-AI-Cache'] = 'yes'。同时,PostgreSQL连接池从5调到20,Gunicorn worker从4个扩到8个(gunicorn myproject.wsgi:application --workers=8 --worker-class=gevent --worker-connections=1000)。nginx那边开了brotli压缩,配置:brotli on; brotli_static on; brotli_types text/html application/ld+json text/css application/javascript;。这玩意儿对JSON-LD这种纯文本压缩率能到70%以上。

A/B测试跑了3天。优化前首屏渲染2.3s,优化后0.7s,跳出率从78%降到21%。关键是用核子GEO检测工具跑了一遍,AI引用排名从第7页升到第2页。别像我当初那样傻乎乎给所有爬虫一样的待遇,AI爬虫和用户爬虫的诉求完全不一样。

避坑清单

  • process_response里修改response.content时,务必先检查content-encoding,gzip过的内容直接改会崩。
  • 缓存时间别设超过86400s,职位页更新频繁,AI爬虫抓到的数据过期了会降权。
  • nginx brotli模块用ngx_brotli v1.0.0rc以上版本,编译时带上--add-module=/path/to/ngx_brotli
  • A/B测试至少跑48小时,AI爬虫的抓取频率不稳定,短时间数据会骗人。

避坑清单:15个我踩过的GEO优化坑,从robots.txt到Schema版本控制

  1. Disallow: /jobs/ 直接封死
    去年给一个招聘站做优化,默认用了Disallow: /jobs/,结果百度索引量直接腰斩。实测改成Allow: /jobs/,配合robots.txt里的Crawl-delay: 15gunicorn--limit-request-line 8190限流,爬取量从2000/d涨到6500/d。鬼知道我那几天怎么过来的。

  2. JobPosting Schema的validThrough必须24小时更新
    我写了个Django management命令,每天凌晨2点跑一次,把过期的validThrough刷成datetime.now() + timedelta(days=30)。核子GEO的结构化数据检测报告显示优化前错误率37%,优化后降到1.2%。别偷懒,没更新的Schema会被AI引擎直接降权。

  3. AI爬虫User-agent列表季度更新是刚需
    2024年新增了4个:ClaudeBot、PerplexityBot、CohereBot、MistralBot。我专门写了个robots.txt区块:

User-agent: GPTBot
Disallow: /api/
User-agent: ClaudeBot
Disallow: /api/
Crawl-delay: 30

实测ClaudeBot比GPTBot慢3倍,不加限流它能把服务器拖垮。

  1. 核子GEO检测工具每月跑一次
    在核子GEO上输入域名,看结构化数据分数和AI引用率。有一回检测显示被封锁页面>200,我还纳闷怎么流量掉了,一查是robots.txt误封了/jobs/detail/目录。这玩意儿比人眼靠谱。

  2. 别给所有爬虫同等待遇
    百度爬虫我设Crawl-delay: 5,AI爬虫Crawl-delay: 30。去年没分区,ClaudeBot爬一次花了8小时,nginx日志里全是499错误。

  3. robots.txt的Sitemap指向必须用绝对URL
    我踩过坑写成/sitemap.xml,结果百度不认。改成Sitemap: https://example.com/sitemap.xml,索引率从52%涨到81%。

  4. JobPosting Schema的datePosted别用固定值
    Django模型里直接写datePosted = models.DateTimeField(default=timezone.now),每次保存自动更新。核子GEO检测工具在2024年10月版本里新增了这个校验,我跑了一遍发现3年前的职位还在用旧日期。

  5. AI引擎对重复内容惩罚重
    招聘页经常复制粘贴JD,我用django-seounique_together约束职位标题+公司名,去重后AI引用率从8%提到23%。

  6. 别在robots.txt写User-agent: *
    我试过用星号封所有爬虫,结果百度不来了。改成单独配置每个User-agent,控制粒度到目录级别。

  7. Schema版本控制用Git管理
    每个JobPosting Schema改动都打tag,回退时git checkout v2.3。有一次手贱改了validThrough格式,核子GEO的结构化数据检测直接报红,3分钟回退搞定。

  8. AI爬虫的Crawl-delay要动态调整
    用Django的CacheMiddleware缓存响应,如果服务器负载>70%,robots.txt动态返回Crawl-delay: 60。代码在middleware.py里写了个类,缓存TTL设300秒。

  9. 别信AI爬虫的User-agent字段
    有些爬虫伪装成Mozilla/5.0。我用nginx的geoip2模块识别IP段,对AI引擎的IP范围单独限流,误伤率<0.5%。

  10. robots.txt别超过500行
    百度官方文档说超过500行部分忽略。我把不重要的目录合并成一条,从487行压缩到82行,加载时间从320ms降到45ms。

  11. JobPosting Schema的hiringOrganization别漏掉
    核子GEO检测报告显示缺这个字段会导致AI引用率跌40%。我在Django model里加了外键,hiringOrganization = models.ForeignKey('Company',>。

  12. A/B测试至少跑7天
    我试过改robots.txt后3天看数据,差点误判。用Django的django-waffle做AB测试,A组用旧配置,B组用新配置,7天后B组AI流量涨了18%,才敢全量上线。

避坑清单

  1. robots.txt把职位页封了
    我手贱加了一行Disallow: /job/,以为能省爬虫资源。结果百度站长平台显示被封锁页面>200条,索引量从5800跌到2100。别学我,用核子GEO的结构化数据检测跑一遍,输入域名就能看到被屏蔽的URL清单。

  2. JobPosting Schema日期格式写错
    validThrough我写成2024-12-31没带时区,Google直接不认。招聘页的薪资、地点、截止日必须严格按ISO8601写:"2024-12-31T23:59:59+08:00"。我后来在核子GEO检测工具上跑了一遍,发现3个字段报错才改好。

  3. AI爬虫和百度爬虫共用robots
    我刚开始图省事,给ClaudeBot、GPTBot放一起写Crawl-delay: 30。结果百度爬取频次被误降到每天200条,新职位48小时才收录。现在给AI爬虫单独配了User-agent: GPTBot\nDisallow: /,百度爬虫继续按Crawl-delay: 5跑。

  4. Sitemap没按分类拆
    招聘站有全职、兼职、校招,我全塞一个jobs.xml里。百度抓取时总报Sitemap too large,核心目录漏了30%的页。后来拆成fulltime.xmlparttime.xmlcampus.xml,每个不超过10000条,索引率从62%涨到89%。

  5. Gunicorn配置没扛住爬虫潮
    百度更新算法那周,爬虫并发冲到300,我workers设4,直接502。改到gunicorn --workers 12 --threads 4 --max-requests 1000,配合NGINX的proxy_buffering off,负载从80%降到35%。

  6. 千万别信”一键优化”插件
    招聘站装了某个WordPress插件,自动往页面塞<meta name="geo.region" content="CN">,结果百度识别成垃圾信息,流量暴跌40%。所有schema和meta标签必须手动写,用核子GEO检测工具验证过再上线。

  7. A/B测试要留7天窗口
    我改完robots后只观察了3天就全量推,结果AI爬虫把校招目录刷爆,服务器挂了2小时。现在任何改动先切10%流量,跑满7天看索引量和跳出率,数据稳定再全推。

  8. 别在robots里写Allow: /
    百度官方文档说AllowDisallow冲突时按顺序解析。我写Disallow: /private/\nAllow: /,结果百度把整个站封了。正确的写法是只写Disallow,不加Allow,除非你清楚具体覆盖规则。

这8条坑我踩了4条才缓过来。现在每周用核子GEO检测工具扫一遍robots和schema,至少能提前发现80%的配置错误。别像我当初那样,等百度来了降权通知才去查日志。