title: python yield && scrapy yield
date: 2020-03-17 16:00:00
categories: python
tags: 語法
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的那幾節