ARTICLE DETAIL

资讯详情

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

pyspider入门教程:WebUI可视化爬虫实战与避坑指南

pyspider入门教程:WebUI可视化爬虫实战与避坑指南 pyspider这名字在爬虫圈里算是老朋友了哪怕现在新工具层出不穷它那个自带可视化操作界面的独特设计依然让很多初学爬虫的朋友念念不忘。我最初接触pyspider的时候还是因为项目里要快速抓一批分类信息网站的列表页那时候对Scrapy的部署还不太熟pyspider的WebUI能让我在浏览器里直接写脚本、点运行、看结果确实省了不少事。今天这篇就围绕pyspider库入门把它的核心组件、脚本编写方式、WebUI调试流程和实战中容易踩的坑一次性讲透。不管你之前有没有写过爬虫只要对Python基础语法有了解这篇文章都能当作一份比较顺手的入门参考。1. pyspider能做什么先搞懂它的核心价值在动手敲代码之前有必要先理解pyspider在设计上和普通爬虫脚本、Scrapy这类框架到底有什么不同。只有搞明白了它的思路后面写脚本、排查问题才会顺手很多。1.1 你有没有遇到过这种爬虫烦恼写爬虫最常见的流程是requests拿页面、正则或者XPath提取数据、循环翻页继续爬。这种脚本写起来很直接但真正放到线上跑几天就会发现痛点很多。比如目标网站偶尔返回超时程序卡住了怎么办某个页面解析规则写错了导致大量数据丢失怎么定位是哪个URL出了问题爬虫跑到一半挂了下次能不能从断点继续自动跳过已经抓过的链接。这些问题在简单的脚本里都很难优雅解决。你需要自己去搞超时重试、URL去重、失败任务的记录和恢复、线程并发调度这些活既繁琐又容易出错。pyspider的定位恰好就是把这些爬虫通用能力全部内置进框架里请求调度、任务去重、失败重试、数据存储、Web监控界面全都打包好了你只需要专注写页面解析逻辑。1.2 pyspider的核心架构和设计思路pyspider的架构可以拆成四个角色scheduler调度器、fetcher抓取器、processor处理器、result worker结果处理器。四个角色之间通过消息队列通信各干各的事互不阻塞。scheduler负责维护待抓取任务队列决定哪个URL在什么时间被抓取同时负责去重、定时触发、失败重试、优先级调度。fetcher负责真正发起HTTP请求拿到HTML或JSON内容然后把响应丢给processor。processor运行你写的Python脚本解析页面、提取链接和数据把新发现的URL提交给scheduler把结构化数据传给result worker。result worker负责把最终数据输出到你指定的地方比如写数据库、存JSON文件默认会打印到WebUI的Results页面。这个架构带来的好处很明显抓取、解析、调度完全异步解耦。某个页面解析特别慢不会卡住整个爬虫调度器可以同时管理大量任务队列。这也是为什么pyspider即便很多年不更新依然有一批忠实用户拿它做中大规模垂直站点的爬取。1.3 它适合什么样的使用场景在我实际使用下来pyspider最适合这几类场景。第一类是中小规模的垂直站点爬取。目标站点的页面结构相对规律比如新闻站、博客站、商品分类页、招聘信息、分类信息网站用pyspider的脚本模型可以很快出活。第二类是站点数量多、需要频繁调整解析规则的场景。因为脚本可以直接在WebUI里改并立即生效不用重新发布代码运维成本很低。第三类是自己想学爬虫框架、希望理解调度器如何管理URL任务的新手。不过pyspider也有不擅长的地方。它本身不支持分布式的直接扩展虽然可以通过部署多个实例配合消息队列来扩展但配置复杂度会高很多。如果是要爬取海量数据的超大型项目、或者需要深度定制底层抓取逻辑Scrapy或是基于协程的新一代爬虫框架可能更合适。所以在选型的时候先想清楚自己的真实需求不要盲目跟风。2. 安装与启动先把环境跑起来这个部分看起来很简单但我在帮朋友排查的时候发现新手在安装启动这一步摔跟头的概率反而很高。很多问题并不是代码写错而是环境没弄对。2.1 Python版本和依赖安装先提醒一句pyspider的官方版本对Python 3.6以下的兼容性比较好如果你用的是Python 3.8以上版本直接pip安装后启动很可能会遇到一个关于async的语法报错原因是pyspider依赖的一个库在Python 3.8以后把async变成了保留关键字。解决办法有两个一是使用Python 3.6或3.7环境来跑二是修改pyspider源码里的一个文件把所有async函数名改成其他名字比如asynctask。我更建议直接用Python 3.6或3.7的虚拟环境省心。安装命令很简单pip install pyspider装完之后在命令行输入pyspider如果一切正常程序会启动一组服务默认监听本机的5000端口。打开浏览器访问http://localhost:5000就能看到pyspider的WebUI界面。这一步能看到界面说明五套服务都跑起来了。2.2 启动后出现常见报错怎么解决很多人在pyspider启动阶段会遇到一个关于pycurl的报错。pyspider默认会尝试加载pycurl来做底层抓取但Windows环境下pycurl的安装经常出问题。如果你只是本地开发调试这个报错可以不用理会pyspider会自动回退到普通的urllib库来抓取功能上差别不大。如果你确实需要pycurl可以在其官网下载对应的wheel包离线安装。另一个常见问题是默认端口被占用。启动命令支持修改端口比如pyspider -p 5555这个参数会在很多场景用得上我一般一台机器上跑多个pyspider实例时会用不同端口区分项目。2.3 认识WebUI的四个核心页面pyspider的WebUI是它的灵魂入门阶段你必须先把这个界面上的东西看懂否则后续写脚本调试会一头雾水。顶部导航栏分别指向四个主要页面Dashboard仪表盘展示所有爬虫任务的状态包括运行中、待抓取、失败任务数是整个pyspider的总控台。Active Tasks活动任务展示当前正在抓取解析的任务队列可以看到每个任务的URL、状态、优先级、已尝试次数。Results结果页展示已抓取解析后的数据结果默认以列表形式展示每条数据。Scripts脚本管理存放和编辑所有爬虫脚本可以新建、修改、删除任务。这四个页面对应了pyspider的四个核心能力任务管理、实时任务监控、数据查看、脚本编辑。第一天用的时候把鼠标在这几个页面之间来回点几遍基本就能建立直觉认知。接下来我详细讲一下脚本管理页面因为这是和代码打交道最多的地方。3. 核心概念与脚本模型吃透这三个回调函数pyspider的脚本逻辑围绕一个继承了BaseHandler的类展开类里的方法会在对应的事件时机被框架自动调用。理解了这几个方法你就理解了pyspider脚本的全部逻辑。3.1 从on_start到index_page再到detail_page一个最基础的pyspider脚本结构长这样#!/usr/bin/env python # -*- encoding: utf-8 -*- from pyspider.libs.base_handler import BaseHandler class Handler(BaseHandler): crawl_config { itag: v1, timeout: 20, max_retries: 3, } def on_start(self): self.crawl(http://example.com/news/, callbackself.index_page) def index_page(self, response): for item in response.doc(a[href^http://example.com/news/]).items(): self.crawl(item.attr(href), callbackself.detail_page) def detail_page(self, response): return { url: response.url, title: response.doc(h1).text(), content: response.doc(.content).text(), }on_start是任务的起点爬虫启动时会被调用一次这里一般填写种子URL也就是第一个要抓的页面。self.crawl()是核心方法告诉调度器“我需要抓这个URL抓完之后用哪个回调函数解析”。index_page通常用来处理列表页或者导航页。它接收response对象里面封装了服务器返回的页面内容。通过response.doc可以直接使用类似jQuery的语法解析页面选择节点、提取属性。我在index_page里做的事很简单找出页面中所有新闻链接逐个交给self.crawl并指定回调函数为detail_page。detail_page负责处理内容页把解析后的数据作为一个字典返回。这个返回值会自动被result worker接收最终显示在Results页面。上述代码中我提取了标题和正文内容。这三个回调函数构成了pyspider中最核心的循环从入口URL出发在列表页发现新链接把新链接推给内容页解析解析后返回数据。理解了这个闭环基本就理解了pyspider的脚本模型。3.2 response对象的常用操作写pyspider脚本时response对象是你打交道最多的东西。它会自动封装好HTTP响应、最终的URL、状态码和页面内容。我常用的属性有这些response.url当前页面的最终URL。如果网站发生了302跳转这里的值就是跳转后的地址。response.status_codeHTTP状态码一般在抓取失败时需要查看。response.doc解析后的PyQuery对象可以直接用CSS选择器提取节点。response.text页面的文本内容。response.json如果返回的是JSON数据直接用这个属性解析成Python字典。response.doc是pyspider最方便的地方。比如要提取一个链接的href和文本可以这样写for a in response.doc(a).items(): href a.attr(href) text a.text()items()会遍历所有匹配的节点每个节点支持继续用CSS选择器查询子节点。这种链式写法在写爬虫规则时效率很高。3.3 自定义callback链的妙用三个回调函数只是最基础的用法。pyspider允许你在任意回调里继续提交新请求形成一个多级回调链。比如有的网站列表页和详情页之间还隔着一个中间跳转页或者详情页中有分页内容都可以用这个方法链搞定。def index_page(self, response): for item in response.doc(.list a).items(): self.crawl(item.attr(href), callbackself.middle_page) def middle_page(self, response): # 某些页面经过一段JS跳转后才到真实地址 real_url response.doc(#redirect-url).text() self.crawl(real_url, callbackself.detail_page)这种多回调链的思路可以处理非常复杂的页面关系。同时self.crawl还支持传递自定义参数例如self.crawl(url, callbackself.detail_page, save{category: news})通过save参数可以把当前页面的上下文信息一并交给下一个回调在detail_page里用response.save[category]取出来。这个功能在做数据归类时特别实用。4. WebUI操作与任务调度从创建到调试的全流程pyspider WebUI本身就是一个完整的爬虫管理平台。以前写爬虫总是在终端和代码编辑器之间来回切换现在所有操作都集成在浏览器里了。4.1 新建项目并填入脚本模板在Scripts页面点击Create按钮输入一个项目名称然后编写脚本。pyspider会自动生成一个包含on_start函数的模板。第一次调试时可以先不写完整解析逻辑只是把on_start里填入一个可以访问的URL点击界面底部的Run按钮看看请求是否成功。WebUI左侧是脚本编辑区右侧是调试操作区。Run按钮负责跑一次on_start或者当前选中的回调函数Follows按钮会显示当前回调提交的所有新的self.crawl任务Result按钮显示当前回调返回的数据这三个按钮是调试中的高频操作。4.2 理解任务状态只有五种状态pyspider的任务状态管理很有特色。每个URL在调度器中都有对应的任务状态WebUI中用不同的颜色和标签标记。这里列一个速查表状态含义常见原因TODO待抓取刚提交的新任务还未被调度器执行RUNNING抓取中或解析中fetcher正在请求URL或者processor正在执行回调SUCCESS成功页面抓取并解析完毕回调正常返回FAILED失败抓取超时、连接错误、解析异常等PAUSED暂停任务被手动暂停或限速理解这五种状态是排查问题的关键。如果遇到大量FAILED任务重点检查目标网站是否反爬、请求头配置是否正确、网络是否稳定。如果发现一堆TODO任务一直没有执行可能是调度器限制了并发或者任务队列里积压的任务太多。4.3 rate与burst参数控制并发抓取速率在WebUI界面右上角有rate和burst两个参数。很多入门教程对这两个参数提都不提但它们直接决定了爬虫抓取的速度。rate表示每秒请求数是长期平均速率burst表示突发流量上限。当rate1, burst3时表示平均每秒只发送1个请求但短时间最大可以突发到3个并发请求。实际使用中对普通站点建议先设置rate1, burst3观察目标网站的反应再逐步调高。把这个概念理解好可以避免因为抓取速度过快而触发反爬策略。调试时可以把rate设成0这样调度器不会限制速度任务会在短时间内迅速跑完适合快速验证解析逻辑。但确认逻辑没问题后一定要恢复合适的限速参数做一个有礼貌的爬虫。4.4 任务优先级与定时抓取self.crawl方法支持指定priority参数数字越大优先级越高。这个参数在处理某些关键页面时很有用比如发现了一个新的种子URL希望它比其他待抓取任务提前执行就可以设置一个较高的priority。另一个常用特性是self.crawl(url, callbackself.detail_page, auto_recrawlTrue)这个参数可以让pyspider在任务成功后根据一定的策略重新抓取。配合crawl_config里的itag参数可以实现增量抓取的效果。比如爬新闻网站时希望每小时自动检查首页是否有新文章这个机制比外部用cron定时触发整个爬虫要方便得多。5. 实战演练爬一个简单的分页站点理论讲了一大堆该上手了。我选一个非常常见的场景爬一个分页展示的文章列表站点每页有若干篇文章标题和链接点进去能看到正文。这类结构在各种内容型网站中几乎处处可见。5.1 实战场景定义与目标假设目标站点是某个示例博客站首页URL是http://example.com/news/文章详情页URL格式为http://example.com/p/12345.html。列表页有翻页链接下一页的URL格式为http://example.com/news/page/2/。目标是抓取所有文章标题、URL、正文内容以及该文章所属的页码区间。这个场景用来演示分页处理、列表解析、详情解析三项能力。5.2 完整脚本示例与详细说明下面是一份可以运行的脚本我把注释写详细一点#!/usr/bin/env python # -*- encoding: utf-8 -*- from pyspider.libs.base_handler import BaseHandler class Handler(BaseHandler): # 全局抓取配置 crawl_config { timeout: 15, # 每次请求的最长等待时间 max_retries: 3, # 请求失败最多重试次数 itag: v20250101, # 版本标记修改后可以触发增量更新 headers: { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), } } def on_start(self): # 从第一页开始抓取 self.crawl(http://example.com/news/, callbackself.index_page) def index_page(self, response): # 解析列表页中的文章链接 for a in response.doc(div.article-list a[href^http://example.com/p/]).items(): self.crawl(a.attr(href), callbackself.detail_page) # 处理翻页找到下一页按钮的链接 next_link response.doc(a.next:contains(下一页)) if next_link: self.crawl(next_link.attr(href), callbackself.index_page) def detail_page(self, response): return { url: response.url, title: response.doc(h1.article-title).text().strip(), content: response.doc(div.article-content).text().strip(), publish_time: response.doc(span.time).text().strip(), }on_start从第一页开始。index_page里先提取所有文章详情链接交给detail_page然后查找下一页链接并继续交给index_page处理。这里用了一个CSS选择器限定div.article-list a[href^http://example.com/p/]目的是过滤掉站内导航、广告等无关链接。这样一个循环接一个循环就能把整站文章全部抓完。详情页解析时返回的字典会成为一条数据。如果某些字段拿不到返回None也不会导致程序崩溃只是数据里对应字段为空。5.3 在WebUI上的完整调试步骤在实际操作中我会按照下面这个顺序来调试保证每步都确认没问题再进入下一步。第一步新建项目并粘贴脚本在on_start函数所在行点击Run按钮观察右侧Log窗口。确认请求的URL返回了200状态码。第二步点击Follows按钮查看index_page解析出的所有链接。如果发现列表没抓对检查CSS选择器是否匹配目标页面结构这一步是调试效率最高的位置因为所有链接都展示在界面前一眼就能看到问题。第三步选中某个详情页链接点击Run按钮。这一步会直接执行对应的回调函数并得到Result不需要真的重新发起请求。第四步确认Result输出的字典字段完整后点击Run大规模执行整站任务。调试过程中有个小技巧在index_page的self.crawl里临时加一个priority: 10参数可以让下一步需要调试的详情页面优先被执行避免在大量任务里翻找某一条链接。5.4 数据入库前的字段清洗建议pyspider的result默认只在WebUI的Results页面展示但实际项目中通常希望数据落到自己的数据库里。一种简单的做法是覆写on_result方法def on_result(self, result): if result: # 在这里执行数据清洗和入库操作 print(result)你可以在这里直接写MySQL、MongoDB的存储逻辑也可以把数据推送到消息队列。pyspider不限制你在这个方法里做什么但值得提醒的是不要在on_result里做耗时过长的操作否则会阻塞result worker。如果数据量很大建议先把结果写入MQ缓冲再异步交给下游存储服务。6. 常见问题与排查技巧这些坑我替你踩过了再顺的框架也有让你头疼的时候。这一节我把自己在pyspider实际使用中遇到的高频问题统一整理一下按照问题现象、可能原因、解决思路来写。6.1 页面能打开但解析不到数据这个问题的典型现象是在浏览器中明明能看到目标数据pyspider抓到的页面却一片空白或者缺少节点。原因八成是目标网站使用了JavaScript动态渲染数据并不是在HTML源码里直接给出的。pyspider支持通过PhantomJS抓取JavaScript渲染后的页面配置方法是在crawl_config里设置crawl_config { js_fetch_interval: 1, js_fetch_timeout: 20, phantomjs: True, }但这里要泼一盆冷水PhantomJS官方已经停止维护用它应付简单的JS渲染场景还可以遇到现代前端框架比如Vue、React渲染的复杂SPA应用依然可能抓取不到内容。遇到这种情况我通常改用直接调目标网站的接口API往往比生啃页面快得多。6.2 大量任务FAILED怎么定位任务失败无外乎网络层面、解析层面、反爬层面。首先在Active Tasks页面点开一条FAILED任务查看详细的错误信息。如果显示connect timeout说明网络不通或者目标站响应太慢可以调大timeout参数或者检查自己的网络环境。如果显示HTTP 403或HTTP 429说明目标站识别出爬虫身份或请求频率过高考虑修改请求头、增加down_time等待或者更换代理IP。比较隐蔽的失败原因是解析过程中抛了Python异常。pyspider会把异常信息记录在任务详情里。这类问题大多出在某个页面结构和预期不一致比如None.text()调用导致的AttributeError。解决办法是在解析方法里对节点做存在性判断例如title response.doc(h1).text() if title: title title.strip()这样即使节点不存在也只是数据为空不会拖垮整个任务。6.3 任务积压不执行怎么办明明提交了成百上千个任务但看Active Tasks却发现大部分处于TODO状态没怎么动。这通常是调度器的速率配置问题检查WebUI右上角的rate参数。如果rate设置得很小比如0.01平均每100秒才抓一个URL那任务自然会像蜗牛一样慢。如果想加速把rate设为较大的值或者设成0即可。另外pyspider默认的并发抓取数受内部配置影响如果你的服务器是低配机器同时运行大量任务时fetcher可能会成为瓶颈。这种场景下可以先把rate调低再观察CPU和内存占用逐步找出合适的一组参数。6.4 增量抓取与重复URL的处理pyspider在调度层面默认对相同URL做去重。同一个on_start重复执行相同URL不会创建重复任务。但如果你希望重新抓取同一批URL需要修改itag配置强制让调度器认为是新任务。比如脚本里itag设为v20250101更新站点上线后想全量刷新数据改成v20250102所有URL会被当成新任务重新入队。如果只是想针对部分URL强制重抓可以在self.crawl时追加额外的参数例如self.crawl(url, callbackself.detail_page, force_updateTrue)这样的任务不会被调度器去重逻辑拦截会直接重新抓取。6.5 脚本调试完但一重启就丢失pyspider的WebUI里的脚本默认存储在本地数据库中正常情况下不会丢失。但如果你在Run之前没有点击页面上的保存按钮或者浏览器缓存异常可能会遇到脚本丢失的情况。规避方法是写脚本时定期点击保存同时本地自己也留一份代码文件。pyspider的数据库文件默认存放在项目的data目录下备份整个目录就能备份所有脚本和任务记录。这里再分享一个更省心的习惯每次修改脚本后把关键版本同步到一个git仓库。这样即使WebUI内部数据出现了问题随时可以从代码仓库恢复。6.6 关于pyspider维护状态的一点个人体会pyspider确实已经很长时间没有大版本更新了它的作者目前的工作重心也不在这个框架上。因此遇到一些很新奇的JavaScript渲染、HTTP2协议、现代TLS指纹层面的反爬手段pyspider会显得力不从心。但它的核心设计理念仍然值得学习尤其是任务调度、状态管理、回调模型这些思路在很多新框架里或多或少都能看到影子。如果你只是做垂直站点的数据采集、内部系统爬取自己公司的页面、或者想尽快搞一个带监控界面的爬虫Demopyspider依然是非常顺手的选择。如果你确定要长期做大规模、强对抗的爬虫项目那可以把pyspider当入门再逐步过渡到Scrapy或者自研框架。就我个人而言每次用pyspider还是会习惯性打开WebUI看着那些任务从TODO变成SUCCESS心里特别踏实。它的简单直接恰好是很多复杂框架所缺失的品质。前面提到的那些代码和配置都是我实际项目中用过验证过的。你照着操作时如果哪一步卡住了先把任务状态和页面源码打开翻一翻八成问题都藏在细节里。最后再分享一个小技巧写完脚本正式跑全量之前先限制rate小范围抓取二三十个页面检查一下数据质量确认字段没有丢失、编码没有乱码再放开速度进入全量阶段。这一条习惯帮我避免过很多因为解析规则写错导致的数据事故。
返回列表