分布式爬虫的部署(scrapy_redis)

理论基础:分布式爬虫的原理

  • 分布式数据库中有4个key:
    • xxx:start_urls : 起始url列表,用于存放我们通过服务器脚本加入的起始url
    • xxx:requests : 用于存储爬虫过程中新产生的那些url对应的请求对象
    • xxx:items : 用于存储抓到的数据
    • xxx:dupefilter: 用于去重

实际操作

  • 系统要求:一台性能较好Linux的主机,和若干台windows系统的主机。

  • 工作环境和安装:

    • Linux主机作为分布式系统服务端(也叫master端),主要提供数据存取等方面服务
    • windows主机作为分布式系统的爬虫业务端(也叫slaver端),windows提供爬虫的业务等工作
    • 安装:
      • master端:安装redis数据库并且能够远程连接(master一般只提供数据服务,不提供业务服务)
      • slaver端:安装scrapy(主要用于做爬虫业务),安装分布式调度组件scrapy_redis(由于scrapy中的调度器是本地调度器,只能进行当前scrapy的相关的业务的调度,无法进行多台主机之间协同调度,所以我们需要一个分布式调度的组件scrapy_redis
    • 分布式部署
      • 建立slaver端与master端的数据通路(首先,把scrapy_redis的管道组件配置上;然后,加上管道组件的主机名、端口号等信息以定位master端的服务程序)

      • 将slaver端的调度器切换成分布式的调度器(首先,配置SCHEDULER值为scrapy_redis里面的相关调度组件的位置,然后给调度组件加入一些配置参数:例如去重组件)

      • 把我们的slaver端的爬虫的父类修改成RedisCrawlSpider

      • 把起始的url提取方式由当前类实例的start_urls属性,切换至从redis的相关的数据库中来提取(把start_urls这个属性去掉,加上redis_key=":start_urls"这个属性)

    • 分布式爬虫执行:
      • 将所有的slaver端的爬虫都运行起来(指令:scrapy crawl 爬虫名),运行起来以后所有的爬虫都在等待master端xxx:start_urls这个键里出现起始url
      • 写一个服务器脚本,用于将我们的起始url全部加入xxx:start_urls这个key中
        • 连接redis,redis-cli -h 主机名,连接成功后 执行lpush 爬虫名:start_urls 网站首页连接
        • 例如:lpush dushu:start_urls https://www. dushu.com/book/1002.html
      • 竞争到起始url的那些爬虫开始爬取,没有竞争到的继续等待
  • settings配置


#settings文件配置

ITEM_PIPELINES = {
   # 'DushuPro.pipelines.DushuproPipeline': 300, # 分布式爬虫数据不存在在slaver的本地,而是要存储在master端
    # 我们应该把数据管道切换至分布式管道,有分布式管道将数据存入相关的master的数据库中
    "scrapy_redis.pipelines.RedisPipeline":300, # 这个组件路径就是scrapy_redis的管道组件路径,这个管道作用就是建立一个slaver与master端的数据通路;这个数据通路的建立除了这个管道组件以外还需要一些配置信息
}
# scrapy_redis组件的配置信息
REDIS_HOST = '主机名'
REDIS_PORT = 6379
# 有密码的需再加一个密码配置
REDIS_PARAMS = {"password":"xxxxxx"}

# 将scrapy的调度器切换成分布式调度器
SCHEDULER ="scrapy_redis.scheduler.Scheduler"
# 调度过程是否允许暂停
SCHEDULER_PERSIST = True
# 调度过程的去重组件
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

分布式爬虫的执行流程

  1. 当所有的slaver主机运行起来以后,会通过管道组件不停的监视数据库中start_urls和requests两个key,从中竞争资源
  2. 通过服务器脚本加入xxx:start_urls这个key以后,某些slaver主机就会竞争到这个资源,就会开始爬取,如果请求成功这个过程中产生的新的url将会封装成一个request对象被加入xxx:requests中,产生的数据将会被加入xxx:items中,这个url本身会被加入到xxx:dupefilter中;如果请求失败,这个start_url对应的request对象将会被重新加入到xxx:requests中供其他主机再提取
  • 新产生的那些requests列表中的url会被所有的slaver竞争提取,重复步骤2)
  • 当所有的数据全部被抓取完,爬虫将继续等到是否有新的任务产生

代码实现

dushu网爬取

爬虫器

import scrapy
from scrapy.linkextractors import LinkExtractor
# 导入链接提取器类,从一个url的网页上根据一定的规则来提取新的链接

from scrapy.spiders import CrawlSpider, Rule
# CrawlSpider是spiders一个派生类,在基本爬虫的基础上扩展功能
# Rule规则对象,根据规则安排url的提取、组合与调度
from DushuPro.items import DushuproItem
from scrapy_redis.spiders import RedisCrawlSpider
# 这个类是CrawlSpider的派生类,在增量爬虫的基础上加入了分布式调度的相关方法

class DushuSpider(RedisCrawlSpider):
    name = 'dushu'
    allowed_domains = ['dushu.com']
    # start_urls = ['https://www.dushu.com/book/1002.html']
    # 分布式爬虫的所有的url都是master端的redis数据库来提供,start_urls这个属性要去掉
    redis_key = "dushu:start_urls" # redis_key属性,指定起始url的应该从哪里来提取

    rules = (
        Rule(LinkExtractor(allow=r'/book/1002_\d\.html'), callback='parse_item', follow=True),)

    def parse_item(self, response):
        booklist = response.xpath("//div[@class='bookslist']//li")
        for book in booklist:
            item = DushuproItem()
            item["title"] = book.xpath(".//h3/a/text()").extract_first()
            # extract_first()从selector列表将内容取出,然后从内容列表中取出首元素,如果列表为空,直接去None

            item["author"] = "".join(book.xpath(".//div[@class='book-info']/p[1]//text()").extract())
            # print(item)
            # 匹配出二级页面的链接
            next_url = "https://www.dushu.com" + book.xpath(".//h3/a/@href").extract_first()

            # 向二级页面发起请求
            yield scrapy.Request(url=next_url,callback=self.parse_Info,meta={"item":item})
    # 回调函数,用于解析下级页面
    def parse_Info(self, response):
        # 把上级页面送的item提取出来
        item = response.meta["item"]
        # 继续解析item
        item["price"] = response.xpath("//span[@class='num']/text()").extract_first()
        item["publisher"] = response.xpath("//div[@class='book-details-left']/table//tr[2]//a/text()").extract_first()
        item["authorInfo"] = response.xpath("//div[@class='text txtsummary']//text()").extract()[1]
        item["content"] = response.xpath("//div[@class='text txtsummary']//text()").extract()[0]
        item["mulu"] = "\n".join(response.xpath("//div[@class='text txtsummary']")[2].xpath(".//text()").extract())
        yield item

zhipin网爬取(代理池的应用)

settings配置

ROBOTSTXT_OBEY = False
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'
DOWNLOAD_DELAY = 1

DOWNLOADER_MIDDLEWARES = {
   'BossPro.middlewares.BossproDownloaderMiddleware': 543,
}
ITEM_PIPELINES = {
   'scrapy_redis.pipelines.RedisPipeline': 300,
}
REDIS_HOST = 'www.fanjianbo.com'
REDIS_PORT = 6379

SCHEDULER = 'scrapy_redis.scheduler.Scheduler'
SCHEDULER_PERSIST = True
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

spiders模块

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from BossPro.items import BossproItem
from scrapy_redis.spiders import RedisCrawlSpider

class JobSpider(RedisCrawlSpider):
    name = 'job'
    allowed_domains = ['zhipin.com']
    # start_urls = ['https://www.zhipin.com/c101010100-p100109/?page=1&ka=page-1']

    redis_key = "job:start_urls"

    rules = (
        Rule(LinkExtractor(allow=r'page=\d+'), callback='parse_item', follow=True),
        # /c101010100-p100109/?page=2
    )

    def parse_item(self, response):
        joblist = response.xpath("//div[@class='job-list']//li")
        for job in joblist:
            # 一级页面没有item中的字段,匹配出下级页面url,请求下级页面取解析
            next_url = "https://www.zhipin.com" + job.xpath(".//div[@class='info-primary']//h3/a/@href").extract_first()
            yield scrapy.Request(url=next_url,callback=self.parse_info)

    # 封装一个回调函数,用于解析职位详情页
    def parse_info(self, response):
        # print(response)
        item = BossproItem()
        item["jobName"] = response.xpath("//div[@class='info-primary']//h1/text()").extract_first()
        item["salary"] = response.xpath("//span[@class='salary']/text()").extract_first()
        item["require"] = " ".join(response.xpath("//div[starts-with(@class,'job-primary')]/div[@class='info-primary']//p//text()").extract()[1:])
        item["jobInfo"] = "".join(response.xpath("//div[@class='job-sec']/div[@class='text']/text()").extract())
        item["companyInfo"] = "".join(response.xpath("//div[@class='job-sec company-info']/div[@class='text']/text()").extract())
        item["companySize"] = " ".join(response.xpath("//div[@class='sider-company']/p//text()").extract()[1:4])
        item["companyFuli"] = " ".join(response.xpath("//div[@class='job-tags']")[0].xpath(".//text()").extract())
        item["address"] = response.xpath("//div[@class='location-address']/text()").extract_first()
        yield item

items模块

import scrapy


class BossproItem(scrapy.Item):
    # 岗位名称
    jobName = scrapy.Field()
    # 薪资待遇
    salary = scrapy.Field()
    # 岗位要求
    require = scrapy.Field()
    # 岗位描述
    jobInfo = scrapy.Field()
    # 公司介绍
    companyInfo = scrapy.Field()
    # 公司规模
    companySize = scrapy.Field()
    # 公司福利
    companyFuli = scrapy.Field()
    # 公司地址
    address = scrapy.Field()

middlewares模块


from scrapy import signals
import redis
import requests

class BossproDownloaderMiddleware(object):

    # 定义一个成员变量用于提取代理
    def get_ippool(self):
        rds = redis.StrictRedis(host="www.fanjianbo.com",port=6379,db=6)
        ip_list = rds.lrange("ippool",0,rds.llen("ippool"))
        return ip_list
    def process_request(self, request, spider):
        print("中间件.....")
        # 取出代理池
        ip_list = self.get_ippool()
        print(ip_list)
        for ip in ip_list:
            try:
                # 检查代理是否能用
                requests.get(url="https://www.baidu.com/", headers={"user-agent":'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'}, proxies={"https": ip})
                print("当前代理为:",ip)
                # 给当前的request设置代理服务器
                request.meta["proxy"] = {"https":ip.decode("utf8")}
                break
            except Exception  as e:
                print("代理%s已经过期!"%ip)

    def process_response(self, request, response, spider):

        return response

拼接url模块(单独解析)

from selenium import webdriver
from bs4 import BeautifulSoup
import redis
from time import sleep
# 【请求】
def request_data(url):
    opt = webdriver.ChromeOptions()
    opt.add_argument("--headless")
    opt.add_argument('--disable-gpu')
    driver = webdriver.Chrome(options=opt)
    driver.get(url)
    sleep(1)
    return driver.page_source

# 【解析】
def analysis_data(html):
    soup = BeautifulSoup(html,'lxml')
    # 解析城市编码
    provence_list = soup.select(".dorpdown-city > ul")[1:]
    city_list = []
    for provence in provence_list:
        cities = provence.select("li")
        for city in cities:
            city_list.append(city.get("data-val"))
    # 解析职位编码
    job_list = soup.select(".job-menu dl ul a")
    for job in job_list:
        # 取出job的编号
        job_code = job.get("href").split("-")[-1]
        # 每一个工作编号和所有的城市组合成一个岗位招聘的url
        for city in city_list:
            job_url = "https://www.zhipin.com/" + "c" + city + "-"  + job_code
            yield job_url
# 【存储】
def write_to_redis(url_list):
    rds = redis.StrictRedis(host="主机名",port=6379,db=0)
    for url in url_list:
        rds.lpush("job:start_urls",url)
        print("链接:%s已经进入数据库!"%url)

if __name__ == '__main__':
    url = "https://www.zhipin.com/"
    html = request_data(url=url)
    url_list = analysis_data(html)
    write_to_redis(url_list)

提取代理服务器与代理池

import requests
import json
import redis

# 提取代理
def get_proxies(url):
    res = requests.get(url=url)
    return res.text

# 质检
def check_proxis(proxies):
    headers = {"user-agent":'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'}
    proxy_list = proxies.split("\r\n")
    print(proxy_list)
    for proxy in proxy_list:
        try:
            requests.get(url="https://www.baidu.com/",headers=headers,proxies={"https":proxy})
            # 如果代理没有失效则存入数据库
            rds = redis.StrictRedis(host="www.fanjianbo.com",port=6379,db=6)
            rds.lpush("ippool",proxy)
            print("代理:%s已经存入数据库!"%proxy)
        except Exception as e:
            print("代理%s已经失效!"%proxy)


if __name__ == '__main__':
    url = "http://api3.xiguadaili.com/ip/?tid=559324242289181&num=100&delay=5&filter=on"
    proxies = get_proxies(url)
    # print(proxies)
    check_proxis(proxies)
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值