ARTICLE DETAIL

资讯详情

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

pyspider爬虫框架入门:从环境搭建到完整项目实战

pyspider爬虫框架入门:从环境搭建到完整项目实战 刚接触 Python 爬虫那会儿我的第一反应是打开 requests 对着页面一顿猛写然后被翻页、去重、断点续抓、异常重试这些事反复折磨。后来换到 pyspider第一次打开它的 Web 控制台时说实话有点恍惚这玩意儿居然自带一个像任务管理器一样的图形界面能直接写代码、点运行、看抓取队列、调调试工具所有状态都摊在眼前。把我手里那个多页面的采集任务迁移过来之后维护成本直接降了一个量级。这篇博文就围绕“pyspider 库入门”展开我会从环境搭建、核心 API、到完整实现一个可运行的爬虫项目把关键设计讲透再附上一堆我实际踩过的坑尽量让零基础的同学也能一遍上手。pyspider 是一个基于 Python 的爬虫框架最大的特点就是“可视化 脚本化”你只需要继承一个 Handler 类在里面写几个方法它就能帮你把调度、抓取、解析、存储全串起来。它适合在数据采集阶段快速验证想法的个人开发者也适合小团队内部复用尤其适合那种目标站点多、结构简单、需要频繁调整策略的场景。1. 为什么值得学 pyspider1.1 pyspider 解决的核心痛点别看现在很多人一上来就推 Scrapypyspider 在“快速上手”和“可观测性”这两个维度上其实非常能打。Scrapy 是命令行工具、需要搭一堆配置新建一个爬虫之后想看抓取状态还得自己接 Telnet。而 pyspider 把整套东西搬到了浏览器里写了代码之后点一下 Run 就能调实时看到 requests、responses、队列堆积、异常信息这对初学爬虫的人太友好了。另一个痛点是任务调度的细节。自己用 requests 写循环免不了处理去重、重试、请求间隔、全局状态保存。pyspider 的 Scheduler 组件把这些全部接管了它自动去重 URL支持按优先级调度支持失败重试甚至能爬着爬着手动暂停和恢复。这就相当于把“能跑就行”的脚本升级成了“有状态、可管理、挂了能续上”的采集系统。1.2 和 Scrapy 的取舍对比很多人会在 pyspider 和 Scrapy 之间纠结我简单说下自己的使用感受。对比维度pyspiderScrapy上手门槛低Web UI 直接调试高需要理解项目结构和命令行可视化强自带控制台和监控面板弱基本靠日志分布式支持依赖组件较多配置略复杂基于 Scrapyd生态相对成熟自定义灵活性高写法自由高中间件机制完善维护状态项目基本停更社区活跃持续更新适用场景中小规模、快速迭代采集大规模生产环境、管道复杂任务我的建议是学习阶段或项目体量不大时pyspider 的体验会好很多它的代码组织方式能帮你快速理解“回调 消息”这套爬虫思维等真正需要上量、做分布式调度再迁移到 Scrapy 也不迟。迁移成本并不高因为核心解析逻辑都是 requests pyquery / BeautifulSoup换框架只是换壳。1.3 pyspider 的底层运行逻辑入门 pyspider 之前得先理解它的五个组件Scheduler调度器、Fetcher抓取器、Processor处理器、MQ消息队列和 Web UI。整个流程简单来说就是你在 on_start 方法里调用 self.crawl 发出首批 URL 消息MQ 投递给 SchedulerScheduler 决定谁先抓、要不要去重然后把任务发给 FetcherFetcher 请求完页面再把结果还给 ProcessorProcessor 执行你自己写的解析回调产生新的 URL 或保存数据。整套链路绕一圈一个爬虫就跑起来了。记住这个数据流很重要因为后面排错的时候你要能判断问题出在“没有发起调度”“请求失败了”还是“解析逻辑报错”这三个环节在 pyspider 里分别对应不同的呈现状态。刚开始看到几百个“active”任务不要慌这代表调度器正在按节奏批量发送任务。2. 环境准备与安装2.1 环境要求与版本兼容性pyspider 对 Python 版本的关系非常微妙这是新手最容易翻车的点。由于项目中部分代码使用了async作为参数名而 Python 3.7 之后async成了保留关键字所以高版本 Python 直接装 pyspider 会报语法错误。如果你用的是 Python 3.6 或更老版本直接pip install pyspider就行如果和我一样在往前看、用了 Python 3.8必须做一些代码兼容处理。我的建议是给 pyspider 单独建一个虚拟环境无需纠结本机默认解释器。用 Anaconda 或者 Python 自带的 venv 都行装个 Python 3.6 版本运行最省事。如果一定要用高版本 Python请先找到 pyspider 的fetcher/tornado_httpclient.py和libs/utils.py把里面async字段全部批量替换成async_否则一启动就会抛SyntaxError。另外 requests、pycurl、Flask 等依赖也需要一并升级到兼容版本。2.2 快速安装与启动在虚拟环境里执行下面三步就能把基础的 pyspider 跑起来# 1. 创建并激活虚拟环境推荐 Python 3.6 python -m venv py36 source py36/bin/activate # Windows 是 py36\Scripts\activate # 2. 安装 pip install pyspider # 3. 启动 pyspider打开浏览器访问http://localhost:5000看到 pyspider 的仪表盘页面就说明安装成功了。第一次打开首页时可能显示一串空任务列表这很正常我们还没创建任何任务。启动时如果提示ValueError: Invalid configuration: Collected scheduler was mistakenly collected as a worker...通常是旧版本配置和 werkzeug 库版本不兼容。解决方式是调整依赖版本或者直接找到 pyspider 的webui/app.py去掉其中误配置的 scheduler worker 初始化代码。这类问题网上方案很多我后面会在排查章节展开。2.3 Web UI 界面速览pyspider 的 Web UI 是它区别于其他框架的杀手锏。页面左侧是任务列表显示每个爬虫的状态、抓取数、错误数点击某个任务进去就到了脚本编辑器里面可以改 Handler 代码、调试器、看运行日志还能直接看到当前的抓取进度。顶部的“Create”按钮用于新建爬虫项目“Rate”和“Burst”两个参数非常好用Rate 是每秒最大抓取频率Burst 是并发数跑目标站点时一定记得把这两个值调低一点既是守法公民也别把自己机房 IP 封了。调试面板是我最喜欢的功能选定一条消息记录点击“run”单步执行可以直接在网页里查看self.crawl发出的子请求、HTML 内容以及 response 状态码。遇到页面重定向或 ajax 加载的情况也可以换不同的 fetch_type 来模拟浏览器行为这部分实操性很强后面我会专门演示。3. 核心 API 与脚本结构3.1 最小可运行脚本在 pyspider 里写爬虫本质上就是复写固定的 Handler 类。下面这个脚本是入门最基础的五行代码from pyspider.libs.base_handler import BaseHandler class Handler(BaseHandler): def on_start(self): self.crawl(http://quotes.toscrape.com/, callbackself.index_page) def index_page(self, response): for item in response.doc(a[href^/tag/]).items(): self.crawl(item.attr.href, callbackself.index_page)on_start是任务启动时的入口self.crawl(url, callbackxxx)负责把 URL 投进调度队列等抓取完成后框架会用 response 对象调用你指定的callback。注意response.doc返回的是 pyquery 选择器对象语法和 jQuery 接近用英文逗号分隔多个 selector 时别漏了引号。继续沿着这种思路我们可以加一个解析函数来抓取页面标题。def detail_page(self, response): return { url: response.url, title: response.doc(h1).text(), }这时的完整流程就是启动后先抓列表页列表页解析出详情页链接再回调detail_page返回结构化数据。一个纯文本、翻页、详情三段式的爬虫总共不到 20 行代码就能写出来老手看完会心一笑新手也知道该往哪个地方填逻辑。3.2 config 装饰器与全局配置pyspider 允许你用装饰器的形式给回调函数预置抓取参数。最常见的写法是设置age和priorityconfig(age10 * 24 * 60 * 60) def detail_page(self, response): return { url: response.url, title: response.doc(h1).text(), }age表示这个任务的过期时间在它未过期之前即使重新调度Scheduler 也会直接忽略重复请求priority则是优先级权重数字越大越先抓。这个机制非常适合“关注更新”类场景列表页设置短 age详情页设置长 age这样重复抓取列表时不会重复抓已经缓存的详情内容能省下不少宽带和反爬风险。crawl方法还支持很多关键字参数比如fetch_type可以设置成js或splash来渲染动态页面method指定 POSTdata携带表单字段headers自定义请求头validate_cert控制是否校验证书。我在处理需要登录的站点时还会把 Cookie 塞进headers效果很好。3.3 消息传递与 on_resultpyspider 的on_result是收集数据的关键出口。如果你在回调函数里直接返回一个 dictpyspider 会自动把结果送到on_result方法里你可以在这里连接 MySQL、MongoDB、或者直接写日志。因为 pyspider 默认的 Web 页面只会显示一条条任务并不会自动落盘所以第一次跑通之后一定要记得自己实现结果保存逻辑否则数据只存在内存里重启就没了。消息机制上self.send_message(project, msg, url)可以在不同项目之间互发消息on_message负责接收这个机制适合拆分成多个独立任务时做数据汇总。比如 A 项目负责全站链接收集B 项目负责解析目标字段两者通过消息队列解耦。不过对入门而言单项目内使用self.crawl的回调链就已经够用。4. 完整实操抓取一个公开练习站点4.1 页面分析与目标定义为了演示稳定我选了专门用于爬虫练习的开放站点quotes.toscrape.com。这个站点上的数据是静态 HTML页面结构稳定不涉及登录验证和复杂反爬非常适合新手上手。我们要做的事情有三个抓取列表页上每一条名言的内容、作者和标签顺着列表页的分页按钮自动翻页把所有结果输出到一个 JSON 文件里作为爬虫的结果验证打开任意一个列表页可以看到名人名言结构是div.quote里面包含span.text名言内容、small.author作者、div.tags a.tag标签。底部有一个li.next a链接指向下一页。这些 selector 在后续解析核心代码中都会用到建议你先在浏览器里用开发者工具验证一下再复制。4.2 编写 Handler 代码在 Web UI 里新建一个项目或者直接在本地编辑器写好再粘贴进去。完整的入口代码如下import json from pyspider.libs.base_handler import BaseHandler class Handler(BaseHandler): retry_delay { : 5, } def on_start(self): self.crawl(http://quotes.toscrape.com/, callbackself.index_page) config(age60 * 60) def index_page(self, response): for quote in response.doc(div.quote).items(): yield { text: quote(span.text).text(), author: quote(small.author).text(), tags: [tag.text() for tag in quote(div.tags a.tag).items()], } next_url response.doc(li.next a).attr.href if next_url: self.crawl(http://quotes.toscrape.com%s % next_url, callbackself.index_page) def on_result(self, result): if result: with open(quotes_output.jsonl, a, encodingutf-8) as f: f.write(json.dumps(result, ensure_asciiFalse) \n)我把解析出的字段用yield返回这样每条名言记录会被独立作为一条 result 保存。config(age60 * 60)表示列表页一小时之内不重复抓取避免循环刷新任务时不断重新抓列表。翻页逻辑要注意拼接域名因为next链接是相对地址。4.3 在调试器里单步运行写完后点击右上角的“Run”按钮pyspider 会打开调试面板。调试面板默认从on_start开始你可以在左侧信息队列里看到当前生成的列表页 URL点击 message 后面的“run”框架会立刻发起请求并展示 HTML 和 response 状态。这里有个小技巧在 debugging 面板中你可以单步执行某些回调也可以直接把一个空闲 agent 拉到最大观察批量请求的实时情况。第一次跑的时候我发现队列里会出现大量状态为 403 的请求原因是quotes.toscrape.com对无 UA 的请求会直接拒绝。解决办法是在self.crawl里显式增加headersself.crawl( http://quotes.toscrape.com/, callbackself.index_page, headers{User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36} )加上 UA 后状态码恢复为 200队列开始正常吞吐。这个排查流程非常典型先看状态码判断是网络问题还是被反爬再对症下药。4.4 定时运行与关闭调度在 Web UI 任务列表里点击这个爬虫项目前面的“运行”开关爬虫才会真正进入定时调度模式。设置好 Rate 和 Burst 后pyspider 会按照你的配置持续运行。日常使用中我习惯把 Rate 设为 1、Burst 设为 3也就是每秒最多抓 1 个页面、同时最多并发 3 个请求这对小型站点非常温和。等到确认数据完整了把开关关掉即可未完成的任务会留在队列中等待下次启动。结果数据会实时追加到quotes_output.jsonl文件里。如果你想保存到数据库在on_result中改成连接 MongoDB 的代码直接insert_one即可。不要小看这个落盘逻辑它是整个爬虫工程化里最容易漏的一环很多新手在 Web UI 里看到数据记录就以为存下来了关了服务才发现字段全丢了。5. 常见问题与排查技巧实录5.1 Web UI 启动报错合集pyspider 的老龄化代码让它在新环境里错误奇多我把遇到过的启动报错整理成了速查表错误信息原因解决方式SyntaxError: invalid syntaxPython 3.7 的 async 关键字冲突将源码中的async批量替换为async_ValueError: Invalid configurationwerkzeug 版本过高降级 werkzeug 到 0.16.x 或修改 app.pyImportError: cannot import name etreelxml 安装异常pip uninstall lxml后重装KeyError: scheduler旧项目配置残留删掉 data 目录下的 json 状态文件重新初始化启动后页面白屏端口被占用或 WebUI 依赖缺失设置--port 5001换端口排查启动类问题最快的方式是看终端里的完整 Traceback而不是只看最后一行。大多数问题都是依赖版本不匹配造成的所以我会在项目目录下维护一个requirements.txt固定版本避免几个月后重新部署时环境变了对不上。5.2 抓取结果为空或乱码如果队列显示请求成功、但回调解析结果为空优先怀疑 HTML 结构和选择器不匹配。pyspider 对目标编码识别偶尔失手尤其是 GBK 或 GB2312 编码的站点此时要在回调里手动指定编码response.encoding gbk text response.text另外pyquery 选择器对动态渲染内容的支持有限。如果页面数据是通过 JS 异步加载的直接在response.doc里找不到对应节点。这时候使用fetch_typejs或接入 Splash 会更好但需要注意这会降低抓取速度。能用接口猜参数就不要优先走渲染既快又稳。5.3 任务堆积和无限重爬“任务状态一直 pending”“active 数量越堆越高”是高频问题。原因一般有两种一是回调抛异常导致任务不断重试二是产生了大量没有被正确回调的重复 URL。前者去 Web UI 的日志面板看异常栈即可后者可以在self.crawl里加上age和itag参数让 Scheduler 基于内容指纹或者时间窗口去重。itag是比age更精细的去重维度比如你要抓一个商品列表可以用“分类ID 页数”作为 itag这样同一分类同一页只会调度一次即使后面增加新的启动入口也不会重复抓。我平时判断一个任务是否应该继续重试会看队列里的错误信息是网络超时还是解析错误这两种的处理策略完全不同。5.4 部署与分布式扩展单个 pyspider 进程跑量有限项目规模变大后你可以把 Scheduler、Fetcher、Processor 拆到不同机器上用 Redis 作为消息队列连接。启动命令分别对应pyspider -c scheduler pyspider -c fetcher pyspider -c processor pyspider -c webui配合配置文件里的message_queue选项把这几个进程串起来就能组成一个简单的分布式爬虫集群。但说实话pyspider 的分布式部署文档相对陈旧踩坑成本不低如果业务并发真的到了每秒上千请求我更推荐一开始就评估 Scrapy Scrapyd 那套体系。6. 写在后面的实用建议6.1 我实际使用中的几个体会用了几年 pyspider我最大的体会是它特别适合“中短生命周期”的采集需求。比如一个活动页需要连续监控一周价格写个 Handler 丢进去挂一天任务完成直接删掉比维护一个大型 Scrapy 项目省心得多。它的 Web UI 在那个年代是极具前瞻性的设计放到现在看依然是很好的产品体验。如果你要拿 pyspider 做长期项目记住三点一是把解析逻辑写得尽量通用用数据配置驱动 URL 和 selector二是所有结果必须尽早落库别依赖 Web UI 的状态展示三是踩坑记录一定要沉淀成自己团队的文档因为很多报错信息在网上已经很难搜到有效答案了。6.2 下一步扩展方向入门之后可以尝试把 pyspider 和可视化监控工具结合定时抓取目标页面的关键指标一旦发生变化就触发告警。另外也可以把on_result收到的数据对接消息队列比如发到 Kafka 或 RabbitMQ再交给下游的大数据链路处理。这样虽然还用着 pyspider但已经把它的能力边界往前推了一大步。最后再分享一个小技巧如果你需要抓的页面结构特别简单、数据量也不大完全可以把 Handler 的代码量控制在 50 行以内但如果页面开始出现下拉加载、滚动翻页、验证码校验就不要继续在 pyspider 里死磕了及时切换到无头浏览器方案或者对接打码平台时间成本比什么都贵。希望这篇入门文章能让你少走一点弯路尽快跑出自己第一个 pyspider 项目。
返回列表