持久化存儲操做分爲兩類:磁盤文件和數據庫。
而磁盤文件存儲方式又分爲:基於終端指令和基於管道html
Scrapy是經過 scrapy 命令行工具進行控制的。 這裏咱們稱之爲 「Scrapy tool」 以用來和子命令進行區分。 對於子命令,咱們稱爲 「command」 或者 「Scrapy commands」。python
改寫parse方法,讓方法返回值爲迭代器。mysql
class QiubaiproSpider(scrapy.Spider): name = 'qiubaipro' # allowed_domains = ['www.qiushibaike.com/text'] # 圖片等信息可能不屬於指定域名之下 start_urls = ['https://www.qiushibaike.com/text/'] # 注意修改默認協議頭 def parse(self, response): # 建議使用xpath來執行指定內容的解析(Scrapy已經集成好了xpath解析的接口) # 段子的內容和做者 div_list = response.xpath('//div[@id="content-left"]/div') # 存儲解析到的頁面數據 data_list = [] for div in div_list: author = div.xpath('./div/a[2]/h2/text()').extract_first() # './'表示解析當前局部div; a[2]表示第二個a標籤 content = div.xpath('.//div[@class="content"]/span/text()').extract_first() # './/'表示當前局部全部元素;@class匹配類屬性 dict = { 'author': author, 'content': content } data_list.append(dict) # parse方法的返回值必須是迭代器或空 return data_list
執行輸出指定格式進行存儲:將爬取到的數據寫入不一樣格式的文件中進行存儲。
經常使用文件格式:json、xml、csv、txt等。web
# scrapy crawl 爬蟲文件名稱 -o 磁盤文件.後綴 $ scrapy crawl qiubaipro -o qiubai.csv --nolog $ ls firstBlood qiubai.csv readme.md scrapy.cfg
scrapy框架中已經爲咱們專門集成好了高效、便捷的持久化操做功能,咱們直接使用便可。redis
想使用scrapy的持久化操做功能,咱們首先來認識以下兩個文件:sql
items.py:數據結構模板文件,定義數據屬性。存儲解析到的頁面數據 pipelines.py:管道文件。接收數據(items),進行持久化存儲的相關操做。
在爬蟲文件qiubaipro.py中引入了項目的items.py文件中的FirstbloodItem類。數據庫
# -*- coding: utf-8 -*- import scrapy from firstBlood.firstBlood.items import FirstbloodItem class QiubaiproSpider(scrapy.Spider): name = 'qiubaipro' # allowed_domains = ['www.qiushibaike.com/text'] # 圖片等信息可能不屬於指定域名之下 start_urls = ['https://www.qiushibaike.com/text/'] # 注意修改默認協議頭 def parse(self, response): div_list = response.xpath('//div[@id="content-left"]/div') # 存儲解析到的頁面數據 data_list = [] for div in div_list: author = div.xpath('./div/a[2]/h2/text()').extract_first() content = div.xpath('.//div[@class="content"]/span/text()').extract_first() # 一、將解析到的數據值(author和content)存儲到items對象(存儲前先聲明item屬性) item = FirstbloodItem() item['author'] = author item['content'] = content
注意:
1)解析的數據存儲到items對象前要先聲明item屬性。
2)items.py文件內容配置以下:json
import scrapy class FirstbloodItem(scrapy.Item): # 必須聽從以下屬性聲明規則 # name = scrapy.Field() # 聲明item屬性 author = scrapy.Field() # 存儲解析到的做者 content = scrapy.Field() # 存儲解析到的內容信息
qiubaipro.py文件中:bash
import scrapy from firstBlood.items import FirstbloodItem class QiubaiproSpider(scrapy.Spider): def parse(self, response): """省略代碼""" for div in div_list: """省略代碼""" # 二、yield將item對象提交給管道進行持久化存儲操做 yield item
class FirstbloodPipeline(object): fp = None def open_spider(self, spider): """ 該方法只會在爬蟲開始爬數據的時候被調用一次 :param spider: :return: """ print("開始爬蟲") # 在該方法中打開文件 self.fp = open('./qiubai_pipe.txt', 'w', encoding='utf-8') def process_item(self, item, spider): """ 接收爬蟲文件中提交的item對象,並對item對象中存儲的頁面數據進行持久化存儲 每當爬蟲文件向管道提交一次item,porcess_item方法就會執行一次 :param item: 表示接收到的item對象 :param spider: :return: """ # 取出Item對象中存儲的數據值 author = item['author'] content = item['content'] # 將item對象中存儲的數據進行持久化存儲 # with open('./qiubai_pipe.txt', 'w', encoding='utf-8') as fp: self.fp.write(author + ":" + content+ '\n\n\n') # 寫入數據 return item def close_spider(self, spider): """ 該方法只會在爬蟲結束時被調用一次 :param spider: :return: """ print("爬蟲結束") # 關閉文件 self.fp.close()
注意:
1)每當爬蟲文件向管道提交一次item,process_item方法就會執行一次,所以最後輸出的爬蟲結果只保存了最後一次打開寫入的數據,前面的數據均被覆蓋。
2)使用open_spider方法特性:只會在爬蟲開始爬數據的時候被調用一次。可解決屢次打開文件的問題。
3)使用close_spider方法特性:只會在爬蟲結束時被調用一次。可在爬蟲結束後關閉文件。數據結構
# Configure item pipelines # See https://doc.scrapy.org/en/latest/topics/item-pipeline.html ITEM_PIPELINES = { 'firstBlood.pipelines.FirstbloodPipeline': 300, # 優先級 }
$ scrapy crawl qiubaipro --nolog
1)建立爬蟲項目和爬蟲應用:
# 建立項目 $ scrapy startproject qiubaiDB # 切換到項目目錄,建立爬蟲應用 $ scrapy genspider qiubaiMysql www.qiushibaike.com/text $ scrapy genspider qiubaiRedis www.qiushibaike.com/text
2)settings.py配置文件(同pip):
# Crawl responsibly by identifying yourself (and your website) on the user-agent USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36' # 假裝請求載體身份 # Obey robots.txt rules ROBOTSTXT_OBEY = False # 不聽從門戶網站robots協議,避免某些信息爬取不到 # Configure item pipelines # See https://doc.scrapy.org/en/latest/topics/item-pipeline.html ITEM_PIPELINES = { 'qiubaiDB.pipelines.QiubaidbPipeline': 300, # 注意這裏須要取消註釋(不能替換) }
3)items.py中聲明屬性:
import scrapy class QiubaidbItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() # 聲明屬性 author = scrapy.Field() content = scrapy.Field()
# 登陸數據庫 $ mysql -uroot -p1234 # 建立qiubai數據庫 mysql> create database qiubai; mysql> use qiubai Database changed # 建立qiubai表 mysql> create table qiubai( -> author varchar(50), -> content varchar(1000) -> ); Query OK, 0 rows affected (0.13 sec)
# -*- coding: utf-8 -*- import scrapy from qiubaiDB.items import QiubaidbItem class QiubaimysqlSpider(scrapy.Spider): name = 'qiubaiMysql' # allowed_domains = ['www.qiushibaike.com/text'] # 圖片等信息可能不屬於指定域名之下 start_urls = ['https://www.qiushibaike.com/text/'] # 注意修改默認協議頭 def parse(self, response): # 段子的內容和做者 div_list = response.xpath('//div[@id="content-left"]/div') # 使用xpath來執行指定內容的解析 for div in div_list: # 經過xpath解析到的指定內容被存儲到了selector對象中 # 須要經過extract()方法來提取selector對象中存儲的數據值 author = div.xpath('./div/a[2]/h2/text()').extract_first() # './'表示解析當前局部div; a[2]表示第二個a標籤 content = div.xpath('.//div[@class="content"]/span/text()').extract_first() # './/'表示當前局部全部元素;@class匹配類屬性 # 建立item對象 item = QiubaidbItem() # 數據值寫入item對象中 item['author'] = author item['content'] = content # 提交給管道(循環幾回就提交幾回) yield item
import pymysql class QiubaidbPipeline(object): conn = None # 鏈接對象聲明爲全局屬性 cursor = None # 遊標對象 def open_spider(self, spider): print("開始爬蟲") # 鏈接數據庫 self.conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='1234', db='qiubai') # 注意端口是整數不是字符串 def process_item(self, item, spider): """ 編寫向數據庫中存儲數據的相關代碼 :param item: :param spider: :return: """ # 一、鏈接數據庫:open_spider() # 二、執行sql語句 sql = 'insert into qiubai values("%s", "%s")' % (item['author'], item['content']) self.cursor = self.conn.cursor() # 建立遊標對象 try: self.cursor.execute(sql) # 執行sql語句 # 三、提交事務 self.conn.commit() except Exception as e: # 出現錯誤的時候:打印錯誤並回滾 print(e) self.conn.rollback() return item def close_spider(self, spider): print("爬蟲結束") self.cursor.close() # 關閉遊標 self.conn.close() # 關閉鏈接對象
$ scrapy crawl qiubaiMysql --nolog # 查看數據庫 $ mysql -uroot -p1234 mysql> use qiubai mysql> select * from qiubai;
import scrapy from qiubaiDB.items import QiubaidbItem class QiubairedisSpider(scrapy.Spider): name = 'qiubaiRedis' allowed_domains = ['www.qiushibaike.com/text'] start_urls = ['https://www.qiushibaike.com/text/'] # 注意修改默認協議頭 def parse(self, response): # 段子的內容和做者 div_list = response.xpath('//div[@id="content-left"]/div') # 使用xpath來執行指定內容的解析 for div in div_list: # 經過xpath解析到的指定內容被存儲到了selector對象中 # 須要經過extract()方法來提取selector對象中存儲的數據值 author = div.xpath('./div/a[2]/h2/text()').extract_first() # './'表示解析當前局部div; a[2]表示第二個a標籤 content = div.xpath('.//div[@class="content"]/span/text()').extract_first() # './/'表示當前局部全部元素;@class匹配類屬性 # 1、建立item對象 item = QiubaidbItem() # 數據值寫入item對象中 item['author'] = author item['content'] = content # 2、提交給管道(循環幾回就提交幾回) yield item
import redis import json class QiubaidbPipeline(object): conn = None # 聲明全局鏈接對象 def open_spider(self, spider): print("開始爬蟲") # 鏈接redis數據庫 self.conn = redis.Redis(host='127.0.0.1', port=6379) def process_item(self, item, spider): """編寫向redis中存儲數據的相關代碼""" dic = { # dic中封裝item對象中獲取的頁面數據 'author': item['author'], 'content': item['content'] } dic_str = json.dumps(dic) # 轉爲字符串(執行時,要求轉化爲byte\string\number) # redis數據庫寫入 self.conn.lpush('data', dic_str) # 每一次獲取的值追加到列表當中 return item
redis的安裝部署和服務啓動方法詳見:
Redis介紹
$ scrapy crawl qiubaiRedis --nolog # 啓動redis客戶端 $ pwd /Users/hqs/redis-5.0.2 $ src/redis-cli 127.0.0.1:6379> lrange data 0 -1 # 返回列表中指定區間內的元素;START:0 表示列表的第一個元素;END:-1 表示列表的最後一個元素
若是最終須要將爬取到的數據一份存儲到磁盤文件,一份存儲到redis數據庫,一份保存在mysql數據庫中。
須要在管道文件中編寫對應平臺的管道類:
# Mysql版本 import pymysql class QiubaiMysqlPipeline(object): conn = None # 鏈接對象聲明爲全局屬性 cursor = None # 遊標對象 def open_spider(self, spider): print("開始爬蟲") # 鏈接數據庫 self.conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='1234', db='qiubai') def process_item(self, item, spider): """ 編寫向數據庫中存儲數據的相關代碼 :param item: :param spider: :return: """ # 一、鏈接數據庫:open_spider() # 二、執行sql語句 sql = 'insert into qiubai values("%s", "%s")' % (item['author'], item['content']) self.cursor = self.conn.cursor() # 建立遊標對象 try: self.cursor.execute(sql) # 執行sql語句 # 三、提交事務 self.conn.commit() except Exception as e: # 出現錯誤的時候:打印錯誤並回滾 print(e) self.conn.rollback() return item def close_spider(self, spider): print("爬蟲結束") self.cursor.close() # 關閉遊標 self.conn.close() # 關閉鏈接對象 # redis版本 import redis import json class QiubaidbPipeline(object): conn = None # 聲明全局鏈接對象 def open_spider(self, spider): print("開始爬蟲") # 鏈接redis數據庫 self.conn = redis.Redis(host='127.0.0.1', port=6379) def process_item(self, item, spider): """編寫向redis中存儲數據的相關代碼""" dic = { # dic中封裝item對象中獲取的頁面數據 'author': item['author'], 'content': item['content'] } dic_str = json.dumps(dic) # 轉爲字符串 # redis數據庫寫入 # lpush:從左往右添加元素。在key對應list的頭部添加字符串元素 self.conn.lpush('data', dic_str) # 每一次獲取的值追加到列表當中 return item # 文件保存 class QiubaiByFilesPipeline(object): """實現將數據值存儲到本地磁盤中""" fp = None def open_spider(self, spider): print("開始爬蟲") # 在該方法中打開文件 self.fp = open('./qiubai_pipe.txt', 'w', encoding='utf-8') def process_item(self, item, spider): # 取出Item對象中存儲的數據值 author = item['author'] content = item['content'] # 持久化存儲 self.fp.write(author + ":" + content+ '\n\n\n') # 寫入數據 return item def close_spider(self, spider): print("爬蟲結束") # 關閉文件 self.fp.close()
在配置文件中對自定義的管道類進行生效操做:
# Configure item pipelines # See https://doc.scrapy.org/en/latest/topics/item-pipeline.html ITEM_PIPELINES = { # 數值多少無所謂,數值大小表明優先級順序: 500>400>300 'qiubaiDB.pipelines.QiubaidbPipeline': 300, # redis 'qiubaiDB.pipelines.QiubaiMysqlPipeline': 500, # mysql 'qiubaiDB.pipelines.QiubaiByFilesPipeline': 400 # 文件 }
需求:將糗事百科全部頁碼的做者和段子內容數據進行爬取切持久化存儲。
需求分析:每個頁面對應一個url,則scrapy工程須要對每個頁碼對應的url依次發起請求,而後經過對應的解析方法進行做者和段子內容的解析。
$ pwd /Users/hqs/ScrapyProjects $ scrapy startproject qiubaiByPages New Scrapy project 'qiubaiByPages', using template directory '/Users/hqs/anaconda3/lib/python3.7/site-packages/scrapy/templates/project', created in: /Users/hqs/ScrapyProjects/qiubaiByPages You can start your first spider with: cd qiubaiByPages scrapy genspider example example.com $ cd qiubaiByPages/ $ scrapy genspider qiubai www.qiushibaike.com/text Created spider 'qiubai' using template 'basic' in module: qiubaiByPages.spiders.qiubai
(1)爬蟲文件:qiubai.py
import scrapy from qiubaiByPages.items import QiubaibypagesItem class QiubaiSpider(scrapy.Spider): name = 'qiubai' # allowed_domains = ['www.qiushibaike.com/text'] # 容許的域名(不少網頁不在域名下) start_urls = ['https://www.qiushibaike.com/text/'] # 默認消息頭是http,這裏手動調整 def parse(self, response): div_list = response.xpath('//*[@id="content-left"]/div') for div in div_list: author = div.xpath('./div[@class="author clearfix"]/a[2]/h2/text()').extract_first() # selector對象 content = div.xpath('.//div[@class="content" ]/span/text()').extract_first() # 將解析的數據值存儲到items對象中 item = QiubaibypagesItem() item["author"] = author item["content"] = content # 將item對象提交給管道文件 yield item
(2)數據存儲模板:items.py文件
import scrapy class QiubaibypagesItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() author = scrapy.Field() content = scrapy.Field()
(3)數據持久化處理:pipelines.py
class QiubaibypagesPipeline(object): fp = None def open_spider(self, spider): print("開始爬蟲") self.fp = open('./qiubai.txt', 'w', encoding="utf-8") def process_item(self, item, spider): self.fp.write(item['author']+ ":" + item['content']) return item def close_spider(self, spider): self.fp.close() print("爬蟲結束")
(4)配置文件:settings.py
# Crawl responsibly by identifying yourself (and your website) on the user-agent USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36' # 假裝請求載體身份 # Obey robots.txt rules ROBOTSTXT_OBEY = False # Configure item pipelines # See https://doc.scrapy.org/en/latest/topics/item-pipeline.html ITEM_PIPELINES = { # 開啓管道 'qiubaiByPages.pipelines.QiubaibypagesPipeline': 300, }
修改爬蟲文件:qiubai.py
import scrapy from qiubaiByPages.items import QiubaibypagesItem class QiubaiSpider(scrapy.Spider): name = 'qiubai' # allowed_domains = ['www.qiushibaike.com/text'] # 容許的域名(不少網頁不在域名下) start_urls = ['https://www.qiushibaike.com/text/'] # 默認消息頭是http,這裏手動調整 # 設計通用的url模板 url = 'https://www.qiushibaike.com/text/page/%d' # 分頁的通用格式 pageNum = 1 def parse(self, response): div_list = response.xpath('//*[@id="content-left"]/div') for div in div_list: author = div.xpath('./div[@class="author clearfix"]/a[2]/h2/text()').extract_first() # selector對象 content = div.xpath('.//div[@class="content" ]/span/text()').extract_first() # 將解析的數據值存儲到items對象中 item = QiubaibypagesItem() item["author"] = author item["content"] = content # 將item對象提交給管道文件 yield item # 請求的手動發送 if self.pageNum <= 13: # 遞歸終止條件(13是最後一頁的頁碼) print('爬取到了第%d頁的頁面數據' % self.pageNum) self.pageNum += 1 # 從第二個頁碼開始手動請求 new_url = format(self.url % self.pageNum) # 'https://www.qiushibaike.com/text/page/2/' # scrapy.Request對指定url發請求 # callback:將請求獲取到的頁面數據進行解析 yield scrapy.Request(url=new_url, callback=self.parse)
注意:
1)第一頁仍是使用的起始url列表這個機制來進行請求發送,從第二頁開始採用手動請求發送。
2)執行請求手動發送必須結合 yield 和 Request 函數一塊來使用。
3)在調用scrapy.Request函數時,有一個參數叫 callback 。這是一個回調函數,會進行遞歸的調用。爲了防止無限循環,須要設置遞歸終止條件。
$ scrapy crawl qiubai --nolog 開始爬蟲 爬取到了第1頁的頁面數據 爬取到了第2頁的頁面數據 爬取到了第3頁的頁面數據 爬取到了第4頁的頁面數據 爬取到了第5頁的頁面數據 爬取到了第6頁的頁面數據 爬取到了第7頁的頁面數據 爬取到了第8頁的頁面數據 爬取到了第9頁的頁面數據 爬取到了第10頁的頁面數據 爬取到了第11頁的頁面數據 爬取到了第12頁的頁面數據 爬取到了第13頁的頁面數據 爬蟲結束