文章目录
scrapy的概念
Scrapy是一个Python编写的开源网络爬虫框架。它是一个被设计用于爬取网络数据、提取结构性数据的框架。同时能够以少量的代码帮助我们快速的抓取数据
scrapy的流程
- 爬虫中起始的url构造成request对象–>爬虫中间件–>引擎–>调度器
- 调度器把request–>引擎–>下载中间件—>下载器
- 下载器发送请求,获取response响应---->下载中间件---->引擎—>爬虫中间件—>爬虫
- 爬虫提取url地址,组装成request对象---->爬虫中间件—>引擎—>调度器,重复步骤2
- 爬虫提取数据—>引擎—>管道处理和保存数据
scrapy的三个内置对象
- request请求对象:由url method post_data headers等构成
- response响应对象:由url body status headers等构成
- item数据对象:本质是个字典
scrapy中每个模块的具体作用
- 引擎(engine): 负责数据和信号在不同模块间的传递
- 调度器(scheduler): 实现一个队列,存放引擎发过来的request请求对象
- 下载器(downloader): 发送引擎发过来的request请求,获取响应,并将响应交给引擎
- 爬虫(spider): 处理引擎发过来的response,提取数据,提取url,并交给引擎
- 管道(pipeline): 处理引擎传递过来的数据,比如存储
- 下载中间件(downloader middleware): 可以自定义的下载扩展,比如设置代理ip。
默认方法的作用:
process_request(self, request, spider):
- 当每个request通过下载中间件时,该方法被调用。
- 返回None值:没有return也是返回None,该request对象传递给下载器,或通过引擎传递给其他权重低的proces当下载器完成http请求,传递响应给引擎的时候调用
- 返回Resposne:通过引擎交给爬虫处理或交给权重更低的其他下载中间件的process_response方法
- 返回Request对象:通过引擎交给调取器继续请求,此时将不通过其他权重低的process_request方法s_request方法
- 返回Response对象:不再请求,把response返回给引擎
- 返回Request对象:把request对象通过引擎交给调度器,此时将不通过其他权重低的process_request方法
使用IP代理:
class ProxyMiddleware(object):
def process_request(self,request,spider):
# proxies可以在settings.py中,也可以来源于代理ip的webapi
proxy = random.choice(proxies)
# 免费的会失效,报 111 connection refused 信息!重找一个代理ip再试
proxy = 'https://1.71.188.37:3128'
request.meta['proxy'] = proxy
return None # 可以不写return
process_response(self, request, response, spider):
在配置文件中启用中间件,权重值越小越优先启用。
- 爬虫中间件(spider middleware): 可以自定义request请求和进行response过滤,与下载中间件作用重复
安装scrapy
yum install scrapy -y
或者pip/pip3 install scrapy
scrapy项目开发流程
创建一个项目
scrapy startproject Yourprojectname
生成一个爬虫
scrapy genspider yourspidername yourdomain
yourspidername: 作为爬虫运行时的参数
yourdomain: 为对于爬虫设置的爬取范围,设置之后用于过滤要爬取的url,如果爬取的url与允许的域不通则被过滤掉。
完善爬虫
在/Yourprojectname/yourspidername /spiders/yourspidername.py中修改内容如下:
import scrapy
class ItcastSpider(scrapy.Spider): # 继承scrapy.spider
# 爬虫名字
name = 'itcast'
# 允许爬取的范围
allowed_domains = ['itcast.cn']
# 开始爬取的url地址
start_urls = ['http://www.itcast.cn/channel/teacher.shtml']
# 数据提取的方法,接收下载中间件传过来的response
def parse(self, response):
# scrapy的response对象可以直接进行xpath
names = response.xpath('//div[@class="tea_con"]//li/div/h3/text()')
print(names)
# 获取具体数据文本的方式如下
# 分组
li_list = response.xpath('//div[@class="tea_con"]//li')
for li in li_list:
# 创建一个数据字典
item = {}
# 利用scrapy封装好的xpath选择器定位元素,并通过extract()或extract_first()来获取结果
item['name'] = li.xpath('.//h3/text()').extract_first() # 老师的名字
item['level'] = li.xpath('.//h4/text()').extract_first() # 老师的级别
item['text'] = li.xpath('.//p/text()').extract_first() # 老师的介绍
print(item)
注意:
- scrapy.Spider爬虫类中必须有名为parse的解析
- 如果网站结构层次比较复杂,也可以自定义其他解析函数
- 在解析函数中提取的url地址如果要发送请求,则必须属于allowed_domains范围内,但是start_urls中的url地址不受这个限制,我们会在后续的课程中学习如何在解析函数中构造发送请求
- 启动爬虫的时候注意启动的位置,是在项目路径下启动
- parse()函数中使用yield返回数据,注意:解析函数中的yield能够传递的对象只能是:BaseItem, Request, dict, None
一些API的作用
- response.xpath方法的返回结果是一个类似list的类型,其中包含的是selector对象,操作和列表一样,但是有一些额外的方法
- 额外方法extract():返回一个包含有字符串的列表
- 额外方法extract_first():返回列表中的第一个字符串,列表为空没有返回None
response响应对象的常用属性
- response.url:当前响应的url地址
- response.request.url:当前响应对应的请求的url地址
- response.headers:响应头
- response.requests.headers:当前响应的请求头
- response.body:响应体,也就是html代码,byte类型
- response.status:响应状态码
pipeline保存数据
管道能够实现数据的清洗和保存,能够定义多个管道实现不同的功能,使用管道pipeline来处理(保存数据),在pipelines.py文件中定义一个管道类,重写管道类的process_item方法,process_item方法处理完item之后必须返回给引擎。
import json
class JDPipeline():
# 在爬虫开启的时候仅执行一次(想起了flask中的请求钩子)
def open_spider(self, spider):
pass
# 在爬虫关闭的时候仅执行一次
def close_spider(self, spider):
pass
# 爬虫文件中提取数据的方法每yield一次item,就会运行一次
# 该方法为固定名称函数,必须要用的函数
def process_item(self, item, spider):
# process_item的方法必须return item,否则后一个pipeline取到的数据为None值
return item
在settings.py配置启用管道:(可以开启多个管道)
ITEM_PIPELINES = {
'myspider.pipelines.ItcastPipeline': 400 #配置项中值为管道的使用顺序,设置的数值约小越优先执行,该值一般设置为1000以内。
}
运行scrapy
在项目目录下执行scrapy crawl yourspidername
数据建模与请求
建模的好处有如下几点:
- 定义item即提前规划好哪些字段需要抓,防止手误,因为定义好之后,在运行过程中,系统会自动检查
- 配合注释一起可以清晰的知道要抓取哪些字段,没有定义的字段不能抓取,在目标字段少的时候可以使用字典代替
- 使用scrapy的一些特定组件需要Item做支持,如scrapy的ImagesPipeline管道类,百度搜索了解更多
在items.py文件中定义要提取的字段:
class MyspiderItem(scrapy.Item):
name = scrapy.Field()
title = scrapy.Field()
desc = scrapy.Field()
使用模板类:
from myspider.items import MyspiderItem # 导入Item,注意路径
def parse(self, response)
item = MyspiderItem() # 实例化后可直接使用
item['name'] = node.xpath('./h3/text()').extract_first()
item['title'] = node.xpath('./h4/text()').extract_first()
item['desc'] = node.xpath('./p/text()').extract_first()
print(item)
实现翻页
requests模块实现翻页请求:
- 找到下一页的URL地址
- 调用requests.get(url)
scrapy实现翻页的思路:
- 找到下一页的url地址
- 构造url地址的请求对象,传递给引擎
实现方法:
3. 确定url地址
4. 构造请求,scrapy.Request(url,callback)
- callback:指定解析函数名称,表示该请求返回的响应使用哪一个函数进行解析
- 把请求交给引擎:yield scrapy.Request(url,callback)
scrapy.Request的更多参数解释:
scrapy.Request(url[,callback,method="GET",headers,body,cookies,meta,dont_filter=False])
- 中括号里的参数为可选参数
- callback:表示当前的url的响应交给哪个函数去处理
- meta:实现数据在不同的解析函数中传递,meta默认带有部分数据,比如下载延迟,请求深度等
- dont_filter:默认为False,会过滤请求的url地址,即请求过的url地址不会继续被请求,对需要重复请求的url地址可以把它设置为Ture,比如贴吧的翻页请求,页面的数据总是在变化;start_urls中的地址会被反复请求,否则程序不会启动
- method:指定POST或GET请求
- headers:接收一个字典,其中不包括cookies
- cookies:接收一个字典,专门放置cookies
- body:接收json字符串,为POST的数据,发送payload_post请求时使用(在下一章节中会介绍post请求)
meta的作用:meta可以实现数据在不同的解析函数中的传递(把当前页面抓取到的数据传递给下一个parse函数进行进一步处理)
注意:
- meta参数是一个字典
- meta字典中有一个固定的键
proxy
,表示代理ip
分布式爬虫scrapy-redis
三个类:
RFPDupeFilter:实现了对request对象的加密
RedisPipeline:进行数据的保存,存入了redis中
Scheduler:实现了决定什么时候把request对象加入带抓取的队列,同时把请求过的request对象过滤掉
ettings.py中关键的配置“
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
SCHEDULER_PERSIST = True
ITEM_PIPELINES = {
'example.pipelines.ExamplePipeline': 300,
'scrapy_redis.pipelines.RedisPipeline': 400,
}
REDIS_URL = "redis://127.0.0.1:6379"
scrapy_redis的流程
-
在scrapy_redis中,所有的待抓取的request对象和去重的request对象指纹都存在所有的服务器公用的redis中
-
所有的服务器中的scrapy进程公用同一个redis中的request对象的队列
-
所有的request对象存入redis前,都会通过该redis中的request指纹集合进行判断,之前是否已经存入过
-
在默认情况下所有的数据会保存在redis中
使用:
爬虫文件:
import scrapy
from scrapy_redis.spiders import RedisSpider
class RdsDoubSpider(RedisSpider):
"""分布式爬虫"""
name = 'rds_doub'
redis_key = 'rds_douban_url'
# allowed_domains = ['douban.com']
# start_urls = ['http://douban.com/']
def __init__(self, *args, **kwargs):
# Dynamically define the allowed domains list.
domain = kwargs.pop('domain', '')
self.allowed_domains = list(filter(None, domain.split(',')))
super(RdsDoubSpider, self).__init__(*args, **kwargs)
def parse(self, response):
node_list = response.xpath('//*[@id="content"]/div/div[1]/ol/li/div/div[2]')
for data in node_list:
item = {}
item['name'] = data.xpath('./div/a/span[1]/text()').extract_first()
item['actor'] = data.xpath('./div/p[1]/text()').extract_first().strip()
item['score'] = data.xpath('./div/div/span[@class="rating_num"]/text()').extract_first()
item['desc'] = data.xpath('./div/p/span/text()').extract_first()
yield item
# # 获取翻页url
part_url = response.xpath('//*[@id="content"]/div/div[1]/div[2]/span[3]/a/@href').extract_first()
if part_url is not None:
url = response.urljoin(part_url)
# 构建请求返回
# callbock:当翻页的页面结构不发生改变依旧可以解析可以写当前的parse方法
yield scrapy.Request(url=url,
callback=self.parse)
启动方式
scrapy crawl spider
往redis中添加起始URL
127.0.0.1:6379> lpush rds_douban_url https://xxx.com250
scrapy_splash
scrapy_splash是scrapy的一个组件,是一个Javascript渲染服务。它实现了HTTP API的轻量级浏览器,Splash是用Python和Lua语言实现的,基于Twisted和QT等模块构建。使用scrapy-splash最终拿到的response相当于是在浏览器全部渲染完成以后的网页源代码。
环境安装:
docker pull scrapinghub/splash
# 运行镜像成为一个容器
docker run -d -p 8050:8050 scrapinghub/splash
安装splash
pip install scrapy-splash
使用splash
import scrapy
from scrapy_splash import SplashRequest # 使用scrapy_splash包提供的request对象
class WithSplashSpider(scrapy.Spider):
name = 'with_splash'
allowed_domains = ['baidu.com']
start_urls = ['https://www.baidu.com/s?wd=13161933309']
def start_requests(self):
yield SplashRequest(self.start_urls[0],
callback=self.parse_splash,
args={'wait': 10}, # 最大超时时间,单位:秒
endpoint='render.html') # 使用splash服务的固定参数
def parse_splash(self, response):
with open('with_splash.html', 'w') as f:
f.write(response.body.decode())
settings.py
SPLASH_URL = 'http://127.0.0.1:8050'
DOWNLOADER_MIDDLEWARES = {
'scrapy_splash.SplashCookiesMiddleware': 723,
'scrapy_splash.SplashMiddleware': 725,
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
}
DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
scrapy_redis和scrapy_splash配合使用的配置
# 渲染服务的url
SPLASH_URL = 'http://127.0.0.1:8050'
# 下载器中间件
DOWNLOADER_MIDDLEWARES = {
'scrapy_splash.SplashCookiesMiddleware': 723,
'scrapy_splash.SplashMiddleware': 725,
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
}
# 使用Splash的Http缓存
HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
# 去重过滤器
# DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
# DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 指纹生成以及去重类
DUPEFILTER_CLASS = 'test_splash.spiders.splash_and_redis.SplashAwareDupeFilter' # 混合去重类的位置
SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 调度器类
SCHEDULER_PERSIST = True # 持久化请求队列和指纹集合, scrapy_redis和scrapy_splash混用使用splash的DupeFilter!
ITEM_PIPELINES = {'scrapy_redis.pipelines.RedisPipeline': 400} # 数据存入redis的管道
REDIS_URL = "redis://127.0.0.1:6379" # redis的url
部署scrapy项目
scrapyd介绍
scrapyd是一个用于部署和运行scrapy爬虫的程序,它允许你通过JSON API来部署爬虫项目和控制爬虫运行,scrapyd是一个守护进程,监听爬虫的运行和请求.
scrapyd的安装
scrapyd服务:(在服务器安装)
pip install scrapyd
scrapyd客户端:(在客户端安装)
pip install scrapyd-client
scrapyd的配置文件
# [root@scrapy gerapy]# vim /etc/scrapyd/scrapyd.conf
[scrapyd]
eggs_dir = eggs
logs_dir = logs
items_dir =
jobs_to_keep = 5
dbs_dir = dbs
max_proc = 0
max_proc_per_cpu = 4
finished_to_keep = 100
poll_interval = 5.0
bind_address = 127.0.0.1
http_port = 6800
debug = off
runner = scrapyd.runner
application = scrapyd.app.application
launcher = scrapyd.launcher.Launcher
webroot = scrapyd.website.Root
[services]
schedule.json = scrapyd.webservice.Schedule
cancel.json = scrapyd.webservice.Cancel
addversion.json = scrapyd.webservice.AddVersion
listprojects.json = scrapyd.webservice.ListProjects
listversions.json = scrapyd.webservice.ListVersions
listspiders.json = scrapyd.webservice.ListSpiders
delproject.json = scrapyd.webservice.DeleteProject
delversion.json = scrapyd.webservice.DeleteVersion
listjobs.json = scrapyd.webservice.ListJobs
daemonstatus.json = scrapyd.webservice.DaemonStatus
启动scrapyd服务
-
在scrapy项目路径下 启动scrapyd的命令:
sudo scrapyd
或scrapyd
-
启动之后就可以打开本地运行的scrapyd,浏览器中访问本地6800端口可以查看scrapyd的监控界面
部署爬虫项目到scrapyd
编辑scrapy.cfg文件:
注意 -p选项的名称可随意,如果部署到服务器(gerapy)报错,修改代码重新部署
,
管理scrapy项目
启动项目:
curl http://localhost:6800/schedule.json -d project=project_name -d spider=spider_name
关闭项目:
curl http://localhost:6800/cancel.json -d project=project_name -d job=jobid
使用requests模块控制scrapy项目
import requests
# 启动爬虫
url = 'http://localhost:6800/schedule.json'
data = {
'project': 项目名,
'spider': 爬虫名,
}
resp = requests.post(url, data=data)
# 停止爬虫
url = 'http://localhost:6800/cancel.json'
data = {
'project': 项目名,
'job': 启动爬虫时返回的jobid,
}
resp = requests.post(url, data=data)
小结:
- 在scrapy项目路径下执行
sudo scrapyd
或scrapyd
,启动scrapyd服务;或以后台进程方式启动nohup scrapyd > scrapyd.log 2>&1 &
- 部署scrapy爬虫项目
scrapyd-deploy -p myspider
- 启动爬虫项目中的一个爬虫`curl http://localhost:6800/schedule.json -d project=myspider -d spider=tencent
使用gerapy管理爬虫项目
Gerapy 是一款 分布式爬虫管理框架,支持 Python 3,基于 Scrapy、Scrapyd、Scrapyd-Client、Scrapy-Redis、Scrapyd-API、Scrapy-Splash、Jinjia2、Django、Vue.js 开发,Gerapy 可以帮助我们:
- 更方便地控制爬虫运行
- 更直观地查看爬虫状态
- 更实时地查看爬取结果
- 更简单地实现项目部署
- 更统一地实现主机管理
Gerapy的安装:
pip3 install gerapy
验证是否安装成功
初始化一个项目:
gerapy init
# 进入gerapy项目对数据库进行迁移
[root@scrapy gerapy]# gerapy migrate
创建一个超级用户
启动服务:
创建主机
项目部署:
只需把项目文件夹上传至gerapy下的project目录下即可。