scrapy parse()中 yield的作用分析
源https://www.jianshu.com/p/7c1a084853d8
開始前的準備工作:
1.MySQL下載:點我
2.python MySQL驅動下載:pymysql(pyMySql,直接用pip方式安裝)
3.全部安裝好之後,我們來熟悉一下pymysql模塊
import pymysql #創建鏈接對象 connection = pymysql.connect(host=‘127.0.0.1‘, port=3306, user=‘root‘, password=‘1234‘, db=‘python‘) #創建遊標 遊標用來進行查詢,修改等操作 cursor = connection.cursor()#定義sql語句 這裏的sql語法根據使用的數據庫不同會有一些小差別 sql = "SELECT * FROM python.text_info where text_title=‘test‘" #執行sql語句 返回受到影響的行數 cursor.execute(sql) #獲取sql語句執行後的返回數據 默認返回的數據類型為元組 #獲取所有返回 r = cursor.fetchall() #獲取一個返回 r = cursor.fetchone() #獲取至多三個返回 不足三個時返回所有 r = cursor.fetchmany(3) #其他的fetch方法可自行百度#將返回數據類型改為字典 cursor = connection.cursor(cursor=pymysql.cursors.DictCursor) #或者在創建連接對象時指定返回數據類型為字典 建議把返回類型修改為字典類型 connection = pymysql.connect(host=‘127.0.0.1‘, port=3306, user=‘root‘, password=‘1234‘, db=‘python‘, cursorclass=pymysql.cursors.DictCursor) #保存所做的修改 在連接關閉之前,如果你沒有調用下面的語句 #那麽,你之前的所有修改將不會被保存connection.commit() #關閉遊標 cursor.close() #關閉連接 connection.close()
一、確定items
我們要爬取的網站是:http://m.50zw.la
要爬取的是小說的信息,如圖:
所以items.py文件如下:
import scrapy class TextInfoItem(scrapy.Item): # name = scrapy.Field() text_name = scrapy.Field() text_author = scrapy.Field() text_type = scrapy.Field() text_status = scrapy.Field() text_latest = scrapy.Field() text_intro = scrapy.Field()
最後信息是要儲存到數據庫裏的,所以我們還得創建一個數據庫表。
- 第一步:在開始菜單裏找到MySQL Workbench,雙擊打開。MySQL Workbench是MySQL自帶的一個可視化管理工具
- 第二步:在 MySQL Workbench裏連接數據庫,並創建一個數據庫 python,然後再在剛剛創建的數據庫裏創建一個表 text_info
- 第三步:在 text_info表裏逐一添加 text_name,text_author 等屬性,類型全部設置為 varchar,大小除了 text_intro是 1000外,其他的全部設置為 50
MySQL的使用就不詳細講了。如果遇到問題,歡迎評論留言。
二、爬取信息
為了簡單,我們只爬取 50zw網站下的玄幻分類的小說信息。
代碼:
import scrapy from text_info.items import TextInfoItem class A50zwSpider(scrapy.Spider): name = ‘50zw‘ allowed_domains = [‘m.50zw.la‘] start_urls = [‘http://m.50zw.la/wapsort/1_1.html‘] #主站鏈接 用來拼接 base_site = ‘http://m.50zw.la‘ def parse(self, response): book_urls = response.xpath(‘//table[@class="list-item"]//a/@href‘).extract() for book_url in book_urls: url = self.base_site + book_url yield scrapy.Request(url, callback=self.getInfo) #獲取下一頁 next_page_url = self.base_site + response.xpath(‘//table[@class="page-book"]//a[contains(text(),"下一頁")]/@href‘).extract()[0] yield scrapy.Request(next_page_url, callback=self.parse) def getInfo(self, response): item = TextInfoItem() #提取信息 item[‘text_id‘] = response.url.split(‘_‘)[1].replace(‘/‘, ‘‘) item[‘text_name‘] = response.xpath(‘//table[1]//p/strong/text()‘).extract()[0] item[‘text_author‘] = response.xpath(‘//table[1]//p/a/text()‘).extract()[0] item[‘text_type‘] = response.xpath(‘//table[1]//p/a/text()‘).extract()[1] item[‘text_status‘] = response.xpath(‘//table[1]//p/text()‘).extract()[2][3:] item[‘text_latest‘] = response.xpath(‘//table[1]//p[5]/text()‘).extract()[0][3:] item[‘text_intro‘] = response.xpath(‘//div[@class="intro"]/text()‘).extract()[0] yield item
========yield用法解析1=========
【主】這裏我們通過 yield
來發起一個請求,並通過 callback
參數為這個請求添加回調函數,在請求完成之後會將響應作為參數傳遞給回調函數。
scrapy框架會根據 yield
返回的實例類型來執行不同的操作:
a. 如果是 scrapy.Request
對象,scrapy框架會去獲得該對象指向的鏈接並在請求完成後調用該對象的回調函數。
b. 如果是 scrapy.Item
對象,scrapy框架會將這個對象傳遞給 pipelines.py做進一步處理。
這裏我們有三個地方使用了 yield
第一個地方是:
for book_url in book_urls: url = self.base_site + book_url yield scrapy.Request(url, callback=self.getInfo)
這裏我們在循環裏不斷提取小說詳細頁面的鏈接,並通過 yield
來發起請求,並且還將函數 getInfo
作為回調函數來從響應中提取所需的數據。
第二個地方是:
#獲取下一頁 next_page_url = self.base_site + response.xpath(‘//table[@class="page-book"]//a[contains(text(),"下一頁")]/@href‘).extract()[0] yield scrapy.Request(next_page_url, callback=self.parse)
這裏是在爬取完一頁的信息後,我們在當前頁面獲取到了下一頁的鏈接,然後通過 yield
發起請求,並且將 parse
自己作為回調函數來處理下一頁的響應。
這有點像遞歸,不過遞歸是函數自己調用自己,這裏看起來好像是 parse
調用了自己,但實際上 parse
是由 scrapy框架在獲得響應後調用的。
yield
的地方在 getInfo
函數裏:def getInfo(self, response): item = TextInfoItem() ... ... item[‘text_intro‘] = response.xpath(‘//div[@class="intro"]/text()‘).extract()[0] yield item
這裏我們通過 yield
返回的不是 Request
對象,而是一個 TextInfoItem
對象。
scrap有框架獲得這個對象之後,會將這個對象傳遞給 pipelines.py來做進一步處理。
我們將在 pipelines.py裏將傳遞過來的 scrapy.Item
對象保存到數據庫裏去
三、將信息插入數據庫
python對數據庫的操作很簡單,我們簡單了解一下步驟:
- 建立數據庫連接
- 創建操作遊標
- 寫sql語句
- 執行sql語句
- 如果執行的是查詢語句,則用fetch語句獲取查詢結果
- 如果執行的是插入、刪除等對數據庫造成了影響的sql語句,還需要執行commit保存修改
貼上代碼:
import pymysql class TextInfoPipeline(object): def __init__(self): #建立數據庫連接 self.connection = pymysql.connect(host=‘127.0.0.1‘, port=3306, user=‘root‘, password=‘1234‘, db=‘python‘,charset=‘utf8‘) #創建操作遊標 self.cursor = self.connection.cursor() def process_item(self, item, spider): #定義sql語句 sql = "INSERT INTO `python`.`text_info` (`text_id`, `text_name`, `text_author`, `text_type`, `text_status`, `text_latest`, `text_intro`) VALUES (‘"+item[‘text_id‘]+"‘, ‘"+item[‘text_name‘]+"‘, ‘"+item[‘text_author‘]+"‘, ‘"+item[‘text_type‘]+"‘, ‘"+item[‘text_status‘]+"‘, ‘"+item[‘text_latest‘]+"‘, ‘"+item[‘text_intro‘]+"‘);" #執行sql語句 self.cursor.execute(sql) #保存修改 self.connection.commit() return item def __del__(self): #關閉操作遊標 self.cursor.close() #關閉數據庫連接 self.connection.close()
寫在最後:
-
代碼敲好後不要忘記在settings裏開啟pipelines
-
pymsql連接時默認的編碼是latin-1,所以在建立數據庫連接時會增加參數charset來修改編碼,要修改為utf-8的話得用charset=’utf8‘,而不是charset=’utf-8‘
-
這個網站有些問題,會時不時報404錯誤,所以在爬的過程中會報list index out of range,這是因為得到了錯誤的網頁,xpath找不到對應得路徑返回了空列表。這是正常現象,並不是代碼出問題了(當然,如果頻繁報錯最好是檢查一下代碼)
貼一張成功後的圖片:
==========yield解析2===========
1. 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要使用在循環中,這樣生成器才有使用的意義。
2. 對scrapy中使用yield循環處理網頁url的分析
首先,scrapy框架對含有yield關鍵字的parse()方法的調用是以叠代的方式進行的。相當於:
for n in parse(self, response):
pass
其次,python將parse()函數視為生成器,但首次調用才會開始執行代碼,每次叠代請求(即上面的for循環)才會執行yield處的循環代碼,生成每次叠代的值。如下方法:
def parse(self, response): # 具體處理邏輯:如,分析頁面,找到頁面中符合規則的內容(校花圖片),保存 hxs = HtmlXPathSelector(response) # 創建查詢對象 # 獲取所有的url,繼續訪問,並在其中尋找相同的url all_urls = hxs.select(‘//a/@href‘).extract() for url in all_urls: if url.startswith(‘http://www.xiaohuar.com/list-1-‘): yield Request(url, callback=self.parse) # 遞歸的找下去 print(url) # Scrapy框架開始執行spider,即是對parse()方法叠代的過程{for n in parse(self, response)}, # 首先程序會將第一個response對象分析提取需要的東西,然後提取該response中所有的urls進行循環處理 # 對urls循環處理過程中,首次執行到parse-for-yield處,會返回一個叠代值,即生成一個Request1 對象(其中定義了回調方法為parse); # 此時,第一次叠代結束。 # 第一次叠代過程中生成的Request1對象,即一個新的url請求,會返回一個新的response,然後框架會使用該response執行回調函數,進行另一個分支的叠代處理 # 分支叠代的程序處理完成,進行第二次叠代,會從yield的下一條語句開始,即print,然後繼續執行for循環,最後執行到yield,又會生 # 成一個request2 對象, # 生成request2 對象,相當於又開始了一個新的分支,這個分支處理完後返回一個對象後開始回到主程序 # 接下來,開始第三次叠代,又從yield後面的print開始執行..... # 最終,直到循環結束。
註:這裏有個疑問,主程序執行到yield後,是等到該次遞歸調用完全結束後(即第一次循環的url,它內部所有子url都處理完),才進行的第二次叠代嗎? 這可以實際測試下,最好子url不要與父url重復。
https://blog.csdn.net/heheyanyanjun/article/details/79199378
scrapy parse()中 yield的作用分析