跳转至

14.4 爬虫:Scrapy 框架

内容梗概

前面三节课演示的单文件爬虫只能"小打小闹",本课引出工程化方案:Python 最有名的爬虫框架 Scrapy。老师先分析单文件写法的四个短板(不会从结果中发现新链接、无多线程、无去重、无模块化),再对照官网架构图讲解 Scrapy 的引擎、调度器、下载器、Spider、Item Pipeline 与中间件各自的分工。随后实操:pipenv 安装 Scrapy、scrapy startproject 建项目、讲解各配置文件(scrapy.cfg、settings.py)的含义,并以知乎话题精华为例写好完整 Spider——用生成器 yield 返回 Item 和新 Request,处理分页,再通过回答→问题的跳转抓取每个热门问题的前 10 条回答,最后由 Item Pipeline 写入 CSV。结尾补充了未展开的高级话题(验证码、代理、模拟登录、cookie 池)并布置作业。

知识点详解

为什么需要框架(约 00:00 - 02:20)

单文件爬虫持续抓取大量数据的四个问题: 1. 不会从抓取结果中解析出新的待抓对象(如抓了知乎 data 后没有分析 paging 继续翻页); 2. 没有多线程/多进程,机器性能没利用起来; 3. 没有去重等机制; 4. 所有过程写在一个文件里,没有模块化(发送请求、解析 response 应拆成不同模块)。

Scrapy 架构(约 02:20 - 13:50)

  • Scrapy 是 Python 里功能最全、配置最灵活的爬虫框架(另有 PySpider 等)。
  • Spider(爬虫):针对每个网站单独写一个(B 站、优酷、爱奇艺各一个),因为各站结构不同;Spider 负责**解析** response(HTML 或 JSON),并提供种子 URL(入口链接)。
  • 数据流:Spider 给出种子 request → 交给**引擎(Engine)** → 引擎转给**调度器(Scheduler)(排队、去重、决定起多少线程)→ 调度器经引擎交给**下载器(Downloader)(可多线程并发下载)→ 返回的 response 扔回 Spider 解析。
  • 对应关系:Downloader ≈ requests/Selenium(发请求拿响应),Spider ≈ BeautifulSoup/json(解析)——本质逻辑和之前的简单写法一样,只是模块化了。
  • Spider 解析的结果有两种(约 08:00):
  • Item:提取到的一条数据,交给 Item Pipeline 处理;
  • Request:从结果中发现的新请求(新链接、下一页),重新扔给引擎→调度器,循环抓取。
  • Item Pipeline(约 10:10):对 Item 做进一步处理——字段清洗/预处理(如没抓到点赞数的补 0)、写入数据库等。
  • 中间件(Middleware)(约 11:40):Downloader 和 Spider 的一进一出都有中间件,对经过的 request/response/item 做统一预处理,例如给 request 加 header、改 User-Agent、加代理。

安装与项目创建(约 13:50 - 23:00)

  • 官方文档安装向导:pipenv install scrapy(也可用 conda)。
  • 坑 1:pipenv install -i 镜像源 的 -i 参数**不会**写入生成的 Pipfile,源没改过来会很慢——需手动把 Pipfile 里的源 URL 改成镜像,删掉 Pipfile.lock 让它重新生成,再 install(约 17:00)。
  • 坑 2:pipenv install 包 在 pipenv shell 之前之后执行都可以,但**执行脚本前必须 shell 进虚拟环境**,安装的库才生效(约 16:00)。
  • 安装后虚拟环境的 Scripts 目录下会多一个 scrapy.exe(就像 pip.exe、pipenv.exe),于是可用 scrapy 命令(约 20:50)。
  • 坑 3:VSCode 多窗口时容易进错虚拟环境(老师误留在 music163 环境里导致 scrapy 命令找不到)——注意终端提示的环境名(约 21:30)。
  • 创建项目:scrapy startproject 项目名(示例项目名"知乎"),自动生成目录结构。
  • 新建 Spider 两种方式:在 spiders/ 文件夹手动新建 py 文件;或命令 scrapy genspider 爬虫名 域名(名字和域名为必选参数)(约 25:20)。老师用 genspider 生成的文件放错了位置——官方文档建议手动创建,照做更稳妥。

项目文件解析(约 27:40 - 39:40)

  • scrapy.cfg:cfg = config。[settings] default = 知乎.settings 用"点"是因为把"知乎"文件夹当作 Python 包(含空的 __init__.py),settings 是包里的模块;[deploy] 部分指定部署目标,本地运行注释掉即可。
  • 运行逻辑:在含 scrapy.cfg 的目录运行命令 → Scrapy 读 cfg → 找到 settings 模块 → 读入全部配置(项目名、spider 位置、UA 等)。
  • settings.py 关键配置:
  • USER_AGENT:伪装成浏览器的 UA;
  • ROBOTSTXT_OBEY:是否遵守 robots.txt——演示设为 False,一般情况应遵守;
  • 并发请求数(默认 16,最大 32);
  • DOWNLOAD_DELAY:下载延迟,默认 0 表示疯狂抓取,"太不礼貌",演示设 3~5 秒(约 37:00);
  • ITEM_PIPELINES = {'知乎.pipelines.AnswerPipeline': 300}:指定处理 Item 的类,后面的数字是**优先级**,数字越小越早处理,可配多个流水线依次处理(约 38:00)。

Spider 编写(约 39:40 - 58:00)

  • 用框架后 Downloader 不用自己写——不用自己发请求拿 response,只需在 Spider 里写好拿到 response 后怎么处理。
  • start_urls 列表:爬虫入口,可放多个 URL。示例放入知乎话题接口(只写 include 参数;不写 offset/limit 时知乎会默认自动加上)。
  • parse(self, response):每个爬虫默认的解析函数——downloader 每抓到一个 response 就交给它处理,抓一个处理一个,循环往复。parse 的产出有 items 和 request 两种。
  • 示例 parse 逻辑:
  • json.loads(response.text) 把 JSON 串转字典;
  • 处理 data(回答列表):for 循环逐条处理;
  • 处理 paging:is_end 为 False 时取 next 链接生成新请求——用**生成器 yield**(不是 return)返回 scrapy.Request(url=next_url, callback=self.parse);callback 指定由谁处理该请求的 response,设为 parse 自身即可无限循环翻页(约 46:40 - 48:30)。
  • 进阶跳转逻辑(约 48:30 - 58:00):示例不直接存话题下的回答,而是 answer.get("target") → .get("question") 取出该回答所属问题的 id,用字符串 format 拼接"获取某问题下所有回答"的接口 URL(改 path 里的 question id 即切换问题,limit=10、offset=0 取前 10 条),发新请求;因该接口返回格式不同,需用**另一个解析函数**作为 callback 处理。
  • 字典 .get(key) 取不到时返回 None,可防 KeyError(示例处老师也少写了非空判断)。

Item 与 Item Pipeline(约 58:00 - 65:00)

  • items.py 中自定义 AnswerItem:只有属性没有方法的类,用 scrapy.Field() 定义六个字段(id、作者 id、作者名、作者性别、content、点赞数)。
  • 解析函数中对每个回答创建一个 item 填好字段,用 yield 扔给 Item Pipeline。
  • Pipeline 类(约 62:50):__init__ 只在爬虫启动时由 scrapy 命令执行一次——示例用内置 csv 模块初始化 answers.csv(utf-8、文本模式 w 而非二进制的 wb),csv.DictWriter 按 fieldnames 写好表头(writeheader)。
  • 此后每 yield 一个 item,框架就调用一次 process_item,把 item 作为一行写入 CSV。
  • CSV 是以逗号分隔的文本格式,类似表格的列分隔。

运行效果(约 65:00 - 67:00)

  • 运行 scrapy crawl 爬虫名(crawl 后必须跟 spider 名字,官方文档"how to run our spider"有写)。
  • 屏幕滚动 debug 信息,因设了 5 秒延迟爬得不快;Ctrl+C 中断后查看 answers.csv——抓到 100 多个回答的作者、内容、点赞数,可基于此做数据分析(如"美食话题点赞前十是谁")。

未展开的高级话题(约 67:00 - 73:40)

推荐崔庆才的爬虫书(GitBook 在线开源版,不用买),课堂内容只是冰山一角: - 验证码识别:模拟登录、爬取次数超限都会遇到,可能需要图像识别技术; - 代理:单机 IP 发 100 个请求会被封;用多台代理机器(B、C…Z)轮换发请求,服务器看到的是不同 IP,且每个 IP 间隔很久才访问一次,像人类正常速度; - 模拟登录; - Cookie 池:HTTP 是无状态协议,靠 cookie 识别登录用户;注册多个账号存下各自的 cookie 构成 cookie 池,配合代理使用——用户和 IP 都不一样,服务器无法封禁,否则正常服务也无法运行。这是对抗的逻辑。

作业(约 73:40 - 75:20)

  • 找一个自己感兴趣的站点,用 Scrapy 抓数据保存到 CSV,可参考知乎示例的存 CSV 逻辑,但**不能照抄知乎那份代码**(课程有源代码比对查重工具,相似度高对学生不利)。
  • 期限两周。

示例与演示

  • 项目搭建演示:mkdir webcrawler → pipenv install(踩镜像源没写进 Pipfile 的坑,手动改源重装)→ pipenv shell → pipenv install scrapy(装了 25 个依赖包)→ scrapy startproject 知乎 → scrapy genspider 知乎 zhihu.com(生成位置放错,说明手动创建更稳)。
  • 完整示例(课前写好,课程仓库 spiders/scrapy 目录下可签出):知乎话题精华爬虫——从话题接口出发翻页,对每条精华回答跳转抓取其所属热门问题的前 10 条回答,字段含回答 id、作者 id/名/性别、内容、点赞数,经 AnswerPipeline 写入 answers.csv。运行约一分钟抓到 100+ 条回答。
  • 作业要求:自选站点 + Scrapy + 存 CSV,禁抄示例,两周完成。

重点与难点

  • **理解框架数据流**比记 API 重要:Engine/Scheduler/Downloader/Spider/Pipeline 各管一段,parse 产出 item 或新 request。
  • parse 函数用 yield(生成器)而非 return 返回结果;新请求通过 scrapy.Request(url, callback=...) 构造,callback 指定解析函数,不同格式的响应要用不同的解析函数。
  • 配置三处易错:scrapy crawl 后必须跟 spider 名;pipenv -i 参数不写入 Pipfile;运行脚本前必须 pipenv shell。
  • 礼貌爬取:DOWNLOAD_DELAY 设延迟、遵守 robots.txt(演示除外)、控制并发。
  • Item Pipeline 优先级数字越小越先执行;__init__ 只执行一次(适合打开文件),process_item 每条执行一次。
  • CSV 是文本文件,打开用 w 不用 wb。

关联内容

  • 直接前置:14.1(requests+bs4 静态页)、14.2(Selenium 动态页)、14.3(知乎接口分析——本课 start_urls、接口参数、json 解析全部复用)。
  • 基础课程关联:模块与包(__init__.py、包.模块 的点号语法)、生成器(yield)、字典 get、字符串 format、csv 模块、虚拟环境。
  • 后续:数据分析章节会用到本课抓取的 CSV(如统计点赞前十);14.4-错误排查课专门解答 scrapy crawl 目录错误和 pip 安装 Scrapy 编译报错两个高频问题。