爬虫之Scrapy架构
创始人
2024-03-21 21:28:56
0

目录

Scrapy架构介绍

Scrapy下载

Scrapy基本使用

Scrapy目录结构

Scrapy解析数据

settings相关配置

基础配置

增加爬虫的爬取效率

去重规则(布隆过滤器)

持久化方案(数据保存)

request和response传递参数

网页解析下一页继续爬取

爬虫和下载中间件

加代理,cookie,header,集成selenium

scrapy-redis实现分布式爬虫


Scrapy架构介绍

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

引擎(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)。

Scrapy下载

安装:

pip install scrapy

如果安装失败,就需要走下面的流程:

1.安装wheel,之后通过wheel文件安装软件,wheel文件官网

pip install wheel
pip install lxml
pip install pyopenssl

3.下载并安装pywin32:Python for Windows Extensions - Browse /pywin32 at SourceForge.net

4.下载twisted的wheel文件:http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted

5.twisted的wheel文件通过pip安装

pip install 下载目录/Twisted-17.9.0-cp36-cp36m-win_amd64.whl

6.安装scrapy

pip install scrapy

Scrapy基本使用

创建项目

通过cmd窗口:在当前目录下创建项目

scrapy startproject 项目名

爬虫创建

在项目目录下,打开cmd窗口:

scrapy genspider 爬虫名 爬虫地址

比如创建爬博客园的爬虫:

scrapy genspider cnblogs cnblogs.com

此时会在项目spiders文件夹下创建cnblogs.py

import scrapyclass CnblogsSpider(scrapy.Spider):name = 'cnblogs'  # 爬虫名字allowed_domains = ['cnblogs.com']  # 允许爬取的地址start_urls = ['http://cnblogs.com/']  # 起始爬取的地址def parse(self, response):  # response就是爬取结果# 爬取完之后的代码,解析代码放在这里print(response.text)

爬虫启动方式一

--nolog代表不要日志输出

scrapy crawl 爬虫名
scrapy crawl 爬虫名 --nolog

比如启动刚刚创建的博客园爬虫:

scrapy crawl cnblogs --nolog

爬虫启动方式二

项目路径下新建py文件:

from scrapy.cmdline import execute
execute(['scrapy','crawl','爬虫名','--nolog'])

启动爬虫运行这个py文件即可。

Scrapy目录结构

项目名├── 项目同名文件夹├    ├── spiders -- 文件夹,专门存放爬虫文件├    ├    ├── 爬虫1.py -- 其中一个爬虫,重点写代码的地方├    ├    └── 爬虫2.py -- 其中一个爬虫,重点写代码的地方├    ├── items.py -- 类比djagno的models,表模型--->类├    ├── middlewares.py -- 爬虫中间件和下载中间件都在里面├    ├── pipelines.py -- 管道,做持久化需要在这写代码├    └── settings.py -- 配置文件└── scrapy.cfg -- 上线配置,开发阶段不用

Scrapy解析数据

创建爬虫时会自动在spiders下创建对应的爬虫名py文件,里面一个类,类中有一个解析数据用的方法parse(self, response):

class CnblogsSpider(scrapy.Spider):name = 'cnblogs'  # 爬虫名字allowed_domains = ['cnblogs.com']  # 允许爬取的地址start_urls = ['http://cnblogs.com/']  # 起始爬取的地址def parse(self, response):  # response就是爬取结果# 爬取完之后的代码,解析代码放在这里print(type(response))

response对象的方法:

方法作用
response.css('css选择器')根据css选择器选择标签
response.css('css选择器::text')获取标签文本内容
response.css('css选择器::attr(class)')获取标签class属性
response.xpath('xpath')根据xpath选择标签
response.xpath('xpath/text()')获取标签文本内容
response.xpath('xpath/@class')获取标签class属性
取个数
response.css().extract_first()根据css选择器选择标签,取一个
response.css().extract()根据css选择器选择标签,取全部

举例:获取博客园首页博客标题

class CnblogsSpider(scrapy.Spider):name = 'cnblogs'  # 爬虫名字allowed_domains = ['cnblogs.com']  # 允许爬取的地址start_urls = ['http://cnblogs.com/']  # 起始爬取的地址def parse(self, response):  print(response.css('.post-item-title::text').extract())

settings相关配置

基础配置

# 是否遵循爬虫协议
ROBOTSTXT_OBEY = False
# 输出日志级别
LOG_LEVEL='ERROR'
# 客户端类型
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'
# 默认请求头
DEFAULT_REQUEST_HEADERS = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8','Accept-Language': 'en',
}
# 爬虫中间件
SPIDER_MIDDLEWARES = {'项目名.middlewares.项目名SpiderMiddleware': 543,
}
# 下载中间件
DOWNLOADER_MIDDLEWARES = {'项目名.middlewares.项目名DownloaderMiddleware': 543,
}
# 持久化配置
ITEM_PIPELINES = {'项目名.pipelines.项目名Pipeline': 300,
}

增加爬虫的爬取效率

1.增加并发:

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

CONCURRENT_REQUESTS = 100

2.降低日志级别:

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

LOG_LEVEL = 'ERROR'

3.禁止cookie:

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

COOKIES_ENABLED = False

4 禁止重试:

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

RETRY_ENABLED = False

5.减少下载超时:

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

DOWNLOAD_TIMEOUT = 10  # 超时时间为10s

去重规则(布隆过滤器)

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

原理:每次根据爬取的地址对象request生成一个指纹(唯一),判断是否在集合中,如果在集合中,就不爬取了,如果不在,就爬取并且把生成的指纹(唯一)放到集合中。

源码剖析

在scrapy自己的配置文件中配置了一个过滤器:

DUPEFILTER_CLASS = 'scrapy.dupefilters.RFPDupeFilter'

RFPDupeFilter类中有一个方法request_seen:集合去重

def request_seen(self, request: Request) -> bool:# 生成指纹fp = self.request_fingerprint(request)# self.fingerprints是一个集合# 判断指纹是否存在if fp in self.fingerprints:# 返回True就说明重复了,不爬取这个地址了return Trueself.fingerprints.add(fp)if self.file:self.file.write(fp + '\n')return False

布隆过滤器

极小内存校验是否重复

01-Python实现布隆过滤器 - 知乎

可以自己写一个类,替换掉内置的去重:

class MyRFPDupeFilter(RFPDupeFilter):fingerprints=布隆过滤器

持久化方案(数据保存)

方式一:保存到文件

解析函数parse,需要返回 [{}, {}] 的格式:

class CnblogsSpider(scrapy.Spider):name = 'cnblogs'  # 爬虫名字allowed_domains = ['cnblogs.com']  # 允许爬取的地址start_urls = ['http://cnblogs.com/']  # 起始爬取的地址def parse(self, response):  # response就是爬取结果# 爬取完之后的代码,解析代码放在这里return [{'title': v} for v in response.css('.post-item-title::text').extract()]

然后终端命令:

scrapy crawl 爬虫名 -o 文件名(json,pkl,csv等结尾)

方式二:pipline模式

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

2.在类中写属性:

import scrapyclass CnblogsItem(scrapy.Item):title = scrapy.Field()

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

class CnblogsSpider(scrapy.Spider):name = 'cnblogs'  # 爬虫名字allowed_domains = ['cnblogs.com']  # 允许爬取的地址start_urls = ['http://cnblogs.com/']  # 起始爬取的地址def parse(self, response):  # response就是爬取结果from ..items import CnblogsItemitem = CnblogsItem()for title in response.css('.post-item-title::text').extract():item['title'] = title# 变成生成器yield item

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

ITEM_PIPELINES = {'项目名.pipelines.CrawlCnblogsPipeline': 300,
}

5.在pipelines.py中写一个pipline,和配置文件中的名字对应

class CrawlCnblogsPipeline:# 数据初始化,打开文件,打开数据库链接def open_spider(self, spider):# spider: 即爬虫对象# 打开文件self.f = open('cnblogs.txt', 'w', encoding='utf-8')# 真正存储的地方,一定不要忘了return item,交给后续的pipline继续使用def process_item(self, item, spider):# 存储self.f.write('标题:' + item['title'] + '\n')return item# 销毁资源,关闭文件,关闭数据库链接def close_spider(self, spider):# spider: 即爬虫对象# 关闭文件self.f.close()

request和response传递参数

传递参数需要使用Request方法。

from scrapy.http import Request
Request(url=爬虫, callback=调用哪个爬虫函数, meta=参数)

举例:博客园爬取首页标题和文章详情

爬虫代码:

import scrapy
from scrapy.http import Requestclass CnblogsSpider(scrapy.Spider):name = 'cnblogs'  # 爬虫名字allowed_domains = ['cnblogs.com']  # 允许爬取的地址start_urls = ['http://cnblogs.com/']  # 起始爬取的地址def parse(self, response):  # response就是爬取结果from ..items import CnblogsItemfor title in response.css('.post-item-title'):# 要写在里面item = CnblogsItem()# 标题item['title'] = title.css('::text').extract_first()# 文章详情地址url = title.css('::attr(href)').extract_first()# 爬取文章详情地址,把item对象传过去修改yield Request(url=url, callback=self.parser_detail, meta={'item': item})def parser_detail(self, response):item = response.meta.get('item')# 文章内容content = response.css('#post_detail').extract_first()item['content'] = contentprint('一篇文章完成')yield item

pipelines.py:(记得添加到配置文件中)

class CrawlCnblogsPipeline:# 数据初始化,打开文件,打开数据库链接def open_spider(self, spider):# spider: 即爬虫对象# 打开文件self.f = open('cnblogs.txt', 'w', encoding='utf-8')# 真正存储的地方,一定不要忘了return item,交给后续的pipline继续使用def process_item(self, item, spider):# 存储self.f.write('标题:' + item['title'] + '\n')self.f.write('内容:' + item['content'] + '\n')return item# 销毁资源,关闭文件,关闭数据库链接def close_spider(self, spider):# spider: 即爬虫对象# 关闭文件self.f.close()

items.py:

import scrapyclass CnblogsItem(scrapy.Item):title = scrapy.Field()  # 标题content = scrapy.Field()  # 文章内容

网页解析下一页继续爬取

import scrapy
from scrapy.http import Requestclass CnblogsSpider(scrapy.Spider):name = 'cnblogs'  # 爬虫名字allowed_domains = ['cnblogs.com']  # 允许爬取的地址start_urls = ['http://cnblogs.com/']  # 起始爬取的地址def parse(self, response):  # response就是爬取结果from ..items import CnblogsItemitem = CnblogsItem()for title in response.css('.post-item-title::text').extract():item['title'] = titleyield item# 下一页的标签的href属性获取next_url = response.css('div.pager>a:last-child::attr(href)').extract_first()next_url = 'https://www.cnblogs.com' + next_urlprint('准备爬取 ' + next_url)# 把下一页的爬取继续交给当前self.parse方法yield Request(url=next_url, callback=self.parse)

爬虫和下载中间件

配置文件中:

# 爬虫中间件
SPIDER_MIDDLEWARES = {'项目名.middlewares.项目名SpiderMiddleware': 543,
}
# 下载中间件
DOWNLOADER_MIDDLEWARES = {'项目名.middlewares.项目名DownloaderMiddleware': 543,
}

用的比较多的是下载中间件,里面的两个方法:

class DownloaderMiddleware:# 请求来的时候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,集成selenium

代理、cookie、header都是在下载中间件中添加

代理

def process_request(self, request, spider):print('下载中间件:',request)# 代理request.meta['proxy'] = 'http://221.6.215.202:9091'return None

cookie

def process_request(self, request, spider):print('下载中间件:',request)# cookierequest.cookies= {}# 或者request.cookies['name']='value'return None

请求头:

def process_request(self, request, spider):print('下载中间件:',request)# 请求头request.headers['Auth']='asdfasdfasdfasdf'request.headers['USER-AGENT']='ssss'return None

随机生成user-agent

pip install fake_useragent
from fake_useragent import UserAgentua = UserAgent()
print(ua.ie)  # 随机打印ie浏览器任意版本
print(ua.firefox)  # 随机打印firefox浏览器任意版本
print(ua.chrome)  # 随机打印chrome浏览器任意版本
print(ua.random)  # 随机打印任意厂家的浏览器

集成selenium

在爬虫类中集成:

import scrapy
from selenium import webdriverclass CnblogsSpider(scrapy.Spider):name = 'cnblogs'allowed_domains = ['cnblogs.com'] start_urls = ['http://cnblogs.com/'] # 添加属性driver = webdriver.Chrome()def parse(self, response): print(response)# 关闭触发def close(self, reason):self.driver.close()

下载中间件中:

def process_request(self, request, spider):"""spider就是爬虫类对象"""from scrapy.http import HtmlResponsespider.driver.get(url=request.url)response = HtmlResponse(url=request.url, body=spider.driver.page_source.encode('utf-8'), request=request)return response

scrapy-redis实现分布式爬虫

第一步:安装scrapy-redis

pip install scrapy-redis

第二步:改造爬虫类

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

第三步:配置文件配置

# 分布式爬虫配置
# 去重规则使用redis
REDIS_HOST = 'localhost'  # 主机名
REDIS_PORT = 6379  # 端口
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 持久化:文件,mysql,redis
ITEM_PIPELINES = {'scrapy_redis.pipelines.RedisPipeline': 400,
}

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

第五步:把起始爬取的地址放到redis的列表中

lpush myspider:start_urls http://www.cnblogs.com/

 

相关内容

热门资讯

汽车油箱结构是什么(汽车油箱结... 本篇文章极速百科给大家谈谈汽车油箱结构是什么,以及汽车油箱结构原理图解对应的知识点,希望对各位有所帮...
美国2年期国债收益率上涨15个... 原标题:美国2年期国债收益率上涨15个基点 美国2年期国债收益率上涨15个基...
嵌入式 ADC使用手册完整版 ... 嵌入式 ADC使用手册完整版 (188977万字)💜&#...
重大消息战皇大厅开挂是真的吗... 您好:战皇大厅这款游戏可以开挂,确实是有挂的,需要了解加客服微信【8435338】很多玩家在这款游戏...
盘点十款牵手跑胡子为什么一直... 您好:牵手跑胡子这款游戏可以开挂,确实是有挂的,需要了解加客服微信【8435338】很多玩家在这款游...
senator香烟多少一盒(s... 今天给各位分享senator香烟多少一盒的知识,其中也会对sevebstars香烟进行解释,如果能碰...
终于懂了新荣耀斗牛真的有挂吗... 您好:新荣耀斗牛这款游戏可以开挂,确实是有挂的,需要了解加客服微信8435338】很多玩家在这款游戏...
盘点十款明星麻将到底有没有挂... 您好:明星麻将这款游戏可以开挂,确实是有挂的,需要了解加客服微信【5848499】很多玩家在这款游戏...
总结文章“新道游棋牌有透视挂吗... 您好:新道游棋牌这款游戏可以开挂,确实是有挂的,需要了解加客服微信【7682267】很多玩家在这款游...
终于懂了手机麻将到底有没有挂... 您好:手机麻将这款游戏可以开挂,确实是有挂的,需要了解加客服微信【8435338】很多玩家在这款游戏...