当前位置:   article > 正文

爬虫之Scrapy框架_scrapy爬虫框架

scrapy爬虫框架

1. Scrapy 介绍

官方文档:https://docs.scrapy.org/en/latest/topics/architecture.html

Scrapy一个开源和协作的框架,其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的,使用它可以以快速、简单、可扩展的方式从网站中提取所需的数据。但目前Scrapy的用途十分广泛,可用于如数据挖掘、监测和自动化测试等领域,也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。

Scrapy 是基于twisted框架开发而来,twisted是一个流行的事件驱动的python网络框架。因此Scrapy使用了一种非阻塞(又名异步)的代码来实现并发。整体架构大致如下
在这里插入图片描述
Components:

  1. 引擎(EGINE)
    引擎负责控制系统所有组件之间的数据流,并在某些动作发生时触发事件。有关详细信息,请参见上面的数据流部分。

  2. 调度器(SCHEDULER)
    用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL的优先级队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
    队列:先进先出,实现深度优先
    栈:后进先出,实现广度优先
    优先级队列:实现优先级

  3. 下载器(DOWLOADER)
    用于下载网页内容, 并将网页内容返回给EGINE,下载器是建立在twisted这个高效的异步模型上的

  4. 爬虫(SPIDERS)
    SPIDERS是开发人员自定义的类,用来解析responses,并且提取items,或者发送新的请求

  5. 项目管道(ITEM PIPLINES)
    在items被提取后负责处理它们,主要包括清理、验证、持久化(比如存到数据库)等操作

  6. 下载器中间件(Downloader Middlewares)
    位于Scrapy引擎和下载器之间,主要用来处理从EGINE传到DOWLOADER的请求request,已经从DOWNLOADER传到EGINE的响应response,你可用该中间件做以下几件事
    1、process a request just before it is sent to the Downloader (i.e. right before Scrapy sends the request to the website);
    2、change received response before passing it to a spider;
    3、send a new Request instead of passing received response to a spider;
    4、pass response to a spider without fetching a web page;
    silently drop some requests.

  7. 爬虫中间件(Spider Middlewares)
    位于EGINE和SPIDERS之间,主要工作是处理SPIDERS的输入(即responses)和输出(即requests)

2. 基础使用

2.1 安装

pip3 install scrapy
  • 1

Windows平台如果安装不了则需要如下配置


    6、下载twisted的wheel文件:http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
    7、执行pip3 install 下载目录\Twisted-17.9.0-cp36-cp36m-win_amd64.whl
    8、pip3 install scrapy

#Linux平台
    1、pip3 install scrapy

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 安装 wheel 、lxml
pip3 install wheel # 安装后,便支持通过wheel文件安装软件,wheel文件官网https://www.lfd.uci.edu/~gohlke/pythonlibs
pip3 install lxml
pip3 install pyopenssl
  • 1
  • 2
  • 3

在这里插入图片描述

  • 查看支持的版本
    查看 pip 支持的版本
python3 -m pip debug --verbose
  • 1

在这里插入图片描述

  • 最后安装 scrapy
pip3 install scrapy
  • 1

2.2 创建项目

安装完成后在解释器的 Scripts 目录下会多出 scrapy.exe 文件,相当于 django 的 django_admin.exe 文件。

在这里插入图片描述

  • 创建项目命令
    创建项目只能使用命令的方式
scrapy startproject 项目名字
  • 1

创建完成如下图所示

在这里插入图片描述

2.3 目录介绍

firstscrapy         	# 项目名
    firstscrapy     	# 项目同名文件夹
        spiders     	# 文件夹,存放一个个爬虫
            cnblogs.py 	# 其中一个爬虫,重点写代码的地方(解析数据,发起请求)
        items.py    	# 类比 djagno 的 models,表模型(类)         
        middlewares.py 	# 中间件,爬虫中间件和下载中间件都在里面  
        pipelines.py   	# 管道,做持久化需要在这写代码           
        settings.py    	# 配置文件                             
    scrapy.cfg    		# 上线配置,开发阶段不用
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

2.4 创建爬虫

scrapy genspider 爬虫名字 爬取的初始网站
scrapy genspider cnblogs cnblogs.com
  • 1
  • 2

2.5 运行爬虫

scrapy crawl 爬虫名字

# --nolog 表示不打印日志
scrapy crawl 爬虫名字 --nolog
  • 1
  • 2
  • 3
  • 4

除了使用命令,还可以在 pycharm 中项目的根目录下创建 main.py 文件(文件名随意),添加如下内容

from scrapy.cmdline import execute

# execute(['scrapy','crawl','爬虫名','--nolog']) --nolog 表示不打印日志
execute(['scrapy','crawl','cnblogs','--nolog'])
  • 1
  • 2
  • 3
  • 4

3. scrapy 解析数据

第一个请求定义在 start_requests() 方法内默认从 start_urls 列表中获得 url 地址来生成 Request请求,默认的回调函数是 parse 方法。回调函数在下载完成返回 response 时自动触发,在回调函数中,可以解析 response 并且返回值

解析的方式通常使用 Scrapy 自带的 Selectors,也可以使用 Beutifulsoup,lxml或其他。最后,针对返回的Items对象将会被持久化到 Mysql、redis 或者文件中。

Spiders总共提供了五种类:

导入语句: from scrapy.spiders import Spider,CrawlSpider,XMLFeedSpider,CSVFeedSpider,SitemapSpider

1、scrapy.spiders.Spider 		#scrapy.Spider等同于scrapy.spiders.Spider
2、scrapy.spiders.CrawlSpider
3、scrapy.spiders.XMLFeedSpider
4、scrapy.spiders.CSVFeedSpider
5、scrapy.spiders.SitemapSpider
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
class CnblogsSpider(scrapy.Spider):
    name = 'cnblogs'
    allowed_domains = ['cnblogs.com']
    start_urls = ['http://cnblogs.com/']

    def parse(self, response):
        print(response.text)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 解析数据
response 对象有css方法和xpath方法,在css中使用css选择器,在xpath中写xpath选择器

	xpath取文本内容
	'.//a[contains(@class,"link-title")]/text()'
    xpath取属性
    './/a[contains(@class,"link-title")]/@href'
    css取文本  ::text
    'a.link-title::text'
    css取属性  ::attr()
    'img.image-scale::attr(src)'

	.extract_first()  取一个
    .extract()        取所有

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

属性方法介绍

  • name = ‘cnblogs’
定义爬虫名,scrapy 会根据该值定位爬虫程序,所以它必须要有且必须唯一
  • 1
  • allowed_domains = [‘cnblogs.com’]
定义允许爬取的域名,如果OffsiteMiddleware启动(默认就启动),那么不属于该列表的域名及其子域名
都不允许爬取如果爬取的网址为:https://www.example.com/1.html,那就添加'example.com'到列表.
  • 1
  • 2
  • start_urls = [‘http://cnblogs.com/’]
如果没有指定 url,就从该列表中读取 url 来生成第一个请求
  • 1
  • custom_settings
值为一个字典,定义一些配置信息,在运行爬虫程序时,这些配置会覆盖项目级别的配置,所以
custom_settings必须被定义成一个类属性,由于settings会在类实例化前被加载
  • 1
  • 2
  • settings
通过 self.settings['配置项的名字'] 可以访问 settings.py 中的配置,如果优先使用自己定义的
custom_settings 配置
  • 1
  • 2
  • logger
日志名默认为spider的名字
self.logger.debug('=============>%s' %self.settings['BOT_NAME'])
  • 1
  • 2
  • start_requests()
该方法用来发起第一个Requests请求,且必须返回一个可迭代的对象。它在爬虫程序打开时就被Scrapy调用
Scrapy只调用它一次。默认从start_urls里取出每个url来生成Request(url, dont_filter=True)
  • 1
  • 2
#针对参数dont_filter,请看自定义去重规则

如果你想要改变起始爬取的Requests,你就需要覆盖这个方法,例如你想要起始发送一个POST请求,如下
class MySpider(scrapy.Spider):
    name = 'myspider'

	def start_requests(self):
	    return [scrapy.FormRequest("http://www.example.com/login",
	                               formdata={'user': 'xxx', 'pass': '123'},
	                               callback=self.logged_in)]

	def logged_in(self, response):
	    # here you would extract links to follow and return Requests for
	    # each of them, with another callback
	    pass
	    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • parse(response)
这是默认的回调函数,所有的回调函数必须返回Request和/或dicts或Item对象的可迭代对象。
  • 1
  • closed(reason)
爬虫程序结束时自动触发
  • 1

解析博客园文章数据

import scrapy


class CnblogsSpider(scrapy.Spider):
    name = 'cnblogs'
    allowed_domains = ['cnblogs.com']
    start_urls = ['http://cnblogs.com/']
    custom_settings = {}

    def parse(self, response):
        article_list = response.css('article.post-item')
        for article in article_list:
            title = article.css('a.post-item-title::text').extract_first()
            # title = article.xpath('.//a/text()').extract_first()
            
            desc = article.css('p.post-item-summary::text').extract()
            # desc = article.xpath('./section/div/p/text()').extract()
            
            real_desc = desc[0].replace('\n', '').replace(' ', '')
            if not real_desc:
                real_desc = desc[1].replace('\n', '').replace(' ', '')
                
            pub_time = article.css('span.post-meta-item>span::text').extract_first()
            # pub_time=article.xpath('.//footer/span/span/text()').extract_first()
            
            author = article.css('footer.post-item-foot span::text').extract_first()
			# author=article.xpath('.//footer/a/span/text()').extract_first()

            url = article.css('a.post-item-title::attr(href)').extract_first()
            # url = article.xpath('./section/div/a/@href').extract_first()
            

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

4. settings 相关配置

4.1 基础配置

配置说明
ROBOTSTXT_OBEYTrue / False是否遵循爬虫协议
LOG_LEVEL‘ERROR’ / ‘INFO’ /…日志级别
USER_AGENTMozilla/5.0 (Windows NT 10.0;…用户代理
DEFAULT_REQUEST_HEADERS如下所示默认请求头
SPIDER_MIDDLEWARES如下所示爬虫中间件
DOWNLOADER_MIDDLEWARES如下所示下载中间件
ITEM_PIPELINES如下所示持久化配置
CONCURRENT_REQUESTS默认 16配置Scrapy执行的最大并发请求
COOKIES_ENABLED默认 True是否禁止 cookie
RETRY_ENABLEDTrue / False是否重试
DOWNLOAD_TIMEOUT数字超时等待时间
# 默认请求头
DEFAULT_REQUEST_HEADERS = {
  'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
  'Accept-Language': 'en',
}
  • 1
  • 2
  • 3
  • 4
  • 5
# 爬虫中间件
SPIDER_MIDDLEWARES = {
   'myfirstscrapy.middlewares.MyfirstscrapySpiderMiddleware': 543,
}
  • 1
  • 2
  • 3
  • 4
# 持久化配置
ITEM_PIPELINES = {
   'myfirstscrapy.pipelines.MyfirstscrapyPipeline': 300,
}
  • 1
  • 2
  • 3
  • 4

4.2 提高爬虫效率配置

  • 增加并发
CONCURRENT_REQUESTS = 100
默认 scrapy 开启的并发线程为 16 个,可以适当进行增加。在 settings 配置文件中如上修改为100
  • 1
  • 2
  • 降低日志级别
在运行scrapy时,会有大量日志信息的输出,为了减少CPU的使用率。可以设置log输出信息为ERROR。
在配置文件中编写:	LOG_LEVEL = 'ERROR'
  • 1
  • 2
  • 禁止 cookie
如果不是真的需要cookie,则在scrapy爬取数据时可以禁止cookie从而减少CPU的使用率,提升爬取效率。
在配置文件中编写:COOKIES_ENABLED = False
  • 1
  • 2
  • 禁止重试
对失败的HTTP进行重新请求(重试)会减慢爬取速度,因此可以禁止重试。在配置文件中编写:
RETRY_ENABLED = False
  • 1
  • 2
  • 减少下载超时
如果对一个非常慢的链接进行爬取,减少下载超时可以能让卡住的链接快速被放弃,从而提升效率。
在配置文件中进行编写:DOWNLOAD_TIMEOUT = 10  # 超时时间为10s
  • 1
  • 2

5. 数据持久化

解析好的数据需要进行存储,存储的形式可以是文件、redis、MySQL等等。

5.1 方案一

一般不使用该方法

1. 解析函数parse返回的格式需要如下形式:
	return [{},{},{}]
2. 运行如下命令
	scrapy crawl cnblogs -o 文件名(json,pkl,csv结尾)
  • 1
  • 2
  • 3
  • 4

5.2 方案二(pipline模式)

  1. 在 items.py 中写一个类,继承 scrapy.Item,并在类中写属性
class CnBlogItem(scrapy.Item):
    title = scrapy.Field()
    author = scrapy.Field()
    real_desc = scrapy.Field()
    pub_time = scrapy.Field()
    url = scrapy.Field()
    content = scrapy.Field()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  1. 在爬虫中导入类,实例化得到对象,把要保存的数据放到对象中
import scrapy
from myfirstscrapy.items import CnBlogItem

class CnblogsSpider(scrapy.Spider):
    name = 'cnblogs'
    allowed_domains = ['cnblogs.com']
    start_urls = ['http://cnblogs.com/']

    def parse(self, response):
        
        for article in article_list:
        	item = CnBlogItem()
            ...
            item['title'] = title
            item['real_desc'] = real_desc
            item['pub_time'] = pub_time
            item['author'] = author
            item['url'] = url
	
            yield item


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  1. 修改配置文件,指定 pipline,数字表示优先级,越小越大
ITEM_PIPELINES = {
   'myfirstscrapy.pipelines.CnBlogPipeline': 300,
}
  • 1
  • 2
  • 3
  1. 编写注册的 pipline
class CnBlogPipeline:
    def open_spider(self, spider):
        # 数据初始化,打开文件,打开数据库链接
        # 打开文件,链接数据库
        self.f = open('cnblogs.txt', 'w', encoding='utf-8')

    def process_item(self, item, spider):
        # 储存数据,必须返回 item 给后续的 pipline 继续使用
        self.f.write('标题:%s,作者:%s\n' % (item['title'], item['author']))
        return item

    def close_spider(self, spider):
        # 销毁资源,关闭文件,关闭数据库链接
        # 关闭文件,关闭数据库
        self.f.close()

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
博客园全站爬取
  • cnblogs.py 爬虫文件
import scrapy
from myfirstscrapy.items import CnBlogItem
from scrapy import Request


class CnblogsSpider(scrapy.Spider):
    # 爬虫名字
    name = 'cnblogs'
    # 域名
    allowed_domains = ['cnblogs.com']
    # 爬取地址
    start_urls = ['http://cnblogs.com/']

    # 回调函数
    def parse(self, response):
        # 获取一页文章内容
        article_list = response.css('article.post-item')
        for article in article_list:
            # 实例化 Items.py 下的类
            item = CnBlogItem()

            title = article.css('a.post-item-title::text').extract_first()
            desc = article.css('p.post-item-summary::text').extract()
            real_desc = desc[0].replace('\n', '').replace(' ', '')
            if not real_desc:
                real_desc = desc[1].replace('\n', '').replace(' ', '')
            pub_time = article.css('span.post-meta-item>span::text').extract_first()
            author = article.css('footer.post-item-foot span::text').extract_first()
            url = article.css('a.post-item-title::attr(href)').extract_first()

            # 给 item 设置文章内容
            item['title'] = title
            item['desc'] = real_desc
            item['pub_time'] = pub_time
            item['author'] = author
            item['url'] = url

            # 发起请求,获取文章详情(另一个页面),回调函数设置为 parser_detail,并通过 meta 实现请求对象和响应对象通信
            yield Request(url=url, callback=self.parser_detail, meta={'item': item})

        # 获取下一页元素链接属性
        next = response.css('div.pager>a:last-child::attr(href)').extract_first()
        # 拼接地址
        next = 'https://www.cnblogs.com' + next
        print(next)
        # 发起请求,设置回调函数为 parse,当然默认也是 parse
        yield Request(url=next, callback=self.parse)

    def parser_detail(self, response):
        # 获取传递的 item,用于数据持久化
        item = response.meta.get('item')
        # 获取文章内容
        content = response.css('#cnblogs_post_body').extract_first()
        # 添加属性
        item['content'] = content
        yield item

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • items.py
import scrapy

class CnBlogItem(scrapy.Item):
    title = scrapy.Field()
    author = scrapy.Field()
    desc = scrapy.Field()
    pub_time = scrapy.Field()
    url = scrapy.Field()
    content = scrapy.Field()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • settings.py
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'

ITEM_PIPELINES = {
    'myfirstscrapy.pipelines.CnBlogPipeline': 300,
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • pipelines.py
import pymysql

class CnBlogPipeline:
    def open_spider(self, spider):
        # 数据初始化,使用 pymysql 打开数据库链接
        self.conn = pymysql.connect(user='root',
                                    password="123123",
                                    host='127.0.0.1',
                                    port=3306,
                                    database='cnblogs')
        self.cursor = self.conn.cursor()

    def process_item(self, item, spider):
        # 编写 sql 语句,例如 desc 这种本身属于关键词的可以使用 `` 包裹,编写的占位符使用 %s
        sql = 'insert into cnblog (title,`desc`,url,pub_time,author,content) values (%s,%s,%s,%s,%s,%s)'
        # 传入参数
        self.cursor.execute(sql, args=[item['title'], item['desc'], item['url'], item['pub_time'], item['author'],
                                       item['content']])
        # 提交确认
        self.conn.commit()

        # 储存数据,必须返回 item 给后续的 pipline 继续使用
        return item

    def close_spider(self, spider):
        # 销毁资源,关闭文件,关闭数据库链接
        self.cursor.close()
        self.conn.close()

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

6. 下载中间件

中间件中 SPIDER_MIDDLEWARES 爬虫中间件 (了解即可,用的少),DOWNLOADER_MIDDLEWARES 下载中间件(用的多)。尤其是下载中间件,里面的两个方法
process_requestprocess_response

class CnBlogDownloaderMiddleware:
    @classmethod
    def from_crawler(cls, crawler):
        s = cls()
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s

    def process_request(self, request, spider):
    	# 请求来的时候执行
        # return None 					继续处理此请求
        # return a Response object 		返回给 engin,去解析
        # return a Request object		返回给 engin,继续去请求
        # raise IgnoreRequest: 			执行 process_exception 方法
        return None

    def process_response(self, request, response, spider):
    	# 请求走的时候执行

        # return a Response object	继续走下一个中间件的process_response,给engin,去解析
        # return a Request object	给engin,进入调度器,等待下一次爬取
        # raise IgnoreRequest		抛出异常
        return response

    def process_exception(self, request, exception, spider):
		# 出现异常时执行
        pass

    def spider_opened(self, spider):
    	# 爬虫运行时执行
        spider.logger.info('Spider opened: %s' % spider.name)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

需要在 settings.py 中配置:

DOWNLOADER_MIDDLEWARES = {
   'myfirstscrapy.middlewares.CnBlogDownloaderMiddleware': 543,
}
  • 1
  • 2
  • 3

7. 下载中间件相关

7.1 添加代理

在下载中间件的 process_request 方法中如下配置:

...
    def process_request(self, request, spider):
    	# 这里是固定的,最好搭建一个代理池,每次随机取出一个使用
        request.meta['proxy'] = 'http://221.6.215.202:9091'
        return None
...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

7.2 添加cookie

在下载中间件的 process_request 方法中如下配置:

...
    def process_request(self, request, spider):
        request.cookies['name'] = 'xwx'
        request.cookies = {}
        return None
...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

7.3 修改请求头

在下载中间件的 process_request 方法中如下配置:

...
    def process_request(self, request, spider):
    	# cookie 也可以写在请求头
		request.headers['Auth'] = 'asdfasdfasdfasdf'
		request.headers['USER-AGENT'] = 'ssss'
        return None
...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

7.4 随机生成 UserAgent

需要使用 fake_useragent 模块
pip3 install fake-useragent
在下载中间件的 process_request 方法中如下配置:

...
    def process_request(self, request, spider):
		from fake_useragent import UserAgent
		ua = UserAgent()
        print(ua.ie)   		# 随机打印ie浏览器任意版本
        print(ua.firefox) 	# 随机打印firefox浏览器任意版本
        print(ua.chrome)  	# 随机打印chrome浏览器任意版本
        print(ua.random)  	# 随机打印任意厂家的浏览器 
        request.headers['USER-AGENT'] = ua.chrome
        return None
...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

7.5 集成 selenium

  • 在爬虫类中如下配置
...
from selenium import webdriver

class CnblogsSpider(scrapy.Spider):
	...
	chrome = webdriver.Chrome()
	...
	def close(spider, reason):
		# 关闭浏览器
        spider.chrome.close()

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 在中间件中的 process_reqeust 中配置:
...
    def process_request(self, request, spider):
        from scrapy.http import HtmlResponse
        url = request.url
        if 'sitehome' in url:
            spider.chrome.get(url=url)
            response = HtmlResponse(url=request.url, body=spider.chrome.page_source.encode('utf-8'), request=request)
            return response
        else:
            return None
...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

8. 去重规则源码分析

scrapy 使用集合实现了去重,也就是爬过的网址不会再爬了。

DUPEFILTER_CLASS = 'scrapy.dupefilters.RFPDupeFilter'

# from scrapy.dupefilters import RFPDupeFilter
  • 1
class RFPDupeFilter(BaseDupeFilter)
	def request_seen(self, request: Request) -> bool:
        # 把request生成指纹,如果request对象的url一样,指纹就一样
        fp = self.request_fingerprint(request)
        if fp in self.fingerprints:
            return True
        self.fingerprints.add(fp)
        if self.file:
            self.file.write(fp + '\n')
        return False

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 爬虫开始爬取时,执行了爬虫类对象的:start_requests 方法
  • start_urls 是起始爬取的地址
  • 爬虫去重的规则应该在 scrapy.core.scheduler 调度器源码中的 enqueue_request 中调用了去重类(RFPDupeFilter)对象的 request_seen 方法来完成去重,本质是使用集合去重的
  • 每次爬取的地址对象 request 生成一个指纹,判断是否在集合中,如果在集合中,就不爬取了,如果不在,就爬取并且把生成的指纹放到集合中
  • 把下面这种地址生成指纹后,生成的是一样的:
www.cnblogs.com?name=xwx&age=19
www.cnblogs.com?age=19&name=xwx
  • 1
  • 2

测试生成指纹

from scrapy.utils.request import request_fingerprint
from scrapy import Request
ur1=Request(url='http://www.cnblogs.com?name=xwx&age=19')
ur2=Request(url='http://www.cnblogs.com?age=20&name=xwx')
print(request_fingerprint(ur1))
print(request_fingerprint(ur2))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

集合的方式如果爬取的网址少还行,如果特别多 ,则会占非常大的内存空间。可以使用 布隆过滤器 来实现极小空间实现去重

9. python 中实现布隆过滤器

9.1 布隆过滤器简介

bloomfilter:是一个通过多哈希函数映射到一张表的数据结构,能够快速的判断一个元素在一个集合内是否存在,具有很好的空间和时间效率。(典型例子,爬虫url去重)

原理: BloomFilter 会开辟一个m位的bitArray(位数组),开始所有数据全部置 0 。当一个元素过来时,能过多个哈希函数(h1,h2,h3…)计算不同的在哈希值,并通过哈希值找到对应的bitArray下标处,将里面的值 0 置为 1 。

关于多个哈希函数,它们计算出来的值必须 [0,m) 之中。

假设长度为 20的bitArray,通过 3 个哈希函数求值。如下图:

在这里插入图片描述

另外说明一下,当来查找对应的值时,同样通过哈希函数求值,再去寻找数组的下标,如果所有下标都为1时,元素存在。当然也存在错误率。(如:当数组全部为1时,那么查找什么都是存在的),但是这个错误率的大小,取决于数组的位数和哈希函数的个数。

9.2 Python 中使用布隆过滤器

安装:pip3 install pybloom_live

# pybloom_live 依赖这个包,直接安装 pybloom_live 会自动下载依赖的包
pip3 install bitarray-0.8.1-cp36-cp36m-win_amd64.whl
  • 1
  • 2
  • 示例一
# ScalableBloomFilter 可以自动扩容
from pybloom_live import ScalableBloomFilter
​
​# 错误达到 0.001 自动扩容
bloom = ScalableBloomFilter(initial_capacity=100, error_rate=0.001, mode=ScalableBloomFilter.LARGE_SET_GROWTH)
​
url = "www.cnblogs.com"
​
url2 = "www.liuqingzheng.top"
​
bloom.add(url)print(url in bloom)print(url2 in bloom)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 示例二
# BloomFilter 是定长的
from pybloom_live import BloomFilter
​
bf = BloomFilter(capacity=1000)
url='www.baidu.com'
bf.add(url)print(url in bf)print("www.liuqingzheng.top" in bf)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

9.3 在 Scrapy 中使用

  • my_filter.py 文件下创建类,替换掉内置的去重
from pybloom_live import ScalableBloomFilter
from scrapy.dupefilters import RFPDupeFilter


class MyRFPDupeFilter(RFPDupeFilter):
    bloom = ScalableBloomFilter(initial_capacity=100, error_rate=0.001, mode=ScalableBloomFilter.LARGE_SET_GROWTH)
    fingerprints = bloom

    def request_seen(self, request):
        fp = self.request_fingerprint(request)
        if fp in self.fingerprints:
            return True
        self.fingerprints.add(fp)
        if self.file:
            self.file.write(fp + '\n')
        return False

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • settings.py 配置
DUPEFILTER_CLASS = 'myfirstscrapy.my_filter.MyRFPDupeFilter'
  • 1

10. scrapy-redis 实现分布式爬虫

  1. 第一步:安装 scrapy-redis
    安装:pip3 install scrapy-redis
  2. 第二步:改造爬虫类
from scrapy_redis.spiders import RedisSpider
   class CnblogSpider(RedisSpider):
       name = 'cnblog_redis'
       allowed_domains = ['cnblogs.com']
       # 写一个key:redis列表的key,起始爬取的地址
       redis_key = 'myspider:start_urls'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. 第三步:配置文件配置
# 分布式爬虫配置
# 去重规则使用redis
REDIS_HOST = 'localhost'                            # 主机名
REDIS_PORT = 6379                                   # 端口
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 持久化:文件,mysql,redis
ITEM_PIPELINES = {
   'cnblogs.pipelines.CnblogsFilePipeline': 300,
   'cnblogs.pipelines.CnblogsMysqlPipeline': 100,
   'scrapy_redis.pipelines.RedisPipeline': 400,
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  1. 第四步:在多台机器上启动scrapy项目
  2. 第五步:手动把起始爬取的地址放到 redis 的列表中
lpush myspider:start_urls value http://www.cnblogs.com/
  • 1
声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号