scrapy框架——架构介绍、安装、项目创建、目录介绍、使用、持久化方案、集成selenium、去重规则源码分析、布隆过滤器使用、redis实现分布式爬虫

文章目录

  • 前言
  • 一、架构介绍
    • 引擎(EGINE)
    • 调度器(SCHEDULER)
    • 下载器(DOWLOADER)
    • 爬虫(SPIDERS)
    • 项目管道(ITEM PIPLINES)
    • 下载器中间件(Downloader Middlewares)
    • 爬虫中间件(Spider Middlewares)
  • 一、安装
  • 一、项目创建
    • 1 创建scrapy项目
    • 2 创建爬虫
    • 3启动爬虫,爬取数据
  • 二、目录介绍
  • 三、解析数据
  • 四、配置
    • 1.基础配置
    • 2.增加爬虫的爬取效率配置
  • 五、持久化方案
  • 第一种:parse返回值(很少使用)
  • 第二种:pipline模式(通用的)
  • 六、爬虫和下载中间件
  • 七、加代理,cookie,header
    • 1.加代理
    • 2.cookie
    • 3.header
  • 八、集成selenium
  • 九、去重规则源码分析
  • 十、布隆过滤器使用
    • 安装
    • 1.定长的布隆过滤器
    • 2.可自动扩容的布隆过滤器
    • 3.布隆过滤器方法
  • 十一、redis实现分布式爬虫


前言

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


一、架构介绍

scrapy框架——架构介绍、安装、项目创建、目录介绍、使用、持久化方案、集成selenium、去重规则源码分析、布隆过滤器使用、redis实现分布式爬虫_第1张图片

引擎(EGINE)

引擎负责控制系统所有组件之间的数据流,并在某些动作发生时触发事件。

调度器(SCHEDULER)

用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL的优先级队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址

下载器(DOWLOADER)

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

爬虫(SPIDERS)

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

项目管道(ITEM PIPLINES)

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

下载器中间件(Downloader Middlewares)

位于Scrapy引擎和下载器之间,主要用来处理从EGINE传到DOWLOADER的请求request,已经从DOWNLOADER传到EGINE的响应response,

爬虫中间件(Spider Middlewares)

位于EGINE和SPIDERS之间,主要工作是处理SPIDERS的输入(即responses)和输出(即requests)


一、安装

针对于mac、linux基本不会出现问题

pip3 install scrapy

针对windows有概率失败
失败解决方案:

1、pip3 install wheel #安装后,便支持通过wheel文件安装软件,wheel文件官网:https://www.lfd.uci.edu/~gohlke/pythonlibs
2、pip3 install lxml
3、pip3 install pyopenssl
4、下载并安装pywin32:https://sourceforge.net/projects/pywin32/files/pywin32/
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


一、项目创建

scrapy与django类似

1 创建scrapy项目

scrapy startproject 项目名

2 创建爬虫

scrapy genspider 爬虫名 爬取网址

3启动爬虫,爬取数据

方法一指令启动:
scrapy crawl 爬虫名字 --nolog(该参数为不打印日志,可忽略)

方法二使用脚本来启动:
项目路径下新建main.py

from scrapy.cmdline import execute
execute(['scrapy','crawl','爬虫名','--nolog'])# --nolog不打印日志,可不带


二、目录介绍

scrapy框架——架构介绍、安装、项目创建、目录介绍、使用、持久化方案、集成selenium、去重规则源码分析、布隆过滤器使用、redis实现分布式爬虫_第2张图片

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

三、解析数据

分析数据写在爬虫文件中的parse方法里(spiders下的爬虫文件,本文为cnblogs.py)

response对象有css方法和xpath方法

  1. css中写css选择器
  2. xpath中写xpath选择

俩种方法可以独立使用也可以混合使用,俩种方法找到的是标签对象,会转化为类对象,所以可以根据类对象再次进行标签查找

示例:

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

取到的标签对象可以是多个或者一个再选择对应的方法即可(使用以下俩个方法获取的标签不再是类对象,无法再次对其下内容进行查找)
.extract_first() 取一个,使得提取内容转换为unicodez字符串
.extract() 取所有,使得提取内容转换为unicodez字符串,返回一个list

示例代码:

import scrapy

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

    def parse(self, response):
        articles = response.xpath('.//article[@class="post-item"]') # 找到article标签
        for article in articles: # 对每一个article标签中再次查找标签
            title = article.xpath('.//a[@class="post-item-title"]/text()').extract_first()
            url = article.xpath('.//a[@class="post-item-title"]/@href').extract_first()
            desc = article.xpath('.//p[@class="post-item-summary"]/text()').extract()
            author = article.xpath('.//footer[@class="post-item-foot"]/a/span/text()').extract_first()

四、配置

修改settings.py

1.基础配置

#1  是否遵循爬虫协议,默认为True
ROBOTSTXT_OBEY = False

#2 LOG_LEVEL 日志级别
LOG_LEVEL='ERROR'  # 报错如果不打印日志,在控制台看不到错误

# 3 USER_AGENT
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'

# 4 DEFAULT_REQUEST_HEADERS 默认请求头
DEFAULT_REQUEST_HEADERS = {
   'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
   'Accept-Language': 'en',
}

# 5 SPIDER_MIDDLEWARES 爬虫中间件
SPIDER_MIDDLEWARES = {
    'cnblogs.middlewares.CnblogsSpiderMiddleware': 543,
}
# 6 DOWNLOADER_MIDDLEWARES  下载中间件
DOWNLOADER_MIDDLEWARES = {
    'cnblogs.middlewares.CnblogsDownloaderMiddleware': 543,
}

# 7 ITEM_PIPELINES 持久化配置,配置数据持久化方式(如文件、数据库等)
ITEM_PIPELINES = {
    'cnblogs.pipelines.CnblogsPipeline': 300,
}

2.增加爬虫的爬取效率配置

#1 增加并发:
#默认scrapy开启的并发线程为32个,可以适当进行增加。在settings配置文件中修改
CONCURRENT_REQUESTS = 100
#值为100,并发设置成了为100。

#2 降低日志级别:
#在运行scrapy时,会有大量日志信息的输出,为了减少CPU的使用率。可以设置log输出信息为INFO或者ERROR即可。在配置文件中编写:
LOG_LEVEL = 'INFO'

# 3 禁止cookie:
#如果不是真的需要cookie,则在scrapy爬取数据时可以禁止cookie从而减少CPU的使用率,提升爬取效率。在配置文件中编写:
COOKIES_ENABLED = False

# 4 禁止重试:
#对失败的HTTP进行重新请求(重试)会减慢爬取速度,因此可以禁止重试。在配置文件中编写:
RETRY_ENABLED = False

# 5 减少下载超时:
#如果对一个非常慢的链接进行爬取,减少下载超时可以能让卡住的链接快速被放弃,从而提升效率。在配置文件中进行编写:
DOWNLOAD_TIMEOUT = 10 #超时时间为10s

五、持久化方案

第一种:parse返回值(很少使用)

解析函数中parse,要return [{},{},{}],返回一个列表套字典的数据

    def parse(self, response):
        articles = response.xpath('.//article[@class="post-item"]')
        article_list = []
        for article in articles:
            item = CnblogsItem()
            title = article.xpath('.//a[@class="post-item-title"]/text()').extract_first()
            url = article.xpath('.//a[@class="post-item-title"]/@href').extract_first()
            desc = article.xpath('.//p[@class="post-item-summary"]/text()').extract()
            author = article.xpath('.//footer[@class="post-item-foot"]/a/span/text()').extract_first()
            article_list.append({'title':title, 'url':url, 'desc':desc, 'desc':desc, 'author':author, })
        return article_list

使用指令 scrapy crawl cnblogs -o 文件名(json,csv等后缀)

scrapy crawl cnblogs -o data.json

第二种:pipline模式(通用的)

1.在items.py中写一个类,继承scrapy.Item
在类中编写属性

import scrapy

class CnblogsItem(scrapy.Item):
    title = scrapy.Field()
    desc = scrapy.Field()
    pub_time = scrapy.Field()
    author = scrapy.Field()
    url = scrapy.Field()
    content = scrapy.Field()

2.在爬虫中导入类,实例化得到对象,把要保存的数据放到对象中

import scrapy
from scrapy import Request
from testscrapy.items import CnblogsItem

class CnblogsSpider(scrapy.Spider):
    name = 'cnblogs'
    allowed_domains = ['cnblogs.com']
    start_urls = ['http://cnblogs.com/']
    
    def parse(self, response):
        articles = response.xpath('.//article[@class="post-item"]')
        for article in articles:
            item = CnblogsItem()
            title = article.xpath('.//a[@class="post-item-title"]/text()').extract_first()
            url = article.xpath('.//a[@class="post-item-title"]/@href').extract_first()
            desc = article.xpath('.//p[@class="post-item-summary"]/text()').extract()
            author = article.xpath('.//footer[@class="post-item-foot"]/a/span/text()').extract_first()
            res_desc = desc[0].replace('\n', '').replace(' ', '')
            if not res_desc:
                res_desc = desc[1].replace('\n', '').replace(' ', '')
            item['title'] = title
            item['url'] = url
            item['desc'] = desc
            item['author'] = author
            item['desc'] = res_desc
            # 由于需要拿到文章正文,再次发送request,url为request的爬取地址,callback为解析数据的函数,meta为需要传递过去的参数
            yield item

3.修改配置文件,指定pipline,数字表示优先级,越小越大

	ITEM_PIPELINES = {
   	'crawl_cnblogs.pipelines.CrawlCnblogsPipeline': 300,
		}

4.写一个pipline:CrawlCnblogsPipeline
-open_spider:数据初始化,打开文件,打开数据库链接
-process_item:真正存储的地方
-一定不要忘了return item,交给后续的pipline继续使用
-close_spider:销毁资源,关闭文件,关闭数据库链接

pipelines.py

# 数据持久化文件形式
class CnblogsFilePipeline:
    def open_spider(self, spider):
        # print(spider)  # 就是爬虫类的对象
        # print(type(spider))  # 就是爬虫类的对象
        print('open')
        # 打开文件,链接数据库
        self.f = open('cnblogs.txt', 'w', encoding='utf-8')

    def close_spider(self, spider):
        # print(spider)  # 就是爬虫类的对象
        # print(type(spider))  # 就是爬虫类的对象
        print('close')
        # 关闭文件,关闭数据库
        self.f.close()

    def process_item(self, item, spider):
        # 具体存数据,存文件
        # with open('cnblog.txt', 'a', encoding='utf-8') as f:
        #     f.write('标题:%s,作者:%s' % (item['title'], item['author']))
        # print(item)
        self.f.write('标题:%s,作者:%s\n' % (item['title'], item['author']))

        return item  # 一定不要忘了返回

import pymysql

class CnblogsMysqlPipeline:
    def open_spider(self, spider):
        self.conn = pymysql.connect(user='root',
                                    password="密码",
                                    host='127.0.0.1',
                                    port=3306,
                                    database='cnblogs')
        self.cursor = self.conn.cursor()

    def close_spider(self, spider):
        # self.conn.commit()
        self.cursor.close()
        self.conn.close()

    def process_item(self, item, spider):
        sql = 'insert into article (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()

        return item  # 一定不要忘了返回


六、爬虫和下载中间件

在settings.py中可看到以下配置

SPIDER_MIDDLEWARES 爬虫中间件 (了解即可,用的少)

SPIDER_MIDDLEWARES = {
    'cnblogs.middlewares.CnblogsSpiderMiddleware': 543,
}

DOWNLOADER_MIDDLEWARES 下载中间件(用的多)

DOWNLOADER_MIDDLEWARES = {
    'cnblogs.middlewares.CnblogsDownloaderMiddleware': 543,
}

最重要的是下载中间件,里面的两个方法
middlewares.py

class CnblogsDownloaderMiddleware:
    # 请求来的时候
    def process_request(self, request, spider):
        # - return None: 继续执行下一个中间件的process_request
        # - 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 :继续走下一个中间件的process_response,给engin,进爬虫解析
        # - return a Request :给engin,进入调度器,等待下一次爬取
        # - raise IgnoreRequest:抛异常
        return response

七、加代理,cookie,header

1.加代理

在下载中间件中重写process_request

def process_request(self, request, spider):
    request.meta['proxy'] = 'http://221.6.215.202:9091' #设置proxy即可
    return None

2.cookie

在中间件中重写process_request

def process_request(self, request, spider):
    request.cookies['name']='bbc‘
    return None

3.header

在中间件中重写process_request

def process_request(self, request, spider):
	request.headers['Auth']='asdfasdfasdfasdf'
	request.headers['USER-AGENT']='ssss'

八、集成selenium

scrapy框架的下载器,是无法触发js的,当我们需要的内容时通过js获取到的,可以在scrapy框架中对下载器进行selenium的集成,实质就是将一些需要触发js的请求交由selenium来处理,其他的请求照常走下载器。

第一步:
在爬虫类中为需要使用selenium的爬虫添加一个类属性以及一个类方法

import scrapy
from selenium import webdriver

class CnblogsSpider(scrapy.Spider):
    name = 'cnblogs'
    allowed_domains = ['cnblogs.com']
    start_urls = ['http://cnblogs.com/']
    driver = webdriver.Edge() # 定义一个selenium对象

def close(spider, reason):
      spider.driver.close() # 使用完毕后关闭selenium对象

第二步:
创建一个下载中间件,重写process_request

class CnblogsDownloaderMiddlewareProxy:
	def process_request(self, request, spider): # 通过分辨url来判断是否使用selenium来获取html源码
		url=request.url
        if 'sitehome' in url:
            spider.driver.get(url)
            response=HtmlResponse(url=url,body=spider.driver.page_source.encode('utf-8'))
            return response
        else:
            return None
	

九、去重规则源码分析

scrapy 实现了去重,爬过的网址不会再爬了

scrapy使用了集合进行去重

第一步:
找到scrapy的配置文件
scrapy.settings下的default_settings.py中
找到DUPEFILTER_CLASS = ‘scrapy.dupefilters.RFPDupeFilter’

第二步:
找到RFPDupeFilter,该类负责处理指纹判断是否爬取过

class RFPDupeFilter(BaseDupeFilter):
	    def request_seen(self, request: Request) -> bool:
        fp = self.request_fingerprint(request) # 生成指纹
        if fp in self.fingerprints: # 对比指纹是否存在
            return True # 返回True就是已爬取
        self.fingerprints.add(fp)
        if self.file:
            self.file.write(fp + '\n')
        return False # 返回false就是未爬取过

第三步:
找到爬虫对象执行的入口
在scrapy.spiders下的_ init _.py中

class Spider(object_ref):
    def start_requests(self):
        if not self.start_urls and hasattr(self, 'start_url'):
            raise AttributeError(
                "Crawling could not start: 'start_urls' not found "
                "or empty (but found 'start_url' attribute instead, "
                "did you miss an 's'?)")
        for url in self.start_urls:
            yield Request(url, dont_filter=True)

第四步:
爬虫执行后在调度器中去重
在scrapy.core下的scheduler中

class Scheduler(BaseScheduler):
    def enqueue_request(self, request: Request) -> bool:
        if not request.dont_filter and self.df.request_seen(request): #此处调用了之前找到的去重类
            self.df.log(request, self.spider)
            return False
        dqok = self._dqpush(request)
        if dqok:
            self.stats.inc_value('scheduler/enqueued/disk', spider=self.spider)
        else:
            self._mqpush(request)
            self.stats.inc_value('scheduler/enqueued/memory', spider=self.spider)
        self.stats.inc_value('scheduler/enqueued', spider=self.spider)
        return True

以上就是scrapy框架的去重解析


十、布隆过滤器使用

中小型的去重使用集合、字典等去重比较适合,并发量大了之后再使用这些方式进行就会极大的影响效率和内存。
这个时候可以使用布隆过滤器。

布隆过滤器是以bit为单位的数组,数组初始状态每一位都是0,当需要去重的数据经过哈希函数处理后,将数组对应位置的0变为1,一但哈希函数计算的位置都是1就表明该数据已存在,反之只要有一位不为1就表示该数据此前没有存在过。布隆过滤器,占用空间很小,但只能进行过滤对比,无法取出值,所以应用时需要考虑是否需要取出已存在的值。

scrapy框架——架构介绍、安装、项目创建、目录介绍、使用、持久化方案、集成selenium、去重规则源码分析、布隆过滤器使用、redis实现分布式爬虫_第3张图片

安装

pip3 install pybloom_live

1.定长的布隆过滤器

from pybloom_live import BloomFilter

bf = BloomFilter(capacity=1000) # capacity为数组长度,持续使用后,定长布隆过滤器准确性会越来越低
url='www.baidu.com'
bf.add(url)
print(url in bf)
print("www.liuqingzheng.top" in bf)

2.可自动扩容的布隆过滤器

from pybloom_live import ScalableBloomFilter

bloom = ScalableBloomFilter(initial_capacity=100, error_rate=0.001, mode=ScalableBloomFilter.LARGE_SET_GROWTH) # initial_capacity为数组初始长度 error_rate为误判概率 
url = "www.cnblogs.com"
url2 = "www.liuqingzheng.top"
bloom.add(url)
print(url in bloom)
print(url2 in bloom)

3.布隆过滤器方法

add、madd

布隆过滤器对象.add {key} {item}

布隆过滤器对象.madd {key} {item} [item…]

往过滤器中添加元素。如果key不存在,过滤器会自动创建。

exists、 mexists

布隆过滤器对象.exists {key} {item}

布隆过滤器对象.mexists {key} {item} [item…]

判断过滤器中是否存在该元素,不存在返回0,存在返回1。


十一、redis实现分布式爬虫

第一步:
安装scrapy-redis

pip3 install scrapy-redis

第二步:
改造爬虫类

from scrapy_redis.spiders import RedisSpider
    class CnblogSpider(RedisSpider):
        name = 'cnblog_redis'
        allowed_domains = ['cnblogs.com']
        # start_urls = ['http://cnblogs.com/'] 该属性去掉
        # 写一个key:redis列表的key,起始爬取的地址
        redis_key = 'myspider:start_urls'

	# 正常编写解析函数
	def parse(self, response):
		...

第三步:
配置文件配置

# 分布式爬虫配置
# 去重规则使用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, # 写入mysql
   'scrapy_redis.pipelines.RedisPipeline': 400, # 写入redis
}

第四步:
在多台机器上启动scrapy项目

第五步:
向redis放入一个起始url,之后分布式项目才会开始爬取

可以在redis中添加、或者使用脚本添加
lpush myspider:start_urls value http://www.cnblogs.com/

你可能感兴趣的:(爬虫,scrapy,架构,selenium)