python yield && scrapy yield

yield 关键字用于生成器。 yield在scrapy中的运用。

1 python yield

1.1

参考
https://www.cnblogs.com/chenxi188/p/10848690.html

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:
      yield 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 异常,循环会正常结束。

理解上面的代码:加入一些输入输出.
根据下面的输出顺序可知,for循环调用fab这个生成器,fab中while循环开始。
fab中的循环运行一直到yield b,然后返回b,返回到for循环,for循环一次结束后,又返回到生成器(fab)刚刚中断的地方(yield之后)继续运行。一直运行到while一次循环结束,下一次循环碰到yield b,再次返回
见输出中
globalll n3 1
globalll n4 1

def fab(max):
    i, a, b = 0, 0, 1
    while i < max:
        print("globalll n4 %d" % (globalll))
        print("b %d i %d" % (b, i))
        yield b
        a, b = b, a + b
        print("a %d b %d i %d" % (a,b, i))
        i = i + 1
        print("globalll n3 %d"%(globalll))

globalll=0

for n in fab(5):
    print("globalll n1 %d"%(globalll))
    print(n)
    globalll=globalll+1
    print("globalll n2 %d" % (globalll))


globalll n4 0
b 1 i 0
globalll n1 0
1
globalll n2 1
a 1 b 1 i 0
globalll n3 1
globalll n4 1
b 1 i 1
globalll n1 1
1
globalll n2 2
a 1 b 2 i 1
globalll n3 2
globalll n4 2
b 2 i 2
globalll n1 2
2
globalll n2 3
a 2 b 3 i 2
globalll n3 3
globalll n4 3
b 3 i 3
globalll n1 3
3
globalll n2 4
a 3 b 5 i 3
globalll n3 4
globalll n4 4
b 5 i 4
globalll n1 4
5
globalll n2 5
a 5 b 8 i 4
globalll n3 5
def ff(max):
   a,b = 0,1
   yield max  # yield不在循环中,这里已经到函数最后所以直接返回,相当于return
for n in ff(5):
   print n    # 输出:5

1.2 yield 迭代对象 生成器

参考了
https://pyzh.readthedocs.io/en/latest/the-python-yield-keyword-explained.html

生成器的优势是,数据不是存在内存中,只能读一次(读的时候生成)

可迭代对象
当你建立了一个列表,你可以逐项地读取这个列表,这叫做一个可迭代对象:

>>> mylist = [1, 2, 3]
>>> for i in mylist :
...    print(i)
1
2
3

mylist 是一个可迭代的对象。当你使用一个列表生成式来建立一个列表的时候,就建立了一个可迭代的对象:

>>> mylist = [x*x for x in range(3)]
>>> for i in mylist :
...    print(i)
0
1
4

所有你可以使用 for .. in .. 语法的叫做一个迭代器:列表,字符串,文件……你经常使用它们是因为你可以如你所愿的读取其中的元素,但是你把所有的值都存储到了内存中,如果你有大量数据的话这个方式并不是你想要的。

生成器
生成器是可以迭代的,但是你 只可以读取它一次 ,因为它并不把所有的值放在内存中,它是实时地生成数据:

mygenerator = (x*x for x in range(3))
for i in mygenerator :
... print(i)
0
1
4
看起来除了把 [] 换成 () 外没什么不同。但是,你不可以再次使用 for i in mygenerator , 因为生成器只能被迭代一次:先计算出0,然后继续计算1,然后计算4,一个跟一个的…

yield关键字
yield 是一个类似 return 的关键字,只是这个函数返回的是个生成器。

>>> def createGenerator() :
...    mylist = range(3)
...    for i in mylist :
...        yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)
0
1
4

这个例子没什么用途,但是它让你知道,这个函数会返回一大批你只需要读一次的值.

为了精通 yield ,你必须要理解:当你调用这个函数的时候,函数内部的代码并不立马执行 ,这个函数只是返回一个生成器对象,这有点蹊跷不是吗。

那么,函数内的代码什么时候执行呢?当你使用for进行迭代的时候.

现在到了关键点了!

第一次迭代中你的函数会执行,从开始到达 yield 关键字,然后返回 yield 后的值作为第一次迭代的返回值. 然后,每次执行这个函数都会继续执行你在函数内部定义的那个循环的下一次,再返回那个值,直到没有可以返回的。

如果生成器内部没有定义 yield 关键字,那么这个生成器被认为成空的。这种情况可能因为是循环进行没了,或者是没有满足 if/else 条件。

2 yield 与 scrapy

参考
https://www.cnblogs.com/chenxi188/p/10848690.html
https://www.oschina.net/question/2254016_238539
https://www.zhihu.com/question/30201428

scrapy中的yield还是生成器。
通过 yield 来发起一个请求,并通过 callback 参数为这个请求添加回调函数,在请求完成之后会将响应作为参数传递给回调函数。 scrapy框架会根据 yield 返回的实例类型来执行不同的操作:
a. 如果是 scrapy.Request 对象,scrapy框架会去获得该对象指向的链接并在请求完成后调用该对象的回调函数。
b. 如果是 scrapy.Item 对象,scrapy框架会将这个对象传递给 pipelines.py做进一步处理。

先说scrapy的spider:
Scrapy为Spider的 start_urls 属性中的每个URL创建了 scrapy.Request 对象,并将 parse 方法作为回调函数(callback)赋值给了Request。

Request对象经过调度,执行生成 scrapy.http.Response 对象并送回给spider parse() 方法。

比如下面,其中parse为默认的方法。

class DmozSpider(scrapy.Spider):
    name = "dmoz"
    allowed_domains = ["dmoz.org"]
    start_urls = [

    def parse(self, response):

当parse中使用了yield,parse就会被当成一个生成器。

1. 因为使用的yield,而不是return。parse函数将会被当做一个生成器使用。scrapy会逐一获取parse方法中生成的结果,并判断该结果是一个什么样的类型;
2. 如果是request则加入爬取队列,如果是item类型则使用pipeline处理,其他类型则返回错误信息。
3. scrapy取到第一部分的request不会立马就去发送这个request,只是把这个request放到队列里,然后接着从生成器里获取;
4. 取尽第一部分的request,然后再获取第二部分的item,取到item了,就会放到对应的pipeline里处理;
5. parse()方法作为回调函数(callback)赋值给了Request,指定parse()方法来处理这些请求 scrapy.Request(url, callback=self.parse)
6. Request对象经过调度,执行生成 scrapy.http.response()的响应对象,并送回给parse()方法,直到调度器中没有Request(递归的思路)
7. 取尽之后,parse()工作结束,引擎再根据队列和pipelines中的内容去执行相应的操作;
8. 程序在取得各个页面的items前,会先处理完之前所有的request队列里的请求,然后再提取items。
9. 这一切的一切,Scrapy引擎和调度器将负责到底。

当parse中有多个yield就可以同时获得页面的内容和url。比如下面的代码,先是yield"返回"了scrapy.Request,返回后就调用scrapy.Request,结束后,继续执行最开始的yield item 返回了网页内容的生成器给pipeline。
暂时的理解是这样做生成了很多生成器而不是数据直接存在内存中,最后通过item生成器把数据给到pipeline,比较适合爬虫的大量数据。(直接递归就会有大量数据在内存)

def parse(self, response):
    #do something
    yield scrapy.Request(url, callback=self.parse)

    #item[key] = value
    yield item

不用yield写一下parse就可以理解。

def parse(self, response):
    result_list = []
    for h3 in response.xpath("//h3").extract():
        result_list.append(MyItem(title=h3)

    for url in response.xpath("//a/@href").extract():
        result_list.append(scrapy.Request(url, callback=self.parse))

    return result_list

区别在于用了yield的函数会返回一个生成器,生成器不会一次把所有值全部返回给你,而是你每调用一次next返回一个值。for已经调用了next()
next()函数作用可以在网上查
一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。

3其他

上面是一些简单的了解,还想继续了解可以查看
https://docs.python.org/3/reference/expressions.html#yieldexpr

建议阅读python官方tutorial的class那章关于iterators和generators的那几节

https://www.jianshu.com/p/d09778f4e055

posted @ 2020-08-11 21:08  Erio  阅读(970)  评论(0编辑  收藏  举报