目录
Scrapy架构介绍
Scrapy下载
Scrapy基本使用
Scrapy目录结构
Scrapy解析数据
settings相关配置
基础配置
增加爬虫的爬取效率
去重规则(布隆过滤器)
持久化方案(数据保存)
request和response传递参数
网页解析下一页继续爬取
爬虫和下载中间件
scrapy-redis实现分布式爬虫
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)。
安装:
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
创建项目
通过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文件即可。
项目名├── 项目同名文件夹├ ├── spiders -- 文件夹,专门存放爬虫文件├ ├ ├── 爬虫1.py -- 其中一个爬虫,重点写代码的地方├ ├ └── 爬虫2.py -- 其中一个爬虫,重点写代码的地方├ ├── items.py -- 类比djagno的models,表模型--->类├ ├── middlewares.py -- 爬虫中间件和下载中间件都在里面├ ├── pipelines.py -- 管道,做持久化需要在这写代码├ └── settings.py -- 配置文件└── scrapy.cfg -- 上线配置,开发阶段不用
创建爬虫时会自动在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())
# 是否遵循爬虫协议
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方法。
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都是在下载中间件中添加
代理:
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
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/