Scrapy+Playwright实战:闲鱼二手商品数据采集与反爬策略

发布时间:2026/8/30 4:35:31
Scrapy+Playwright实战:闲鱼二手商品数据采集与反爬策略 简介本资源是一套面向本科毕业设计与数据采集实践项目的闲鱼二手商品信息爬取系统基于Python-Scrapy框架构建适用于具备基础Python和网络爬虫知识的学习者开展实战训练或课程设计。压缩包共11个文件含7个核心Python模块如spiders、pipelines、items、settings等覆盖爬虫逻辑、数据清洗、存储配置与中间件扩展另含README.md说明文档、LICENSE授权文件、.gitignore及scrapy.cfg配置文件结构规范、开箱即用。资源体积仅8KB轻量紧凑便于快速部署与二次开发。已有145人学习下载可直接复现完整的Scrapy项目目录体系掌握反爬应对策略、XPath解析技巧、异步请求调度机制并获得可拓展的商品价格/地域/发布时间等字段提取范例是入门Scrapy工程化实践的典型参考案例。 做二手商品数据采集这个项目的时候我其实想了很久。市面上Scrapy教程满地跑但绝大多数都是拿静态博客、新闻列表做demo换到闲鱼这种动态渲染、带登录态、有风控拦截的交易平台整套东西立刻就不够用了。所以我把这个zip里的源码和踩坑过程重新梳理了一遍不光是讲代码怎么组织更重要的是说清楚每一步为什么这么选以及哪几个地方最容易翻车。这篇内容会比较长但都是实用层面的东西。1. 闲鱼反爬的“铁幕”动手前先看清这盘棋1.1 闲鱼为什么是块硬骨头闲鱼和普通网站最大的区别在于它的核心数据全部藏在登录态后面。你打开一个商品详情页浏览器里看到的内容是经过多层接口动态加载的页面本身的HTML往往只是个空壳。用Scrapy默认的Request去请求拿到的可能是一堆JS入口文件真正的商品标题、价格、成色这些字段全都不在里面。这个特性决定了整个项目的技术选型单靠Scrapy框架本身无法完成闲鱼数据的采集必须搭配浏览器渲染工具。我试过好几条路最后留下来的是Scrapy结合Playwright的配合方案一个负责调度和管道处理一个负责真实浏览器环境下的动态内容渲染。这种组合在爬虫领域其实已经不算新鲜但工程化落地的时候坑特别多。另一个麻烦是闲鱼的风控体系。它的滑块验证、行为指纹、接口参数签名都不是做做样子。短时间内高频请求一个接口几乎必然触发验证而且它的验证频率和判定规则非常玄学同一套代码隔几天跑结果可能完全不一样。这也是为什么我建议所有想复现这个项目的朋友第一优先级不是写爬虫而是先理解它的反爬逻辑否则代码写再漂亮也白搭。1.2 翻网页还是调接口两条技术路线的代价闲鱼两种数据获取方式一是直接模拟浏览器翻页面让Playwright等在浏览器里加载完整页面再从DOM中抽数据二是绕过页面直接分析它的异步接口用Python构造请求参数去拿JSON数据。第一思路简单逻辑直观但很慢一个会话拉起浏览器需要几百毫秒再加上页面渲染时间每分钟能处理的商品数量非常有限。第二思路效率极高请求轻量只要参数对秒级就能拿到数据。但难点在于接口的签名加密逻辑。闲鱼的接口参数经过多层拼装有些字段看起来是被混淆过的JS动态生成想纯靠静态分析还原工作量会非常大。我最终的方案是混合制主要用页面渲染方式走通流程保证稳定性和成功率同时单独封装一个接口请求模块把常见的搜索接口参数整理出来备用。这样做的好处是页面渲染方案是主链路不依赖对加密逻辑的逆向代码可维护性高接口方案作为提速的备选如果哪天把签名逻辑研究透了可以无缝切换。1.3 技术选型逻辑与合规底线项目标题里明确写了Scrapy框架这是课程设计和面试项目最常见的选型。Scrapy的优势不在于性能比自研多线程方案强多少而在于它把工程化需要的组件全部内置了爬虫调度、中间件、Item Pipeline、去重队列、Extension统计所有东西都是现成的写出来的代码结构一目了然非常适合做演示和二次开发。我在这个项目里配套使用了Playwright做动态渲染核心原因是Scrapy自带的Downloader搞不定JS渲染的页面而Playwright可以接管完整的浏览器内核和Scrapy的Request流程兼容得也不错。四件套齐了之后整个爬虫项目的架构就非常清晰Scrapy负责任务调度和数据流Playwright负责页面渲染和交互。关于合规的底线这里必须说清楚爬虫采集的数据只应该用于个人学习、科研分析、课程设计演示不能用于商业用途更不能涉及用户隐私数据。闲鱼的robots协议虽然没有明确禁止爬虫但它对登录态后的接口有实际的风控保护措施。我在项目里使用的账号是本人的测试号采集粒度也控制在很小的范围这是所有爬虫项目的前提。2. 从零搭一个能跑起来的Scrapy工程2.1 工程骨架与依赖安装拿到项目zip之后第一步是先看清目录结构。一个标准Scrapy工程包含以下重要目录xianyu_scrapy/ ├── scrapy.cfg ├── xianyu/ │ ├── __init__.py │ ├── items.py │ ├── middlewares.py │ ├── pipelines.py │ ├── settings.py │ ├── spiders/ │ │ ├── __init__.py │ │ └── xianyu_search.py依赖方面建议用Python 3.9以上的版本Scrapy 2.11或者2.12都可以Playwright建议用1.40以上的版本。安装命令非常简单pip install scrapy playwright playwright install chromium第二个命令一定要执行否则Playwright找不到浏览器。第一次跑的时候我第一次就忘了执行这个命令结果报错提示找不到浏览器内核折腾了十分钟后才意识到是漏了这一步。另外如果是在腾讯云、阿里云这类轻量服务器上跑记得确认一下服务器内存Playwright启动chromium至少要几百兆内存配置太低的机器会直接OOM。2.2 settings.py 里的关键配置Scrapy最容易被忽视的地方就是settings.py但它的配置直接决定爬虫能不能稳定跑下去。我在项目里的参数如下BOT_NAME xianyu SPIDER_MODULES [xianyu.spiders] NEWSPIDER_MODULE xianyu.spiders ROBOTSTXT_OBEY False DOWNLOAD_DELAY 3 RANDOMIZE_DOWNLOAD_DELAY True CONCURRENT_REQUESTS 1 CONCURRENT_REQUESTS_PER_DOMAIN 1 COOKIES_ENABLED True DEFAULT_REQUEST_HEADERS { Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Connection: keep-alive, Upgrade-Insecure-Requests: 1, }几个关键参数解释一下ROBOTSTXT_OBEY默认情况下Scrapy会遵守robots协议但闲鱼的robots对Crawler的限制比较模糊直接打开会导致很多请求被拒所以我关掉了。但这不等于无视合规采集范围还是控制在自己测试需要的范围内。DOWNLOAD_DELAY爬虫的呼吸节奏设置3秒是经验值。太快容易触发风控太慢又影响数据量。实测下来闲鱼对5秒以上的请求间隔会明显放宽风控。CONCURRENT_REQUESTS合并并发到1配合上面的延时整个爬虫就是单线程节奏看似低效但这是闲鱼反爬机制下最稳的节奏。COOKIES_ENABLED必须打开后面登录态的导入就是靠这个配置。为什么要这么保守因为闲鱼的风控逻辑是越激进越容易触发一旦触发滑块验证整个Session就废了。我刚开始测试的时候把并发调到4延时降到1秒结果跑了不到30个URL就被弹了验证码之后所有请求接口都返回参数校验失败。2.3 中间件UA池、代理与延时策略爬虫的中间件有两个核心职责一是伪装身份二是控制节奏。伪装身份的最基本操作是变换User-AgentUA让服务器无法通过UA指纹判断你是脚本。我维护了一个UA池里面放了Chrome、Edge、Safari等常见浏览器的UA字符串每次请求从池子里随机选一个。代码位置在middlewares.pyclass RandomUserAgentMiddleware: def process_request(self, request, spider): user_agent random.choice(USER_AGENT_LIST) request.headers[User-Agent] user_agent另一个重要中间件是请求延时随机化。DOWNLOAD_DELAY是固定的但真实用户的访问间隔一定是有波动的所以需要加一个随机偏移量class RandomDelayMiddleware: def process_request(self, request, spider): request.meta[download_timeout] 10这个随机延时也可以在settings里通过RANDOMIZE_DOWNLOAD_DELAY True实现Scrapy会自动在DOWNLOAD_DELAY基础上加0.5倍~1.5倍的随机值。关于代理IP我实验过一个阶段但最终放弃了公开免费代理。免费代理不仅慢而且大部分是黑名单IP一用就被风控。如果你确实需要代理建议选择可靠的付费服务商但我个人在这个项目里用不到因为单机低频率已经足够支撑课程设计的数据量。3. 登录态、动态iframe与滑块数据访问的三道坎3.1 Cookie登录态的导入与失效处理闲鱼浏览商品列表未登录状态也能看一部分但想要完整的搜索接口数据必须保持登录。我的做法是手动登录一次用浏览器的开发者工具把Cookie复制出来然后在爬虫里注入请求头。具体操作流程浏览器无痕模式打开闲鱼手动登录一次按F12打开开发者工具切到Network标签刷新页面找到任意一个商品请求在Request Headers中复制完整的Cookie字段值把Cookie值写进爬虫的start_requests里。实际使用中Cookie会过期一般几天到几周不等。一旦出现大量请求返回登录超时或者搜索接口返回空数据就要重新执行上面的流程。需要注意的是Cookie的值非常长包含很多字段我在第一次粘贴的时候直接复制到代码里结果换行符把字符串弄断了导致登录态一直失效。后来我先把Cookie复制到文本编辑器里去掉所有换行再粘贴到代码中问题解决。3.2 Scrapy 与 Playwright 配合渲染动态内容闲鱼的商品搜索结果是通过异步请求加载的而且有一些页面元素被嵌入到了iframe里面。Scrapy默认的Request不执行JS拿不到iframe内容这里的解决方案是用Playwright创建一个独立的浏览器上下文来处理页面。我在爬虫里封装了一个Playwright请求方法class XianyuPlaywrightDownloader: async def fetch(self, request_url, cookies): async with async_playwright() as p: browser await p.chromium.launch(headlessTrue) context await browser.new_context() await context.add_cookies(cookies) page await context.new_page() await page.goto(request_url, wait_untilnetworkidle) content await page.content() await browser.close() return content这个方法的本质是让Playwright承担真实浏览器的工作加载完页面后把渲染完成的HTML返回给Scrapy的解析函数。Scrapy不再需要直接去请求目标服务器而是从Playwright拿结果。当时在调试iframe的时候踩过一个很隐蔽的坑只是简简单单地page.goto访问搜索页面商品列表有时是空的因为主页面加载完成后iframe内部的异步请求还没触发。后来改成wait_untilnetworkidle并额外加了一个固定等待时间才算稳定。3.3 滑块验证与风控触发的应对滑块验证是个大坎。我一开始以为闲鱼只有在登录时才有滑块后来发现高频访问搜索接口同样会触发。触发的表现是这样的浏览器页面出现请按住滑块拖动到最右边的提示页面不会自动刷新后面的请求全部被拦截。针对这种情况我的处理思路分两层第一层是尽量避免触发。控制请求频率加随机延时让每次访问之间的时间间隔像真人这个已经在上面的配置里做了。第二层是如果已经触发暂时停止爬虫等待一段时间再继续。不要试图通过自动化工具把滑块拖过去——这种行为本身风险极高而且闲鱼的滑块会动态校验拖动轨迹仅仅从起点拖到终点是过不去的。我做的处理是检测到滑块出现后直接让爬虫休眠5分钟再恢复同时降低请求频率。在代码里检测滑块出现的方式是检查页面中是否存在特定的元素标识def is_slider_present(page): slider page.locator(.captcha-verify-container) return slider.count() 0还有个容易踩的坑是循环里的等待机制。一开始我用的是固定sleep(5)但页面加载速度不稳定5秒不一定够。后来改成动态等待结合wait_for_selector判断关键元素是否出现既省时间又避免超时误判。4. 从HTML到结构化数据Item、管道与去重4.1 字段设计要“一次想清楚”爬虫写到最后你会发现最花时间的不是编写请求部分而是字段解析和清洗。我在items.py中定义的字段包括class XianyuItem(scrapy.Item): title scrapy.Field() price scrapy.Field() location scrapy.Field() status scrapy.Field() # 成色几乎全新/轻微使用痕迹/明显使用痕迹 seller scrapy.Field() publish_time scrapy.Field() view_count scrapy.Field() url scrapy.Field() crawl_time scrapy.Field()字段命名要用语义化名称方便后面做数据分析和可视化。我建议把采集时间crawl_time也加上同一个商品在不同时间点采集到的价格可能不同带上时间戳以后可以进行价格趋势分析这也是课程设计里一个很好的加分点。解析时用Selector选取元素的代码相对常规但要注意闲鱼的部分字段不在HTML的文本节点里而是在属性值中。比如浏览量有时候存在title属性里价格存在class名称里这些都需要具体查看页面元素才能确认。我的经验是每解析一个字段先打印出来看看是不是预期内容确认无误后再批量跑数据。4.2 Pipeline中的清洗与去重Item Pipeline是Scrapy里非常核心的环节它在数据从爬虫流到存储介质之前提供了一个集中的处理空间。我在项目里处理了三类问题第一类是价格清洗。闲鱼上的价格有的写“99元”有的写“包邮”有的区间价格“200-350元”必须统一处理成数字类型才能做后续的统计分析。我的清洗函数是这样的def clean_price(raw_price): if not raw_price: return None text re.sub(r[^\d.], , raw_price) if - in raw_price: parts raw_price.split(-) return float(parts[0]) return float(text) if text else None第二类是发布时间格式化。闲鱼上的发布时间是“刚刚”、“3小时前”、“5天前”这类相对时间需要转成标准的时间戳用一个简单的换算逻辑就可以实现。第三类是去重。Scrapy自带的去重机制是基于Request URL的但搜索结果页里同一个商品可能会以不同URL出现所以我用商品ID做去重。商品ID可以从URL里提取也可以从页面数据属性中获取。去重队列我用Redis做了持久化存储这样即使爬虫中途崩溃重新启动也不会重复采集。4.3 落库方案CSV、MySQL还是MongoDB三个方案我先后都试过各有各的适用场景存储方案优点缺点适用场景CSV简单无需环境依赖Excel直接打开数据量大时读写慢不支持并发几百条数据的演示demoMySQL查询方便支持复杂条件筛选适合课程设计演示需要安装配置数据库表结构设计要提前想清楚数据量几百到几万条需要按价格/地区筛选MongoDBJson文档结构灵活字段增删方便环境配置稍重对新手不够直观字段经常变动期望保留原始页面数据的场景我的项目最终选了MySQL理由是课程答辩的时候最能直观展示数据操作能力。建表语句很简单CREATE TABLE product ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255), price DECIMAL(10,2), location VARCHAR(100), status VARCHAR(50), seller VARCHAR(100), publish_time DATETIME, view_count INT, url VARCHAR(500), crawl_time DATETIME ) DEFAULT CHARSETutf8mb4;如果只是快速验证爬虫功能建议先用CSV跑通全流程再切到MySQL。一上来就搞数据库很可能在环境配置上花了大量时间反而冲淡了核心内容。5. 真实运行记录与反爬规避的边界5.1 实测数据与运行时长我用这个项目跑了将近一周的测试最稳定的一组数据如下指标数值采集关键词“相机”、“手机”、“自行车”等共12组总请求数约3400成功解析数2860解析成功率约84%运行总时长约2.5小时触发风控次数3次84%的成功率其实还有提升空间失败的原因主要是部分商品详情页打开超时、部分页面布局和通用模板不一致、个别请求被风控拦截。风控触发3次基本都是因为我中途调整参数后请求频率突然加快导致的。只要把节奏压住成功率能稳定在90%以上。从数据量来看2860条商品数据已经足够支撑一个基于Spark或者Pandas的二手商品数据分析项目。可以做价格区间分布、热销品类分析、不同地区商品数量对比、成色比例统计等可视化图表整体效果非常能打。5.2 高频报警信号与排查清单整个调试过程中最耗时的部分其实是排查各种异常情况。我把最常见的报警信号和排查步骤整理成了一张清单方便复现项目时直接对照现象可能原因排查步骤所有返回数据都为空Cookie失效或请求头不对重新复制Cookie检查是否包含换行符响应速度突然变慢触发了限速IP被临时标记暂停爬虫等待5-10分钟降低请求频率只拿到首页翻页拿不到数据分页参数被签名校验检查翻页参数生成逻辑或改用页面渲染方式弹滑块验证请求频率过高或UA异常更换UA降低并发延长休眠时间iframe区域无数据页面未完全渲染改为networkidle等待策略这里面最容易忽略的是最后一个情况。我一开始以为返回的HTML里没有iframe内容是请求头的问题后来把Playwright的等待策略改掉之后才发现是加载时机问题。这种问题在没有真实浏览器调试的情况下非常难看透。5.3 这些数据能做什么、不能做什么采集数据只是第一步真正体现项目价值的是数据分析和可视化。我基于这批数据做了几个维度的分析效果不错这里简单说下思路价格分布用Matplotlib画出不同品类商品的价格分布直方图能直观看到二手商品的定价区间地区分布按卖家所在地做统计看出哪些地区二手商品供给最旺盛成色结构饼图展示“几乎全新/轻微使用痕迹/明显使用痕迹”的比例这个数据对买卖双方都有参考价值关键词关联统计标题中的高频词理解二手市场用户的搜索习惯。这些分析结果完全可以写进毕业论文、课程报告或面试作品集。但必须强调采集数据仅限于个人研究和学习不能用于商业变现、不能批量转售、不能涉及用户个人信息。这是爬虫项目的基本红线如果越界了再漂亮的技术实现都毫无意义。还有个实用的小经验项目代码里最好在采集前明确声明数据来源和采集目的代码注释不要写得太“攻击性”保持一个正常的学术研究姿态这也是对自己的一种保护。如果项目用于课程答辩建议在文档里专门写清楚数据采集策略和合规性说明这部分加分非常明显。我在实际运行这个项目的过程中最大的体会是爬虫项目的难点从来不是代码本身而是对整个系统节奏的控制。参数调优、延时设置、异常处理这些细节没有任何教材能给你标准答案只能靠一次次试错和耐心观察。如果你也在复现类似的爬虫项目我建议一开始就保持低频率低并发跑通全流程稳住之后再逐步优化速度不要一上来就追求数据量。稳才是爬虫项目的最高优先级。本文还有配套的精品资源点击获取