scrapy——7
什么是scrapy-redis
怎么安装scrapy-redis
scrapy-redis常用配置文件
scrapy-redis键名介绍
实战-利用scrapy-redis分布式爬取用药助手网站
实战-利用scrapy-redis分布式爬取Boss直聘网站
如何使用代理
什么是scrapy-redis-->简介
scrapy-redis是scrapy框架基于redis数据库的组件,用于scrapy项目分布式开发和部署
特征:
分布式爬取
你可以启动多个spider工程,相互之间共享单个redis的request队列。最适合广泛的多个域名网站的内容爬取。
分布式数据处理
爬取到的scrapy的item数据可以推入到redis队列中,这意味着你可以根据需求启动尽可能多的处理器程序来共享item队列,进行item数据持久化处理。
scrapy即插即用组件
Schedule调度器 + Duplication复制过滤器,itemPipleline,基本spider
什么是scrapy_redis?-->框架
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,所有我们选择获取药品类型进行爬取,查看源代码
不难看出,所有的h3标签就是我们需要的药品分类url,第一步获取所有的药品分类物品我们就完成了;
随便点一个进去,我们可以看到红线框中的就是我们需要爬取的数据
以及翻页
- 主要的流程分析完毕以后,再用scrapy shell进行测试,这里就不讲解测试的部分了,直接新建项目
- 我们编写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>(.*?) ''', 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的键值,如图,其他的不用改
- 接下来要在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是MondoDB
实战2 爬取Boss直聘
关于武汉,pthon的工作信息 https://www.zhipin.com/c101200100/?query=python&
- 分析网站,需要爬取的数据有 岗位,工资,信息,公司,职位描述,发布时间,还有一个翻页标签
- 思路比上一个要简单一些,这里简单讲解一下
- 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运行以后会有这三个数据,详情请参考前面提到的键名介绍
运行结果展示
如何使用代理
在前面的Boss直聘网站数据爬取中,有没有注意到setting中设置了延时为5秒,这是因为Boss直聘反爬还是很有严重的,如果你的短时间内访问的次数过多的话,或者爬取速度过快的话,ip会被封掉,这个时候,我们就需要接入代理了,使用代理爬取数据的话,就不用担心这个问题了。
网上也有有些免费的代理供游客使用,但是一般能用的都不多,而且也是各种不好用,在这里推荐使布云购买代理使用,经济实惠质量高(绝非广告,只是个人喜好),你也可以选择别的代理产品,例如快代理什么的。
在网站快代理中,有许多免费的ip代理,如图
如果可以用的话,在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了