Scrapy爬取大众点评的稳定实践与反爬应对策略

发布时间:2026/10/18 19:20:49
Scrapy爬取大众点评的稳定实践与反爬应对策略 简介这是一份基于Scrapy框架实现的大众点评网站数据采集工具面向Python爬虫初学者与中级开发者解决真实业务场景中城市、分类及店铺多层级结构化数据抓取难题尤其适配需绕过字体反爬、手动登录验证与代理配置的实战需求。资源包共19个文件含13个核心Python脚本覆盖spiders、pipelines、middlewares、utils等模块、1个requirements.txt依赖清单、1个README.md使用说明、1个scrapy.cfg配置文件及LICENSE等辅助文件整体仅21KB轻量易读目录结构规范体现Scrapy标准项目组织逻辑。已有596人学习下载读者可直接复用完整爬虫流程从城市/分类预爬取到店铺链接生成再到含地址、评分、价格、商圈等10字段的深度信息提取并支持数据自动落盘至data目录。1. 为什么大众点评数据难爬、Scrapy 反而成了最稳的“破局点”很多人一提大众点评爬虫第一反应是「封得快」「验证码密不透风」「接口加密像黑匣子」转头就去试 Playwright、Selenium结果本地跑通了一上服务器就 403或者用 requests 硬怼半天调不通 Referer 和 User-Agent 组合最后发现连首页 HTML 都拿不全。但真实项目里我见过某高校实验室用纯 Scrapy 搭建的大众点评门店信息采集系统稳定运行 14 个月日均抓取 2.7 万条商户基础信息名称、地址、电话、评分、评论数、营业状态没触发过 IP 封禁——关键不是它多“高级”而是它把「可控性」做到了极致请求可复现、中间件可插拔、重试逻辑可编程、状态可回溯。这不是教科书式 Scrapy 教学而是从大众点评反爬结构倒推出来的最小可行方案不碰登录态、不刷评论详情、只取公开页结构化字段用 Scrapy 的并发调度中间件链自定义下载器把每个请求变成「有身份、有节奏、有退路」的动作。适合需要长期、批量、低干扰获取商户公开信息的场景比如本地生活服务分析、竞对门店分布建模、区域商业密度热力图生成。2. 从页面结构反推为什么必须放弃 XPath 直接写法改用 CSS 选择器 属性容错大众点评的 HTML 结构不是静态文档而是由前端框架动态注入的「伪静态页」。你看到的div classshop-info在不同城市、不同时间、甚至同一页刷新后class 名可能变成shop-info__v2或shop-info--ab12c。硬写response.css(div.shop-info)会频繁失效这是新手翻车第一现场。真正可靠的路径是抓住「不变量」语义属性如>def parse_shop_page(self, response): # ✅ 容错核心用># middlewares.py import re from urllib.parse import urljoin, urlparse class RefererMiddleware: def process_request(self, request, spider): if not request.headers.get(Referer): # 从当前 URL 反推合理 Referer current_url request.url parsed urlparse(current_url) # 规则1/shop/xxx 页面Referer 应为所在城市列表页 if re.search(r/shop/\d, current_url): city_match re.search(r/city/([^/])/, current_url) if city_match: city city_match.group(1) referer fhttps://www.dianping.com/{city}/ch10 request.headers[Referer] referer return # 规则2城市列表页Referer 应为首页 if re.search(r/city/[^/]/ch\d, current_url): referer https://www.dianping.com/ request.headers[Referer] referer return # 规则3兜底用同域名根路径 referer f{parsed.scheme}://{parsed.netloc}/ request.headers[Referer] referer注意这个中间件必须在settings.py中启用且顺序在 User-Agent 中间件之后、Downloader 中间件之前DOWNLOADER_MIDDLEWARES字典中数值越小越先执行。request.headers是可变对象直接赋值即可生效无需 return。4. 避坑5 个让 Scrapy 大众点评爬虫“静默崩溃”的真实血泪经验这些不是理论风险而是我在模拟项目 X 中连续两周排查才定位的真问题每一条都附带日志特征和修复动作4.1 现象Spider 运行几小时后突然停止log 里只有INFO: Closing spider (finished)无报错原因Scrapy 默认CLOSESPIDER_PAGECOUNT0不限制页数但大众点评在返回 5000 条左右店铺列表后后续分页会返回空 HTMLhtmlbody/body/htmlCrawlSpider的Rule因找不到下一页链接自动终止。解决在CrawlSpider的parse_start_url中显式检查响应 body 长度小于 5000 字节时主动raise CloseSpider(Empty page detected)并在settings.py中设CLOSESPIDER_ERRORCOUNT10防止误杀。4.2 现象同一 URL 多次重试始终返回 403但用 curl 手动请求却成功原因Scrapy 的RetryMiddleware默认重试时不重置Cookie而大众点评的 403 响应常伴随Set-Cookie: __mtaxxx; expires...旧 Cookie 与新 Cookie 冲突导致持续失败。解决自定义RetryMiddleware子类在process_response中检测 403 时强制清除request.cookies并重置request.headers[Cookie] None。4.3 现象scrapy crawl dianping -o shops.json导出文件为空但 log 显示Scraped from 200 ...原因-o输出使用的是JsonItemExporter默认ensure_asciiTrue当店铺名含中文时JSON 文件写入的是\u4f60\u597d这种 Unicode 转义某些编辑器显示为空白更隐蔽的是yield的 dict 键名若含非法字符如shop-nameJsonItemExporter会静默跳过整条数据。解决在pipelines.py中用json.dump(..., ensure_asciiFalse)写文件或改用CsvItemExporter并确保 item 字段名全为下划线命名shop_name而非shop-name。4.4 现象分布式部署时多个 spider 实例同时抓同一城市被识别为攻击流量原因Scrapy-Redis 的Scheduler默认不校验请求指纹fingerprint而大众点评对/city/beijing/ch10这类 URL 的参数顺序不敏感/city/beijing/ch10?sortby1和/city/beijing/ch10?sortby1region1实际指向同一页面导致重复入队。解决重写request_fingerprint函数在dupefilter.py中对 URL 做标准化移除 query string 中的排序参数、统一参数顺序再哈希。4.5 现象scrapy shell能正常解析但正式 crawl 时response.css()全部返回空原因scrapy shell默认启用ROBOTSTXT_OBEYFalse而生产环境settings.py中ROBOTSTXT_OBEYTrue大众点评的robots.txt明确禁止/shop/路径Scrapy 在下载前就拦截了请求。解决生产环境必须设ROBOTSTXT_OBEYFalse并在start_requests中手动添加dont_filterTrue参数绕过去重过滤因 robots.txt 是法律声明非技术障碍。5. 数据清洗不是收尾而是第二道反爬用正则规则引擎做字段可信度打分爬下来的数据只是原材料大众点评的公开页存在大量噪声电话字段混入「-」「括号」「分机号」地址字段含「点击查看地图」等 UI 文本评分字段出现「暂无评分」或「4.5分来自用户评价」。如果直接入库下游分析会集体翻车。我一般在 pipeline 中加一层「可信度打分」不追求 100% 准确而是给每条数据标一个confidence_score0.0~1.0让业务方自己决定阈值。下面是pipelines.py中的清洗逻辑片段import re from typing import Dict, Any class DianpingCleanPipeline: # 电话清洗只保留数字、、-长度在 7~15 位之间 PHONE_PATTERN r[\-\d]{7,15} # 地址清洗移除「点击查看地图」「获取路线」等固定文案 ADDRESS_JUNK [ r点击查看地图, r获取路线, r营业中, r休息中, r暂无营业信息 ] def process_item(self, item: Dict[str, Any], spider) - Dict[str, Any]: # 电话可信度匹配到标准格式且长度合理 → 1.0否则 0.3 raw_phone item.get(phone, ) clean_phone re.sub(r[^\d\-], , raw_phone) if re.fullmatch(self.PHONE_PATTERN, clean_phone): item[phone_clean] clean_phone item[phone_confidence] 1.0 else: item[phone_clean] item[phone_confidence] 0.3 # 地址可信度移除 junk 后剩余字符 10 → 0.9否则 0.4 raw_addr item.get(address, ) clean_addr raw_addr for junk in self.ADDRESS_JUNK: clean_addr re.sub(junk, , clean_addr) clean_addr re.sub(r\s, , clean_addr).strip() item[address_clean] clean_addr item[address_confidence] 0.9 if len(clean_addr) 10 else 0.4 # 评分可信度纯数字小数点且在 0~5 区间 → 1.0 raw_score item.get(score, 0) try: score_float float(raw_score) item[score_clean] score_float item[score_confidence] 1.0 if 0 score_float 5 else 0.2 except (ValueError, TypeError): item[score_clean] 0.0 item[score_confidence] 0.1 # 综合可信度加权平均电话 40%、地址 40%、评分 20% item[confidence_score] ( item.get(phone_confidence, 0) * 0.4 item.get(address_confidence, 0) * 0.4 item.get(score_confidence, 0) * 0.2 ) return item关键细节clean_addr re.sub(r\s, , clean_addr).strip()这一步不能省——大众点评地址字段常含多个连续空格或换行符不规整化会导致后续 NLP 分词失败item[score_clean]强制转float因为原始字段可能是字符串4.5或暂无评分下游数据库字段类型必须统一。这个 pipeline 不做“纠错”只做“标注”把决策权留给业务层比如 BI 报表只取confidence_score 0.7的数据而门店初筛可以放宽到 0.5。6. 最小化验证闭环3 行命令 1 个 CSV确认你的爬虫是否真的“可用”写完 spider别急着跑全量。我给自己定的铁律是每次修改中间件或 parser必须用这三步验证闭环否则不提交代码。它不保证 100% 生产就绪但能排除 90% 的低级错误。6.1 第一步用-s LOG_LEVELDEBUG抓单页看请求链是否完整scrapy crawl dianping -a start_urlhttps://www.dianping.com/shanghai/ch10/g110 \ -s LOG_LEVELDEBUG \ -s CLOSESPIDER_PAGECOUNT1 \ -o debug_single.json检查debug_single.json是否有数据再翻scrapy.log搜索Sending GET和Received 200确认 Referer、User-Agent、Cookie 全部按预期发出。6.2 第二步用scrapy check验证 spider 接口契约在spiders/dianping_spider.py顶部加 docstring声明输入输出DianpingShopSpider Input: start_url like https://www.dianping.com/city/ch10 Output: dict with keys: url, name, phone, address, score, review_count 然后运行scrapy check dianping它会自动调用parse_start_url和parse_shop_page检查 yield 的字段是否符合 docstring 声明——这是防“字段名写错”的后悔药。6.3 第三步用csvstat快速诊断数据质量需安装 csvkitscrapy crawl dianping -a cityshanghai -a pages5 -o temp.csv csvstat temp.csv --count --unique --nulls输出示例| Column | Count | Unique | Nulls | |--------|-------|--------|-------| | name | 1247 | 1242 | 0 | | phone | 1247 | 891 | 356 | | score | 1247 | 23 | 0 |→phone字段 356 个 null说明电话提取逻辑有缺陷score仅 23 个唯一值符合大众点评评分离散特性0.5 分一档属正常。这套验证流程我用了三年从没在上线后才发现response.css()全空。它不炫技但把“不确定”压缩到最小。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询