學習目標:
-
分散式概念與使用場景
-
-
淺談斷點續爬
-
分散式爬蟲編寫流程
-
基於scrapy_redis的分散式爬蟲(陽關院務與京東圖書案例)
環境準備:
-
下載redis-cli(客戶端)以及redis-server(服務端)
-
安裝Another Redis Desktop Manager視覺化工具
-
連結:https://pan.baidu.com/s/1rl8IUY7Lq54aePT54LnAkQ 提取碼:1234
-
scrapy-redis原始碼:
git clone https://github.com/rolando/scrapy-redis.git
分散式概念與使用場景:
-
分散式聽起來很高大上,但是它只是提高爬蟲功能與執行效率的一個環節,
-
當你的資料是海量的時候,或者老闆叫你在短時間內獲得大量的資料,這時候才是分散式出場的時候,然而當你使用分散式的時候,難點不在於怎麼部署以及編寫程式碼;
-
爬蟲的速度越快,所造成對方的伺服器負擔越重,這時候反爬才是你所真正考慮以及應對的。
-
概念:需要搭建一個分散式機群,然後再機群的每一臺電腦中執行同一組程式,讓對某一個網站的資料進行聯合分佈爬取
淺談去重:
-
好處:能夠減少伺服器的壓力以及保證資料的準確性;
-
每核心次請求的時候,先判斷這個請求是否在已經爬取的佇列當中,存在捨去,不存在爬取;
-
採用scrapy-redis中的set集合做的去重(可做持久化儲存)。
淺談斷點續爬:
-
如果執行爬蟲down掉了,在下一次啟動的時候可以接入上次end的位置繼續。
-
斷點續爬就是將資料佇列 集合以及任務佇列實現本地持久化儲存
分散式爬蟲編寫流程:
-
編寫普通scrapy爬蟲
-
建立專案
-
明確目標
-
建立爬蟲(普通scrapy爬蟲以及crawlSpider爬蟲)
-
儲存內容
-
-
改造分散式爬蟲
-
匯入scrapy-redis中的分散式爬蟲類
-
繼承類
-
登出start_url & allowed-domains
-
設定redis_key獲取start_urls
-
設定
__init__
獲取允許的域
-
-
改造settings檔案
-
copy配置檔案(配置如下)
-
1 #所有的JDspider---換成自己的爬蟲名稱 2 SPIDER_MODULES = ['JDspider.spiders'] 3 NEWSPIDER_MODULE = 'JDspider.spiders' 4 5 USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36 Edg/84.0.522.40' 6 7 # 設定重複過濾器的模組 8 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" 9 # 設定調取器,scrap_redis中的排程器具備與資料庫互動的功能 10 SCHEDULER = "scrapy_redis.scheduler.Scheduler" 11 # 設定當爬蟲結束的時候是否保持redis資料庫中的去重集合與任務佇列,程式結束後不清空redis資料庫 12 SCHEDULER_PERSIST = True 13 #SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderPriorityQueue" 14 #SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderQueue" 15 #SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderStack" 16 17 ITEM_PIPELINES = { 18 # 'JD.pipelines.ExamplePipeline': 300, 19 # 當開啟該管道,該管道將會把資料存到Redis資料庫中,也可以自己設定資料庫 20 'scrapy_redis.pipelines.RedisPipeline': 400, 21 } 22 # 設定redis資料庫 23 REDIS_URL = "redis://127.0.0.1:6379" 24 25 # LOG_LEVEL = 'DEBUG' 26 27 # Introduce an artifical delay to make use of parallelism. to speed up the 28 # crawl. 29 #請求間隔時長 30 DOWNLOAD_DELAY = 1
-
陽光院務平臺scrapy-redis-Crawlspider:
編寫Spider:基本程式碼很好理解就沒寫註釋
1 import scrapy 2 from sunsite.items import SunsiteItem 3 4 class SunproSpider(scrapy.Spider): 5 name = 'sunpro' 6 # allowed_domains = ['www.xxx.com'] 7 start_urls = ['http://wz.sun0769.com/political/index/politicsNewest?id=1&page=1'] 8 9 def parse(self, response): 10 li_list = response.xpath("/html/body/div[2]/div[3]/ul[2]//li") 11 for li in li_list: 12 item = SunsiteItem() 13 item['title'] = li.xpath("./span[3]/a/text()").extract_first() 14 status= li.xpath("./span[2]/text()").extract_first().split('\n ')[1] 15 16 item['status'] = status.split("\n ")[0] 17 # print(item) 18 yield item
編寫CrawlSpider:
1 import scrapy 2 from scrapy.linkextractors import LinkExtractor 3 from scrapy.spiders import CrawlSpider, Rule 4 from sunsite.items import SunsiteItem 5 from scrapy_redis.spiders import RedisCrawlSpider 6 7 class SunprocrawlSpider(RedisCrawlSpider): 8 name = 'Sunprocrawl' 9 # allowed_domains = ['www.xxx.com'] 10 # start_urls = ['http://wz.sun0769.com/political/index/politicsNewest?id=1&page=1'] 11 redis_key = 'sunurl' 12 rules = ( 13 Rule(LinkExtractor(allow=r'id=1&page=\d+'), callback='parse_item', follow=True), 14 ) 15 16 def parse_item(self, response): 17 li_list = response.xpath("/html/body/div[2]/div[3]/ul[2]//li") 18 for li in li_list: 19 item = SunsiteItem() 20 item['title'] = li.xpath("./span[3]/a/text()").extract_first() 21 status = li.xpath("./span[2]/text()").extract_first().split('\n ')[1] 22 23 item['status'] = status.split("\n ")[0] 24 # print(item) 25 yield item
item編寫:
1 import scrapy 2 3 4 class SunsiteItem(scrapy.Item): 5 title = scrapy.Field() 6 status = scrapy.Field()
京東圖書scrapy-redis:
JDSpider:(基礎程式碼在Github中)
1 # -*- coding: utf-8 -*- 2 #該spider在基礎spider上進行分散式修改 3 import scrapy 4 from JDspider.items import JdspiderItem 5 import json 6 #-----1匯入分散式爬蟲類 7 from scrapy_redis.spiders import RedisSpider 8 9 class JdproSpider(RedisSpider): #----2繼承RedisSpider類方法 10 name = 'JDpro' 11 # start_urls = ['https://book.jd.com/booksort.html'] 12 # ----4 設定redis-key 13 redis_key = 'tranurl' 14 15 # ----5 設定__init__ 16 def __init__(self, *args, **kwargs): 17 domain = kwargs.pop('domain', '') 18 self.allowed_domains = list(filter(None, domain.split(','))) 19 super(JdproSpider, self).__init__(*args, **kwargs) 20 21 def parse(self, response): 22 #獲取圖書大分類中的列表 23 big_node_list = response.xpath("//div[@class='mc']//dt/a") 24 25 # 【:1】切片,先獲取一類資料測試 26 # for big_node in big_node_list[:1]: 27 for big_node in big_node_list: 28 #大分類的名稱 29 big_category = big_node.xpath("./text()").extract_first() 30 #大分類的URL 31 big_category_link = response.urljoin(big_node.xpath("./@href").extract_first()) 32 # print(big_category, big_category_link) 33 # 獲取所有圖書小分類節點列表 34 #注意點---獲取兄弟節點的xpath語法結構;小分類的整體節點 35 small_node_list = big_node.xpath("../following-sibling::dd[1]/em/a") 36 #【:1】切片,先獲取一類資料測試 37 for small_node in small_node_list[:1]: 38 temp = {} 39 temp['big_category'] = big_category 40 temp['big_category_link'] = big_category_link 41 #獲取小分類的名稱 42 temp['small_category'] = small_node.xpath("./text()").extract_first() 43 #獲取小分類的URL 44 temp['small_category_link'] = response.urljoin(small_node.xpath("./@href").extract_first()) 45 # print(temp) 46 #注意點,篩選出來的資料持續傳輸,meta的使用 47 yield scrapy.Request( 48 url=temp['small_category_link'], 49 callback= self.parse_book_link, 50 #上面儲存的item傳遞給下一個解析函式 51 meta = {'data':temp} 52 ) 53 54 #解析詳情 55 def parse_book_link(self,response): 56 temp = response.meta['data'] 57 58 #獲取到Book的標籤 59 book_list = response.xpath("//*[@id='J_goodsList']/ul/li/div") 60 # print(len(book_list)) 61 #遍歷標籤頁 62 for book in book_list: 63 item = JdspiderItem() 64 65 item['big_category'] = temp['big_category'] 66 item['big_category_link'] = temp['big_category_link'] 67 item['small_category'] = temp['small_category'] 68 item['small_category_link'] = temp['small_category_link'] 69 #書的名字 70 item['bookname'] = book.xpath('./div[3]/a/em/text()|./div/div[2]/div[2]/div[3]/a/em/text()').extract_first() 71 #書的作者 72 item['author'] = book.xpath('./div[4]/span[1]/a/text()|./div/div[2]/div[2]/div[4]/span[1]/span[1]/a/text()').extract_first() 73 #書的URL 74 item['link'] = response.urljoin(book.xpath('./div[1]/a/@href|./div/div[2]/div[2]/div[1]/a/@href').extract_first()) 75 # print(item) 76 # 獲取圖書編號,目的拼接圖書的Price 77 skuid = book.xpath('.//@data-sku').extract_first() 78 # skuid = book.xpath('./@data-sku').extract_first() 79 # print("skuid:",skuid) 80 # 拼接圖書價格地址 81 pri_url = 'https://p.3.cn/prices/mgets?skuIds=J_' + skuid 82 # print(pri_url) 83 84 yield scrapy.Request(url=pri_url, callback=self.parse_price, meta={'meta_1': item}) 85 #拿到一條資料測試,可以開啟 86 # break 87 def parse_price(self,response): 88 #拿到傳遞過來的item 89 item = response.meta['meta_1'] 90 #解析json頁面 91 dict_data = json.loads(response.body) 92 #解析價錢,傳遞到item中 93 item['price'] = dict_data[0]['p'] 94 # print(item) 95 yield item 96
程式執行方式:
-
開啟redis-server.exe
-
開啟redis-cli.exe
-
找到爬蟲檔案下的spider
-
scrapy runspider spiderName
-
在redis-cli中輸入:lpush redis-keyName(spider中定義的redis-key名字) URL(網頁的連結)
實現效果:
完整專案程式碼:
Github:https://github.com/xbhog/scrapyRedis