通過實例說明在scrapy中 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對數據庫的操作很簡單,我們簡單了解一下步驟:

  1. 建立數據庫連接
  2. 創建操作游標
  3. 寫sql語句
  4. 執行sql語句
  5. 如果執行的是查詢語句,則用fetch語句獲取查詢結果
  6. 如果執行的是插入、刪除等對數據庫造成了影響的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()

寫在最后:

  1. 代碼敲好后不要忘記在settings里開啟pipelines

  2. pymsql連接時默認的編碼是latin-1,所以在建立數據庫連接時會增加參數charset來修改編碼,要修改為utf-8的話得用charset=’utf8‘,而不是charset=’utf-8‘

  3. 這個網站有些問題,會時不時報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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM