Scrapy爬虫实战:破解大众点评反爬,全流程数据采集项目拆解 简介这是一套基于Scrapy框架的大众点评爬虫实现面向有一定Python基础、想研究电商及生活服务平台数据采集的爬虫开发者。资源覆盖城市、分类、店铺链接到店铺信息的完整链路内置浏览器登录与手动过验证、代理设置、字体反爬等实用机制能帮助读者快速搭建可运行的点评数据爬取项目。压缩包共19个文件以Python源码为主13个py包含spiders爬虫模块、pipelines管道、middlewares中间件、settings配置和utils工具等另附依赖清单、README说明与许可证整包仅21KB轻量易部署。项目明确了先爬城市和分类、再爬店铺链接最后获取店铺信息的依赖顺序DATA目录会自动落盘结果结构清晰便于二次开发。已有596人学习下载适合作为Scrapy框架进阶练习或改造为其他平台采集器的参考基底。1. Scrapy 爬虫攻大众点评一个能跑通全流程的实战项目拆解大众点评的反爬在业内是出了名的硬骨头字体反爬、CSS 偏移、轨迹检测、滑块验证一层套一层网上能找到的爬虫教程十个里有八个跑到店铺详情页就断了。这份基于 Scrapy 的爬虫项目好在它不是玩具——城市、分类、店铺链接、店铺信息四级爬取链路完整登录和滑块过验证用浏览器手动完成后续请求全程带 Cookie 跑还内置了代理中间件和字体反爬处理。也就是说你拿到手不是看个 demo而是能直接在自己的机器上跑出城市列表、商圈和店铺结构化数据的可用工程。适合谁有一定 Python 基础、想研究 Scrapy 框架在生产场景里怎么落地的人以及需要大众点评店铺数据做分析、做市场调研但不想从零造轮子的从业者。接下来我会按「工程结构 → 核心配置 → 字段与存储 → 避坑记录 → 实战技巧」的顺序拆全程贴关键代码和参数照着复现就行。2. 工程结构拆解与依赖安装先看懂 Scrapy 项目的五脏六腑2.1 目录里每个文件是干什么的dianping-spider-master/ ├── scrapy.cfg ├── requirements.txt ├── .gitignore ├── LICENSE └── dianping/ ├── __init__.py ├── items.py ├── pipelines.py ├── settings.py ├── middlewares.py ├── spiders/ │ ├── __init__.py │ └── (城市/分类/店铺链接/店铺信息 爬虫) └── utils/标准 Scrapy 工程结构scrapy.cfg是项目配置文件dianping/是主模块。spiders/目录下放的是具体爬虫middlewares.py管请求拦截和代理切换pipelines.py管数据落盘utils/放字体反爬映射这类工具函数。README 里对运行步骤有说明但先看结构能少走弯路。2.2 环境准备Python3 与依赖安装项目要求 Python3依赖集中在requirements.txt里。在文件所在目录执行pip install -r requirements.txt核心依赖是 scrapy、requests 和 Pillow。requests 用于直接拉取字体文件Pillow 用于把字体渲染成图片做映射这两项是字体反爬处理的关键。安装完可以用scrapy version验证框架是否就绪常见问题有 pip 源慢导致超时建议加-i https://pypi.tuna.tsinghua.edu.cn/simple走国内镜像。依赖装完不要急着跑爬虫先看settings.py——Scrapy 的绝大多数反爬策略都在这个文件里配置项目能不能稳定运行一半看这里。3. settings.py 与中间件代理、UA、下载延迟与验证码策略3.1 settings.py 里的关键参数打开settings.py重点看几个配置项。机器人的ROBOTSTXT_OBEY必须设成False大众点评的 robots 协议不允许抓取很多新手栽在这——默认是 True一跑就被拒。# settings.py 关键配置 ROBOTSTXT_OBEY False DOWNLOAD_DELAY 2.5 RANDOMIZE_DOWNLOAD_DELAY True DEFAULT_REQUEST_HEADERS { Accept: text/html,application/json;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9, Connection: keep-alive, } DOWNLOADER_MIDDLEWARES { dianping.middlewares.RandomUserAgentMiddleware: 400, dianping.middlewares.ProxyMiddleware: 500, } ITEM_PIPELINES { dianping.pipelines.JsonPipeline: 300, }DOWNLOAD_DELAY 2.5表示每个请求间隔 2.5 秒RANDOMIZE_DOWNLOAD_DELAY True会在 2.5 秒基础上做随机波动这是模拟人类浏览行为的基础操作。DEFAULT_REQUEST_HEADERS里设置基础请求头但 UA 的随机化由中间件处理。3.2 UA 中间件让每个请求都换个身份打开middlewares.pyUA 中间件的逻辑是每次请求都从预置的 UA 池里随机取一个# middlewares.py 中的 UA 中间件示意 import random class RandomUserAgentMiddleware: def __init__(self): self.user_agents [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Version/17.0 Safari/605.1.15, ] def process_request(self, request, spider): request.headers[User-Agent] random.choice(self.user_agents) return None参数说明process_request是 Scrapy 中间件钩子函数在请求发出前被调用random.choice决定了每次请求的 UA 都不同。400 这个数值是中间件优先级数值越小越早执行。UA 池里建议放 Chrome 和 Safari 各几个版本防指纹识别的基本功。3.3 代理中间件给请求套上 IP 轮换机制项目的代理设计是开放的——本身不带代理池而是留了接口让你接入。ProxyMiddleware的process_request会去读取自定义的代理配置# middlewares.py 代理中间件示意 class ProxyMiddleware: def process_request(self, request, spider): proxy spider.settings.get(PROXY_SERVER) if proxy: request.meta[proxy] proxy return Nonerequest.meta[proxy]是 Scrapy 原生支持的代理传递方式值可以是http://ip:port格式。项目支持设置代理意味着你可以把PROXY_SERVER配成自己买的长效代理或自建代理池的入口。我在实际使用中会改成从池子里动态取 IP而不是写死一个如果被反爬盯上IP 被封是家常便饭代理是必须的。3.4 手动登录与过验证别想着用代码硬刚滑块README 里明确写了「通过浏览器登录和过验证手动」这是最省心也最稳的策略。Scrapy 本身不做浏览器渲染滑块验证码的轨迹模拟如果靠代码硬刚成功率很低还容易被封账号。正确姿势是# 先手动用浏览器登录大众点评确保 Cookie 有效 # 然后把 Cookie 导出或手动填入请求头实际操作是抓取一次浏览器里的请求把 Cookie 字符串复制出来配置进DEFAULT_REQUEST_HEADERS或中间件里。Cookie 的失效周期大概是几天失效后需要重新手动过验证。这个流程看起来不优雅但它避免了大量无谓的技术对抗血泪经验告诉我能用人工解决的验证码问题绝不上自动化。3.5 settings 边界哪些参数要按场景调整DOWNLOAD_DELAY不是越小越好。大众点评对高频率请求的封禁策略是累计式的——短期高频触发滑块长期高频封 IP。我的建议是刚开始跑用 3 秒稳定跑 5 分钟没触发验证再把延迟降到 2 秒。CONCURRENT_REQUESTS默认 16建议调到 46多并发 高延迟的组合在反爬场景里更稳。代理这块如果只跑少量数据可以不开批量采集必须开。4. items.py 字段设计与 pipelines.py 存储数据从抓取到落盘全流程4.1 四级爬虫的数据模型设计大众点评的数据结构是层级嵌套的城市 → 分类 → 店铺链接 → 店铺详情。items.py里定义了多个 Item 类分别对应这四层# items.py import scrapy class CityItem(scrapy.Item): city_name scrapy.Field() # 城市名称 city_pinyin scrapy.Field() # 城市拼音 class CategoryItem(scrapy.Item): category_name scrapy.Field() # 分类名称 category_id scrapy.Field() # 分类编号 class ShopLinkItem(scrapy.Item): shop_url scrapy.Field() # 店铺链接 class ShopInfoItem(scrapy.Item): shop_name scrapy.Field() # 店铺名称 shop_address scrapy.Field() # 店铺地址 city scrapy.Field() # 所在城市 star scrapy.Field() # 店铺星星 star_score scrapy.Field() # 星星分数 review_count scrapy.Field() # 评价数量 avg_price scrapy.Field() # 消费价格 score scrapy.Field() # 店铺评分 biz_area scrapy.Field() # 热门商圈字段设计是数据质量的地基。city_pinyin用于后续 URL 拼接category_id对应大众点评分类页的编号biz_area是热门商圈名。注意这里的字段类型没有定义死scrapy.Field()默认是 Any但后续在 pipelines 里写 JSON 时需要注意类型统一。4.2 数据保存链路JsonPipeline 与 data 目录爬虫跑到yield item后数据会进入 pipelines 链。项目里pipelines.py实现了JsonPipeline把每条 item 追加写入对应的 JSON 文件# pipelines.py 简写示意 import json from scrapy.exporters import JsonItemExporter class JsonPipeline: def open_spider(self, spider): self.files {} self.exporters {} def process_item(self, item, spider): name item.__class__.__name__ if name not in self.exporters: f open(fdata/{name}.json, wb) self.files[name] f self.exporters[name] JsonItemExporter(f, ensure_asciiFalse) self.exporters[name].start_exporting() self.exporters[name].export_item(item) return item def close_spider(self, spider): for exporter in self.exporters.values(): exporter.finish_exporting() for f in self.files.values(): f.close()逻辑说明用 item 的类名做文件区分城市数据落到CityItem.json店铺信息落到ShopInfoItem.json避免多种数据混在一个文件里。JsonItemExporter是 Scrapy 内置的 JSON 导出器ensure_asciiFalse保证中文正常显示而不是变成\uXXXX转义。4.3 爬取顺序为什么先城市再分类再店铺README 里强调的两条顺序规则是硬约束「爬取店铺链接之前必须先爬取城市和分类爬取店铺信息之前必须先爬取店铺链接」。原因很直接——大众点评的店铺链接 URL 长这样http://www.dianping.com/shop/1234567这个店铺编号只有从分类列表页才能提取到而分类列表页的 URL 又依赖城市拼音和分类编号。整条链路是城市列表 → 城市拼音 分类编号 → 分类列表页 → 店铺链接shop_id→ 店铺详情页 → 结构化字段项目里spiders/下应该有对应四个爬虫文件。我一般这样组织运行顺序scrapy crawl city # 第一步拿城市列表 scrapy crawl category # 第二步拿分类列表依赖城市拼音 scrapy crawl shoplink # 第三步从分类页提取店铺链接依赖城市分类 scrapy crawl shopinfo # 第四步爬取店铺详情依赖店铺链接4.4 数据完整性检查跑完后校验一下爬虫跑完不要急着收工先检查 data 目录下的文件大小和行数。比如城市列表如果只有几十条而实际应该有三百多个城市那就是解析出了问题。我的检查方式是wc -l data/*.json如果CityItem.json只有 30 行大概率是提取规则写窄了或者请求被重定向到了验证码页。这个习惯能帮你提前发现反爬被触发的问题而不是等数据分析时才意识到数据是残缺的。5. 避坑手册字体反爬、验证码、链接失效与数据漂移5.1 字体反爬页面数据全变乱码现象页面用浏览器打开显示正常但用 requests 或 Scrapy 抓下来店铺评分、评价数这类数字全部变成奇怪的符号或乱码。原因大众点评使用动态字体WOFF做数字混淆。页面的 class 对应一个字体坐标同一个数字在不同时间可能对应不同字形直接按 HTML 文本提取必然出错。这是业界最成熟的反爬手段之一。解决项目utils/目录下的工具函数就是干这个的。基本流程是先下载.woff字体文件用 Pillow 渲染出字形图片然后和已知数字的字体库比对建立字符映射表再把页面里的乱码字符映射回真实数字。核心逻辑是# utils 中字体映射示意伪代码级理解 font TTFont(font.woff) glyph_map {} # 对每个字形渲染成小图片与标准数字图片做相似度比较 for glyph_name in font.getGlyphOrder(): if is_digit_glyph(glyph_name): image render_glyph_to_image(font, glyph_name) digit match_digit(image) # 匹配 0-9 glyph_map[glyph_name] digit这套方法的思路是字体文件虽然变化但数字 09 的字形本质不变只是映射到的 Unicode 编码变了所以通过渲染比对拿到底层映射即可别迷信网上某个固定的 CSS 映射表。5.2 滑块验证码Cookie 明明没过期为什么还弹现象手动登录成功后跑了几百个请求突然页面返回的不是数据而是滑块验证中心之后所有请求都变成验证码页。原因频率触发了风控。大众点评的反爬是动态阈值的——短时间内的请求密度超过阈值就弹验证和 Cookie 是否有效无关。这和直接用 Selenium 模拟点击滑块还不一样即使 Cookie 有效请求频率图谱也会出卖你。解决降低并发、增大延迟、开代理。具体操作是把DOWNLOAD_DELAY调到 3 秒以上CONCURRENT_REQUESTS降到 4并确保代理中间件已生效。另外重要一点跑批量的时段尽量避开饭点等业务高峰时段服务端风控在高峰时段更敏感。5.3 店铺链接爬取不全分类页有分页你只爬了第一页现象ShopLinkItem.json里只有一千多条而分类下实际有上万家店铺。原因分类列表页有分页机制但翻页 URL 不是简单递增页码部分页面带有pn参数和lat/lng地域参数直接拼 URL 容易漏数据。解决检查你爬的页面源码里的翻页逻辑大众点评的分类页通常通过># middlewares.py 改造示意动态代理池 import random class DynamicProxyMiddleware: def __init__(self, proxy_api_url): self.proxy_api_url proxy_api_url classmethod def from_crawler(cls, crawler): return cls( proxy_api_urlcrawler.settings.get(PROXY_API_URL) ) def process_request(self, request, spider): proxy self.get_random_proxy() request.meta[proxy] proxy return None def get_random_proxy(self): # 从代理 API 拉取可用代理列表简单随机取一个 import requests resp requests.get(self.proxy_api_url, timeout3) proxies resp.json().get(data, []) return random.choice(proxies)from_crawler是 Scrapy 中间件的标准入口能从settings.py里读配置。get_random_proxy每次请求前向代理服务请求一个 IP如果代理服务支持按需拨号这套方案的实际效果是 IP 永不重复风控很难定位。设置里只需要加一行PROXY_API_URL http://your-proxy-service/api。验证方法可以这样开两个会话一个不带代理跑 100 个请求一个带动态代理跑 100 个请求观察日志里被重定向到验证码页的请求比例。正常情况下不带代理的会话大概跑到 20 个请求就会触发滑块带代理池的会话能坚持到 100 个以上才出现第一次风险提示。另外一个容易被忽略的小技巧在pipelines.py里把每个 item 的抓取时间字段加上比如crawled_at time.strftime(%Y-%m-%d %H:%M:%S)。这样既能追溯数据时效性又能在数据质量分析时快速排查是不是用的旧数据。从那以后我每次拿完数据都会先跑一遍wc -l和python -m json.tool做完整性校验强制校验一遍才放心入库存取希望帮到你。本文还有配套的精品资源点击获取