scrapy_yield详解
一、yield
的核心作用
1. 生成器(Generator)特性
• yield
是 Python 生成器的核心关键字,允许函数暂停执行并返回中间结果,后续可从暂停处继续执行。
• 在 Scrapy 中,这种特性被用来按需生成请求或数据,而不是一次性生成所有内容,节省内存。
2. 异步调度机制
• Scrapy 的引擎会循环处理 Spider 通过 yield
提交的请求或数据。
• 每当 yield
一个 Request
对象时,引擎会将其加入调度队列,等待下载器处理;当 yield
一个 Item
对象时,引擎会将其传递给 Pipeline 处理。
二、yield
在 Spider 中的典型用途
1. 生成请求(Request)
import scrapy
class MySpider(scrapy.Spider):
name = 'example'
start_urls = ['http://example.com']
def parse(self, response):
# 生成新的请求
next_page = response.css('a.next-page::attr(href)').get()
if next_page:
yield scrapy.Request(url=next_page, callback=self.parse)
• 关键点:yield scrapy.Request()
会将新请求加入调度队列,Scrapy 引擎会异步处理这些请求。
• callback
参数指定处理该请求响应的回调函数(默认为 parse
)。
2. 生成数据项(Item)
import scrapy
class MyItem(scrapy.Item):
title = scrapy.Field()
content = scrapy.Field()
class MySpider(scrapy.Spider):
name = 'example'
start_urls = ['http://example.com']
def parse(self, response):
# 提取数据并生成 Item
item = MyItem()
item['title'] = response.css('h1::text').get()
item['content'] = response.css('div.content::text').get()
yield item # 将数据传递给 Pipeline
• 关键点:yield item
会将数据项传递给 Item Pipeline,进行后续处理(如清洗、存储)。
3. 组合使用请求和数据
def parse(self, response):
# 提取当前页数据
yield MyItem(...)
# 生成下一页请求
yield scrapy.Request(...)
• 可以在同一个方法中混合 yield
请求和数据,Scrapy 会分别处理。
三、yield
的优势
- 非阻塞异步处理:
• 不需要等待一个请求完成后再处理下一个,适合大规模爬取。 - 内存高效:
• 按需生成请求和数据,避免一次性加载所有内容到内存。 - 代码简洁性:
• 避免手动管理复杂的回调链。
四、常见问题与注意事项
-
忘记
yield
:
• 如果直接return
一个Request
或Item
,Scrapy 将无法处理它,必须使用yield
。 -
生成器单次迭代:
• 生成器只能遍历一次,但在 Scrapy 中通常不会直接操作生成器,框架已处理了调度。 -
调试技巧:
• 如果发现请求未执行或数据未生成,检查是否漏掉了yield
。
五、完整示例
import scrapy
from myproject.items import ArticleItem
class NewsSpider(scrapy.Spider):
name = 'news'
start_urls = ['http://news.example.com']
def parse(self, response):
# 提取文章链接
for article_url in response.css('a.article-link::attr(href)').getall():
yield scrapy.Request(
url=response.urljoin(article_url),
callback=self.parse_article
)
# 分页处理
next_page = response.css('a.next-page::attr(href)').get()
if next_page:
yield scrapy.Request(url=response.urljoin(next_page), callback=self.parse)
def parse_article(self, response):
# 生成数据项
item = ArticleItem()
item['title'] = response.css('h1.headline::text').get()
item['author'] = response.css('div.author::text').get()
item['content'] = response.xpath('//div[@class="article-body"]//text()').getall()
yield item
通过合理使用 yield
,你可以构建出高效、可维护的 Scrapy Spider,轻松应对复杂的网页抓取场景。
六、回调函数
在进一步的请求yield scrapy.Request(url=response.urljoin(next_page), callback=self.parse)
中,Request里有两个参数,通过 yield
来发起一个请求,并通过 callback
参数为这个请求添加回调函数,在请求完成之后会将响应作为参数传递给回调函数,scrapy框架会根据 yield
返回的实例类型来执行不同的操作,a. 如果是 scrapy.Request
对象,scrapy框架会去获得该对象指向的链接并在请求完成后调用该对象的回调函数。b. 如果是 scrapy.Item
对象,scrapy框架会将这个对象传递给 pipelines.py做进一步处理。
生成器
yield 的作用就是把一个函数变成一个生成器(generator),带有yield的函数不再是一个普通函数,Python解释器会将其视为一个generator,单独调用(如fab(5))不会执行fab函数,而是返回一个 iterable 对象!
在for循环执行时,每次循环都会执行fab函数内部的代码,执行到yield b时,fab函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。参考实例如下:
def fab(max):
n, a, b = 0, 0, 1
while n < max:
# print b
yield b
# print b
a, b = b, a + b
n = n + 1
print(fab(5)) # 输出:<generator object fab at 0x00000000069D8A68>
for n in fab(5):
print n # 依次1,1,2,3,5
#对于含有yield的函数,外部要以迭代的方式调用,当函数执行结束时,generator 自动抛出 StopIteration 异常,表示迭代完成。
# 在 for 循环里,无需处理 StopIteration 异常,循环会正常结束。
def ff(max):
a,b = 0,1
yield max # yield不在循环中,这里已经到函数最后所以直接返回,相当于return
for n in ff(5):
print n # 输出:5
综上可知,yield要使用在循环中,这样生成器才有使用的意义。
那么迭代器与生成器又有什么区别呢?可以看我的这篇文章:
迭代器与生成器区别