一.scrapy框架基於CrawlSpider的全站數據爬取css
1.使用html
1.建立scrapy工程:scrapy startproject projectName正則表達式
2.建立爬蟲文件:scrapy genspider -t crawl spiderName www.xxx.comredis
生成的爬蟲文件數據庫
import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule class ChoutiSpider(CrawlSpider): # name = 'chouti' # # allowed_domains = ['www.xxx.com'] # start_urls = ['https://dig.chouti.com/r/scoff/hot/1'] # # #鏈接提取器: # #allow:表示的就是連接提取器提取鏈接的規則(正則) # link = LinkExtractor(allow=r'/r/scoff/hot/\d+') # # rules = ( # #規則解析器:將連接提取器提取到的鏈接所對應的頁面數據進行指定形式的解析 # Rule(link, callback='parse_item', follow=True), # # 讓鏈接提取器繼續做用到連接提取器提取到的鏈接所對應的頁面中 # ) # # def parse_item(self, response): # print(response) name = 'qiubai' # allowed_domains = ['www.xxx.com'] start_urls = ['https://www.qiushibaike.com/pic/'] # 鏈接提取器: # allow:表示的就是連接提取器提取鏈接的規則(正則)/pic/page/3?s=5172496 link = LinkExtractor(allow=r'/pic/page/\d+\?s=\d+') link1 = LinkExtractor(allow=r'/pic/$') # link1 = LinkExtractor(allow=r'') rules = ( # 規則解析器:將連接提取器提取到的鏈接所對應的頁面數據進行指定形式的解析 Rule(link, callback='parse_item', follow=True), # 讓鏈接提取器繼續做用到連接提取器提取到的鏈接所對應的頁面中 Rule(link1, callback='parse_item', follow=True), ) def parse_item(self, response): print(response)
LinkExtractor:顧名思義,連接提取器。服務器
LinkExtractor(框架
allow=r'Items/',# 知足括號中「正則表達式」的值會被提取,若是爲空,則所有匹配。dom
deny=xxx, # 知足正則表達式的則不會被提取。scrapy
restrict_xpaths=xxx, # 知足xpath表達式的值會被提取分佈式
restrict_css=xxx, # 知足css表達式的值會被提取
deny_domains=xxx, # 不會被提取的連接的domains。
)
做用:提取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中,而後提交給管道進行持久化存儲
示例:
爬取糗事百科糗圖板塊的全部頁碼數據
import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule class CrawldemoSpider(CrawlSpider): name = 'qiubai' #allowed_domains = ['www.qiushibaike.com'] start_urls = ['https://www.qiushibaike.com/pic/'] #鏈接提取器:會去起始url響應回來的頁面中提取指定的url link = LinkExtractor(allow=r'/pic/page/\d+\?') #s=爲隨機數 link1 = LinkExtractor(allow=r'/pic/$')#爬取第一頁 #rules元組中存放的是不一樣的規則解析器(封裝好了某種解析規則) rules = ( #規則解析器:能夠將鏈接提取器提取到的全部鏈接表示的頁面進行指定規則(回調函數)的解析 Rule(link, callback='parse_item', follow=True), Rule(link1, callback='parse_item', follow=True), ) def parse_item(self, response): print(response)
爬蟲文件
import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from qiubaiBycrawl.items import QiubaibycrawlItem import re class QiubaitestSpider(CrawlSpider): name = 'qiubaiTest' #起始url start_urls = ['http://www.qiushibaike.com/'] #定義連接提取器,且指定其提取規則 page_link = LinkExtractor(allow=r'/8hr/page/\d+/') rules = ( #定義規則解析器,且指定解析規則經過callback回調函數 Rule(page_link, callback='parse_item', follow=True), ) #自定義規則解析器的解析規則函數 def parse_item(self, response): div_list = response.xpath('//div[@id="content-left"]/div') for div in div_list: #定義item item = QiubaibycrawlItem() #根據xpath表達式提取糗百中段子的做者 item['author'] = div.xpath('./div/a[2]/h2/text()').extract_first().strip('\n') #根據xpath表達式提取糗百中段子的內容 item['content'] = div.xpath('.//div[@class="content"]/span/text()').extract_first().strip('\n') yield item #將item提交至管道
item.py
import scrapy class QiubaibycrawlItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() author = scrapy.Field() #做者 content = scrapy.Field() #內容
管道文件
class QiubaibycrawlPipeline(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): print('結束爬蟲') self.fp.close()
二.基於scrapy-redis的分佈式爬蟲
scrapy-redis組件中爲咱們封裝好了能夠被多臺機器共享的調度器和管道,咱們能夠直接使用並實現分佈式數據爬取。
實現方法:
基於該組件的RedisSpider類
基於該組件的RedisCrawlSpider類
分佈式實現流程
下載scrapy-redis組件:pip install scrapy-redis
redis配置文件的配置:
註釋該行:bind 127.0.0.1,表示可讓其餘ip訪問redis
將yes該爲no:protected-mode no,表示可讓其餘ip操做redis
在配置文件中進行相關配置,開啓使用scrapy-redis組件中封裝好的管道
ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 400 }
在配置文件中進行相關配置,開啓使用scrapy-redis組件中封裝好的調度器
# 使用scrapy-redis組件的去重隊列 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 使用scrapy-redis組件本身的調度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 是否容許暫停 SCHEDULER_PERSIST = True
在配置文件中進行爬蟲程序連接redis的配置:
REDIS_HOST = 'redis服務的ip地址' REDIS_PORT = 6379 REDIS_ENCODING = ‘utf-8’ REDIS_PARAMS = {‘password’:’123456’}
啓動
開啓redis服務器:redis-server 配置文件 開啓redis客戶端:redis-cli 運行爬蟲文件:scrapy runspider SpiderFile 向調度器隊列中扔入一個起始url(在redis客戶端中操做):lpush redis_key屬性值 起始url
- 爲何原生的scrapy不能實現分佈式? - 調度器不能被共享 - 管道沒法被共享 - scrapy-redis組件的做用是什麼? - 提供了能夠被共享的調度器和管道 - 分佈式爬蟲實現流程 1.環境安裝:pip install scrapy-redis 2.建立工程 3.建立爬蟲文件:RedisCrawlSpider RedisSpider - scrapy genspider -t crawl xxx www.xxx.com 4.對爬蟲文件中的相關屬性進行修改: - 導報:from scrapy_redis.spiders import RedisCrawlSpider - 將當前爬蟲文件的父類設置成RedisCrawlSpider - 將起始url列表替換成redis_key = 'xxx'(調度器隊列的名稱) 5.在配置文件中進行配置: - 使用組件中封裝好的能夠被共享的管道類: ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 400 } - 配置調度器(使用組件中封裝好的能夠被共享的調度器) # 增長了一個去重容器類的配置, 做用使用Redis的set集合來存儲請求的指紋數據, 從而實現請求去重的持久化 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 使用scrapy-redis組件本身的調度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 配置調度器是否要持久化, 也就是當爬蟲結束了, 要不要清空Redis中請求隊列和去重指紋的set。若是是True, 就表示要持久化存儲, 就不清空數據, 不然清空數據 SCHEDULER_PERSIST = True - 指定存儲數據的redis: REDIS_HOST = 'redis服務的ip地址' REDIS_PORT = 6379 - 配置redis數據庫的配置文件 - 取消保護模式:protected-mode no - bind綁定: #bind 127.0.0.1 - 啓動redis 6.執行分佈式程序 scrapy runspider xxx.py 7.向調度器隊列中仍入一個起始url: 在redis-cli中執行:
三.增量式爬蟲
1.定義
經過爬蟲程序監測某網站數據更新的狀況,以即可以爬取到該網站更新出的新數據。
2.如何進行增量式的爬取工做
在發送請求以前判斷這個URL是否是以前爬取過
在解析內容後判斷這部份內容是否是以前爬取過
寫入存儲介質時判斷內容是否是已經在介質中存在
示例:
爬取糗事百科中的段子和做者數據。
爬蟲文件
import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from incrementByDataPro.items import IncrementbydataproItem from redis import Redis import hashlib class QiubaiSpider(CrawlSpider): name = 'qiubai' # allowed_domains = ['www.xxx.com'] start_urls = ['https://www.qiushibaike.com/text/'] rules = ( Rule(LinkExtractor(allow=r'/text/page/\d+/'), callback='parse_item', follow=True), Rule(LinkExtractor(allow=r'/text/$'), callback='parse_item', follow=True), ) #建立redis連接對象 conn = Redis(host='127.0.0.1',port=6379) def parse_item(self, response): div_list = response.xpath('//div[@id="content-left"]/div') for div in div_list: item = IncrementbydataproItem() item['author'] = div.xpath('./div[1]/a[2]/h2/text() | ./div[1]/span[2]/h2/text()').extract_first() item['content'] = div.xpath('.//div[@class="content"]/span/text()').extract_first() #將解析到的數據值生成一個惟一的標識進行redis存儲 source = item['author']+item['content'] source_id = hashlib.sha256(source.encode()).hexdigest() #將解析內容的惟一表示存儲到redis的data_id中 ex = self.conn.sadd('data_id',source_id) if ex == 1: print('該條數據沒有爬取過,能夠爬取......') yield item else: print('該條數據已經爬取過了,不須要再次爬取了!!!')
管道文件
from redis import Redis class IncrementbydataproPipeline(object): conn = None def open_spider(self, spider): self.conn = Redis(host='127.0.0.1', port=6379) def process_item(self, item, spider): dic = { 'author': item['author'], 'content': item['content'] } # print(dic) self.conn.lpush('qiubaiData', dic) return item
爬取4567tv網站中全部的電影詳情數據。
爬蟲文件
import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from redis import Redis from incrementPro.items import IncrementproItem class MovieSpider(CrawlSpider): name = 'movie' # allowed_domains = ['www.xxx.com'] start_urls = ['http://www.4567tv.tv/frim/index7-11.html'] rules = ( Rule(LinkExtractor(allow=r'/frim/index7-\d+\.html'), callback='parse_item', follow=True), ) #建立redis連接對象 conn = Redis(host='127.0.0.1',port=6379) def parse_item(self, response): li_list = response.xpath('//li[@class="p1 m1"]') for li in li_list: #獲取詳情頁的url detail_url = 'http://www.4567tv.tv'+li.xpath('./a/@href').extract_first() #將詳情頁的url存入redis的set中 ex = self.conn.sadd('urls',detail_url) if ex == 1: print('該url沒有被爬取過,能夠進行數據的爬取') yield scrapy.Request(url=detail_url,callback=self.parst_detail) else: print('數據尚未更新,暫無新數據可爬取!') #解析詳情頁中的電影名稱和類型,進行持久化存儲 def parst_detail(self,response): item = IncrementproItem() item['name'] = response.xpath('//dt[@class="name"]/text()').extract_first() item['kind'] = response.xpath('//div[@class="ct-c"]/dl/dt[4]//text()').extract() item['kind'] = ''.join(item['kind']) yield item
管道文件
from redis import Redis class IncrementproPipeline(object): conn = None def open_spider(self,spider): self.conn = Redis(host='127.0.0.1',port=6379) def process_item(self, item, spider): dic = { 'name':item['name'], 'kind':item['kind'] } print(dic) self.conn.lpush('movieData',dic) return item