一 redis分布式部署
1.scrapy框架是否可以自己实现分布式?
- 不可以。原因有二。
其一:因为多台机器上部署的scrapy会各自拥有各自的调度器,这样就使得多台机器无法分配start_urls列表中的url。(多台机器无法共享同一个调度器)
其二:多台机器爬取到的数据无法通过同一个管道对数据进行统一的数据持久出存储。(多台机器无法共享同一个管道)
2.基于scrapy-redis组件的分布式爬虫
- scrapy-redis组件中为我们封装好了可以被多台机器共享的调度器和管道,我们可以直接使用并实现分布式数据爬取。
- 实现方式:
1.基于该组件的RedisSpider类
2.基于该组件的RedisCrawlSpider类
二 项目分布式方式
基于 scrapy -redis的第二种方式的分布式爬虫
1 基于RedisSpider实现的分布式爬虫(网易新闻)
2 UA池
3 代理池
4 seleniumm应用到scrapy
二 项目需求
爬取的基于文字的新闻数据()国内,国际,军事,航空)
先试用基于crawlspider框架爬取
爬虫代码
class WangyiSpider(scrapy.Spider): name = 'wangyi' # allowed_domains = ['xxx.com'] start_urls = ['https://news.163.com/'] def parse(self, response): # 获取 国内 国际 军事 航空的标签 lis = response.xpath('//div[@class="ns_area list"]/ul/li') indexs = [3, 4, 6, 7] # 获取四个标题的url和标题 for index in indexs: url = lis[index].xpath('./a/@href').extract_first() title = lis[index].xpath('./a/text()').extract_first() # print(url+":"+title) # 对每一个板块的url发起请求,获取页面数据(标题,缩略图,关键字,发布时间,url) print(url,title) yield scrapy.Request(url=url, callback=self.parseSecond) def parseSecond(self, response): div_list = response.xpath('//div[@class="data_row news_article clearfix "]') print('长度',len(div_list)) for div in div_list: head = div.xpath('./div[@class="na_detail clearfix "]//h3/a/text()').extract_first() url = div.xpath('./div[@class="na_detail clearfix "]//h3/a/@href').extract_first() imgUrl = div.xpath('./a/img/@src').extract_first() tag = div.xpath('.//div[@class="news_tag"]//a/text()').extract() tag=','.join(tag) print('**************************8') print(head, url, imgUrl, tag)
经过测试发现 国内 国际 军事 航空四个模块下的数据是通过动态加载的,所以我们引入selenium.
selenium 引入
wangyi.py
from selenium import webdriver 实例化浏览器对象,在构造方法中,只会调用一次,__init__ def __init__(self): # 实例化浏览器对象,仅执行一次 self.bro = webdriver.Chrome('G:day021 爬虫srapytextchromedriver.exe') 关闭selenium,必须在爬虫结束后使用 close方法中 def close(self,spider): print('爬虫结束') self.bro.quit()
自动化执行过程代码,写在中间件里面对特定url请求拦截并修改,代码如下
from scrapy.http import HtmlResponse class WangyeproDownloaderMiddleware(object): def process_response(self, request, response, spider): '拦截响应' # 响应对象中存储页面数据串改 # spider.bro.get('http://www.baidu.com') print('request.url', request.url) if request.url in ['http://war.163.com/', 'http://news.163.com/domestic/', 'http://news.163.com/world/', 'http://news.163.com/air/']: # 满足条件,就使用selenium请求加载动态数据的页面 spider.bro.get(url='https://news.163.com/domestic/') time.sleep(2) page_text = spider.bro.page_source return HtmlResponse(url=spider.bro.current_url, body=page_text, encoding="utf-8", request=request) else: return response
settings.py
放开下载中间件
DOWNLOADER_MIDDLEWARES = { 'wangyePro.middlewares.WangyeproDownloaderMiddleware': 543, }
军事页面如何获取更多的数据,使用selenium执行js代码.
js='window.scrollTo(0,document.body.scrollHeight)' spider.bro.execute_script(js)
优化中间件代码,获取更多的数据.
from scrapy.http import HtmlResponse class WangyeproDownloaderMiddleware(object): def process_response(self, request, response, spider): '拦截响应' # 响应对象中存储页面数据串改 # spider.bro.get('http://www.baidu.com') print('request.url', request.url) if request.url in ['http://war.163.com/', 'http://news.163.com/domestic/', 'http://news.163.com/world/', 'http://news.163.com/air/']: # 满足条件,就使用selenium请求加载动态数据的页面 spider.bro.get(url='https://news.163.com/domestic/') #隐式等待 # spider.bro.implicitly_wait(5) js = 'window.scrollTo(0,document.body.scrollHeight)' spider.bro.execute_script(js) spider.bro.implicitly_wait(2) while '加载更多' in spider.bro.page_source: try: #点击加载更多数据 spider.bro.find_element_by_class_name('post_addmore').click() except: break #隐式等待 spider.bro.implicitly_wait(5) page_text = spider.bro.page_source return HtmlResponse(url=spider.bro.current_url, body=page_text, encoding="utf-8", request=request) else: return response
selenium应用到scrapy总结
items.py中定义字段
class WangyeproItem(scrapy.Item): head = scrapy.Field() url = scrapy.Field() imgUrl = scrapy.Field() tag = scrapy.Field() title = scrapy.Field() content=scrapy.Field()
解析函数之间传递参数
def parse(self, response): ... # meta 作为响应体的参数传递给解析函数 yield scrapy.Request(url=url, callback=self.parseSecond,meta={'title':title}) def parseSecond(self, response): ... #接受传递的参数 title=response.meta['title'] ...
解析详情页
def getContent(self,response): #获取item item=response.meta['item'] #解析当前网页面中存储的新闻数据 content_list=response.xpath('//div[@id="endText"]/p/text()').extract() content=''.join(content_list) item['content']=content yield item
settings.py
中放开item pipeline
引入ua池
- 作用:尽可能多的将scrapy工程中的请求伪装成不同类型的浏览器身份。
- 操作流程:
1.在下载中间件中拦截请求
2.将拦截到的请求的请求头信息中的UA进行篡改伪装
3.在配置文件中开启下载中间件
#导包 from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware import random #UA池代码的编写(单独给UA池封装一个下载中间件的一个类) class RandomUserAgent(UserAgentMiddleware): def process_request(self, request, spider): #从列表中随机抽选出一个ua值 ua = random.choice(user_agent_list) #ua值进行当前拦截到请求的ua的写入操作 request.headers.setdefault('User-Agent',ua) user_agent_list = [ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 " "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1", "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 " "(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 " "(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 " "(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6", "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 " "(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 " "(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5", "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 " "(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 " "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24", "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 " "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24" ]
settings中配置中间件开启
DOWNLOADER_MIDDLEWARES = { ... 'wangyePro.middlewares.RandomUserAgent': 544, }
引入ip池
- 作用:尽可能多的将scrapy工程中的请求的IP设置成不同的。
- 操作流程:
1.在下载中间件中拦截请求
2.将拦截到的请求的IP修改成某一代理IP
3.在配置文件中开启下载中间件
代码展示:
#批量对拦截到的请求进行ip更换 #单独封装下载中间件类 class Proxy(object): def process_request(self, request, spider): #对拦截到请求的url进行判断(协议头到底是http还是https) #request.url返回值:http://www.xxx.com h = request.url.split(':')[0] #请求的协议头 if h == 'https': ip = random.choice(PROXY_https) request.meta['proxy'] = 'https://'+ip else: ip = random.choice(PROXY_http) request.meta['proxy'] = 'http://' + ip #可被选用的代理IP PROXY_http = [ '153.180.102.104:80', '195.208.131.189:56055', ] PROXY_https = [ '120.83.49.90:9000', '95.189.112.214:35508', ]
开启中间件
DOWNLOADER_MIDDLEWARES = { ... 'wangyePro.middlewares.Proxy': 545, }
运行无误后,开始代码重构
基于scrapy-redis 分布式代码重构
1 代码修改
1 导包 from scrapy_redis.spiders import RedisSpider 2 将爬虫类的父类修改为RedisSpider class WangyiSpider(RedisSpider): 3 将起始url列表注释,添加一个redis_key(调度队列的名称)的属性 from scrapy_redis.spiders import RedisSpider class WangyiSpider(RedisSpider): name = 'wangyi' # allowed_domains = ['wangyi.com'] # start_urls = ['https://news.163.com/'] redis_key='wangyi'
下载scrapy-redis组件:
pip install scrapy-redis
- 3.2 redis配置文件的配置:
- 注释该行:bind 127.0.0.1,表示可以让其他ip访问redis
- 将yes该为no:protected-mode no,表示可以让其他ip操作redis
3.3 修改爬虫文件中的相关代码:
- 将爬虫类的父类修改成基于RedisSpider或者RedisCrawlSpider。注意:如果原始爬虫文件是基于Spider的,则应该将父类修改成RedisSpider,如果原始爬虫文件是基于CrawlSpider的,则应该将其父类修改成RedisCrawlSpider。
- 注释或者删除start_urls列表,切加入redis_key属性,属性值为scrpy-redis组件中调度器队列的名称
3.4 在配置文件中进行相关配置,开启使用scrapy-redis组件中封装好的管道
ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 400 }
3.5 在配置文件中进行相关配置,开启使用scrapy-redis组件中封装好的调度器
# 使用scrapy-redis组件的去重队列 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 使用scrapy-redis组件自己的调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 是否允许暂停 SCHEDULER_PERSIST = True
3.6 在配置文件中进行爬虫程序链接redis的配置:
REDIS_HOST = 'redis服务的ip地址' REDIS_PORT = 6379 REDIS_ENCODING = ‘utf-8’ REDIS_PARAMS = {‘password’:’123456’}
3.7 开启redis服务器:redis-server 配置文件
rotected-mode no
bind 0.0.0.0
3.8 开启redis客户端:redis-cli
3.9 运行爬虫文件:scrapy runspider SpiderFile
scrapy runspider wangyi.py
3.10 向调度器队列中扔入一个起始url(在redis客户端中操作):lpush redis_key属性值 起始url
127.0.0.1:6379> lpush wangyi https://news.163.com
redis中查看数据
keys *
lrange wangyi:items 0 -1