Scrapy-redis的兩種分布式爬蟲

思考:
  1. Scrapy分布式爬蟲意味著幾臺機器通過某種方式共同執(zhí)行一套爬取任務,這就首先要求每臺機器都要有Scrapy框架,一套Scrapy框架就有一套Scrapy五大核心組件,引擎--調度器--下載器--爬蟲--項目管道,各自獨有的調度器沒有辦法實現(xiàn)任務的共享,所以不能實現(xiàn)分布式爬取。
  2. 假設可以實現(xiàn)Scrapy框架的調度器共享,那么就能實現(xiàn)分布式爬取了嗎?答案是不能,因為我們實現(xiàn)了任務的共享,但是框架之間的項目管道是單獨的,我們的任務下載完之后,我們爬取的有效信息還是不能全部存放在某個指定的位置,所以要想實現(xiàn)分布式爬蟲,需要同時滿足調度器和項目管道的共享才可以達到分布式的效果。

實現(xiàn):基于Scrapy-redis實現(xiàn)分布式爬蟲:
  scrapy-redis內部實現(xiàn)了調度器和項目管道共享,可以實現(xiàn)分布式爬蟲

一、redis數據庫實現(xiàn)RedisCrawlSpider分布式操作

案例簡述:分布式爬蟲爬取抽屜網全棧主題文本數據

redis的準備工作:
  1.對redis配置文件進行配置:
    - 注釋該行:bind 127.0.0.1,表示可以讓其他ip訪問redis
    - 將yes該為no:protected-mode no,表示可以讓其他ip操作redis
  2.啟動redis:
    mac/linux: redis-server redis.conf
    windows: redis-server.exe redis-windows.conf

實現(xiàn)分布式爬蟲的操作步驟:
  1. 將redis數據庫的配置文件進行改動: .修改值 protected-mode no .注釋 bind 127.0.0.1
  2. 下載scrapy-redis
  pip3 install scraps-redis
  3. 創(chuàng)建工程 scrapy startproject 工程名
  scrapy startproject 工程名
  4. 創(chuàng)建基于scrawlSpider的爬蟲文件
  cd 工程名
  scrapy genspider -t crawl 項目名
  5. 導入RedisCrawlSpider類
  from scrapy_redis.spiders import RedisCrawlSpider
  6. 在現(xiàn)有代碼的基礎上進行連接提取和解析操作
  class RidesdemoSpider(RedisCrawlSpider):
  redis_key = "redisQueue"
  7. 將解析的數據值封裝到item中,然后將item對象提交到scrapy-redis組件中的管道里(自建項目的管道沒什么用了,可以直接刪除了,用的是組件封裝好的scrapy_redis.pipelines中)
  ITEM_PIPELINES = {
   'scrapy_redis.pipelines.RedisPipeline': 400,
  }
.  8. 管道會將數據值寫入到指定的redis數據庫中(在配置文件中進行指定redis數據庫ip的編寫)
  REDIS_HOST = '192.168.137.76'
  REDIS_PORT = 6379
  REDIS_ENCODING = ‘utf-8’
  # REDIS_PARAMS = {‘password’:’123456’}
  9. 在當前工程中使用scrapy-redis封裝好的調度器(在配置文件中進行配置)
  # 使用scrapy-redis組件的去重隊列(過濾)
  DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
  # 使用scrapy-redis組件自己的調度器(核心代碼共享調度器)
  SCHEDULER = "scrapy_redis.scheduler.Scheduler"
  # 是否允許暫停
  SCHEDULER_PERSIST = True
  11. 啟動redis服務器:
  redis-server redis.windows.conf windows系統(tǒng)
  redis-server redis.conf mac系統(tǒng)
  12. 啟動redis-cli
  redis-cli
  13. 執(zhí)行當前爬蟲文件:
  scrapy runspider 爬蟲文件.py
  14. 向隊列中扔一個起始url>>>在redis-cli執(zhí)行扔的操作:
  lpush redis_key的value值 起始url

spider.py文件:

# -*- coding: utf-8 -*-
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import Rule
from scrapy_redis.spiders import RedisCrawlSpider
from redisScrapyPro.items import RedisscrapyproItem

class RidesdemoSpider(RedisCrawlSpider):
    name = 'redisDemo'

    # scrapy_redis的調度器隊列的名稱,最終我們會根據該隊列的名稱向調度器隊列中扔一個起始url
    redis_key = "redisQueue"

    link = LinkExtractor(allow=r'https://dig.chouti.com/.*?/.*?/.*?/\d+')
    link1 = LinkExtractor(allow=r'https://dig.chouti.com/all/hot/recent/1')
    rules = (
        Rule(link, callback='parse_item', follow=True),
        Rule(link1, callback='parse_item', follow=True),
    )

    def parse_item(self, response):
        div_list = response.xpath('//*[@id="content-list"]/div')
        for div in div_list:
            content = div.xpath('string(./div[@class="news-content"]/div[1]/a[1])').extract_first().strip().replace("\t","")
            print(content)
            item = RedisscrapyproItem()
            item['content'] = content
            yield item

settings.py

BOT_NAME = 'redisScrapyPro'

SPIDER_MODULES = ['redisScrapyPro.spiders']
NEWSPIDER_MODULE = 'redisScrapyPro.spiders'

# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36'

# Obey robots.txt rules
ROBOTSTXT_OBEY = False

ITEM_PIPELINES = {
    'scrapy_redis.pipelines.RedisPipeline': 400,
}
REDIS_HOST = '192.168.137.76'
REDIS_PORT = 6379
REDIS_ENCODING = 'utf-8'
# REDIS_PARAMS = {‘password’:’123456’}

# 使用scrapy-redis組件的去重隊列
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 使用scrapy-redis組件自己的調度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 是否允許暫停
SCHEDULER_PERSIST = True

更多項目代碼
https://github.com/wangjifei121/FB-RedisCrawlSpider

二、redis數據庫實現(xiàn)RedisSpider分布式操作

案例簡述:分布式爬蟲爬取網易新聞(國內,國際,軍事,航空四個板塊)

擴展知識點使用:

  • selenium如何被應用到scrapy框架
  • UA池的使用
  • 代理IP池的使用

RedisSpider分布式操作的步驟和RedisCrawlSpider分布式的搭建步驟是相同的,參照以上步驟來學習搭建就可以。

接下來主要講解一下拓展知識點的使用:

一、selenium如何被應用到scrapy框架

首先看spider類中增加的兩個方法:
def __init__(self):
  pass
def closed(self, spider):
  pass
  通過對網易新聞網頁分析,我們可以看出網頁的數據采取了動態(tài)加載數據的反爬措施,這樣我們要想獲取更多數據就需要使用到selenium的webdriver類了。
  使用webdriver的第一步就是實例化一個webdriver對象,而實例化的對象只需要實例化一次就能在爬取的過程中使用,所以就應該想到類的實例化方法init。
  實例化的webdriver對象在結束使用后需要關閉,正好spider類給我們提供了closed方法來做關閉操作。這樣我們就可以通過這兩個方法來實現(xiàn)我們的想法了。
.
  那我們實例化好了webdriver對象該怎么用它呢?在哪里用?
  首先我們在不用selenium的時候我們發(fā)現(xiàn)頁面的數據我們是獲取不到的,也可以換個角度來說,我們獲取到了數據但不是我們想要的。這樣我們的需求就要求我們重新來獲取想要的response,可以肯定的是,每次請求我們都要做相應的處理,這時候經驗豐富的你就應該想到了三個字《中間件》,那在哪個中間件來做對應的處理呢?
  很顯然要在process_response中間件中來執(zhí)行我們的selenium的相關操作,這個中間件的作用就是攔截到響應對象(下載器傳遞給Spider的響應對象),通過處理、偽裝response從而得到我們想要的數據。
.
process_response中間件中參數解釋:
  request:響應對象對應的請求對象
  response:攔截到的響應對象
  spider:爬蟲文件中對應的爬蟲類的實例
.
在中間件中我們主要做了哪些操作呢?
通過實例化好的瀏覽器對象發(fā)動請求-->執(zhí)行相應的js代碼-->獲取對應的頁面數據-->篡改相應對象-->返回response

二、UA池代碼的編寫
和上面的方法一樣,UA池代碼也需要在中間件中編寫,需要導入一個類來繼承,然后構建我們的UA類:
from scrapy.contrib.downloadermiddleware.useragent import UserAgentMiddleware

image

三、IP代理池的編寫
問題的思考方式是一樣的,這里就略過了

image

這里需要著重注意:請求的協(xié)議頭有http 和 https兩種,我們需要做相應的判斷

最后中間件一定要在settings中進行注冊?。。∽裕。。∽裕。?!

最后附上spider.py和middlewares.py的代碼:
spider.py

# -*- coding: utf-8 -*-
import scrapy
from selenium import webdriver
from wangyiPro.items import WangyiproItem
from scrapy_redis.spiders import RedisSpider

class WangyiSpider(RedisSpider):
    name = 'wangyi'
    # allowed_domains = ['www.xxxx.com']
    # start_urls = ['https://news.163.com']
    redis_key = 'wangyi'

    def __init__(self):
        # 實例化一個瀏覽器對象(實例化一次)
        self.bro = webdriver.Chrome(executable_path='/Users/wangjifei/my_useful_file/chromedriver')

    # 必須在整個爬蟲結束后,關閉瀏覽器
    def closed(self, spider):
        print('爬蟲結束')
        self.bro.quit()

    def parse(self, response):
        lis = response.xpath('//div[@class="ns_area list"]/ul/li')
        indexs = [3, 4, 6, 7]
        li_list = []  # 存儲的就是國內,國際,軍事,航空四個板塊對應的li標簽對象
        for index in indexs:
            li_list.append(lis[index])
        # 獲取四個板塊中的鏈接和文字標題
        for li in li_list:
            url = li.xpath('./a/@href').extract_first()
            title = li.xpath('./a/text()').extract_first()

            # 對每一個板塊對應的url發(fā)起請求,獲取頁面數據(標題,縮略圖,關鍵字,發(fā)布時間,url)
            yield scrapy.Request(url=url, callback=self.parseSecond, meta={'title': title})

    def parseSecond(self, response):
        div_list = response.xpath('//div[@class="data_row news_article clearfix "]')
        # print(len(div_list))
        for div in div_list:
            head = div.xpath('.//div[@class="news_title"]/h3/a/text()').extract_first()
            url = div.xpath('.//div[@class="news_title"]/h3/a/@href').extract_first()
            imgUrl = div.xpath('./a/img/@src').extract_first()
            tag = div.xpath('.//div[@class="news_tag"]//text()').extract()
            tags = []
            for t in tag:
                t = t.strip(' \n \t')
                tags.append(t)
            tag = "".join(tags)

            # 獲取meta傳遞過來的數據值title
            title = response.meta['title']

            # 實例化item對象,將解析到的數據值存儲到item對象中
            item = WangyiproItem()
            item['head'] = head
            item['url'] = url
            item['imgUrl'] = imgUrl
            item['tag'] = tag
            item['title'] = title

            # 對url發(fā)起請求,獲取對應頁面中存儲的新聞內容數據
            yield scrapy.Request(url=url, callback=self.getContent, meta={'item': item})
            # print(head+":"+url+":"+imgUrl+":"+tag)

    def getContent(self, response):
        # 獲取傳遞過來的item
        item = response.meta['item']

        # 解析當前頁面中存儲的新聞數據
        content_list = response.xpath('//div[@class="post_text"]/p/text()').extract()
        content = "".join(content_list)
        item['content'] = content

        yield item

middlewares.py

from scrapy.http import HtmlResponse
import time
from scrapy.contrib.downloadermiddleware.useragent import UserAgentMiddleware
import random
#UA池代碼的編寫(單獨給UA池封裝一個下載中間件的一個類)
#1,導包UserAgentMiddlware類
class RandomUserAgent(UserAgentMiddleware):

    def process_request(self, request, spider):
        #從列表中隨機抽選出一個ua值
        ua = random.choice(user_agent_list)
        #ua值進行當前攔截到請求的ua的寫入操作
        request.headers.setdefault('User-Agent',ua)

#批量對攔截到的請求進行ip更換
class Proxy(object):
    def process_request(self, request, spider):
        #對攔截到請求的url進行判斷(協(xié)議頭到底是http還是https)
        #request.url返回值:http://www.xxx.com
        h = request.url.split(':')[0]  #請求的協(xié)議頭
        if h == 'https':
            ip = random.choice(PROXY_https)
            request.meta['proxy'] = 'https://'+ip
        else:
            ip = random.choice(PROXY_http)
            request.meta['proxy'] = 'http://' + ip

class WangyiproDownloaderMiddleware(object):
    #攔截到響應對象(下載器傳遞給Spider的響應對象)
    #request:響應對象對應的請求對象
    #response:攔截到的響應對象
    #spider:爬蟲文件中對應的爬蟲類的實例
    def process_response(self, request, response, spider):
        #響應對象中存儲頁面數據的篡改
        if request.url in['http://news.163.com/domestic/','http://news.163.com/world/','http://news.163.com/air/','http://war.163.com/']:
            spider.bro.get(url=request.url)
            js = 'window.scrollTo(0,document.body.scrollHeight)'
            spider.bro.execute_script(js)
            time.sleep(2)  #一定要給與瀏覽器一定的緩沖加載數據的時間
            #頁面數據就是包含了動態(tài)加載出來的新聞數據對應的頁面數據
            page_text = spider.bro.page_source
            #篡改響應對象
            return HtmlResponse(url=spider.bro.current_url,body=page_text,encoding='utf-8',request=request)
        else:
            return response

PROXY_http = [
    '153.180.102.104:80',
    '195.208.131.189:56055',
]
PROXY_https = [
    '120.83.49.90:9000',
    '95.189.112.214:35508',
]

user_agent_list = [
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
        "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
        "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 "
        "(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 "
        "(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 "
        "(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
        "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 "
        "(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 "
        "(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
        "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 "
        "(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
        "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 "
        "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
        "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 "
        "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
]

作者:SlashBoyMr_wang
鏈接:http://www.itdecent.cn/p/5baa1d5eb6d9
來源:簡書

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容