scrapy分散式淺談+京東示例

Xbhog發表於2020-08-15

scrapy分散式淺談+京東示例:

學習目標:

  1. 分散式概念與使用場景

  2. 淺談去重

  3. 淺談斷點續爬

  4. 分散式爬蟲編寫流程

  5. 基於scrapy_redis的分散式爬蟲(陽關院務與京東圖書案例)

環境準備:

  1. 下載redis-cli(客戶端)以及redis-server(服務端)

  2. 安裝Another Redis Desktop Manager視覺化工具

  3. 連結:https://pan.baidu.com/s/1rl8IUY7Lq54aePT54LnAkQ 提取碼:1234

  4. scrapy-redis原始碼:git clone https://github.com/rolando/scrapy-redis.git

 

分散式概念與使用場景:

  1. 分散式聽起來很高大上,但是它只是提高爬蟲功能與執行效率的一個環節,

  2. 當你的資料是海量的時候,或者老闆叫你在短時間內獲得大量的資料,這時候才是分散式出場的時候,然而當你使用分散式的時候,難點不在於怎麼部署以及編寫程式碼;

  3. 爬蟲的速度越快,所造成對方的伺服器負擔越重,這時候反爬才是你所真正考慮以及應對的。

  4. 概念:需要搭建一個分散式機群,然後再機群的每一臺電腦中執行同一組程式,讓對某一個網站的資料進行聯合分佈爬取

 

淺談去重:

  1. 好處:能夠減少伺服器的壓力以及保證資料的準確性;

  2. 每核心次請求的時候,先判斷這個請求是否在已經爬取的佇列當中,存在捨去,不存在爬取;

  3. 採用scrapy-redis中的set集合做的去重(可做持久化儲存)。

 

淺談斷點續爬:

  1. 如果執行爬蟲down掉了,在下一次啟動的時候可以接入上次end的位置繼續。

  2. 斷點續爬就是將資料佇列 集合以及任務佇列實現本地持久化儲存

 

分散式爬蟲編寫流程:

  1. 編寫普通scrapy爬蟲

    • 建立專案

    • 明確目標

    • 建立爬蟲(普通scrapy爬蟲以及crawlSpider爬蟲)

    • 儲存內容

  2. 改造分散式爬蟲

    • 匯入scrapy-redis中的分散式爬蟲類

    • 繼承類

    • 登出start_url & allowed-domains

    • 設定redis_key獲取start_urls

    • 設定__init__獲取允許的域

  3. 改造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

程式執行方式:

  1. 開啟redis-server.exe

  2. 開啟redis-cli.exe

  3. 找到爬蟲檔案下的spider

  4. scrapy runspider spiderName

  5. 在redis-cli中輸入:lpush redis-keyName(spider中定義的redis-key名字) URL(網頁的連結)

實現效果:

scrapy分散式淺談+京東示例

 

完整專案程式碼:

Github:https://github.com/xbhog/scrapyRedis

致謝:如果對您有幫助,希望隨手一個star,感謝!!

相關文章