scrapy——7 scrapy-redis分布式爬虫,用药助手实战,Boss直聘实战,阿布云代理设置

时间:2023-03-08 15:39:30
scrapy——7    scrapy-redis分布式爬虫,用药助手实战,Boss直聘实战,阿布云代理设置

scrapy——7

  1. 什么是scrapy-redis

  2. 怎么安装scrapy-redis

  3. scrapy-redis常用配置文件

  4. scrapy-redis键名介绍

  5. 实战-利用scrapy-redis分布式爬取用药助手网站

  6. 实战-利用scrapy-redis分布式爬取Boss直聘网站

  7. 如何使用代理


什么是scrapy-redis-->简介

scrapy-redis是scrapy框架基于redis数据库的组件,用于scrapy项目分布式开发和部署

特征:

  • 分布式爬取

你可以启动多个spider工程,相互之间共享单个redis的request队列。最适合广泛的多个域名网站的内容爬取。

  • 分布式数据处理

爬取到的scrapy的item数据可以推入到redis队列中,这意味着你可以根据需求启动尽可能多的处理器程序来共享item队列,进行item数据持久化处理。

  • scrapy即插即用组件

Schedule调度器 + Duplication复制过滤器,itemPipleline,基本spider

什么是scrapy_redis?-->框架

scrapy——7    scrapy-redis分布式爬虫,用药助手实战,Boss直聘实战,阿布云代理设置

1. 首先Slaver端从Master端(装有redis的系统)拿任务(Request、url)进行数据抓取,Slaver抓取数据的同时,产生新任务的Request便提交给 Master 处理;

2. Master端只有一个Redis数据库,负责将未处理的Request去重和任务分配,将处理后的Request加入待爬队列,并且存储爬取的数据

Scrapy_Redis默认使用的就是这种策略,我们实现起来很简单,因为任务调度等工作Scrapy-Redis都已经帮我们做好了,我们只需要继承RedisSpider、指定redis_key就行了。

缺点是,Scrapy_Redis调度的任务是Request对象,里面信息量比较大(不仅包含url,还有callback函数、headers等信息),可能导致的结果就是会降低爬虫速度、而且会占用Redis大量的存储空间,所以如果要保证效率,那么就需要一定硬件水平。

怎样安装scrapy-redis

通过pip安装: pip install scrapy-redis

依赖环境:

  • python
  • redis
  • scrapy

官方文档:https://scrapy-redis.readthedocs.io/en/stable/
源码位置:https://github.com/rmax/scrapy-redis
博客:https://www.cnblogs.com/kylinlin/p/5198233.html

怎样使用scrapy-redis?-->常用配置文件

  • (必须)使用了scrapy_redis的去重组件,在redis数据库里做去重
    DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
  • (必须)使用了scrapy_redis的调度器,在redis里分配请求
    SCHEDULER = "scrapy_redis.scheduler.Scheduler"
  • (可选)在redis中保持scrapy-redis用到的各个队列,从而True允许暂停和暂停后和恢复,也就是不清理redis queues
    SCHEDULER_PERSIST = True
  • (必须)通过配置RedisPipline将item写入key为spider.name:items的redis的list中,供后面的分布式处理item;这个已经由scrapy-redis实现,不需要我们写代码,直接使用即可
    ITEM_PIPELINES = {
    'scrapy_redis.pipelines.RedisPipeline': 100
    }
  • (必须)指定redis数据库的连接参数
    REDIS_HOST = '127.0.0.1'
    REDIS_PORT = 6379

怎样使用scrapy-redis?-->redis键名介绍

  • “项目名: start_urls”
    List 类型,用于获取spider启动时爬取的第一个url
  • “项目名:dupefilter”
    set类型,用于爬虫访问的URL去重
    内容是 40个字符的 url 的hash字符串
  • “项目名:requests”
    zset类型,用于scheduler调度处理 requests
    内容是 request 对象的序列化 字符串
  • “项目名:items”
    list 类型,保存爬虫获取到的数据item
    内容是 json 字符串

实战-利用scrapy-redis分布式写爬虫项目

实战·1 爬取用药助手的所有药品简单信息,链接    http://drugs.dxy.cn/index.htm

首先分析网站,我们发现所有的药理分类用的都是网站内的锚点,获取不到url,所有我们选择获取药品类型进行爬取,查看源代码

scrapy——7    scrapy-redis分布式爬虫,用药助手实战,Boss直聘实战,阿布云代理设置

不难看出,所有的h3标签就是我们需要的药品分类url,第一步获取所有的药品分类物品我们就完成了;

随便点一个进去,我们可以看到红线框中的就是我们需要爬取的数据

scrapy——7    scrapy-redis分布式爬虫,用药助手实战,Boss直聘实战,阿布云代理设置

以及翻页

scrapy——7    scrapy-redis分布式爬虫,用药助手实战,Boss直聘实战,阿布云代理设置

  • 主要的流程分析完毕以后,再用scrapy shell进行测试,这里就不讲解测试的部分了,直接新建项目

scrapy——7    scrapy-redis分布式爬虫,用药助手实战,Boss直聘实战,阿布云代理设置

  • 我们编写scrapy-redis分布式爬取的时候,编写的逻辑可以先是单纯的scrapy框架,等程序逻辑没有问题以后,再用分布式(导入库,更换父类,定义redis_key,在更改setting)
  • drugs\drugs\items.py    items中,定义我们的数据,这里我们定义  药品名,生产公司,成分,适应症四个数据
    import scrapy
    
    class DrugsItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field() drugs_name = scrapy.Field() company = scrapy.Field() composition = scrapy.Field() indications = scrapy.Field()
  • 为了避免程序出错,先设置部分setting数据
    ROBOTSTXT_OBEY = False
    DOWNLOAD_DELAY = 3
  • drugs\drugs\spiders\drugs_spider.py    编写主要逻辑代码
    # -*- coding: utf-8 -*-
    import scrapy
    import re from ..items import DrugsItem class DrugsSpiderSpider(RedisSpider):
    name = 'drugs_spider'
    start_urls = ['http://drugs.dxy.cn/index.htm'] def parse(self, response):
    # 匹配每一种药品类型
    category_urls = response.xpath('//ul[@class="ullist clearfix"]/li/h3/a/@href').extract() for category_url in category_urls:
    yield scrapy.Request(url=category_url, callback=self.detail_parse) def detail_parse(self, response):
    try:
    # 翻页
    next_url = response.xpath('//a[@title="下一页"]/@href').extract_first()
    yield scrapy.Request(url=response.url+next_url, callback=self.detail_parse)
    # 药品名
    drugs_name = re.findall(r'''<b>商品名:</b>(.*?)&nbsp;&nbsp;''', response.text, re.S)
    # 公司
    company = response.xpath('//div[@class="fl"]/h3/a/text()').extract()
    company = [i.strip().replace('\t\t\t\t\t\t\t\t\t\t\t', '') for i in company]
    # 成分
    composition = [i.replace('\n\n', '') for i in (re.findall(r'''<b>成份:</b>(.*?)<br/>''', response.text, re.S))]
    # 适应症
    indications = re.findall(r'''<b>适应症:</b>(.*?)\s*</p>''', response.text, re.S)
    indications = [i.replace('\n\n', '') for i in indications] for drugs_name, company, composition, indications in zip(drugs_name, company, composition, indications):
    items = DrugsItem()
    items['drugs_name'] = drugs_name
    items['company'] = company
    items['composition'] = composition
    items['indications'] = indications yield items
    except:
    pass
  • 如果代码完成并无逻辑和编写错误的话,现在就开始就之前的代码进行分布式操作
  • drugs\drugs\settings.py    设置setting分布式的必须参数
    ITEM_PIPELINES = {
    'drugs.pipelines.DrugsPipeline': 300, # 这是本身就有的
    'scrapy_redis.pipelines.RedisPipeline': 100 # 这是需要添加进去的
    } DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # scrapy_redis的去重组件
    SCHEDULER = "scrapy_redis.scheduler.Scheduler" # scrapy_redis的调度器
    SCHEDULER_PERSIST = True # 不清理redis queues
    # redis数据库的连接参数(根据个人电脑安装情况定义)
    REDIS_HOST = '127.0.0.1'
    REDIS_PORT = 6379
  • drugs\drugs\spiders\drugs_spider.py   在之前的spider代码中,导入  from scrapy_redis.spiders import RedisSpider 
  • 将DrugsSpiderSpider类改成继承RedisSpider,注释掉start_urls,定义redis_key = 'drugs_start:urls'
  • redis_key是固定词,后面的是在redis数据库中需要lpush的键值,如图,其他的不用改scrapy——7    scrapy-redis分布式爬虫,用药助手实战,Boss直聘实战,阿布云代理设置
  • 接下来要在pipeline中保存数据,将数据保存在MongoDB中   drugs\drugs\pipelines.py
    import pymongo
    
    class DrugsPipeline(object):
    
        def open_spider(self, spider):
    self.conn = pymongo.MongoClient(host='127.0.0.1', port=27017)
    self.db = self.conn['tanzhou_homework']
    self.connection = self.db['drugs_helper'] def process_item(self, item, spider):
    self.connection.insert(dict(item))
    return item def close_spider(self,spider):
    pass
  • 然后分布式爬虫就已经完成了,使用scrapy crawl drugs_spider 运行,控制台会阻塞,然后到redis中,使用lpush drugs_start:url http://drugs.dxy.cn/index.htm把需要爬取的链接传入进去,之前阻塞的控制台就会继续运行了,爬虫也就开始了。这样的话,就可以控制台多开了如图0,2,3,4在同时爬取数据,1是redis,5是MondoDBscrapy——7    scrapy-redis分布式爬虫,用药助手实战,Boss直聘实战,阿布云代理设置

    scrapy——7    scrapy-redis分布式爬虫,用药助手实战,Boss直聘实战,阿布云代理设置


实战2    爬取Boss直聘

关于武汉,pthon的工作信息   https://www.zhipin.com/c101200100/?query=python&

  • 分析网站,需要爬取的数据有 岗位,工资,信息,公司,职位描述,发布时间,还有一个翻页标签

scrapy——7    scrapy-redis分布式爬虫,用药助手实战,Boss直聘实战,阿布云代理设置

scrapy——7    scrapy-redis分布式爬虫,用药助手实战,Boss直聘实战,阿布云代理设置

  • 思路比上一个要简单一些,这里简单讲解一下
  • items设置数据
    import scrapy
    
    class BossItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    job_title = scrapy.Field() wage = scrapy.Field() infomation = scrapy.Field() company = scrapy.Field() public_time = scrapy.Field() requirements = scrapy.Field()
  • setting
    # 爬虫协议
    ROBOTSTXT_OBEY = False
    # 下载延迟
    DOWNLOAD_DELAY = 5
    # 请求头
    DEFAULT_REQUEST_HEADERS = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'en',
    'cookie': '_uab_collina=154233017050308004748422; lastCity=101010100; JSESSIONID=""; ...........,
    }
    # 管道
    ITEM_PIPELINES = {
    'boss.pipelines.BossPipeline': 200,
    'scrapy_redis.pipelines.RedisPipeline': 100
    }
    # redis设置
    DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
    SCHEDULER = "scrapy_redis.scheduler.Scheduler"
    SCHEDULER_PERSIST = True
    REDIS_HOST = '127.0.0.1'
    REDIS_PORT = 6379
  • boss_spider.py
    # -*- coding: utf-8 -*-
    import scrapy
    from scrapy_redis.spiders import RedisSpider
    from ..items import BossItem class BossSpiderSpider(RedisSpider):
    name = 'boss_spider'
    # start_urls = ['https://www.zhipin.com/c101200100/?query=python&']
    redis_key = 'zp_start:urls' def parse(self, response):
    # 翻页
    next_url = response.xpath('//a[@class="next"]/@href').extract_first()
    yield scrapy.Request(url='https://www.zhipin.com'+next_url) job_title = response.xpath('//div[@class="job-title"]/text()').extract()
    wage = response.xpath('//span[@class="red"]/text()').extract()
    info = response.xpath('//div[@class="info-primary"]/p/text()').extract()
    infomations = [info[i:i+3] for i in range(0, len(info), 3)]
    infomation = [''.join(i) for i in infomations]
    companys = response.xpath('//div[@class="company-text"]/h3/a/text()').extract()
    company = [company for company in companys if '\n' not in company]
    public_time = response.xpath('//div[@class="info-publis"]/p/text()').extract()
    detail_urls = response.xpath('//div[@class="info-primary"]//h3/a/@href').extract()
    detail_urls = ['https://www.zhipin.com' + detail_url for detail_url in detail_urls] for url, job_title, wage, infomation, company, public_time in zip(detail_urls, job_title, wage, infomation, company, public_time):
    item = BossItem()
    item['job_title'] = job_title,
    item['wage'] = wage,
    item['infomation'] = infomation,
    item['company'] = company,
    item['public_time'] = public_time yield scrapy.Request(url=url, callback=self.detail_parse, meta={'item':item}) def detail_parse(self, response):
    item = response.meta.get('item')
    requirements = response.xpath('string(//div[@class="text"])').extract_first()
    print(requirements.strip())
    item['requirements'] = requirements yield item
  • 运行和上面方法一样,就不多做描述,这里还要讲的东西就是,redis运行以后会有这三个数据,详情请参考前面提到的键名介绍

scrapy——7    scrapy-redis分布式爬虫,用药助手实战,Boss直聘实战,阿布云代理设置

运行结果展示

scrapy——7    scrapy-redis分布式爬虫,用药助手实战,Boss直聘实战,阿布云代理设置


如何使用代理

在前面的Boss直聘网站数据爬取中,有没有注意到setting中设置了延时为5秒,这是因为Boss直聘反爬还是很有严重的,如果你的短时间内访问的次数过多的话,或者爬取速度过快的话,ip会被封掉,这个时候,我们就需要接入代理了,使用代理爬取数据的话,就不用担心这个问题了。

网上也有有些免费的代理供游客使用,但是一般能用的都不多,而且也是各种不好用,在这里推荐使布云购买代理使用,经济实惠质量高(绝非广告,只是个人喜好),你也可以选择别的代理产品,例如快代理什么的。

在网站快代理中,有许多免费的ip代理,如图

scrapy——7    scrapy-redis分布式爬虫,用药助手实战,Boss直聘实战,阿布云代理设置

如果可以用的话,在scrapy中的middlewares.py中,新添上代码,

class process_ProxiesMiddlewares(object):

    def process_request(self, request, spider):
# 知道ip和端口的免费代理
proxies = 'http://000.00.00.00:0000'
request.meta['proxy'] = proxies def process_response(self, request, response, spider):
# 返回数据的处理
return .....

再在setting中,添加如下代码就可以使用了

DOWNLOADER_MIDDLEWARES = {'boss.middlewares.process_ProxiesMiddlewares': 543 # 添加的
}

如果我们选择使用付费的代理,就需要使用别人的API接入,比如之前说到的阿布云代理,scrapy的接入方法如下

    #! -*- encoding:utf-8 -*-

    import base64

    # 代理服务器
proxyServer = "http://http-dyn.abuyun.com:9020" # 代理隧道验证信息
proxyUser = "H01234567890123D"
proxyPass = "" # for Python2
proxyAuth = "Basic " + base64.b64encode(proxyUser + ":" + proxyPass) # for Python3
#proxyAuth = "Basic " + base64.urlsafe_b64encode(bytes((proxyUser + ":" + proxyPass), "ascii")).decode("utf8") class ProxyMiddleware(object):
def process_request(self, request, spider):
request.meta["proxy"] = proxyServer request.headers["Proxy-Authorization"] = proxyAuth

requests的接入方法

#! -*- encoding:utf-8 -*-

    import requests

    # 要访问的目标页面
targetUrl = "http://test.abuyun.com" # 代理服务器
proxyHost = "http-dyn.abuyun.com"
proxyPort = "" # 代理隧道验证信息
proxyUser = "H01234567890123D"
proxyPass = "" proxyMeta = "http://%(user)s:%(pass)s@%(host)s:%(port)s" % {
"host" : proxyHost,
"port" : proxyPort,
"user" : proxyUser,
"pass" : proxyPass,
} proxies = {
"http" : proxyMeta,
"https" : proxyMeta,
} resp = requests.get(targetUrl, proxies=proxies) print resp.status_code
print resp.text

还有别的方法不在此叙述,详情可以参考链接

然后这里主要讲解一下scrapy的使用方法

import base64
class ProxyMiddleware(object):
# 代理服务器
proxyServer = "http://http-dyn.abuyun.com:9020" # 代理隧道验证信息 这里填入你购买的验证信息
proxyUser = "H01234567890123D"
proxyPass = "" proxyAuth = "Basic " + base64.urlsafe_b64encode(bytes((proxyUser + ":" + proxyPass), "ascii")).decode("utf8") def process_request(self, request, spider):
request.meta["proxy"] = self.proxyServer
request.headers["Proxy-Authorization"] = self.proxyAuth def process_response(self, request, response, spider):
# 返回数据的处理
if response.status == 200:
return response
# 不成功重新请求
return request

setting设置对应的类

DOWNLOADER_MIDDLEWARES = {
'boss.middlewares.process_ProxiesMiddlewares': 543 # 添加的
}
 

如此,设置了IP代理的爬虫程序就不怕被封IP了