一、什么是Scrapy?
Scrapy是一個為了爬取網站數據,提取結構性數據而編寫的應用框架,非常出名,非常強悍。所謂的框架就是一個已經被集成了各種功能(高性能異步下載,隊列,分布式,解析,持久化等)的具有很強通用性的項目模板。對于框架的學習,重點是要學習其框架的特性、各個功能的用法即可。
二、安裝Scrapy:
Linux:
pip3 install scrapy
Windows:
a. pip3 install wheel
b. 下載twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
c. 進入下載目錄,執行 pip3 install Twisted?17.1.0?cp35?cp35m?win_amd64.whl
d. pip3 install pywin32
e. pip3 install scrapy
三、Scrapy基礎使用步驟:
1.創建項目:scrapy startproject 項目名稱
項目結構:
project_name/
scrapy.cfg:
project_name/
_init.py
items.py
pipelines.py
settings.py
spiders/
_init.py
scrapy.cfg 項目的主配置信息。(真正爬蟲相關的配置信息在settings.py文件中)
items.py 設置數據存儲模板,用于結構化數據,如:Django的Model
pipelines 數據持久化處理
settings.py 配置文件,如:遞歸的層數、并發數,延遲下載等
spiders 爬蟲目錄,如:創建文件,編寫爬蟲解析規則
.
2.創建爬蟲應用程序:
cd project_name(進入項目目錄)
scrapy genspider 應用名稱 爬取網頁的起始url (例如:scrapy genspider qiubai www.qiushibaike.com)
.
3.編寫爬蟲文件:在步驟2執行完畢后,會在項目的spiders中生成一個應用名的py爬蟲文件,文件源碼如下:
Scrapy.png
4.設置修改settings.py配置文件相關配置:
相關配置
5.執行爬蟲程序:scrapy crawl 應用名稱
流程演示代碼示例:
import scrapy
class QiubaiSpider(scrapy.Spider):
name = 'qiubai'
allowed_domains = ['https://www.qiushibaike.com/']
start_urls = ['https://www.qiushibaike.com/']
def parse(self, response):
// xpath為response中的方法,可以將xpath表達式直接作用于該函數中
odiv = response.xpath('//div[@id="content-left"]/div')
with open('./data.txt', 'w') as fp:
for div in odiv:
// xpath函數返回的為列表,列表中存放的數據為Selector類型的數據。我們解析到的
//內容被封裝在了Selector對象中,需要調用extract()函數將解析的內容從Selecor中取出。
author = div.xpath('.//div[@class="author clearfix"]/a/h2/text()')[0].extract()
content=div.xpath('.//div[@class="content"]/span/text()')[0].extract()
// 持久化存儲爬取到的內容
fp.write(author + ':' + content + '\n')
上述代碼表示的持久化操作是我們自己通過IO操作將數據進行的文件存儲。在scrapy框架中已經為我們專門集成好了高效、便捷的持久化操作功能,我們直接使用即可。要想使用scrapy的持久化操作功能,我們首先來認識如下兩個文件:
items.py:數據結構模板文件。定義數據屬性。
pipelines.py:管道文件。接收數據(items),進行持久化操作。
.
持久化流程:
1.爬蟲文件爬取到數據后,需要將數據封裝到items對象中。
2.使用yield關鍵字將items對象提交給pipelines管道進行持久化操作。
3.settings.py配置文件中開啟管道
小試牛刀:
將糗事百科首頁中的段子和作者數據爬取下來,然后進行持久化存儲
爬蟲文件:qiubaiDemo.py
import scrapy
from secondblood.items import SecondbloodItem
class QiubaidemoSpider(scrapy.Spider):
name = 'qiubaiDemo'
allowed_domains = ['www.qiushibaike.com']
start_urls = ['http://www.qiushibaike.com/']
def parse(self, response):
odiv = response.xpath('//div[@id="content-left"]/div')
for div in odiv:
//xpath函數返回的為列表,列表中存放的數據為Selector類型的數據。我們解析到的內
//容被封裝在了Selector對象中,需要調用extract()函數將解析的內容從Selecor中取出。
author = div.xpath('.//div[@class="author clearfix"]//h2/text()').extract_first()
author = author.strip('\n')#過濾空行
content = div.xpath('.//div[@class="content"]/span/text()').extract_first()
content = content.strip('\n')#過濾空行
// 將解析到的數據封裝至items對象中
item = SecondbloodItem()
item['author'] = author
item['content'] = content
// 提交item到管道文件(pipelines.py)
yield item
items文件:items.py
import scrapy
class SecondbloodItem(scrapy.Item):
//存儲作者
author = scrapy.Field()
//存儲段子內容
content = scrapy.Field()
管道文件:pipelines.py
class SecondbloodPipeline(object):
// 構造方法
def __init__(self):
self.fp = None #定義一個文件描述符屬性
// 下列都是在重寫父類的方法:
// 開始爬蟲時,執行一次
def open_spider(self,spider):
print('爬蟲開始')
self.fp = open('./data.txt', 'w')
// 因為該方法會被執行調用多次,所以文件的開啟和關閉操作寫在了另外兩個只會各自執行一次的方法中。
def process_item(self, item, spider):
// 將爬蟲程序提交的item進行持久化存儲
self.fp.write(item['author'] + ':' + item['content'] + '\n')
return item
// 結束爬蟲時,執行一次
def close_spider(self,spider):
self.fp.close()
print('爬蟲結束')
配置文件:settings.py
// 開啟管道
ITEM_PIPELINES = {
'secondblood.pipelines.SecondbloodPipeline': 300, #300表示為優先級,值越小優先級越高
}
四、Scrapy遞歸爬取多頁數據:
需求:將糗事百科所有頁碼的作者和段子內容數據進行爬取且持久化存儲:
import scrapy
from qiushibaike.items import QiushibaikeItem
class QiushiSpider(scrapy.Spider):
name = 'qiushi'
allowed_domains = ['www.qiushibaike.com']
start_urls = ['https://www.qiushibaike.com/text/']
// 爬取多頁
pageNum = 1 #起始頁碼
url = 'https://www.qiushibaike.com/text/page/%s/' #每頁的url
def parse(self, response):
div_list=response.xpath('//*[@id="content-left"]/div')
for div in div_list:
// //*[@id="qiushi_tag_120996995"]/div[1]/a[2]/h2
author=div.xpath('.//div[@class="author clearfix"]//h2/text()').extract_first()
author=author.strip('\n')
content=div.xpath('.//div[@class="content"]/span/text()').extract_first()
content=content.strip('\n')
item=QiushibaikeItem()
item['author']=author
item['content']=content
yield item #提交item到管道進行持久化
// 爬取所有頁碼數據
if self.pageNum <= 13: #一共爬取13頁(共13頁)
self.pageNum += 1
url = format(self.url % self.pageNum)
//遞歸爬取數據:callback參數的值為回調函數(將url請求后,
//得到的相應數據繼續進行parse解析),遞歸調用parse函數
yield scrapy.Request(url=url,callback=self.parse)
五、Scrapy核心組件介紹:
Scrapy核心組件image.png
引擎(Scrapy)
用來處理整個系統的數據流處理, 觸發事務(框架核心)
調度器(Scheduler)
用來接受引擎發過來的請求, 壓入隊列中, 并在引擎再次請求的時候返回. 可以想像成一個URL(抓取網頁的網址或者說是鏈接)的優先隊列, 由它來決定下一個要抓取的網址是什么, 同時去除重復的網址
下載器(Downloader)
用于下載網頁內容, 并將網頁內容返回給蜘蛛(Scrapy下載器是建立在twisted這個高效的異步模型上的)
爬蟲(Spiders)
爬蟲是主要干活的, 用于從特定的網頁中提取自己需要的信息, 即所謂的實體(Item)。用戶也可以從中提取出鏈接,讓Scrapy繼續抓取下一個頁面
項目管道(Pipeline)
負責處理爬蟲從網頁中抽取的實體,主要的功能是持久化實體、驗證實體的有效性、清除不需要的信息。當頁面被爬蟲解析后,將被發送到項目管道,并經過幾個特定的次序處理數據。
面試題:
如果最終需要將爬取到的數據值一份存儲到磁盤文件,一份存儲到數據庫中,則應該如何操作scrapy?答案:
管道文件中的代碼為:
管道文件
在settings.py開啟管道操作代碼為:
settings配置代碼
六、Scrapy發起post請求:
- 問題:在之前代碼中,我們從來沒有手動的對start_urls列表中存儲的起始url進行過請求的發送,但是起始url的確是進行了請求的發送,那這是如何實現的呢?
- 解答:其實是因為爬蟲文件中的爬蟲類繼承到了Spider父類中的start_requests(self)這個方法,該方法就可以對start_urls列表中的url發起請求屏幕快照 2018-11-02 下午4.29.33.png
【注意】該方法默認的實現,是對起始的url發起get請求,如果想發起post請求,則需要子類重寫該方法。
-方法: 重寫start_requests方法,讓其發起post請求:
屏幕快照 2018-11-02 下午4.36.19.png
七、scrapy框架之日志等級和請求傳參
Scrapy的日志等級
- 在使用scrapy crawl spiderFileName運行程序時,在終端里打印輸出的就是scrapy的日志信息。
- 日志信息的種類:
ERROR : 一般錯誤
WARNING : 警告
INFO : 一般的信息
DEBUG : 調試信息
默認的顯示級別是DEBUG
- 設置日志信息指定輸出:
在settings.py配置文件中,加入LOG_LEVEL = ‘指定日志信息種類’即可。LOG_FILE = 'log.txt'則表示將日志信息寫入到指定文件中進行存儲。請求傳參
- 在某些情況下,我們爬取的數據不在同一個頁面中,例如,我們爬取一個電影網站,電影的名稱,評分在一級頁面,而要爬取的其他電影詳情在其二級子頁面中。這時我們就需要用到請求傳參。
傳參代碼示例:
// spider.py文件
import scrapy
from moviePro.items import MovieproItem
class MovieSpider(scrapy.Spider):
name = 'movie'
allowed_domains = ['www.id97.com']
start_urls = ['http://www.id97.com/']
def parse(self, response):
div_list = response.xpath('//div[@class="col-xs-1-5 movie-item"]')
for div in div_list:
item = MovieproItem()
item['name'] = div.xpath('.//h1/a/text()').extract_first()
item['score'] = div.xpath('.//h1/em/text()').extract_first()
#xpath(string(.))表示提取當前節點下所有子節點中的數據值(.)表示當前節點
item['kind'] = div.xpath('.//div[@class="otherinfo"]').xpath('string(.)').extract_first()
item['detail_url'] = div.xpath('./div/a/@href').extract_first()
#請求二級詳情頁面,解析二級頁面中的相應內容,通過meta參數進行Request的數據傳遞
yield scrapy.Request(url=item['detail_url'],callback=self.parse_detail,meta={'item':item})
def parse_detail(self,response):
#通過response獲取item
item = response.meta['item']
item['actor'] = response.xpath('//div[@class="row"]//table/tr[1]/a/text()').extract_first()
item['time'] = response.xpath('//div[@class="row"]//table/tr[7]/td[2]/text()').extract_first()
item['long'] = response.xpath('//div[@class="row"]//table/tr[8]/td[2]/text()').extract_first()
#提交item到管道
yield item
//items.py文件
import scrapy
class MovieproItem(scrapy.Item):
# define the fields for your item here like:
name = scrapy.Field()
score = scrapy.Field()
time = scrapy.Field()
long = scrapy.Field()
actor = scrapy.Field()
kind = scrapy.Field()
detail_url = scrapy.Field()
//pipelines.py文件
import json
class MovieproPipeline(object):
def __init__(self):
self.fp = open('data.txt','w')
def process_item(self, item, spider):
dic = dict(item)
print(dic)
json.dump(dic,self.fp,ensure_ascii=False)
return item
def close_spider(self,spider):
self.fp.close()
八、Scrapy框架之CrawlSpider操作
提問:如果想要通過爬蟲程序去爬取”糗百“全站數據新聞數據的話,有幾種實現方法?
方法一:基于Scrapy框架中的Spider的遞歸爬取進行實現(Request模塊遞歸回調parse方法)。
方法二:基于CrawlSpider的自動爬取進行實現(更加簡潔和高效)。
CrawlSpider簡介
CrawlSpider其實是Spider的一個子類,除了繼承到Spider的特性和功能外,還派生除了其自己獨有的更加強大的特性和功能。其中最顯著的功能就是”LinkExtractors鏈接提取器“。Spider是所有爬蟲的基類,其設計原則只是為了爬取start_url列表中網頁,而從爬取到的網頁中提取出的url進行繼續的爬取工作使用CrawlSpider更合適。CrawSpider的使用
1.創建scrapy工程:scrapy startproject projectName
2.創建爬蟲文件:scrapy genspider -t crawl spiderName www.xxx.com
--此指令對比以前的指令多了 "-t crawl",表示創建的爬蟲文件是
基于CrawlSpider這個類的,而不再是Spider這個基類。
3.settings.py文件配置:
USER_AGENT、ROBOTSTXT_OBEY、ITEM_PIPELINES
4.執行爬蟲程序:scrapy crawl 應用名稱 --nolog
爬蟲文件示例:
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from redisScrapyPro.items import RedisscrapyproItem
class RedisdemoSpider(CrawlSpider):
name = 'redisDemo'
start_urls = ['https://dig.chouti.com/r/scoff/hot/1']
# 實例化了一個鏈接提取器對象:allow:正則表達式
# 作用:將起始url對應的頁面數據中符合allow指定的正則表達式的鏈接進行提取
link = LinkExtractor(allow=r'/r/scoff/hot/\d+')
rules = (
# Rule規則解析器
# 作用:可以將連接提取器提取到的鏈接對應的頁面數據進行指定規則的數據進行解析
# 參數follow作用:將連接提取器繼續作用到連接提取器提取出的鏈接所對應的頁面中
Rule(link, callback='parse_item', follow=True),
)
def parse_item(self, response):
div_list = response.xpath('//*[@id="content-list"]/div')
for div in div_list:
text = div.xpath('./*[@class="news-content"]/div/a/text()').extract_first().strip("\n")
item = RedisscrapyproItem()
item['text'] = text
yield item
items.py
import scrapy
class RedisscrapyproItem(scrapy.Item):
text = scrapy.Field()
pipelines.py
class RedisscrapyproPipeline(object):
def process_item(self, item, spider):
print(item['text'])
CrawlSpider類和Spider類的最大不同是CrawlSpider多了一個rules屬性,其作用是定義”提取動作“。在rules中可以包含一個或多個Rule對象,在Rule對象中包含了LinkExtractor對象。
參數介紹:
LinkExtractor:顧名思義,鏈接提取器。提取response中符合規則的鏈接。
Rule: 規則解析器。根據鏈接提取器中提取到的鏈接,根據指定規則提取解析器鏈接網頁中的內容。 Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True)
- 參數介紹:
參數1:指定鏈接提取器
參數2:指定規則解析器解析數據的規則(回調函數)
參數3:是否將鏈接提取器繼續作用到鏈接提取器提取出的鏈接網頁中。當callback為None,參數3的默認值為true。
rules=( ):指定不同規則解析器。一個Rule對象表示一種提取規則。
CrawlSpider整體爬取流程:
a)爬蟲文件首先根據起始url,獲取該url的網頁內容
b)鏈接提取器會根據指定提取規則將步驟a中網頁內容中的鏈接進行提取
c)規則解析器會根據指定解析規則將鏈接提取器中提取到的鏈接中的網頁內容根據指定的規則進行解析
d)將解析數據封裝到item中,然后提交給管道進行持久化存儲
九、Scrapy框架鏈接數據庫操作:
數據持久化:
1.對數據做持久化可以直接將數據寫入文件中,可以在pipelines.py中做IO操作,也可以直接使用終端指令存儲指定文件格式:
scrapy crawl qiubai -o qiubai.json
scrapy crawl qiubai -o qiubai.xml
scrapy crawl qiubai -o qiubai.csv
2.可以通過mysql、redis、mongodb等數據庫來存儲數據,注意配置settings文件!
Mysql數據庫的使用pipelines.py示例:
import pymysql
class QiubaiproPipeline(object):
conn = None
cursor = None
def open_spider(self,spider):
print('開始爬蟲')
#1. 鏈接數據庫
self.conn = pymysql.Connect(host='127.0.0.1',port=3306,user='root',
password='123456',db='qiubai')
#編寫向數據庫中存儲數據的相關代碼
def process_item(self, item, spider):
#2. 執行sql語句
sql = 'insert into qiubai values("%s","%s")'%(item['author'],item['content'])
self.cursor = self.conn.cursor()
# 3.提交事務
try:
self.cursor.execute(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數據庫的使用pipelines.py示例:
import redis
class QiubaiproPipeline(object):
conn = None
def open_spider(self,spider):
print('開始爬蟲')
self.conn = redis.Redis(host='127.0.0.1',port=6379)
def process_item(self, item, spider):
dict = {
'author':item['author'],
'content':item['content']
}
self.conn.lpush('data', dict)
return item
十、Scrapy框架的中間件:
中間件的作用
中間件可以攔截請求對象,可以將請求對象的UA進行偽裝,也可以將請求對象的url進行篡改等。settings.py文件配置
DOWNLOADER_MIDDLEWARES = {
'proxyPro.middlewares.Myproxy': 543,
}
middleware.py文件
from scrapy import signals
class Myproxy(object):
#該方法被調用后可以攔截請求對象
#將請求對象的UA進行偽裝
#將請求對象的url進行篡改
def process_request(self, request, spider):
#進行請求代理ip設置
#參數request就是中間件攔截到的請求對象
request.meta['proxy'] = "https://151.106.15.8:1080"
十一、scrapy框架之分布式操作
兩種分布式爬蟲方案鏈接:http://www.lxweimin.com/p/5baa1d5eb6d9