基于python,scrapy,redis实现主从式(分布式的一种)master-slave爬虫

前言

这是本人的第一篇博客,感触还是很多的,最近在帮朋友做一个分布式爬虫的论文,遇到很多坑,不过已经一一填平,废话不多说啦。

分类

(1)主从分布式爬虫:
由一台master服务器, 来提供url的分发, 维护待抓取url的list。由多台slave服务器执行网页抓取功能, slave所抽取的新url,一律由master来处理解析,而slave之间不需要做任何通信。
(2)对等分布式爬虫:
由多台相同的服务器集成,每台服务器可单独运作,完成爬虫工作,每台服务器之间的分工有一定的运算逻辑(ex: hash),由运算(配置)的结果,来决定由那台服务器做抓取网页的工作。

具体查看:http://www.dataguru.cn/thread-529666-1-1.html

本文讲解第一种-主从式,只是简单的阐明,所以master端只负责爬取url保存到redis数据库,slave端取出redis里url队列进行爬取网页内容,解析并保存到mongodb数据库。

准备

python3(本人使用的是3.6版本)
scrapy
redis

mongodb

安装教程自行百度,使用到的python模块(这里是需要使用pip安装,最好是pip新版本):

scrapy-redis
pymongo

python链接mongodb例子:

import pymongo as pm

host = 'localhost'
port = 27017

# 链接数据库
client = pm.MongoClient(host,port)
# 选择db
db = client.demo
# 选择集合test
# 注意这里的集合可以直接使用,如果没有mongodb会自动创建
db.test.insert({"name":"hello"})

client.close()

实现

我们以58同城为例,爬取二手房,爬取中间有些错误但不影响,由于网站整改302重定向了。
1. master端

spider文件

from scrapy.spider import CrawlSpider,Rule
from scrapy.linkextractors import LinkExtractor
from master.items import MasterItem

class myspider(CrawlSpider):

    name = 'master'
    allowed_domains = ['58.com']
    item = MasterItem()
    start_urls = ['http://cd.58.com/ershoufang/']
    rules = (
        Rule(LinkExtractor(allow=('http://cd.58.com/ershoufang/\d{14}x.shtml.*?',)), callback='parse_item',
             follow=True),
    )

    def parse_item(self,response):
        item = self.item
        item['url'] = response.url
        return item

继承CrawlSpider可以遍历整个网站,Rule和LinkExtractor限制遍历那些网页,allow为允许的网址(正则表达式),callback回调函数,注意一定不能写默认的parse函数。

item文件

import scrapy

class MasterItem(scrapy.Item):
    # define the fields for your item here like:
    url = scrapy.Field()
    pass

只保存url

middleware文件

import random
from .useragent import agents

class UserAgentMiddleware(object):

    def process_request(self, request, spider):
        agent = random.choice(agents)
        referer = request.meta.get('referer', None)
        request.headers["User-Agent"] = agent
        request.headers["Referer"] = referer

这里对默认的middleware文件进行修改,useragent是自建的文件,里面agents=['内容略']数组,字段代码表示为每次请求随机一个User-Agent(浏览器身份),一定程度避免认为是爬虫。

pipeline文件

import redis

class MasterPipeline(object):

    def __init__(self):
        self.redis_url = 'redis://123456:@localhost:6379/'
        self.r = redis.Redis.from_url(self.redis_url,decode_responses=True)

    def process_item(self, item, spider):
        self.r.lpush('myredis:start_urls', item['url'])
保存url到redis数据库,redis_url中123456为数据库密码,你的redis没有密码就不用写,第二种链接方式:
redis.Redis(host="localhost",port=6379)

默认使用db0,一定要使用db0,否则slave端取不到数据(可能由于本人才疏学浅没有找到链接其他db供slave端使用的方式)

setting文件

ROBOTSTXT_OBEY = False
DOWNLOADER_MIDDLEWARES = {
   'master.middlewares.UserAgentMiddleware': 543,
}
ITEM_PIPELINES = {
   'master.pipelines.MasterPipeline': 300,
}

注意这里只是部分代码不要覆盖上去,替换相同的地方即可。爬虫一开始会取得网站的robot.txt文件(也叫君子协议文件),得知那些网址可爬,我们设置ROBOTSTXT_OBEY=False不遵循他的协议。

2. slave端

spider文件

import re
from scrapy_redis.spiders import RedisSpider
from scrapy.http import Request
from slave.items import SlaveItem

class myspider(RedisSpider):
    name = 'slave'
    item = SlaveItem()
    redis_key = 'myredis:start_urls'

    def parse(self, response):
        item = self.item
        item['title'] = response.xpath('//div[@class="house-title"]/h1/text()').extract()[0]
        item['price'] = response.xpath('//div[@id="generalSituation"]//li[1]/span[2]/text()').extract()[0]
        item['type'] = response.xpath("//div[@id='generalSituation']//li[2]/span[2]/text()").extract()[0]
        item['area'] = response.xpath("//div[@id='generalSituation']//li[3]/span[2]/text()").extract()[0]
        item['direct'] = response.xpath("//div[@id='generalSituation']//li[4]/span[2]/text()").extract()[0]
        item['floor'] = response.xpath(
            "//div[@id='generalSituation']//ul[@class='general-item-right']/li[1]/span[2]/text()").extract()[0]
        item['decorat'] = response.xpath(
            "//div[@id='generalSituation']//ul[@class='general-item-right']/li[2]/span[2]/text()").extract()[0]
        item['start'] = response.xpath(
            "//div[@id='generalSituation']//ul[@class='general-item-right']/li[4]/span[2]/text()").extract()[0]
        item['village'] = response.xpath("string(/html/body/div[4]/div[2]/div[2]/ul/li[1]/span[2])").extract()[0]
        item['position'] = response.xpath("string(/html/body/div[4]/div[2]/div[2]/ul/li[2]/span[2])").extract()[0]
        item['phone'] = response.xpath("//div[@id='houseChatEntry']//p[@class='phone-num']/text()").extract()[0]
        txt = response.xpath("/html/head/script[1]/text()").extract()[0]
        pattern = re.compile(".*?____json4fe.brokerUrl = '(.*?)';.*?", re.S)
        result = re.findall(pattern, txt)
        yield Request("http://" + result[0], callback=self.get_user)

    def get_user(self, response):
        item = self.item
        item['user'] = response.xpath("/html/body/div[2]/div[2]/div[1]/div[1]/div/div[1]/text()").extract()[0]
        return item

这里不在讲解xpath的用法,告诉你们一个好方法,浏览器F12选中一个元素,右键->复制->xpath。用redis_key代替start_urls,‘myredis:start_urls’为redis数据库db0的键,就是我们master端保存的,直接使用默认回调函数parse。

item文件

import scrapy

class SlaveItem(scrapy.Item):
    title = scrapy.Field()
    price = scrapy.Field()
    type = scrapy.Field()
    area = scrapy.Field()
    direct = scrapy.Field()
    floor = scrapy.Field()
    decorat = scrapy.Field()
    start = scrapy.Field()
    village = scrapy.Field()
    position = scrapy.Field()
    user = scrapy.Field()
    phone = scrapy.Field()
    pass

middleware文件(同master端)

pipeline文件

import pymongo as pm

host = 'localhost'
port = 27017
client = pm.MongoClient(host,port)
db = client.demo.tongcheng

class SlavePipeline(object):
    def process_item(self, item, spider):
        db.insert(dict(item))

保存到mongodb数据库

setting文件

# 启用Redis调度存储请求队列
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
#不清除Redis队列、这样可以暂停/恢复 爬取
# SCHEDULER_PERSIST = True
# 确保所有的爬虫通过Redis去重
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

#指定用于连接redis的URL(可选)
#如果设置此项,则此项优先级高于设置的REDIS_HOST 和 REDIS_PORT
REDIS_URL = 'redis://123456@127.0.0.1:6379/'

BOT_NAME = 'slave'

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

ROBOTSTXT_OBEY = False
DOWNLOADER_MIDDLEWARES = {
   'slave.middlewares.UserAgentMiddleware': 543,
}
ITEM_PIPELINES = {
   'slave.pipelines.SlavePipeline': 300,
}

加入和替换配置

结果

redis数据库

mongodb数据库

最后

本人也是小白,写的粗糙,有不懂或见解的地方一起交流。

ps:本文的github地址:https://github.com/tchtsm/scrapy


评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值