写爬虫这块,Scrapy算得上Python生态里绕不开的老牌框架。前后有太多人因为它“上手陡峭”而劝退,其实说穿了,你只要把“创建一个爬虫项目、写一个Spider文件、跑起来看到数据”这三步走通,后面所有高级玩法都是在这个基础上加东西。这篇就是Scrapy入门实战笔记,重点放在“创建爬虫”这件事上,把命令、目录结构、代码写法、运行调试整个链路过一遍。
这篇内容适合两类人:一是刚学完Python基础、想找个方向练手的同学;二是之前用过requests但发现写多页面抓取或者大规模采集时手忙脚乱,想换框架的开发者。不论你是哪一种,跟着这篇文章把一个能跑的爬虫创建出来,你就算正式迈进Scrapy大门了。
1. 写Scrapy之前,先建立几个基础认知
别急着敲命令,有两个概念想先聊清楚,我发现很多新手卡住的根源都在这里。
1.1 Scrapy到底替你做了什么
requests写爬虫的时候,流程是自己发请求、自己拿响应、自己解析、自己处理翻页、自己管失败重试。单页还好,一旦页数多、字段多,代码很容易变成一团乱麻。Scrapy的核心思路是把“爬虫”这件事拆成几个模块,每个模块只干一件事:
- Spiders:定义往哪爬、怎么解析
- Items:定义数据长什么样
- Pipelines:处理数据,比如清洗、去重、存库
- Middlewares:在请求和响应中间拦截、加工
- Engine和Scheduler:负责调度请求、控制并发
你写的核心代码其实就两部分:Spider(抓什么、怎么解析)和Pipeline(数据怎么处理)。剩下的框架全帮你兜住了。
用生活里的事打比方:requests是“你自己开车去买菜”,所有路线自己规划;Scrapy是“上了一辆公交车,你只需要管下车去哪站买什么菜”,车怎么跑你不用管。
这个认知到位了,后面看代码就不会觉得莫名其妙。
1.2 安装Scrapy时最容易踩的坑
安装本身一行命令的事:
pip install scrapy但很多人在这里碰壁,主要体现在两个地方。
一是Python版本。Scrapy对Python版本有要求,过老的版本(比如3.6以下)装新版Scrapy会报找不到对应whl包。建议直接用Python 3.8以上版本,省心。二是Windows环境编译问题。部分依赖包(比如twisted)在Windows上需要预编译的二进制包,如果安装过程报Microsoft Visual C++ 14.0 is required,说明你缺C++构建工具。最快的解法是去下载对应版本的.whl文件手动安装,或者直接装Anaconda,conda环境里依赖都是编译好的。
提示:装完之后在终端敲
scrapy version,能正常输出版本号就说明环境OK。这一步确认了我才建议继续往下走。
1.3 Scrapy项目的核心目录结构
用命令创建项目后,你会看到下面这堆文件和文件夹。初次接触的人容易懵,其实记住几个关键的就行。
myproject/ ├── scrapy.cfg ├── myproject/ │ ├── __init__.py │ ├── items.py │ ├── middlewares.py │ ├── pipelines.py │ ├── settings.py │ └── spiders/scrapy.cfg:项目配置文件,部署时要用,平时不用动items.py:定义你要提取的数据字段pipelines.py:写数据处理逻辑settings.py:全局配置,比如并发数、下载延迟、UA伪装spiders/:放你写的爬虫文件,一个网站一个文件,也可一个网站多个文件
新手阶段不用管middlewares.py,那是做反爬策略才用到的。先集中火力把spiders、items、pipelines、settings这四个文件弄明白就够起步了。
2. 创建项目的标准动作和背后原理
环境没问题了,接下来就是实操环节。这里我会把命令和使用原理一块讲了,免得你敲完不知道发生了什么。
2.1 startproject创建项目骨架
打开终端,进到你准备存放代码的目录,执行:
scrapy startproject bookspider这条命令会生成一个名为bookspider的项目文件夹。敲完之后你进入这个目录:
cd bookspider注意,后面所有scrapy命令都要在这个目录下执行,因为你得让Scrapy找到scrapy.cfg这个配置文件,否则它会提示找不到项目。
有一个细节容易忽略:项目名别用scrapy、test这种词,前者会和框架本身冲突,后者在某些工具链里也有歧义,尽量用语义化命名,比如bookspider、news_spider。
2.2 genspider创建第一个爬虫文件
光有项目骨架还不行,你得告诉Scrapy“我要爬哪个网站”。标准做法是:
scrapy genspider book books.example.com这条命令干了三件事:在spiders/目录下创建了book.py文件;自动填充了基础模板代码;顺便把allowed_domains设置成了books.example.com。
生成的模板代码长这样:
import scrapy class BookSpider(scrapy.Spider): name = "book" allowed_domains = ["books.example.com"] start_urls = ["https://books.example.com/"] def parse(self, response): pass我建议你现在就把start_urls改成实际要爬的入口地址,比如图书分类页的URL。改的时候注意URL必须带http://或https://前缀,不带的话Scrapy会当成相对路径处理,直接报错。
2.3 手写Spider文件 vs genspider模板选哪个
新手会纠结“到底用命令生成还是自己新建文件”。我的建议是:刚入门用genspider,写过几个项目之后,全部手写。
原因很简单。genspider生成的是模板,帮你把name、allowed_domains、parse方法这些必备要素都列好了,你只需要填空,容错率高。但等你熟练以后,你会发现模板生成的骨架其实挺死板的,比如有些爬虫入口不是多个URL而是需要从某个API拿,还有些爬虫不需要parse而是用start_requests方法,这时候手写一个Spider会更快。
无论用哪种方式,Spider类必须有的三个要素是:
name:爬虫唯一标识,必须在项目内唯一allowed_domains:限定抓取域名范围,防止爬到别的网站parse:响应处理入口,框架把下载好的网页传到这里
这三个少了任何一个,爬虫都没法正常工作。
3. 核心实操:从零写一个能跑的Scrapy爬虫
这一节是整个笔记的重点。前面建好项目、生成好爬虫文件,接下来就是往里面填逻辑。我会用“抓取图书列表页的书名、价格、评分”这个例子演示,整个流程走完你就知道Spider、Item、Pipeline三者是怎么配合的。
3.1 先定义数据模型:把Item写好
写爬虫之前先想清楚一个问题:你要提取哪些字段?这一步很多人偷懒跳过了,把数据直接用字典返回,短平快可以,但项目一旦复杂点就后悔。
Item的作用就是给数据一个明确的“结构”。比如抓图书信息,在items.py里这么写:
import scrapy class BookItem(scrapy.Item): title = scrapy.Field() price = scrapy.Field() rating = scrapy.Field()这里定义三个字段:title书名、price价格、rating评分。Item结构确定后,Spider里返回的数据都是一个模子刻出来的,后续Pipeline处理也好写。
有人觉得Item多此一举,直接yield {"title": xxx, "price": xxx}多省事。但在真实项目里,字段一多就容易手滑写错键名,而且Pipeline里引用item["title"]的时候,如果字典键写错了,报错信息会绕一大圈才能发现。用Item之后,字段缺失或者拼写错误当场就能暴露。
3.2 手写parse解析:选择器的第一个坑
写parse(self, response)的时候,优先搞清楚response里有什么。Scrapy的Response对象有两个高频方法:
response.xpath():用XPath路径提取节点response.css():用CSS选择器提取节点
以提取每本书的信息为例。假设页面里每本书都在<article class="product_pod">这个标签下,XPath写法是:
def parse(self, response): for book in response.xpath('//article[contains(@class, "product_pod")]'): item = BookItem() item['title'] = book.xpath('.//h3/a/@title').get() item['price'] = book.xpath('.//p[@class="price_color"]/text()').get() item['rating'] = book.xpath('.//p[contains(@class, "star-rating")]/@class').get() yield item这里有几个新手必踩的坑,我一个个说。
第一个坑:XPath开头忘点。看你写book.xpath('.//h3/a/@title'),那个点.不能丢。它的意思是“从当前节点往下找”,没有点就变成“从整个文档找”,等于你完全没用到循环里取出来的book节点,命中率会错得离谱。
第二个坑:忘了.get()。xpath()返回的是选择器列表,不是字符串。不调用.get()的话,你拿到的是一堆Selector对象,打印出来全是<Selector xpath="..." data="xxx">,就跟你点外卖只收到外卖袋没收到饭一样。.get()取第一个匹配结果,.getall()取所有,这个区分要牢牢记住。
第三个坑:属性提取和文本提取的表达式不一样。提取链接或属性用@属性名,比如@title、@href;提取文本内容用text()。混用基本拿不到数据。
3.3 翻页处理早期常见方案
能提取出第一页的条目不算完,真实场景都是几十上百页。Scrapy里处理翻页的思路很直接:在当前页面解析响应里找到“下一页”的链接,然后用response.follow()继续请求。
代码示例如下:
next_page = response.xpath('//li[@class="next"]/a/@href').get() if next_page: yield response.follow(next_page, callback=self.parse)response.follow()会自动处理URL拼接,你传进去的next_page即使是相对路径(比如page-2.html),它也知道拼到当前域名后面去。这一点相当贴心,如果自己手动拼URL还得用urljoin,容易出错。
同时,callback=self.parse表示下一页下载完之后继续交给parse处理,于是整个爬虫就在这个循环里一级一级地往下一页爬,直到没有下一页链接为止。
这个翻页写法是Scrapy入门的标志性动作。理解它之后,你就算是真的把Spider的“自动抓取”能力握在手里了。
3.4 start_requests方法的作用
默认情况下,Scrapy拿到start_urls列表后会逐个生成Request对象,下载完成后直接交给parse处理。但有些场景你需要在请求之前先做一些额外操作,比如:
- 需要登录后才能访问的页面,得先带Cookie
- 需要在URL里注入不同参数
- 需要动态修改请求头
这时候就需要覆盖start_requests方法:
def start_requests(self): for url in self.start_urls: yield scrapy.Request(url, callback=self.parse, headers={"User-Agent": "Mozilla/5.0"})你没覆盖的时候,Scrapy从start_urls生成请求也是一样的效果。覆盖之后可以自由控制callback、headers、cookies等参数。入门阶段不用急着用,但看到别人代码里有这个方法,心里要清楚它存在的意义。
4. 数据做完解析之后,怎么让它落地
很多教程讲到这里就结束了:yield item,控制台打印一下,完事。但真实项目里数据要存文件、进数据库,这就轮到Pipeline登场了。
4.1 Pipeline的启用逻辑与常见存储姿势
在pipelines.py里写一个类:
import json class BookPipeline: def process_item(self, item, spider): with open("books.jsonl", "a", encoding="utf-8") as f: line = json.dumps(dict(item), ensure_ascii=False) + "\n" f.write(line) return item注意,要让这个Pipeline生效,还得在settings.py里打开开关:
ITEM_PIPELINES = { "bookspider.pipelines.BookPipeline": 300, }那个数字300是这个Pipeline的执行优先级,数字越小越先执行。同一个项目可以配置多个Pipeline,比如一个Pipeline做数据清洗、一个做去重、一个做入库,就靠这个数字控制先后顺序。
我知道有人图省事,直接在Spider里把数据写文件,不通过Pipeline。这种做法对付一两个脚本还行,但如果你后面要同时跑多个Spider,每个Spider都写一份存文件的代码,重复代码就泛滥了。Pipeline的定位就是“数据落地统一出口”,应该在设计阶段就纳入考虑。
4.2 用配置直接导出CSV/JSON
如果只是本地跑一次,不需要写Pipeline那么重,Scrapy提供了一个更轻量的方式:运行爬虫的时候直接用-o参数指定输出文件。示例:
scrapy crawl book -o books.csv scrapy crawl book -o books.json框架会根据文件扩展名自动选择输出格式。这个方法做数据验证、快速测试的时候非常方便,几乎不写额外代码。但它的问题是:每次运行都会覆盖之前的文件。想追加数据或者需要复杂数据处理,还是得回到Pipeline。
4.3 settings.py里不调会后悔的配置项
分享几个我凡是写爬虫必调的配置,调完能省掉一堆琐碎问题。
ROBOTSTXT_OBEY = False这一项默认是True,意思是Scrapy会先读网站的robots.txt来决定能不能爬。某些网站robots写得比较粗,把不该禁止的路径也禁了,新手开着这个选项很容易碰到“页面明明能打开但爬虫就是抓不到数据”的诡异问题。学习期间先关掉它,但要明白:真正上线做采集时,尊重robots协议和网站访问规则是基本素养。
DOWNLOAD_DELAY = 1给请求加一个间隔时间,单位秒。不加这个的话,Scrapy默认并发跑起来非常猛,有可能给目标站造成压力,也不太道德。加了这个之后爬取速度会慢一些,但稳定很多,也降低被封概率。
DEFAULT_REQUEST_HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", }Scrapy默认的User-Agent是一个无比显眼的Scrapy/版本号,很多网站看到这个UA就直接拒绝访问。把它换成一个浏览器UA,是入门阶段最有用的反拦截手段。
4.4 运行爬虫的完整命令与输出观察
运行爬虫就一个命令:
scrapy crawl book控制台会滚动输出日志,包括抓取状态、响应状态码、item信息等。第一次看到控制台刷屏别慌,主要看两个指标:
200:代表请求成功item:代表解析并yield了一条数据
如果日志里出现大量404或者403,多半是URL写错或UA被识别了,优先检查这两处。想让日志精简一点,可以加--nolog参数,但调试阶段不建议,因为你得靠日志定位问题。
5. 调试是入门必修课:Scrapy Shell及其他实操心法
写了爬虫不代表能跑通,跑通了也不代表数据是对的。就我的经验,Scrapy入门约一半时间花在调试和排错上。这里分享几个能大幅加速调试的动作。
5.1 scrapy shell,比打印大法强十倍
写XPath最容易出问题,但你又不可能每次都把整个爬虫跑起来去验证,于是Scrapy提供了一个交互式调试工具—scrapy shell。用法:
scrapy shell "https://books.example.com/"它会拉取这个页面,然后给你一个交互式环境。你可以在里面测试XPath和CSS表达式:
>>> response.xpath('//article[contains(@class, "product_pod")]') >>> response.xpath('//p[@class="price_color"]/text()').get()直接把要写的表达式在shell里跑一遍,能拿到数据再写回爬虫文件。这个习惯能省掉大量“写完跑一遍、跑完发现拿不到数据、再回去改”的无效循环。
5.2 提取不到数据的三个排查方向
遇到“爬虫没报错但就是没数据”的情况,别急着怀疑代码,按照下面这个顺序排查:
第一,先用shell打开目标页面,确认能否拿到响应。如果提示403,看UA问题;如果超时,可能是网络限制。这一步排除“请求层面的问题”。
第二,检查XPath表达式。开发工具里按F12能看到XPath的位置,但很多工具给出的XPath是绝对路径,非常脆弱,比如/html/body/div[2]/div/div[1]/ul/li[1]这种。稍微多一层标签就挂了。尽量自己写基于属性或class特征的相对路径。
第三,检查页面内容是否由JavaScript动态渲染。如果网页源码里都不包含你要的数据,那说明数据是异步加载的,这时候单纯靠解析HTML就拿不到。解决办法通常是找到数据真实API接口,或者配合渲染工具处理。入门阶段先确认好自己爬的是静态页面。
5.3 了解Scrapy的运作流程,排查会容易很多
Scrapy的整个执行流程,我有一个极简版的理解方式:你的爬虫先产生Request,引擎把Request交给调度器排队,然后由下载器下载页面,网页响应再交回给你的Spider的parse方法,parse方法提取出Item或者生成新的Request,循环往复。
基于这个思路,排查问题时其实就在问自己一句话:数据到底是在哪一步丢的?是请求没成功、还是响应没解析对、还是item没传回Pipeline?一步步定位,比瞎改代码有效得多。
很多人用Scrapy一开始就扎到反爬策略、分布式这种高级话题里,我觉得大可不必。把基础链路先跑通,心里对整个流程有数,后面进阶才有抓手。
6. 收尾前,聊几个我后来才想明白的事
写爬虫这事情,代码本身不难,难的是对“数据获取”这件事有边界感。爬虫不是只要技术上能访问就算完,还需要考虑:网站有没有禁止爬取的声明、请求频率会不会影响对方服务、采到的数据用途是否正当。学技术的同时把这些边界意识一并建立起来,我个人觉得是入门阶段就该养成的习惯。
再分享一个实操细节:每次写新爬虫,先小规模验证,别一上来就全站开爬。可以先限制只抓一两页数据,确认字段、存储逻辑都正确之后,再放开翻页和并发。这个习惯帮我避了很多次“数据存了一大半才发现字段不对,白跑半小时”的低级错误。
另外,pipelines.py里的数据处理逻辑尽量写简单点。新手容易在Pipeline里堆砌各种功能,格式化、清洗、去重、入库全塞进去,结果一旦报错很难定位。先把原始数据存下来,后面再慢慢处理和迭代,这个思路对个人项目来说更务实。
这篇笔记的核心就是“创建爬虫”这个动作:从建项目、写Spider、解析页面、翻页处理到数据落地,整个过程跑通了,你就已经掌握Scrapy最常用的80%的能力。后面要做的更多是围绕实战去积累经验,遇到需求再学对应的模块,这比一口气啃完所有文档轻松得多。
下次再写项目,试着别用模板生成,直接手写一个Spider文件,你会发现对结构的理解比敲十次命令都管用。这一个坎翻过去,Scrapy的学习就算是真正入门了。