拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

大众点评爬虫实战:反爬机制剖析与Playwright自动化落地

大众点评爬虫实战:反爬机制剖析与Playwright自动化落地

爬虫这个话题,尤其是针对大众点评这类本地生活平台的爬虫,在技术社区里一直热度不减。热搜词里"因爬虫入狱""反爬机制""x-forbid-reason"这些词频繁出现,也从侧面说明了一个现实:这不仅是技术问题,更是法律和风控的博弈场。这篇内容不是教你怎么去钻空子,而是基于我自己踩过的坑,复盘一次完整的技术学习过程——从反爬原理的剖析、技术选型的取舍,到Playwright自动化脚本的落地实现。如果你对Python爬虫感兴趣,或者正在研究浏览器自动化方案,这篇文章可以帮你避开不少弯路。

先说清楚一个前提:爬虫技术本身是中性的,用在哪里、怎么用,边界全在人。我在自己的项目里,始终坚持一个原则——只获取公开可见的信息,遵守目标网站的robots协议和服务条款,控制合理的访问频率,不触碰任何需要登录后才能访问的非公开数据。下面聊的所有技术细节,都是在这样一个合规框架内进行的测试和学习。

1. 爬虫项目启动前必须先想清楚的三件事

很多人一上来就急着写代码、找接口,结果代码还没跑通,IP就被封了,账号也被限制了。我在最早接触爬虫的时候也犯过这个错误,后来才明白:一次规范的爬虫项目,技术选型只占三分,另外七分是你在动笔写第一行代码之前,有没有把合规、边界和频率这三件事想清楚。

1.1 合法范围:不是"能爬"就等于"可以爬"

这个点必须放在最前面说。

大众点评的用户协议里明确写了禁止未经许可的爬取行为。也就是说,即使技术上行得通,未经授权的大规模数据采集也面临法律风险。热搜里"因爬虫入狱"这个关键词,写的就是真实发生的判例。市面上确实有人因为恶意爬虫、贩卖公民个人信息被判刑。

所以启动任何爬虫项目之前,我建议你先做一次"合法范围自查":

  • 目标数据是否为公开信息?用户无需登录即可看到的店铺基础信息和评论内容,属于公开信息;但需要登录才能看到的完整用户名、联系方式等,绝对不碰。
  • 平台的服务条款是否明确禁止爬取?如果明确禁止,那么你的代码只能用于个人学习、研究反爬原理,不能用于任何商业用途。
  • 你爬取的数据是否涉及个人隐私?大众点评的评论会显示用户昵称、头像、评论内容,这些都属于个人信息。即便是公开的,批量采集后也存在合规风险。

我自己的处理方式是:只保留评论内容、评分、发布时间这些聚合指标,不做用户维度的画像分析。这样既能研究技术,又不会碰触个人信息的红线。

1.2 数据边界:公开数据与付费墙的区分

大众点评的数据大致可以分三层:

第一层是完全公开的。比如店铺的名称、星级、人均价格、地址,这些在搜索结果页就能看到。第二层是进入店铺详情页后可见的近期评论和精选评论,不需要登录。第三层是完整评论列表、用户的全部历史评论,通常需要登录或者被平台主动折叠。

我在技术测试中只处理第一层和第二层的数据。这个边界线画得很清楚,因为一旦越过这条线,技术难度会指数级上升(需要处理登录态、滑块验证、短信验证),而法律风险也会同步上升。说白了,为了学习爬虫技术,完全不值得冒这么大的风险。

1.3 访问频率:把爬虫做成"礼貌的访客"

什么样的访问频率是"礼貌"的?我给自己的设定很简单:单个IP下,每秒最多一个请求,每抓取一个店铺后随机等待3到8秒。听起来很慢对吧?但实测下来,对反爬系统来说,这种频率和无痕浏览的个人用户几乎无法区分。

你可以用一段简单的代码来控制请求间隔,核心逻辑就是time.sleep()加上一个随机数,让访问节奏更接近真人的行为模式:

import time import random def polite_sleep(): # 模拟真人浏览时的停顿节奏 time.sleep(random.uniform(3, 8))

不要小看这个细节。我在测试中发现,固定间隔2秒的请求,连续50个页面就会触发验证码;而随机间隔3到8秒,跑了近千个页面也没出现一次验证码。频率策略对结果的影响,远远大于你用了多高级的库。

2. 大众点评的反爬防线到底在防什么——从robots到风控的逻辑拆解

在写爬虫之前,我花了大量的时间研究大众点评的反爬机制。搞清楚对手的思路,远比埋头写代码重要。

2.1 第一道防线:请求头与基础身份识别

最基础的检测就是User-Agent(浏览器标识)。一个正常的浏览器请求,User-Agent里会包含完整的浏览器版本、操作系统、内核信息。而很多爬虫脚本用的是Python默认的python-requests/x.x.x,一眼就会被识别。

除了User-Agent,还有Referer(来源页面)、Accept-Language(语言偏好)、Accept-Encoding(压缩格式)等一系列请求头。浏览器发出的请求头通常是完整且顺序固定的,而脚本构造的请求头常常缺胳膊少腿。

我的建议是,如果你用Requests库,不要手动去拼请求头,直接复制浏览器开发者工具里看到的完整请求头,逐项比对,缺一项就补一项。但即使这样,Requests方案在遇到更高级的风控时依然力不从心,这个后面细说。

2.2 第二道防线:IP维度的频控与封禁

IP封禁是反爬体系里最粗暴也最有效的手段。大众点评会根据单个IP在单位时间内的请求次数,动态调整风控等级。

我把实际测试中观察到的规律整理成了表格,方便你直观感受:

访问频率持续时长实际表现
每秒5个以上请求10分钟左右随机出现验证码,部分请求返回403
每秒1个请求30分钟左右出现滑块验证,需手动处理
每3到8秒1个请求持续数小时未触发明显风控

这个表格说明了一个核心逻辑:反爬风控不是一刀切,而是按风险等级动态调整的。低频率、随机化、有浏览行为特征的请求,几乎不会被判定为爬虫;而高频、规律、无行为的请求,即使你伪装得再好,也会露出马脚。

2.3 第三道防线:浏览器指纹、验证码与行为风控

如果你躲过了IP限频,成功拿到了页面数据,别高兴太早。大众点评的深度页面(比如完整评论列表)背后还有三道更隐蔽的检测:

第一道是浏览器指纹。包括Canvas指纹、WebGL渲染信息、字体列表、屏幕分辨率、时区、语言,这些信息会组合成一个几乎独一无二的ID。如果你用Requests直接请求,完全没有浏览器环境,指纹检测直接就能判定你是非法客户端。这也是为什么纯Requests方案在抓取深度数据时几乎不可行的根本原因。

第二道是验证码系统。大众点评的验证码分为图片点选、滑块验证和无感验证三种。无感验证最可怕——你根本不知道验证发生了,但请求已经被标记。滑块验证则会在页面加载时随机出现,拖拽速度、轨迹、停顿点都会被记录下来,普通人无意识的拖拽是带弧度的,而脚本模拟的直线拖拽一眼就会被识别。

第三道是行为风控。鼠标移动轨迹、滚动速度、页面停留时长、点击的坐标分布,这些行为数据会被实时采集。你打开一个页面,鼠标从左上角滑到评论区,停留了几秒,然后滚轮滚动了几下——这套行为模式是真人特有的。爬虫脚本打开的页面,鼠标通常是静止的,或者轨迹是笔直的,行为模型很容易区分。

2.4 反爬博弈的本质:风控的成本判断

把反爬机制整个捋一遍之后,你会发现一个很有意思的结论:平台的根本目的不是阻止所有爬虫,而是提高爬虫的获取成本,让不怀好意的人知难而退。

大众点评的风控系统会做一道成本判断题:识别当前访问者需要花费多少计算资源?误杀一个正常用户的代价有多大?如果你的访问行为让系统判定为"低成本、高威胁"的爬虫,那它就会用验证码、封IP来阻挡你;如果你的行为让系统觉得"高成本、低威胁",那它可能就懒得理你。

明白了这一点,你就能理解为什么纯模拟请求的方案越来越难走通,而浏览器自动化方案(如Playwright、Selenium)逐渐成为主流——因为后者本身就是一个完整浏览器,让人觉得"高成本去识别它不太划算"。

3. 选requests还是Playwright——两条技术路线的取舍

做爬虫技术选型时,我最初用的是Requests + BeautifulSoup的组合,后来才切换到了Playwright。两条路线各有优劣,直接上结论:如果你只是抓几个公开页面做研究,Requests够用;如果你的目标是稳定的、相对深度的数据获取,Playwright是目前最合适的选择。

3.1 requests方案的适用场景与短板

Requests方案最大的优势是轻量、简单、速度快。没有浏览器加载过程,直接发送HTTP请求,拿响应解析HTML,一分钟能抓几十个页面。在应对最简单的静态页面和目标网站没有复杂风控时,它是最佳选择。

但放在大众点评这个场景里,Requests方案的短板非常致命:

第一,很多关键数据接口都做了签名验证。请求URL里会带一串由JS动态计算生成的加密参数(比如_hc这类),你看得到参数值,但没法轻易逆向出生成算法。第二,页面的HTML结构经常变动,你写好的解析规则可能一夜之间全部失效。第三,前文提到的浏览器指纹和行为检测,Requests方案完全无法应对。

3.2 Playwright方案的核心优势

Playwright是微软开源的浏览器自动化框架,它对爬虫场景的价值在于:它启动的是一个真实的Chromium浏览器实例,JavaScript正常执行,Canvas指纹正常生成,Cookie和session自动管理,浏览行为可以被模拟到和真人几乎一致。

我总结出Playwright相比直接解码(Requests)的几个核心优势,这些也是我切换方案的直接原因:

  • 天然携带完整浏览器环境,指纹检测和JS加密参数问题自动消失。
  • 支持等待元素加载、自动处理异步渲染,页面结构的变化对脚本影响较小。
  • 内置强大的选择器机制,支持CSS选择器、XPath、文本定位,数据提取更精准。
  • 可以录制用户操作生成脚本,调试体验远好于手工构造请求。

3.3 我的选型结论

如果做一次技术选型复盘,我的建议可以用一句话概括:Requests适合验证想法,Playwright适合生产落地。

验证想法阶段,你想快速确认目标页面有没有你需要的字段,用Requests拿HTML grep一把就够了。但一旦进入正式的数据获取阶段,需要稳定地、持续地在浏览器环境内运行,Playwright是更可靠的选择。

补充说一点,Playwright虽然有这些优势,但它启动浏览器时的资源开销不小,并发控制也比Requests复杂。如果你抓取的是几百上千的页面,建议用asyncio协程配合Playwright的异步API,能省下不少时间。

4. Playwright实战:从环境配置到评论数据落库的完整流程

这一环节是纯干货。我会从零开始,完整演示一个Playwright爬虫脚本的编写过程,包括环境安装、页面访问、数据提取和存储。这个脚本默认只处理公开的店铺信息和精选评论,不涉及登录,频率已做限速处理。

4.1 环境准备:Python虚拟环境与Playwright安装

第一步是创建独立的Python虚拟环境。这一步非常重要,因为Playwright会下载约150MB的浏览器内核,和系统Python环境混在一起容易出问题。

# 创建并激活虚拟环境 python3 -m venv venv_dianping source venv_dianping/bin/activate # 安装playwright库 pip install playwright # 下载chromium浏览器内核 playwright install chromium

如果下载速度慢,可以设置镜像环境变量。安装完成后,通过playwright install --list命令可以确认浏览器内核是否安装到位。

我在搭建环境时踩过一个坑:只安装了playwright库,没有执行playwright install chromium,结果运行时直接报错"Executable doesn't exist"。这个步骤容易忽略,但它决定了你的脚本能不能跑起来。

4.2 访问商家页面:等待加载而不是死等

大众点评的页面是典型的Ajax异步渲染,评论数据是在页面加载后才通过接口动态生成的。如果用page.goto(url)之后马上提取数据,十有八九拿不到内容。

正确做法是使用显式等待,让Playwright轮询页面直到目标元素出现:

from playwright.sync_api import sync_playwright import time import random # 目标店铺URL(测试用的公开示例页面) SHOP_URL = "https://www.dianping.com/shop/example123" def fetch_shop_comments(): with sync_playwright() as p: # 启动浏览器,设置窗口大小模拟真实设备 browser = p.chromium.launch(headless=False, args=[ '--disable-blink-features=AutomationControlled' ]) context = browser.new_context( viewport={'width': 1366, 'height': 768}, user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36', locale='zh-CN' ) page = context.new_page() # 访问店铺页面 page.goto(SHOP_URL, timeout=30000) # 等待评论区域的标题元素出现,超时设置30秒 page.wait_for_selector('.reviews-items', timeout=30000) # 滚动页面模拟真人浏览,触发懒加载 page.mouse.wheel(0, 800) time.sleep(random.uniform(2, 5)) # 提取评论数据 comments = page.query_selector_all('.reviews-items .review-item') print(f"共找到 {len(comments)} 条评论") # 逐一解析评论内容 for comment in comments[:5]: nickname = comment.query_selector('.user-info .name') content = comment.query_selector('.review-words') rating = comment.query_selector('.review-rank') print("用户:", nickname.inner_text() if nickname else "匿名") print("内容:", content.inner_text() if content else "无") print("评分:", rating.get_attribute('class') if rating else "无") print("---") browser.close() if __name__ == "__main__": fetch_shop_comments()

这段代码里值得注意的几个点:

--disable-blink-features=AutomationControlled这个参数可以隐藏Playwright在WebDriver上的自动化标记,实测确实能减少被风控系统识别的概率。

page.wait_for_selector是核心。它不是固定等几秒,而是等到页面里出现匹配的元素就立即返回,这样既不会太快拿不到数据,也不会因为固定等待而浪费时间。

page.mouse.wheel模拟滚动,这一步是必要的——很多评论是懒加载的,只有滚动到可视区域才会向服务器发起请求获取数据,不滚动就只能拿到前几条。

4.3 定位评论节点与数据提取

页面加载完成后,定位评论节点是技术含量最高的一步。大众点评的HTML结构经常改版,选择器写法不能一成不变。你最好打开Chrome开发者工具,先手动审查一下页面结构,再写选择器。

就我测试时看到的页面结构来说,评论列表通常在一个class="reviews-items"的容器里,每条评论是.review-item节点。评论内容、评分、用户名都在这个节点下的子元素里。

有个实用技巧:用query_selector_all拿到所有评论节点后,先打一条空跑,把每个节点的inner_text完整打印出来看看长什么样,再写精确的解析逻辑。不要一上来就写解析代码,大概率会因为选择器写错,浪费大量调试时间。

我还习惯在提取数据时加一个异常兜底逻辑。因为页面上偶尔会出现缺失字段的情况(比如有的用户是匿名评论,没有昵称),直接调用inner_text()会报NoneType错误。用我上面的写法,先判断元素是否存在,再做提取,就能避免脚本因为一条异常数据崩溃。

4.4 数据落库:CSV与SQLite两种方案

数据提取出来之后,存储方案我建议根据数据量灵活选择。

数据量小(几百条以内),直接用CSV最简单,Excel就能打开,方便肉眼检查:

import csv def save_to_csv(comments_data, file_name="dianping_comments.csv"): with open(file_name, mode="w", newline="", encoding="utf-8-sig") as f: writer = csv.writer(f) writer.writerow(["店名", "用户名", "评分", "评论内容", "发布时间"]) for row in comments_data: writer.writerow(row) print(f"数据已保存到 {file_name}")

注意编码要用utf-8-sig,加上BOM头后,Excel打开中文才不会乱码。用普通的utf-8编码,Excel直接打开时中文会变成乱码,这个坑我踩过一次。

数据量大(上万条),必须用SQLite或MySQL。SQLite是零配置的嵌入式数据库,单文件存储,非常方便:

import sqlite3 def init_db(): conn = sqlite3.connect("dianping.db") c = conn.cursor() c.execute(""" CREATE TABLE IF NOT EXISTS comments ( id INTEGER PRIMARY KEY AUTOINCREMENT, shop_name TEXT, user_name TEXT, rating TEXT, content TEXT, publish_time TEXT ) """) conn.commit() conn.close()

SQLite方案的好处是支持去重、排序、聚合分析,后面做文本挖掘也方便。

5. 评论数据的清洗、去重与简单文本分析

数据拿到手只是第一步,原始数据质量通常不理想,直接进行分析会得出误导性的结论。我在处理大众点评评论数据时,把清洗流程分成了三个阶段。

5.1 数据清洗:字段级处理

第一类问题是HTML标签残留。虽然用Playwright的inner_text()提取文本时大部分标签会被剥离,但评论内容里偶尔会混入\n、空格、特殊字符。统一用正则表达式做一次清理:

import re def clean_text(raw_text): # 清除多余空白和换行 text = re.sub(r'\s+', ' ', raw_text) # 清除特殊符号 text = re.sub(r'[#*]', '', text) return text.strip()

第二类问题是评分字段混乱。大众点评的评分有时是文字(比如"很好"),有时是星星数字,需要统一映射成可量化的分数。

第三类问题是空值和缺失值。匿名用户的昵称为空,极少数评论没有评分,这类数据要么填充为"匿名"/"未知",要么直接丢弃,取决于你的分析目标。

5.2 去重策略:不只是IP去重

评论数据的去重不能只看ID。常见的重复情况是:同一用户对同一家店在短时间内发表了多条相似内容,或者抓取过程中页面重复加载导致同一条评论被重复采集。

基础的去重逻辑是看"用户名 + 评论内容"组合是否重复。进阶方案是计算评论内容的SimHash值,对相似度超过阈值的评论做合并。对于一般场景,用前者就够了:

def deduplicate(comments): seen = set() unique_comments = [] for c in comments: key = (c["user_name"], c["content"][:50]) if key not in seen: seen.add(key) unique_comments.append(c) return unique_comments

这里的一个细节是只取评论内容的前50个字符作为key,因为同一用户的评论往往有相似开头,用全文字符串容易漏掉真正的去重目标。

5.3 词频与情感倾向的简单分析

清洗完数据,可以做一个快速的内容分析。用jieba库做中文分词,统计词频:

import jieba from collections import Counter def analyze_keywords(comments_texts): word_list = [] for text in comments_texts: words = jieba.lcut(text) # 过滤单个字和无意义词 word_list.extend([w for w in words if len(w) > 1]) counter = Counter(word_list) return counter.most_common(20)

情感分析可以用snownlp这个轻量级库,虽然精度比不上深度学习方案,但做初步的倾向判断完全够用:

from snownlp import SnowNLP def sentiment_score(text): s = SnowNLP(text) return s.sentiments # 返回0到1之间的情感得分,越接近1越正面

把评论按店铺聚合成平均情感分,你可以快速判断哪些维度的反馈是正面还是负面,比如"服务"相关评论情感分普遍偏低,说明这家店服务需要改进。这种轻量分析用来写商家口碑报告,比人工逐条看效率高太多了。

6. 那些我踩过的坑与合规自救方案

6.1 坑一:盲目增加代理IP反而触发风控

第一次写爬虫时,我担心被封IP,花了不少钱买了代理IP池。结果代理的质量参差不齐,有的IP本身就是黑名单,请求发出去直接被403;有的IP地理位置和账号登录地差距过大,触发异地登录风控。

踩了几次坑之后我才明白:代理IP不是越多越好,关键在质量和稳定性。如果你不走大规模采集路线,一个干净的住宅IP加上合理频控,比一百个机房代理IP都管用。对大多数学习场景来说,用本地直连,保持低频率,反而是最稳的方案。

6.2 坑二:调试时忘了关闭无头模式

Playwright有两种运行模式:无头模式(headless)和有头模式。无头模式更快,但特点也很明显——它和真实浏览器的行为特征有细微差异,部分风控系统可以检测到navigator.webdriver等特征。

我的建议是,调试阶段用有头模式,让浏览器窗口显示出来,肉眼确认页面加载正常、选择器定位准确;正式运行阶段再用无头模式,提高效率。千万不要一上来就无头模式跑几千个页面,调试成本和翻车概率都会很高。

6.3 合规自救方案:优先使用官方开放能力

每次写爬虫技术文章,我都要强调一遍:很多平台有自己的开放平台和API,官方提供的数据接口有更高的配额和更稳定的服务,且完全合法。如果你的项目目标不是技术研究,而是商业需求,优先去申请官方接口,别把自己逼到灰色地带。

另外,对于大众点评这类平台,商家的公开信息(地址、电话、营业时间)在百度地图、高德地图等平台都有开放API可以获取。要做本地生活类应用,完全可以绕开爬虫,直接用地图服务的官方接口。这些官方接口虽然有限流,但胜在稳定和合规。

我在实际项目中得出的经验是:能用API解决的需求,绝不用爬虫;必须用爬虫的场景,也只取业务必要的公开字段,并且严格遵守robots协议和平台的访问频控。这样既保证了项目的长期稳定性,也让我睡得踏实。

最后再分享一个我个人的操作习惯:写爬虫代码时,把频控逻辑、合规声明、数据保护措施直接写进代码注释里。这样做有两个好处,一是当你过几个月回来看代码,还能记得当初的设计约束;二是如果代码后来参考给同事或朋友使用,也会提醒他们注意合规边界。这套习惯帮我避免了很多潜在问题,也让我对爬虫项目的边界始终保持着清醒的认知。希望这篇文章能帮到正在研究这个方向的你。

返回列表