资讯动态

点评类平台反爬体系拆解与Python采集方案实战

发布时间:2026/10/15 1:18:47 来源:尧图企业网站定制
1. 从一次数据采集需求说起为什么点评类平台的反爬值得单独研究做过本地生活类数据采集的人都有一个共识电商平台的商品数据相对好抓因为页面结构规整、字段固定、更新频率可预期。但一旦把目标换成点评类平台难度会陡然上升一个量级。原因不复杂——点评类平台的核心资产就是用户评价内容和商户信息这些数据直接关系到平台的商业护城河所以它们在反爬虫上的投入远比一般内容站点要大。我最早接触这类采集需求是在做一个本地餐饮市场分析的小项目需要拿到某城市几个商圈内餐厅的评分分布、评价数量和热门菜品关键词。当时想得很简单不就是列表页翻页加详情页解析吗结果第一轮请求发出去不到两百次IP就被限了换了请求头继续跑撑了大概十分钟又被识别再后来连页面返回的HTML结构都开始出现动态混淆原本能直接解析的字段全部变成了加密字符串。前后折腾了将近一周才把一套相对稳定的采集方案跑通。这篇文章就是把这套踩坑经验完整拆开从反爬机制的原理分析到具体的技术应对方案再到实操中遇到的典型问题和排查思路全部讲清楚。适合有一定Python基础、做过基础爬虫但还没系统接触过反爬对抗的开发者参考。如果你正在做类似的数据采集项目或者单纯想搞清楚点评类平台的防护体系是怎么运作的下面的内容应该能帮你省下不少试错时间。2. 点评类平台反爬体系的全景拆解2.1 反爬不是单一手段而是一套分层防御体系很多人对反爬的理解还停留在“换个User-Agent就行”的阶段这在五六年前或许管用但现在的点评类平台早就不是靠单一手段做防护了。它们用的是一套分层递进的防御体系从你发出第一个请求开始到最终拿到数据中间要过好几道关卡。第一层是传输层识别。服务器会检查你的请求头是否完整、请求频率是否异常、TCP连接特征是否符合真实浏览器行为。这一层相对好过但也是最基础的筛选机制过不了这一层后面的都不用谈。第二层是身份层验证。平台会通过Cookie、Token、设备指纹等机制来确认“你是谁”。真实用户访问时会携带一整套由前端JS生成的标识信息如果你的请求里缺少这些信息或者信息格式不对就会被标记为可疑流量。第三层是行为层分析。这一层是最难绕的。平台会分析你的访问路径、页面停留时间、点击行为、滚动行为等判断你到底是人在操作还是程序在跑。一个正常的用户不会在0.5秒内连续翻10页也不会每次都精确地按照固定间隔请求。第四层是数据层混淆。就算你前面三层都过了平台还有最后一招把页面里的关键数据用自定义字体加密、CSS偏移、SVG混淆等方式处理让你就算拿到了HTML也解析不出正确的内容。这一层是专门针对解析环节的很多采集方案就是卡在这里。理解这个分层结构很重要因为后面的所有技术方案都是围绕“如何逐层突破”来设计的。你不能指望一个方案解决所有问题而是要针对每一层分别制定策略。2.2 请求频率控制最容易被忽视的第一道红线我见过太多人在这一步翻车。包括我自己第一次做的时候觉得只要加了延时就行设了个time.sleep(1)就开跑结果还是被封。后来才搞明白平台的频率检测不是简单地看你每秒发几个请求而是综合多个维度来判断的。具体来说它会看单位时间内的请求总数、请求间隔的规律性、同一IP下的并发连接数、请求的时间分布等。你设了1秒的固定间隔在平台看来反而更可疑——真实用户的访问间隔是随机的有时看几秒就翻页有时看半分钟才动。固定间隔的请求模式一眼就能被识别出来。所以频率控制的核心思路是模拟真实用户的访问节奏。具体做法包括使用随机延时而不是固定延时延时范围要根据页面内容的平均阅读时间来设定控制单IP的日请求总量不要贪多在请求序列中随机插入一些“无意义”的页面访问比如偶尔点进首页、偶尔访问一下分类页让访问路径看起来更自然。2.3 请求头与浏览器指纹细节决定成败请求头这块很多人只知道加个User-Agent但实际上平台检查的远不止这一个字段。一个完整的浏览器请求头包含几十个字段其中比较关键的包括请求头字段作用常见错误User-Agent标识浏览器类型和版本使用过时或罕见的UAAccept声明可接受的内容类型缺失或值与实际请求不符Accept-Language声明语言偏好与IP所在地不匹配Accept-Encoding声明支持的压缩方式缺失导致响应体积异常Referer标识请求来源页面缺失或来源不合理Connection连接管理方式与真实浏览器不一致Cookie会话标识缺失关键字段或格式错误除了请求头浏览器指纹也是重要的识别维度。平台会通过JS收集你的屏幕分辨率、时区、语言、插件列表、Canvas渲染特征等信息生成一个唯一的指纹ID。如果你的请求头声称是Chrome浏览器但指纹信息显示的是一个无头浏览器的特征那就会被直接标记。2.4 数据加密与混淆最顽固的一道防线前面几层如果都过了恭喜你来到了最后一关。点评类平台的数据混淆手段主要有以下几种自定义字体加密是最常见的一种。页面上的数字比如评分、评论数、人均价格在HTML源码里显示的是乱码字符浏览器通过加载一个自定义的woff字体文件来把这些乱码渲染成正常的数字。你直接解析HTML拿到的是乱码必须下载字体文件、解析字体映射关系才能还原出真实数值。CSS偏移是另一种常见手段。页面上的文字位置被CSS的text-indent或position属性做了偏移你在HTML里看到的文字顺序和实际显示的顺序不一样。比如源码里写的是“好评率99%”但通过CSS偏移后用户看到的是“99%好评率”。如果你不处理CSS解析出来的数据就是错的。SVG混淆相对少见但更难处理。关键数据被转换成SVG路径绘制在页面上HTML里根本没有文本内容你需要通过图像识别或者解析SVG路径来提取数据。接口签名是针对API请求的。平台的前端在调用数据接口时会带上一个签名参数这个签名由时间戳、请求参数、密钥等经过特定算法生成。你如果直接构造API请求而不带正确的签名服务器会直接拒绝。这四种手段往往组合使用比如列表页用字体加密详情页用CSS偏移API接口用签名验证。你需要针对不同的页面类型分别制定解析策略。3. 核心实操从零搭建一套可用的采集方案3.1 环境准备与基础工具选型在开始写代码之前先把工具链搭好。以下是我实际用下来比较顺手的组合Python 3.10语法特性够用异步支持成熟httpx比requests更好用的HTTP客户端原生支持异步和HTTP/2parselScrapy的解析器独立版支持XPath和CSS选择器比BeautifulSoup更灵活fontTools解析woff字体文件处理字体加密的利器playwright需要渲染JS页面时的首选比Selenium更轻量、更快安装命令如下pip install httpx parsel fonttools playwright playwright install chromium选httpx而不是requests的原因很简单点评类平台的页面加载往往需要HTTP/2支持requests对HTTP/2的支持一直不太好而httpx原生支持。另外httpx的异步能力在需要并发采集时优势明显。选parsel而不是BeautifulSoup的原因是parsel同时支持XPath和CSS选择器在处理复杂的DOM结构时更灵活。而且parsel的解析速度比BeautifulSoup快不少在批量解析时差距很明显。3.2 请求会话的构建与维护构建一个“像真人”的请求会话是整个方案的基础。下面是我常用的会话初始化代码import httpx import random import asyncio class SessionManager: def __init__(self): self.client httpx.AsyncClient( http2True, follow_redirectsTrue, timeouthttpx.Timeout(30.0), headersself._build_headers() ) def _build_headers(self): return { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Accept: text/html,application/xhtmlxml, application/xml;q0.9,image/avif, image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Accept-Encoding: gzip, deflate, br, Connection: keep-alive, Upgrade-Insecure-Requests: 1, Sec-Fetch-Dest: document, Sec-Fetch-Mode: navigate, Sec-Fetch-Site: none, Sec-Fetch-User: ?1, Cache-Control: max-age0 } async def random_delay(self, min_sec1.5, max_sec4.5): delay random.uniform(min_sec, max_sec) await asyncio.sleep(delay)这段代码有几个关键点值得说明。http2True让客户端支持HTTP/2协议很多现代网站会检查这一点。follow_redirectsTrue自动处理重定向避免因为手动处理不当暴露爬虫特征。请求头里的Sec-Fetch-*系列字段是Chrome特有的加上它们能让请求看起来更像真实浏览器发出的。random_delay方法使用随机延时而不是固定延时延时范围设定在1.5到4.5秒之间。这个范围是根据实际测试得出的——太短容易被识别太长采集效率太低。当然具体数值要根据目标平台的响应速度和你的采集量来调整。3.3 页面数据提取的完整流程拿到页面HTML之后数据提取分两种情况处理普通字段直接解析加密字段需要额外处理。普通字段的解析用parsel就能搞定from parsel import Selector def parse_shop_list(html): sel Selector(texthtml) shops [] for item in sel.css(.shop-item): shop { name: item.css(.shop-name::text).get().strip(), address: item.css(.shop-address::text).get().strip(), category: item.css(.shop-category::text).get().strip(), } shops.append(shop) return shops加密字段的处理要复杂一些。以字体加密为例完整流程是这样的from fontTools.ttLib import TTFont import re def decode_font_encrypted(html, font_url): # 下载字体文件 font_data download_font(font_url) # 解析字体映射 font TTFont(font_data) cmap font.getBestCmap() # 建立乱码字符到真实数字的映射 mapping {} for code, glyph_name in cmap.items(): # 通过glyph的轮廓特征判断真实数字 real_num identify_number_by_glyph(font, glyph_name) mapping[chr(code)] real_num # 替换HTML中的乱码字符 for fake_char, real_num in mapping.items(): html html.replace(fake_char, str(real_num)) return htmlidentify_number_by_glyph这个函数是核心难点。字体文件里的每个glyph都有一个名字但这个名字往往是随机生成的比如uniE123不能直接用来判断对应的数字。我的做法是通过glyph的轮廓坐标来判断——数字0-9的轮廓特征各不相同可以通过比较坐标点的相对位置来识别。具体实现比较复杂后面在问题排查部分会详细讲。3.4 数据存储与去重策略采集到的数据需要及时存储和去重否则重复采集会浪费大量请求配额。我一般用SQLite做本地存储轻量且够用import sqlite3 import hashlib def init_db(db_pathshops.db): conn sqlite3.connect(db_path) conn.execute( CREATE TABLE IF NOT EXISTS shops ( id TEXT PRIMARY KEY, name TEXT, address TEXT, category TEXT, rating REAL, review_count INTEGER, collected_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ) conn.commit() return conn def generate_shop_id(shop_name, shop_address): raw f{shop_name}|{shop_address} return hashlib.md5(raw.encode()).hexdigest() def save_shop(conn, shop): shop_id generate_shop_id(shop[name], shop[address]) conn.execute( INSERT OR REPLACE INTO shops (id, name, address, category, rating, review_count) VALUES (?, ?, ?, ?, ?, ?) , (shop_id, shop[name], shop[address], shop[category], shop.get(rating), shop.get(review_count))) conn.commit()用商户名称加地址的MD5作为主键这样即使多次采集到同一条数据也不会重复插入。INSERT OR REPLACE保证新数据能覆盖旧数据方便后续更新。4. 常见问题与排查技巧实录4.1 请求被限的典型表现与恢复策略请求被限的表现有好几种不同的表现对应不同的原因和解决方案表现可能原因解决方案返回403状态码IP被拉黑或请求头被识别更换IP检查请求头完整性返回200但内容是验证页触发了人机验证降低频率模拟真实验证流程返回空数据或默认值接口签名校验失败分析签名算法重新构造请求连接超时或重置TCP层被拦截检查TLS指纹调整连接参数数据字段变成乱码字体加密或CSS偏移解析字体文件处理CSS样式被限之后的恢复策略也很重要。如果只是轻度限流暂停一段时间比如半小时到一小时再继续往往就能恢复。如果是IP被拉黑那就需要更换出口IP。如果是账号级别的限制那就需要重新走一遍登录流程获取新的会话凭证。我个人的经验是预防比恢复重要得多。与其被封了再想办法不如一开始就把频率控制好、把请求特征伪装好。具体来说单IP的日请求量控制在5000次以内比较安全单次会话的连续请求不超过200次每完成一批采集后主动休息几分钟。4.2 字体加密的识别与破解细节字体加密是点评类平台最常用的数据保护手段也是很多人卡住的地方。我详细说一下破解思路。首先你需要确认页面是否使用了字体加密。方法很简单在浏览器里打开开发者工具查看页面上的数字元素如果HTML源码里显示的是类似#xE123;这样的私有区字符而页面上显示的是正常数字那就是字体加密。确认之后下一步是找到字体文件的URL。通常在页面的style标签里会有一个font-face规则里面定义了字体文件的路径。把这个文件下载下来用fontTools解析。解析的关键是建立乱码字符到真实数字的映射关系。前面提到可以通过glyph轮廓来判断具体做法是from fontTools.ttLib import TTFont from fontTools.pens.pointPen import SegmentToPointPen def get_glyph_signature(font, glyph_name): glyph_set font.getGlyphSet() glyph glyph_set[glyph_name] points [] pen SegmentToPointPen(points) glyph.draw(pen) # 提取坐标特征 coords [(p.x, p.y) for p in points if hasattr(p, x)] return coords def identify_number_by_glyph(font, glyph_name): coords get_glyph_signature(font, glyph_name) # 根据坐标特征匹配数字 # 这里需要预先建立数字0-9的特征库 signature normalize_coords(coords) for num, ref_sig in NUMBER_SIGNATURES.items(): if match_signature(signature, ref_sig): return num return NoneNUMBER_SIGNATURES是一个预先建立的特征库包含数字0-9的标准轮廓特征。建立这个库的方法是自己生成一套标准字体的数字提取它们的轮廓特征作为参考。由于不同平台的字体文件可能不同这个特征库可能需要针对性地调整。注意有些平台会动态更换字体文件每次请求返回的字体映射都不一样。这种情况下你需要每次请求都重新下载和解析字体文件不能缓存映射关系。4.3 验证码触发后的处理思路验证码是另一个常见的拦路虎。点评类平台常用的验证码类型包括滑块验证、点选验证和图形字符验证。触发验证码通常意味着你的行为已经被判定为可疑这时候继续硬跑只会让情况更糟。我的处理思路是先暂停再分析后调整。暂停当前采集任务避免进一步触发风控。然后分析触发验证码的原因——是频率太高请求头有问题还是访问路径太规律找到原因后调整策略再重新开始。如果验证码频繁触发可以考虑接入打码平台来处理。但这不是长久之计因为打码平台的处理速度慢、成本高而且平台的风控系统也在不断升级今天能过的验证码明天可能就过不了了。更根本的解决方案还是把请求特征做好从源头上降低被判定为可疑的概率。4.4 采集效率与稳定性的平衡很多人一开始追求采集速度把并发开到很高结果很快就被封了。我的经验是稳定性优先效率其次。一个稳定的低速采集方案长期来看比一个高速但频繁被封的方案效率更高。具体来说我一般会把并发控制在3-5个每个请求之间加1.5-4.5秒的随机延时。这样单IP的采集速度大概是每分钟10-20条数据一天下来能采集一万条左右。如果需要更大量级的数据就通过多IP轮换或者分布式部署来扩展而不是提高单IP的并发。另外建议在采集过程中加入自适应限速机制。如果连续几次请求都返回正常结果可以适当加快速度如果出现了异常响应比如403、验证码、空数据就自动降低速度甚至暂停一段时间。这样能让采集过程更加智能和稳定。5. 反爬对抗的边界与长期维护思路做数据采集这些年我越来越意识到一件事反爬对抗不是一场能“赢”的战争而是一个需要持续维护的平衡。平台的风控系统在升级你的采集方案也需要跟着调整。今天能用的方法可能下个月就失效了。所以与其追求一套“一劳永逸”的方案不如建立一套可持续的维护机制。具体包括定期检查采集成功率如果发现成功率下降就及时排查原因关注平台前端代码的变化特别是JS文件和字体文件的更新保持采集频率的克制不要因为短期需求而过度请求。另外从合规角度来说采集的数据应该仅用于个人学习和研究不要用于商业用途也不要大规模抓取用户隐私信息。尊重平台的规则和用户的权益这是做任何数据采集工作的基本前提。我在实际操作中的体会是把精力花在“理解平台的风控逻辑”上比花在“寻找绕过方法”上更有价值。当你真正理解了平台为什么要这样设计风控、它在保护什么、它的检测逻辑是什么你自然就能找到更优雅的应对方式。这比盲目试错要高效得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑