Scrapy框架之基于RedisSpider实现的分布式爬虫

需求:爬取的是基于文字的网易新闻数据(国内、国际、军事、航空)。
  基于Scrapy框架代码实现数据爬取后,再将当前项目修改为基于RedisSpider的分布式爬虫形式。

一、基于Scrapy框架数据爬取实现

1、项目和爬虫文件创建

$ scrapy startproject wangyiPro
$ cd wangyiPro/
$ scrapy genspider wangyi news.163.com    # 基于scrapy.Spider创建爬虫文件

2、爬虫文件编写——解析新闻首页获取四个板块的url

import scrapy

class WangyiSpider(scrapy.Spider):
    name = 'wangyi'
    # allowed_domains = ['news.163.com']
    start_urls = ['https://news.163.com/']

    def parse(self, response):
        lis = response.xpath('//div[@class="ns_area list"]/ul/li')
        # 获取指定的四个列表元素(国内3、国际5、军事6、航空7)
        indexes = [3, 4, 6, 7]
        li_list = []   # 四个板块对应的li标签对象
        for index in indexes:
            li_list.append(lis[index])

        # 获取四个板块中的超链和文字标题
        for li in li_list:
            url = li.xpath('./a/@href').extract_first()
            title = li.xpath('./a/text()').extract_first()   # 板块名称

            print(url + ":" + title)   # 测试

  执行爬虫文件,控制台打印输出四个url,说明解析成功:

$ scrapy crawl wangyi --nolog
http://news.163.com/domestic/:国内
http://news.163.com/world/:国际
http://war.163.com/:军事
http://news.163.com/air/:航空

3、爬虫文件编写——对每个板块url发请求,进一步解析

import scrapy

class WangyiSpider(scrapy.Spider):
    name = 'wangyi'
    # allowed_domains = ['news.163.com']
    start_urls = ['https://news.163.com/']

    def parse(self, response):
        lis = response.xpath('//div[@class="ns_area list"]/ul/li')
        # 获取指定的四个列表元素(国内3、国际5、军事6、航空7)
        indexes = [3, 4, 6, 7]
        li_list = []   # 四个板块对应的li标签对象
        for index in indexes:
            li_list.append(lis[index])

        # 获取四个板块中的超链和文字标题
        for li in li_list:
            url = li.xpath('./a/@href').extract_first()
            title = li.xpath('./a/text()').extract_first()   # 板块名称

            """对每一个板块对应url发起请求,获取页面数据"""
            # 调用scrapy.Request()方法发起get请求
            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))   # 非空则验证xpath是正确的
        for div in div_list:
            # 文章标题
            head = div.xpath('.//div[@class="news_title"]/h3/a/text()').extract_first()
            # 文章url
            url = div.xpath('.//div[@class="news_title"]/h3/a/@href').extract_first()
            # 缩略图
            imgUrl = div.xpath('./a/img/@src').extract_first()
            # 发布时间和标签:提取列表中所有的元素
            tag = div.xpath('.//div[@class="news_tag"]//text()').extract()
            # 列表装化为字符串
            tag = "".join(tag)

  编写到这里时,再次执行爬虫脚本,会发现print(len(div_list))输出的是4个0,但是xpath表达式却是正确的。
  这是由于新浪网的新闻列表信息是动态加载的,而爬虫程序向url发请求无法获取动态加载的页面信息。
  因此需要selenium帮忙在程序中实例化一个浏览器对象,由浏览器对象向url发请求,再通过调用page_source属性拿到selenium实例化对象中获取的页面数据,这个数据中包含动态加载的数据内容。

二、将selenium应用到Scrapy项目中

  需求分析:当点击国内超链进入国内对应的页面时,会发现当前页面展示的新闻数据是被动态加载出来的,如果直接通过程序对url进行请求,是获取不到动态加载出的新闻数据的。则就需要我们使用selenium实例化一个浏览器对象,在该对象中进行url的请求,获取动态加载的新闻数据。
  响应对象response从下载器传给Spiders爬虫文件时,一定会穿过下载中间件。
  可以在下载中间件对响应对象进行拦截,对响应对象中存储的页面数据进行篡改,将动态加载的页面数据加入到响应对象中。
  通过selenium可以篡改响应数据,并将页面数据篡改成携带了新闻数据的数据。

1、selenium在scrapy中使用原理

  当引擎将国内板块url对应的请求提交给下载器后,下载器进行网页数据的下载,然后将下载到的页面数据,封装到response中,提交给引擎,引擎将response在转交给Spiders。
  Spiders接受到的response对象中存储的页面数据里是没有动态加载的新闻数据的。要想获取动态加载的新闻数据,则需要在下载中间件中对下载器提交给引擎的response响应对象进行拦截,切对其内部存储的页面数据进行篡改,修改成携带了动态加载出的新闻数据,然后将被篡改的response对象最终交给Spiders进行解析操作。

2、selenium在scrapy中使用流程总结

(1)在爬虫文件中导入webdriver类

from selenium import webdriver

(2)重写爬虫文件的构造方法
  在构造方法中使用selenium实例化一个浏览器对象(因为浏览器对象只需要被实例化一次)

class WangyiSpider(scrapy.Spider):
    def __init__(self):
        # 实例化浏览器对象(保证只会被实例化一次)
        self.bro = webdriver.Chrome(executable_path='/Users/hqs/ScrapyProjects/wangyiPro/wangyiPro/chromedriver')

(3)重写爬虫文件的closed(self,spider)方法
  在其内部关闭浏览器对象。该方法是在爬虫结束时被调用。

class WangyiSpider(scrapy.Spider):
    def closed(self, spider):
        # 必须在整个爬虫结束后关闭浏览器
        print('爬虫结束')
        self.bro.quit()   # 浏览器关闭

(4)重写下载中间件的process_response方法
  让process_response方法对响应对象进行拦截,并篡改response中存储的页面数据。

(5)在配置文件中开启下载中间件

3、项目代码示例

(1)引入selenium定义浏览器开启和关闭

import scrapy
from selenium import webdriver
from wangyiPro.items import WangyiproItem

class WangyiSpider(scrapy.Spider):
    name = 'wangyi'
    # allowed_domains = ['news.163.com']
    start_urls = ['https://news.163.com/']

    def __init__(self):
        # 实例化浏览器对象(保证只会被实例化一次)
        self.bro = webdriver.Chrome(executable_path='./wangyiPro/chromedrive')

    def closed(self, spider):
        # 必须在整个爬虫结束后关闭浏览器
        print('爬虫结束')
        self.bro.quit()   # 浏览器关闭

(2)使用下载中间件拦截settings.py修改

# Enable or disable downloader middlewares
# See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html
DOWNLOADER_MIDDLEWARES = {
    'wangyiPro.middlewares.WangyiproDownloaderMiddleware': 543,
}

(3)在下载中间件中进行拦截

  让浏览器对象去发起get请求,获取四大版块对应的页面数据,浏览器对url发送请求,浏览器是可以获取到动态加载的页面数据的。
  获取到这部分动态数据后,可以将这部分数据装回到拦截的response对象中去。然后将篡改好的response对象发给Spiders。
  Spiders接收到response对象后,将response赋值给回调函数parseSecond的response参数中。
  middlewares.py内容如下所示:

# 下载中间件
from scrapy.http import HtmlResponse   # 通过这个类实例化的对象就是响应对象
import time

class WangyiproDownloaderMiddleware(object):
    def process_request(self, request, spider):
        """
        可以拦截请求
        :param request:
        :param spider:
        :return:
        """
        return None

    def process_response(self, request, response, spider):
        """
        可以拦截响应对象(下载器传递给Spider的响应对象)
        :param request: 响应对象对应的请求对象
        :param response: 拦截到的响应对象
        :param spider: 爬虫文件中对应的爬虫类的实例
        :return:
        """
        print(request.url + "这是下载中间件")
        # 响应对象中存储页面数据的篡改
        if request.url in ['http://news.163.com/domestic/', 'http://news.163.com/world/', 'http://war.163.com/', 'http://news.163.com/air/']:
            # 浏览器请求发送(排除起始url)
            spider.bro.get(url=request.url)
            # 滚轮拖动到底部会动态加载新闻数据,js操作滚轮拖动
            js = 'window.scrollTo(0, document.body.scrollHeight)'  # 水平方向不移动:0;竖直方向移动:窗口高度
            spider.bro.execute_script(js)  # 拖动到底部,获取更多页面数据
            time.sleep(2)  # js执行给页面2秒时间缓冲,让所有数据得以加载
            # 页面数据page_text包含了动态加载出来的新闻数据对应的页面数据
            page_text = spider.bro.page_source
            # current_url就是通过浏览器发起请求所对应的url
            # body是当前响应对象携带的数据值
            return HtmlResponse(url=spider.bro.current_url, body=page_text, encoding="utf-8", request=request)
        else:
            # 四个板块之外的响应对象不做修改
            return response   # 这是原来的响应对象

三、爬虫代码完善及item处理

1、爬虫文件

import scrapy
from selenium import webdriver

from wangyiPro.items import WangyiproItem

class WangyiSpider(scrapy.Spider):
    name = 'wangyi'
    # allowed_domains = ['news.163.com']
    start_urls = ['https://news.163.com/']

    def __init__(self):
        # 实例化浏览器对象(保证只会被实例化一次)
        self.bro = webdriver.Chrome(executable_path='/Users/hqs/ScrapyProjects/wangyiPro/wangyiPro/chromedriver')

    def closed(self, spider):
        # 必须在整个爬虫结束后关闭浏览器
        print('爬虫结束')
        self.bro.quit()   # 浏览器关闭

    def parse(self, response):
        lis = response.xpath('//div[@class="ns_area list"]/ul/li')
        # 获取指定的四个列表元素(国内3、国际5、军事6、航空7)
        indexes = [3, 4, 6, 7]
        li_list = []   # 四个板块对应的li标签对象
        for index in indexes:
            li_list.append(lis[index])

        # 获取四个板块中的超链和文字标题
        for li in li_list:
            url = li.xpath('./a/@href').extract_first()
            title = li.xpath('./a/text()').extract_first()   # 板块名称

            """对每一个板块对应url发起请求,获取页面数据"""
            # 调用scrapy.Request()方法发起get请求
            yield scrapy.Request(url=url, callback=self.parseSecond, meta={'title': title})

    def parseSecond(self, response):
        """声明回调函数"""
        # 找到页面中新闻的共有标签类型,排除广告标签
        div_list = response.xpath('//div[@class="data_row news_article clearfix"]')
        # print(len(div_list))   # 非空则验证xpath是正确的
        for div in div_list:
            # 文章标题
            head = div.xpath('.//div[@class="news_title"]/h3/a/text()').extract_first()
            # 文章url
            url = div.xpath('.//div[@class="news_title"]/h3/a/@href').extract_first()
            # 缩略图
            imgUrl = div.xpath('./a/img/@src').extract_first()
            # 发布时间和标签:提取列表中所有的元素
            tag = div.xpath('.//div[@class="news_tag"]//text()').extract()

            # 列表装化为字符串
            tags = []
            for t in tag:
                t = t.strip(' \n \t')   # 去除空格 \n换行 \t相当于tab
                tags.append(t)   # 重新装载到列表中
            tag = "".join(tags)

            # 获取meta传递的数据值
            title = response.meta['title']

            # 实例化item对象,将解析到的数据值存储到item对象中
            item = WangyiproItem()
            item['head'] = head
            item['url'] = url
            item['imgUrl'] = imgUrl
            item['tag'] = tag
            item['title'] = title

            # 对url发起请求,获取对应页面中存储的新闻内容数据
            yield scrapy.Request(url=url, callback=self.getContent, meta={"item":item})

    def getContent(self, response):
        """新闻内容解析的回调函数"""
        # 获取传递过来的item对象
        item = response.meta['item']

        # 解析当前页码中存储的页面数据
        # 由于新闻的段落可能有多个,每个段落在一个p标签中。因此使用extract()方法
        content_list = response.xpath('//div[@class="post_text"]/p/text()').extract()

        # 列表转字符串(字符串才能保持在item对象中)
        content = "".join(content_list)
        item["content"] = content

        # item对象提交给管道
        yield item

注意:

(1)将解析到的数据值存储到item对象

  由于爬虫做了两次解析,因此如何将第一次解析的数据加入item对象是最大的难点。
  解决方法:meta属性请求传参。

# 对url发起请求,获取对应页面中存储的新闻内容数据
yield scrapy.Request(url=url, callback=self.getContent, meta={"item":item})

  对文章url发起请求,欲获取对应页面中存储的新闻内容数据,调用新的回调函数getContent。

(2)新闻内容解析后将item对象提交给管道

class WangyiSpider(scrapy.Spider):
    """同上省略"""
    def getContent(self, response):
        """新闻内容解析的回调函数"""
        # 获取传递过来的item对象
        item = response.meta['item']

        # 解析当前页码中存储的页面数据
        # 由于新闻的段落可能有多个,每个段落在一个p标签中。因此使用extract()方法
        content_list = response.xpath('//div[@class="post_text"]/p/text()').extract()

        # 列表转字符串(字符串才能保持在item对象中)
        content = "".join(content_list)
        item["content"] = content

        # item对象提交给管道
        yield item

2、items.py文件

import scrapy

class WangyiproItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    head = scrapy.Field()
    url = scrapy.Field()
    imgUrl = scrapy.Field()
    tag = scrapy.Field()
    title = scrapy.Field()
    content = scrapy.Field()

3、管道文件pipeline.py处理

(1)pipelines.py

class WangyiproPipeline(object):
    def process_item(self, item, spider):
        print(item['title']+ ':'+ item['content'])
        return item

(2)settings.py中放开管道

# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
    'wangyiPro.pipelines.WangyiproPipeline': 300,
}

(3)执行爬虫输出爬取的新闻信息


  

四、UA池和代理池在Scrapy中应用

原文地址:https://www.cnblogs.com/xiugeng/p/10090033.html

时间: 2024-11-08 15:50:48

Scrapy框架之基于RedisSpider实现的分布式爬虫的相关文章

基于requests+redis的分布式爬虫

简单的网络爬虫是对一个url进行请求,并等待其返回响应.在数据量小的情况下很实用,但是当你的数据量很大,显然分布式爬虫就更占优势!关于分布式,一般是使用一台主机(master)充当多个爬虫的共享redis队列,其他主机(slave)采用远程连接master,关于redis如何安装,这里不多做介绍! 以爬虫伯乐在线的python文章为例,我的分布式爬虫由main01 main02 main03三个python文件构成,main01的主要任务是运行在master上,将文章的url爬取下来存入redi

【Scrapy框架基于scrapy-redis实现分布式爬虫】 򓮘

原文: http://blog.gqylpy.com/gqy/370 "Scrapy框架无法自己实现分布式,原因有二 多台机器上部署的Scrapy各自拥有各自的调度器,这样就使得多台机器无法分配 start_urls 列表中的url,即多台机器无法共享同一个调度器. 多台机器爬取到的数据无法通过同一个管道进行统一的持久化存储,即多台机器无法共享同一个管道. ___ 基于 scrapy-redis 组件的分布式爬虫 安装 :pip install scrapy-redis scrapy-redis

聚焦Python分布式爬虫必学框架Scrapy 打造搜索引擎

第1章 课程介绍 介绍课程目标.通过课程能学习到的内容.和系统开发前需要具备的知识 1-1 python分布式爬虫打造搜索引擎简介 第2章 windows下搭建开发环境 介绍项目开发需要安装的开发软件. python虚拟virtualenv和 virtualenvwrapper的安装和使用. 最后介绍pycharm和navicat的简单使用 2-1 pycharm的安装和简单使用 2-2 mysql和navicat的安装和使用 2-3 windows和linux下安装python2和python

基于scrapy-redis两种形式的分布式爬虫

redis分布式部署 1.scrapy框架是否可以自己实现分布式? - 不可以.原因有二. 其一:因为多台机器上部署的scrapy会各自拥有各自的调度器,这样就使得多台机器无法分配start_urls列表中的url.(多台机器无法共享同一个调度器) 其二:多台机器爬取到的数据无法通过同一个管道对数据进行统一的数据持久出存储.(多台机器无法共享同一个管道) 2.基于scrapy-redis组件的分布式爬虫 - scrapy-redis组件中为我们封装好了可以被多台机器共享的调度器和管道,我们可以直

19.基于scrapy-redis两种形式的分布式爬虫

redis分布式部署 1.scrapy框架是否可以自己实现分布式? - 不可以.原因有二. 其一:因为多台机器上部署的scrapy会各自拥有各自的调度器,这样就使得多台机器无法分配start_urls列表中的url.(多台机器无法共享同一个调度器) 其二:多台机器爬取到的数据无法通过同一个管道对数据进行统一的数据持久出存储.(多台机器无法共享同一个管道) 2.基于scrapy-redis组件的分布式爬虫 - scrapy-redis组件中为我们封装好了可以被多台机器共享的调度器和管道,我们可以直

基于scrapy-redis的分布式爬虫

1.scrapy框架是否可以自己实现分布式? 答:不可以.原因有二: 其一:因为多台机器上部署的scrapy会各自拥有各自的调度器,这样就使得多台机器无法分配start_urls列表中的url.(多台机器无法共享同一个调度器) 其二:多台机器爬取到的数据无法通过同一个管道对数据进行统一的数据持久出存储.(多台机器无法共享同一个管道) 2.基于scrapy-redis组件的分布式爬虫 - scrapy-redis组件中为我们封装好了可以被多台机器共享的调度器和管道,我们可以直接使用并实现分布式数据

17,基于scrapy-redis两种形式的分布式爬虫

redis分布式部署 1.scrapy框架是否可以自己实现分布式? - 不可以.原因有二. 其一:因为多台机器上部署的scrapy会各自拥有各自的调度器,这样就使得多台机器无法分配start_urls列表中的url.(多台机器无法共享同一个调度器) 其二:多台机器爬取到的数据无法通过同一个管道对数据进行统一的数据持久出存储.(多台机器无法共享同一个管道) 2.基于scrapy-redis组件的分布式爬虫 - scrapy-redis组件中为我们封装好了可以被多台机器共享的调度器和管道,我们可以直

爬虫学习 17.基于scrapy-redis两种形式的分布式爬虫

爬虫学习 17.基于scrapy-redis两种形式的分布式爬虫 redis分布式部署 1.scrapy框架是否可以自己实现分布式? - 不可以.原因有二. 其一:因为多台机器上部署的scrapy会各自拥有各自的调度器,这样就使得多台机器无法分配start_urls列表中的url.(多台机器无法共享同一个调度器) 其二:多台机器爬取到的数据无法通过同一个管道对数据进行统一的数据持久出存储.(多台机器无法共享同一个管道) 2.基于scrapy-redis组件的分布式爬虫 ? - scrapy-re

scrapy框架的使用

Scrapy    框架架构 一.Scrapy框架是基于Twisted的异步框架,纯Python实现的爬虫框架,耦合程度低,可拓展性极强. 1.Engine引擎,处理整个系统的数据流.触发事物.框架的核心 2.item项目,定义爬虫爬取结果的数据结构,爬取的数据会被赋值成该item对象 3.Schedule调度器,接受engine发过来的request放入队列,然后engine再次请求时,将request发送给engine 4.Downloader下载器,下载网页内容,并将网页返回给spider