0

    【Scrapy学习心得】爬虫实战五(Scrapy-Redis分布式爬虫)

    2023.04.15 | admin | 226次围观

    声明:仅供技术交流,请勿用于非法用途,如有其它非法用途造成损失,和本文无关

    前言

    废话不多说,直接开始吧~本次爬取的网站是:有缘网

    一、基本配置

    一台master机,一台slave机,本文使用的master是云服务器(Ubuntu),而slave是我的笔记本电脑(Windows)

    两台机都需要安装Python3.7、Scrapy框架、Scrapy-Redis框架

    在master机上安装Redis数据库,并配置好能够远程连接等等

    二、分析页面

    我们随便筛选几个条件搜索一下,发现了这个列表页的url是存在一定规律的:例如:筛选的条件为广东的18岁以上的mm:

    http://www.youyuan.com/find/guangdong/mm18-0/advance-0-0-0-0-0-0-0/p1/

    例如:筛选的条件为广东的18岁至25岁的mm:

    http://www.youyuan.com/find/guangdong/mm18-25/advance-0-0-0-0-0-0-0/p1/

    其次,按F12打开开发者工具,然后可以看到这一列表页上的所有信息都在四、开始敲代码

    首先创建Scrapy爬虫项目:scrapy startproject youyuan

    然后再创建爬虫文件:cd youyuan && scrapy genspider yy youyuan.com

    最后开始敲代码

    (一)原始Scrapy框架下的爬虫代码(非分布式)

    items.py 代码如下:

    from scrapy import Item, Field

    class YouyuanItem(Item):
        name=Field()      #姓名
        address = Field() #地址
        age = Field()     #年龄
        height = Field()  #身高
        salary = Field()  #收入
        house = Field()   #房子
        hobby = Field()   #爱好
        image = Field()   #照骗
        motto = Field()   #内心独白
        detail = Field()  #详细资料
        boy_condition = Field() #男友标准
        url=Field()       #个人主页

    pipelines.py 代码如下:(开发阶段只先输出一下,没有特别的地方)

    # -*- coding: utf-8 -*-
    # Define your item pipelines here
    class YouyuanPipeline(object):
        def process_item(self, item, spider):
            if spider.name == 'yy':
                print(item)
             return item

    yy.py 代码如下:

    # -*- coding: utf-8 -*-
    import scrapy
    from youyuan.items import YouyuanItem
    import re

    # 这个函数是用于处理详细资料和征友条件,返回一个字典
    def process_data(data):
        key = data[::2]
        value = [re.sub(' ','',i) for i in data[1::2]]
        return dict(zip(key, value))

    class YySpider(scrapy.Spider):
        name = 'yy'
        allowed_domains = ['youyuan.com']
        start_urls = ['http://www.youyuan.com/city/']

        def parse(self, response):
         # 构造每个城市中的18岁以上的mm请求url
            base_url = 'http://www.youyuan.com/find{0}mm18-0/advance-0-0-0-0-0-0-0/p1/'
            a_list=response.xpath('//div[@class="yy_city_info"]/ul/li/font/a')
            for a in a_list:
                yield scrapy.Request(
                    base_url.format(a.xpath('./@href').get()),
                    callback=self.parse_all,
                    priority=3
                )

        def parse_all(self, response):
         # 拿到列表页中所有mm的详情页url
            li_list = response.xpath('//ul[@class="mian search_list"]/li')
            for li in li_list:
                detail_url = response.urljoin(li.xpath('./dl/dt/a/@href').get())
                yield scrapy.Request(
                    detail_url,
                    callback=self.parse_item,
                    priority=1
                )
            # 若有下一页,则继续发起请求
            temp = response.xpath('//a[@class="pe_right"]/@href').get()
            if temp != '###':
                next_page = response.urljoin(temp)
                yield scrapy.Request(
                    next_page,
                    callback=self.parse_all,
                    priority=2
                )

        def parse_item(self, response):
         # 详情页里爬取mm的所有想要的信息
         # 判断是否已经进入了详情页,有一些页面会重定向到首页,从而会报错的
            flag=response.xpath('//p[@class="top_tit"]')
            if flag:
                item=YouyuanItem()
                item['name'] = response.xpath('//div[@class="main"]/strong/text()').get()  # 姓名
                temp=response.xpath('//p[@class="local"]/text()').get().split()
                item['address'] = temp[0]  # 地址
                item['age'] = temp[1]  # 年龄
                item['height'] = temp[2]  # 年龄
                item['salary'] = temp[3]  # 收入
                item['house'] = temp[4]  # 房子
                item['hobby'] = [i.strip() for i in response.xpath('//ol[@class="hoby"]/li//text()').getall()]  # 爱好
                item['image'] = response.xpath('//li[@class="smallPhoto"]/@data_url_full').getall()  # 照骗
                item['motto'] = response.xpath('//ul[@class="requre"]/li[1]/p/text()').get().strip()  # 内心独白
                item['detail'] = process_data(response.xpath('//div[@class="message"]')[0].xpath('./ol/li//text()').getall())  # 详细资料
                item['boy_condition'] = process_data(response.xpath('//div[@class="message"]')[1].xpath('./ol/li//text()').getall())  # 男友标准
                item['url'] = response.url  # 个人主页
                return item

    settings.py 代码如下:

    # -*- coding: utf-8 -*-
    # Scrapy settings for youyuan project
    BOT_NAME = 'youyuan'
    SPIDER_MODULES = ['youyuan.spiders']
    NEWSPIDER_MODULE = 'youyuan.spiders'

    # 日志级别
    LOG_LEVEL='DEBUG'

    # 用户代理
    USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'

    # Obey robots.txt rules
    ROBOTSTXT_OBEY = False

    # 下载延迟
    DOWNLOAD_DELAY = 0.5

    # pipeline管道
    ITEM_PIPELINES = {
        'youyuan.pipelines.YouyuanPipeline'300,
    }

    (二)Scrapy-Redis(分布式爬虫)

    其实应用Scrapy-Redis框架来写分布式爬虫,只需要在原始Scrapy框架下的爬虫代码里修改一部分代码即可。

    from scrapy_redis.spiders import RedisSpider

    # class YySpider(scrapy.Spider):
    class YySpider(RedisSpider):
        name = 'yy'
        allowed_domains = ['youyuan.com']
        # start_urls = ['http://www.youyuan.com/city/']
        redis_key = 'yy:start_urls'

    # 去重
    DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

    # 调度器
    SCHEDULER = "scrapy_redis.scheduler.Scheduler"

    # 优先级队列(以下3个随便选一个)
    # SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderPriorityQueue"
    SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderQueue"
    #SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderStack"

    # 允许暂停
    SCHEDULER_PERSIST = True

    # Redis连接
    REDIS_HOST = 'master机的ip地址'
    REDIS_PORT = 6379
    REDIS_PARAMS = {'password':'redis设置的密码'# 没有配置密码的话可以去掉
    # 并发请求数量(默认是16,若不更改小一点的话,会出现有一台机一直处于等待状态)
    CONCURRENT_REQUESTS = 2

    # pipeline管道(开启redis的管道)
    ITEM_PIPELINES = {
        'youyuan.pipelines.YouyuanPipeline'300,
        'scrapy_redis.pipelines.RedisPipeline'400,
    }

    五、运行分布式爬虫项目

    首先,在master机上开启redis服务端,即在控制台运行命令:redis-server奇迹服务端修改教程,或者以指定配置文件的方式来启动:redis-server /etc/redis/redis.conf

    然后,在master机上的另一个终端上进入redis客户端,即在控制台运行命令:redis-cli,之后如果你的redis设置了密码,那么再输入:auth 你的密码,然后才能继续操作redis数据库

    再者,分别运行两台机的爬虫项目(不分先后,随意即可),运行的命令是:scrapy crawl yy(在有scrapy.cfg文件的目录下进入cmd命令),此时两台机都是处于监听的状态中

    最后,在master机的redis客户端中,输入:lpush yy:start_urls ,之后,会看到两台机都开始跑起来了!ohhhhhhhhhhh~

    六、将数据转存至MySQL

    即使爬取到的数据会自动存到Redis数据库中(开启Redis的pipeline之后),可是我的云服务器内存还是比较小,不想占用太多的内存,所以,可以将Redis数据库中的数据转存到本地MySQL数据库中。

    Python中操作Redis数据库的包是:redis,可以直接用pip来安装;而操作MySQL数据库的包是:pymysql,同样可以用pip来安装。

    将数据转存到MySQL的代码如下:

    import json
    import redis
    import pymysql
    import time

    # 连接redis数据库
    rediscli = redis.StrictRedis(host='', port = 6379, db = 0, password='')
    # 连接mysql数据库
    mysqlcli = pymysql.connect(host='',user='',password='',database='',charset='utf8')

    while True:
        if rediscli.exists("yy:items") == 0:
            sj = time.perf_counter() # 计时
            # 超过1分钟,退出循环
            if sj >= 60:
                mysqlcli.close()
                break
        else:
         # 将Redis数据库中的数据pop出来
            source, data = rediscli.blpop(["yy:items"])
            item = json.loads(data)
            cursor = mysqlcli.cursor()
            sql = '''insert into youyuan(name,address,age,height,salary,house,hobby,image,motto,
                        detail,boy_condition,url) values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)'''

            cursor.execute(sql,[item['name'],item['address'],item['age'],item['height'],item['salary'],item['house'],
                                json.dumps(item['hobby'],ensure_ascii=False),
                                json.dumps(item['image'],ensure_ascii=False),
                                item['motto'],json.dumps(item['detail'],ensure_ascii=False),
                                json.dumps(item['boy_condition'],ensure_ascii=False),item['url']])
            mysqlcli.commit()
            cursor.close()

    七、一些tips、一些坑

    在本机上先敲好代码,然后打包项目,直接上传至云服务器即可。比如打包时的命令:tar -cvf youyuan.tar youyuan(在youyuan项目的最上层文件目录下进入cmd命令,再运行即可);然后在云服务器上解包的命令:tar -xvf youyuan.tar即可;其中,将打包后项目上传到云服务器上可以在xshell里面操作。

    Redis数据库的安装以及配置,可以看这里的教程点击跳转,还有,可能在redis运行的日志文件中会发现有个警告:WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.这时奇迹服务端修改教程,只需按照它的提示在这个/etc/sysctl.conf配置文件中加入vm.overcommit_memory = 1,并且在控制台中运行命令:sysctl vm.overcommit_memory=1即可。

    这个有缘网站应该是有限制可查看数量的,因为一开始我只是想爬取全国18岁~25岁的mm的,但是跑完之后才1000左右条数据,而且我发现直接跳到几百页后,会有重复的数据出现,所以说那些页数都是假的?!所以我才一怒之下将25岁的上限给去掉了(捂脸)。不过也有可能是我没有注册,可能登陆之后会看到更多的信息??这个后面再说吧哈哈。

    参考链接

    写在最后

    其实一开始我是用RedisCrawlSpider来爬的,可是呢,如果只是一台机运行的话,master和salve都可以跑,没问题的,但是,一旦两台机一起跑,就会有其中一台机跑不了,报错的信息是builtins.ValueError:Method '_callback' not found,不是callback找不到就是其他的函数,总之总有一台机跑不了(百度了很久,也没有解决。),本来我都想放弃了,然后突然有个想法就是不用RedisCrawlSpider来爬,换成RedisSpider,之后竟然奇迹般地成功了!!哈哈哈哈~

    这个是补全之前的Scrapy框架的学习心得,其实在那个时候就知道分布式爬虫的一些原理了,只是没有去实践。所以,现在有时间了,就补上了之前学习Scrapy框架未完成的事。还是会学到很多东西的哈哈~

    本文首发于我的CSDN博客。

    ps:【Python王者之后】公众号中回复:20200626即可获取本文项目的源代码

    版权声明

    本文仅代表作者观点。
    本文系作者授权发表,未经许可,不得转载。

    发表评论