1 scrapy全站爬取
1.1 全站爬取簡(jiǎn)介
CrawlSpider
:全站數(shù)據(jù)爬蟲的方式本昏,它是一個(gè)類供汛,屬于Spider的子類
如果不使用CrawlSpider
,那么就相當(dāng)于基于spider
涌穆,手動(dòng)發(fā)送請(qǐng)求怔昨,太不方便
基于CrawlSpider
可以很方便地進(jìn)行全站數(shù)據(jù)爬取
1.2 CrawlSpider
1.2.1 基本講解
基本步驟:
- 創(chuàng)建一個(gè)工程:
scrapy startproject ProjectName
- 切換到爬蟲工程中后,創(chuàng)建爬蟲文件:
scrapy genspider -t crawl xxx www.xxx.com
使用CrawlSpider
和spider
產(chǎn)生的爬蟲文件除了繼承類不一樣外還有一個(gè)rules
的規(guī)則解析器
rules = (
Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
)
在rules
規(guī)則解析器內(nèi)有一個(gè)鏈接提取器LinkExtractor(allow=r'Items/')
蒲犬,callback
是規(guī)則解析器指定的解析方法朱监,follow
是指爬取頁(yè)面內(nèi)可見部分頁(yè)面還是全部
頁(yè)面內(nèi)可見部分頁(yè)面如下:
鏈接提取器作用:根據(jù)指定的規(guī)則
allow=r'Items/'
進(jìn)行指定的鏈接的提取規(guī)則解析器作用:把鏈接提取器提取到的鏈接進(jìn)行指定規(guī)則
callback='parse_item'
的解析操作follow
作用:True
可以把 ==鏈接提取器== 繼續(xù)作用到 鏈接提取器提取到的鏈接
所對(duì)應(yīng)的 頁(yè)面
中,False
爬取頁(yè)面內(nèi)可見部分頁(yè)面
1.2.2 使用CrawlSpider
1.2.2.1 爬蟲文件
使用CrawlSpider
生成爬蟲文件時(shí)原叮,在規(guī)則解析器rules
里面添加正則表達(dá)式進(jìn)而發(fā)起請(qǐng)求赫编,如果要一個(gè)請(qǐng)求內(nèi)需要再次發(fā)起請(qǐng)求巡蘸,就需要在rules
中添加鏈接請(qǐng)求并指定對(duì)應(yīng)的解析方法
注意
:xpath
中最好不要出現(xiàn)tbody
標(biāo)簽
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from sunPro.items import SunproItem,DetailItem
class SunSpider(CrawlSpider):
name = 'sun'#爬蟲文件名
# allowed_domains = ['www.xxx.com']#允許的url
start_urls = ['http://dk.test.com/mail/?ac=list&tid=1']#url列表
#規(guī)則解析器
rules = (
#LinkExtractor(allow=r'Items/')連接提取器,就是用來提取連接擂送,根據(jù)指定規(guī)則(allow=r'Items/')進(jìn)行指定連接的提取
Rule(LinkExtractor(allow=r'ac=list&tid=1&order=1&page=\d+'), callback='parse_item', follow=False),
#獲取詳情信息
Rule(LinkExtractor(allow=r'ct=index&ac=detail&id=\d+'), callback='parse_detail', follow=False),
)
def parse_item(self, response):
tr_list=response.xpath('/html/body/table[2]//tr/td/table//tr[3]/td/table//tr/td[1]/table//tr/td/table//tr[1]/td/div/table//tr[@bgcolor="#FFFFFF"]')
# print(tr_list)
for tr in tr_list:
news_num = tr.xpath('./td[1]/text()').extract_first()
news_title = tr.xpath('./td[2]/a/text()').extract_first()
print(news_num,news_title)
""" item=SunproItem()
item['news_title']=news_title
item['news_num']=news_num
yield item """
def parse_detail(self,response):
news_id=response.xpath('/html/body/table[2]//tr/td/table//tr[3]/td/table//tr/td[1]/table//tr[1]/td/table//tr/td/table//tr[2]/td/table//tr[1]/td[1]/span/text()').extract_first()
news_content=response.xpath('/html/body/table[2]//tr/td/table//tr[3]/td/table//tr/td[1]/table//tr[1]/td/table//tr/td/table//tr[3]/td/table//tr[1]/td/table//tr[2]/td//text()').extract()
news_content=''.join(news_content)
item=DetailItem()
item['news_id']=news_id
item['news_content']=news_content
yield item
1.2.2.2 items.py文件
由于不能發(fā)送請(qǐng)求時(shí)傳參因此悦荒,需要兩個(gè)item類文件
import scrapy
class SunproItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
news_title=scrapy.Field()
news_num=scrapy.Field()
class DetailItem(scrapy.Item):
news_id=scrapy.Field()
news_content=scrapy.Field()
2 分布式爬蟲
2.1 分布式爬蟲概念
分布式爬蟲:需要搭建一個(gè)分布式的集群,讓其對(duì)一組資源進(jìn)行分布聯(lián)合爬取嘹吨,主要是為了提升爬取數(shù)據(jù)效率
2.2 環(huán)境安裝
安裝一個(gè)scrapy-redis
的組件:pip install scrapy-redis
搬味,由于原生的scrapy
不可以失效分布式爬蟲,必須讓scrapy
結(jié)合scrapy-redis
組件一起實(shí)現(xiàn)分布式爬蟲
那么為什么原生scrapy
不可以實(shí)現(xiàn)分布式蟀拷?
- 調(diào)度器不可以被分布式集群共享
- 管道不可以被分布式集群共享
但是scrapy-redis
組件可以提供共享的管道和調(diào)度器
2.3 使用方法
2.3.1 CrawlSpider配置
基本使用步驟:
- 創(chuàng)建基于
CrawlSpider
的爬蟲文件碰纬,修改爬蟲文件
導(dǎo)包:from scrapy_redis.spiders import RedisCrawlSpider
把start_urls
和allowed_domains
注釋掉
添加一個(gè)新屬性:redis_key='sun'
作為可以被共享的調(diào)度器隊(duì)列名稱
編寫數(shù)據(jù)解析相關(guān)操作
把當(dāng)前父類修改為RedisCrawlSpider
- 修改配置文件
settings.py
,不要開啟項(xiàng)目自帶的pipelines
不然還是走的原來的管道问芬,需要指定共享的管道RedisPipeline
悦析,還要指定調(diào)度器
指定管道
ITEM_PIPELINES = {
#'sunPro.pipelines.SunproPipeline': 300,
'scrapy_redis.pipelines.RedisPipeline':400
}
指定調(diào)度器
#增加一個(gè)去重容器類的配置,作用使用redis的set集合來存儲(chǔ)請(qǐng)求的指紋數(shù)據(jù)此衅,從而實(shí)現(xiàn)請(qǐng)求去重的持久化
DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'
#使用scrapy-redis組件自己的調(diào)度器
SCHEDULER='scrapy_redis.scheduler.Scheduler'
#配置調(diào)度器是否需要持久化强戴,也就是當(dāng)爬蟲結(jié)束了,要不要清空reids中請(qǐng)求隊(duì)列
#如果服務(wù)器宕機(jī)了挡鞍,重啟后從爬取的位置繼續(xù)爬取
SCHEDULER_PERSIST = True
指定redis地址和端口
REDIS_HOST='127.0.0.1'
REDIS_PORT='6379'
2.3.2 redis相關(guān)配置
把redis.windows-server.conf
文件修改把bind 127.0.0.1
給注釋掉骑歹,由于要把爬到的數(shù)據(jù)庫(kù)儲(chǔ)存到不同地方,因此不要綁定本地
關(guān)閉保護(hù)模式protected-mode yes
修改為protected-mode no
墨微,如果開啟了保護(hù)模式道媚,那么其他客戶端只能讀取redis而不能寫入
2.3.3 啟動(dòng)工程
分布式爬蟲啟動(dòng)和scrapy工程不同,需要定位到爬蟲文件.py
目錄內(nèi)翘县,執(zhí)行scrapy runspider xxx.py
工程啟動(dòng)后在redis客戶端中向redis添加調(diào)度隊(duì)列:lpush sun www.xxx.com
(由于之前寫過redis_key='sun'的共享調(diào)度屬性)
3 增量式爬蟲
3.1 概念講解
增量式爬蟲:檢測(cè)網(wǎng)站數(shù)據(jù)更新的情況衰琐,只會(huì)爬取網(wǎng)站最新出來的數(shù)據(jù)
還是基于CrawlSpider
獲取其他頁(yè)碼鏈接處理的,每次爬取時(shí)炼蹦,都會(huì)對(duì)已經(jīng)爬取的數(shù)據(jù)進(jìn)行比較羡宙,若爬取過了,就不再爬取
3.2 使用
3.2.1 爬蟲文件
主要通過redis來判斷是否已經(jīng)存儲(chǔ)過
from redis import Redis
from sunPro.items import SunproItem
class SunSpider(CrawlSpider):
name = 'sun'
# allowed_domains = ['www.xxx.com']
start_urls = ['http://www.xxx.com/']
rules = (
Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
)
#創(chuàng)建redis對(duì)象
conn = Redis(host='127.0.0.1',port=6379)
def parse_item(self, response):
li_list=response.xpath('xxxxx');
for li in li_list:
# 獲取詳情url
detail_url=li.xpath('xxxxxxxxxx').extract_first()
ex=self.conn.sadd('urls',detail_url)
if ex==1:
print('該url沒有爬取過掐隐,可以進(jìn)行數(shù)據(jù)爬取')
yield scrapy.Request(url=detail_url,callback=self.parse_detail)
else:
print('數(shù)據(jù)沒有更新狗热,暫無新數(shù)據(jù)可爬取')
def parse_detail(self,response):
item = SunproItem()
item['name']=response.xpath('xxxxxxxxxxxxx').extract()
3.2.2 管道文件
在管道文件中獲取redis
class SunproPipeline:
conn=None
# 開啟爬蟲時(shí)執(zhí)行,只執(zhí)行一次
def open_spider(self,spider):
self.conn=spider.conn
#理提取的數(shù)據(jù)(保存數(shù)據(jù))
def process_item(self, item, spider):
dict={
'name':item['name']
}
self.conn.lpush('test',dict)
return item
# 關(guān)閉爬蟲時(shí)執(zhí)行虑省,只執(zhí)行一次匿刮。 (如果爬蟲中間發(fā)生異常導(dǎo)致崩潰,close_spider可能也不會(huì)執(zhí)行)
def close_spider(self, spider):
# 可以關(guān)閉數(shù)據(jù)庫(kù)等
pass