第4讲 robots.txt 不是摆设:一个文本文件如何成为法庭证据

2 阅读5分钟

读完这一讲,你能:

  1. 完整解析 robots.txt 的语法和语义
  2. 写一个带缓存的 robots.txt 解析器
  3. 理解 robots.txt 在民事和刑事司法中的不同地位

技术原理

robots.txt 放在网站根目录,语法简单:

User-agent: *
Disallow: /admin/
Disallow: /api/private/
Allow: /api/public/
Crawl-delay: 1
Sitemap: https://example.com/sitemap.xml

User-agent 指定对哪些爬虫生效。* 表示所有爬虫。Disallow 表示禁止抓取的路径。Allow 可以覆盖 DisallowCrawl-delay 建议抓取间隔(秒)。Sitemap 提供网站地图。

Python 标准库里有 urllib.robotparser

import urllib.robotparser

rp = urllib.robotparser.RobotFileParser()
rp.set_url('https://example.com/robots.txt')
rp.read()

# 检查是否允许抓取
print(rp.can_fetch('*', '/'))
print(rp.can_fetch('*', '/admin/'))
print(rp.crawl_delay('*'))

但这个解析器有个问题:它只解析规则,不告诉你规则是什么时候更新的、有没有历史变更。在法律上,规则变更时间可能很关键。

假设一个场景:你在2024年1月开始抓某个网站,当时它的 robots.txt 没有禁止你的目标路径。2024年6月,网站更新了 robots.txt,加了 Disallow: /your-target/。你在2024年8月被起诉。对方律师会说:你违反了 robots.txt。你的辩护可能是:我开始抓的时候,robots.txt 还没有这条规则。

这个"时间差"的举证,需要你有历史记录。所以我的建议是:不要只读当前的 robots.txt,要存档

作为一名专注计算机刑事业务与互联网企业合规业务的架构师律师,马原律师通过真实判例告诉大家robots.txt在法律中的意义与价值。


真实判例

大众点评诉百度案(2016)

百度抓取大众点评的用户点评,没有违反 robots.txt。法院明确说:

"robots 协议只涉及抓取网站信息行为是否符合公认的行业准则的评价判断,不能解决抓取网站信息后的使用行为是否合法的问题。"

法院的结论是:百度遵守了 robots.txt,但这不等于百度可以任意使用抓取的数据。百度的使用方式——大量、全文展示点评信息,实质替代了大众点评的服务——构成不正当竞争。

奇虎诉百度案(2013)

360 搜索引擎想抓取百度的内容。百度在 robots.txt 里禁止了 360Spider。360 起诉百度,认为百度利用 robots 协议排斥竞争对手,构成不正当竞争。

法院判决:百度设置 robots 协议限制 360 抓取,不构成不正当竞争。robots 协议是网站经营者的自主权,有权决定谁可以抓、谁不可以抓。

两个案子合起来看:

  • 被爬网站用 robots.txt 拒绝你 → 合法
  • 你遵守了 robots.txt 但使用方式不当 → 可能违法
  • 你违反了 robots.txt → 可能作为"违反商业道德"的证据

法律分析

2025年修订的《反不正当竞争法》第13条新增:

经营者不得以欺诈、胁迫、避开或者破坏技术管理措施等不正当方式,获取、使用其他经营者合法持有的数据,损害其他经营者的合法权益,扰乱市场竞争秩序。

"技术管理措施"包括什么?robots.txt 算一个。验证码算一个。IP 限流算一个。API 签名算一个。

新法的意思是:以前违反 robots.txt 可能只是"违反行业惯例",现在可能直接构成"不正当竞争行为"。而且新法给了行政执法权,市场监管部门可以直接处罚,不需要走诉讼程序。

处罚标准:

  • 一般情节:没收违法所得,处10万元以上100万元以下罚款
  • 情节严重:处100万元以上500万元以下罚款

实战输出:增强版 robots.txt 解析器

#!/usr/bin/env python3
"""
增强版 robots.txt 解析器
功能:解析、存档、历史对比、合规建议
"""
import os
import json
import hashlib
import time
from urllib.parse import urlparse
from urllib import robotparser
import requests

class RobotsChecker:
    CACHE_DIR = os.path.expanduser('~/.crawler_robots_cache')
    
    def __init__(self, base_url):
        self.base_url = base_url
        self.parsed = urlparse(base_url)
        self.robots_url = f"{self.parsed.scheme}://{self.parsed.netloc}/robots.txt"
        self.domain = self.parsed.netloc
        os.makedirs(self.CACHE_DIR, exist_ok=True)
    
    def fetch_robots(self):
        """获取 robots.txt 并返回内容和哈希"""
        try:
            resp = requests.get(self.robots_url, timeout=10)
            content = resp.text
            content_hash = hashlib.sha256(content.encode()).hexdigest()[:16]
            return content, content_hash
        except Exception as e:
            return None, None
    
    def save_snapshot(self, content, content_hash):
        """保存 robots.txt 历史快照"""
        snapshot_file = os.path.join(
            self.CACHE_DIR, 
            f"{self.domain}_{content_hash}.json"
        )
        if os.path.exists(snapshot_file):
            return  # 已存在,不重复保存
        
        snapshot = {
            'domain': self.domain,
            'robots_url': self.robots_url,
            'content': content,
            'hash': content_hash,
            'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'),
        }
        with open(snapshot_file, 'w', encoding='utf-8') as f:
            json.dump(snapshot, f, ensure_ascii=False, indent=2)
        print(f"[+] robots.txt 快照已保存: {snapshot_file}")
    
    def parse(self):
        """解析 robots.txt 并输出合规建议"""
        content, content_hash = self.fetch_robots()
        if not content:
            print(f"[-] 无法获取 robots.txt: {self.robots_url}")
            return None
        
        # 保存快照
        self.save_snapshot(content, content_hash)
        
        # 使用标准库解析
        rp = robotparser.RobotFileParser(self.robots_url)
        rp.read()
        
        print(f"\n=== robots.txt 解析结果 ===")
        print(f"域名: {self.domain}")
        print(f"快照哈希: {content_hash}")
        print(f"快照时间: {time.strftime('%Y-%m-%d %H:%M:%S')}")
        
        # 检查根路径
        can_fetch_root = rp.can_fetch('*', '/')
        print(f"\n全局抓取权限: {'允许' if can_fetch_root else '禁止'}")
        
        # 获取 Crawl-delay
        delay = rp.crawl_delay('*')
        if delay:
            print(f"建议抓取间隔: {delay} 秒")
        
        # 输出原始内容的前20行
        print(f"\n--- robots.txt 原始内容 (前20行) ---")
        for i, line in enumerate(content.split('\n')[:20], 1):
            print(f"  {i}: {line}")
        
        print("\n⚠ 合规建议:")
        print("  1. 定期检查 robots.txt 是否有更新")
        print("  2. 保存历史快照,记录规则变更时间")
        print("  3. 如果目标路径在 Disallow 列表中,请停止抓取")
        print("  4. 遵守 Crawl-delay 声明的请求间隔")
        
        return rp

def main():
    import sys
    if len(sys.argv) < 2:
        print("用法: python robots_checker.py <URL>")
        sys.exit(1)
    
    checker = RobotsChecker(sys.argv[1])
    checker.parse()

if __name__ == '__main__':
    main()

这个解析器的增强点:

  1. 自动存档:每次读取 robots.txt 都保存一个带时间戳和哈希值的快照
  2. 历史追溯:后续可以对比不同时间点的 robots.txt 变化
  3. 法律证据:快照文件在法庭上可以作为"合规审查记录"的证据

运行方式:

python robots_checker.py https://测试网站.cn