- 一. Scrapy的简介与安装
- (1) Mac或Linux的安装
- (2) Windows安装
- (3) Scrapy框架简介
- (4) 大致流程
- 二. 创建项目
- 三. 运行项目
- (1)命令行运行
- (2)创建py文件运行
- 四. 设置settings.py
- 五. Scrapy解析数据
- 六. 存储数据
- (1)基于终端指令进行持久化存储
- (2)基于管道进行持久化存储(重点)
- 七. 手动发送请求
- 八. 请求传参
- 九. 中间件Middlewares
- 下载器中间件
- 十. CrawlSpider的使用
- (1) 创建工程
- (2) 链接提取器与规则解析器
- 十一. 分布式爬虫
- (1) 简介
- (2) 实现流程
- 参考
直接pip install scrapy
(2) Windows安装-
pip install wheel
-
下载Twisted,下载地址:https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
选择适配自己电脑和Python版本的twisted即可:
-
安装Twisted,在存放刚才下载文件的文件夹下打开cmd,使用pip install 即可,如:
pip install Twisted-20.3.0-cp39-cp39-win_amd64.whl -
pip install pywin32
-
pip install scrapy
- Scrapy是一个适用爬取网站数据、提取结构性数据的应用程序框架(五大核心组件)
- 中间引擎(Scrapy)的作用:引擎接收spider产生的request对象(给调度器,也向调度器索取分配);引擎接收downloader传来的response对象(将response对象传给spider)。
- 爬虫(spiders)的作用:每个函数产生独立的新的request对象(给引擎放到调度器里面,给引擎从调度器里面取给下载器);也接收独立的response对象(下载器会把response对象给引擎,引擎会把response对象给spider);spider收到response对象后,要么生成字典给引擎发给pipeline处理,要么生成新的request对象给引擎给调度器等待调度。
- 下载器(Downloader)的作用:接收request对象,得到对应的response对象,发给引擎,引擎再发给spider。
- 项目管道(pipeline)的作用:接收item字典(引擎从spider里面拿到的)进行存储处理,主要是持久化实体、验证实体的有效性、清除不需要的信息。
- 调度器(Scheduler)的作用:用来接受引擎发过来的请求,压入队列中,并在引擎再次请求的时候返回,可以想象成一个URL的优先队列,由它来决定下一个要抓取的网页是什么,同时去除重复的网址。
- 启动爬虫项目时,引擎根据爬取的目标站点找到处理该站点的Spider,Spider会根据最初的需要爬取的页面生成一个或多个Request对象,发给引擎。
- 引擎从Spider获取Request对象,发给调度器里面等待调度。
- 引擎从调度器里面抽取合适的Request对象,然后转发给下载器,转发过程会经历一些中间件(从左到右)。
- 引擎从下载器里面拿到Response对象,然后转发给Spider,转发过程会经历一些中间件(从右到左)。
- Spider拿到Response对象处理,生成item字典或者新的Request对象给引擎。
- 引擎将Spider发来的一个或者多个item字典转发给pipelines进行处理,将一个或者多个Request对象转发给调度器等待下一次调度。
- 重复步骤②~⑥,直到调度器中没有更多的Request对象,这时候引擎会关闭Spider,爬取过程结束。
创建一个scrapy项目: 主要经历以下三个步骤,即可创建一个scrapy项目
scrapy startproject [项目名称] cd [项目名称路径] scrapy genspider [爬虫文件名] [爬虫URL域名]
此时项目即创建完成。
三. 运行项目 (1)命令行运行cd [项目文件夹] scrapy list #查看文件夹下的爬虫项目 scrapy crawl [爬虫文件名] #可选参数--nolog不输出日志(2)创建py文件运行
在项目名称文件夹下新建一个start.py文件,然后写命令,然后运行start.py文件即可
from scrapy import cmdline cmdline.execute(['scrapy', 'crawl', '爬虫文件名'])四. 设置settings.py
# Obey robots.txt rules
ROBOTSTXT_OBEY = False # robots协议遵守
CONCURRENT_REQUESTS = 10 # 并发量
RETRY_HTTP_CODES = [403, 500, 502, 503, 504] # 需要重试的状态码,如果不进行这样的设置,该请求失败了则会被丢弃
HTTPERROR_ALLOWED_CODES = [202, 400, 412] # 允许的错误状态码,进行进一步处理
LOG_LEVEL = 'ERROR' #运行时终端只显示等级为错误的日志
DOWNLOAD_TIMEOUT = 10 # 超时时间
RETRY_TIMES = 5 # 重试次数
ITEM_PIPELINES = {
'[项目名称].pipelines.[项目名称]Pipeline': 300,
} #开启管道,300指优先级,数字越小优先级越高
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36'
} #设置请求头
COOKIES_ENABLED = False #True表示开启cookie传递功能,即每次请求带上上一次的cookie,做状态保持
五. Scrapy解析数据
- scrapy提供了两个方法,response.xpath()和response.css()
- getall()、extract()匹配所有元素,extract_first()匹配单个元素
- get()传递一个默认值参数,可以在xpath匹配不到结果的时候,返回这个参数代替;
- 正则匹配:re()匹配所有结果,re_first()匹配第一个
response.xpath()使用例子
response.xpath("//a/text()").getall()
response.xpath("//a/text()").get()
response.xpath("//a/text()").get("Default None")
response.xpath("//a/text()").re('Name:s(.*)')
response.xpath("//a/text()").re_first('Name:s(.*)')
response.css()使用
response.css("a[href='image.html']::text").extract()
response.css("a[href='image.html']::text").get()
response.css("a[href='image.html'] img::attr(src)").get("Default None")
六. 存储数据
(1)基于终端指令进行持久化存储
基于终端指令进行持久化存储,只有json、jsonlines、jl、csv、xml、marshall、pickle等几种形式。
scrapy crawl [爬虫文件名] -o ./test.csv'
爬虫文件例:
class YkjzSpider(scrapy.Spider):
name = 'xxxx'
start_urls = ['http://www.xxxx.com']
def parse(self, response):
li_list = response.xpath('//div[@class = "listbox"]/ul/li')
all_data = []
for li in li_list:
href = 'http://www.xxxx.com' + li.xpath('./a[2]/@href').extract_first()
title = ''.join(li.xpath('./a//text()').getall())
one_data = {
'url':href,
'title':title
}
print(one_data)
all_data.append(one_data)
return all_data #此处需要返回数据
优点:简洁、高效
缺点:局限性比较强,数据只可以存储到指定后缀的文本文件中
一般流程:
- 数据解析
- 在item类中定义相关的属性——xxx = scrapy.Field()
- 将解析的数据封装存储到item类型的对象——如在爬虫文件的parse中写,如item['href'] = href
- 将item类型的对象提交给管道进行持久化存储——Pipeline中
- 在管道类的process_item中要将其接收到的item对象中存储的数据进行持久化存储操作
- 在配置文件中开启管道——ITEM_PIPELINES
例:
- 先写好parse方法:
def parse(self, response):
li_list = response.xpath('//div[@class = "listbox"]/ul/li')
all_data = []
for li in li_list:
href = li.xpath('./a[2]/@href').extract_first()
title = ''.join(li.xpath('./a//text()').getall())
item = [项目名称]Item() #from [项目名称].items import [项目名称]Item
item['href'] = href
item['title'] = title
yield item # 将item提交给管道
- 定义item:
class [项目名称]Item(scrapy.Item):
href = scrapy.Field()
title = scrapy.Field()
- 将解析的数据封装存储到item类型的对象(在第一步中已经写好代码,详见第一步)
- 将item类型的对象提交给管道进行持久化存储
class [项目名称]Pipeline:
fp = None
#重写父类的一个方法,该方法只在开始爬虫的时候被调用一次
def open_spider(self,spider):
print('开始爬虫!')
self.fp = open('./test.txt','w',encoding = 'utf-8')
#专门用来处理item类型对象,该方法可以接收爬虫文件提交过来的item对象,该方法每接收到一个item就会被调用一次
def process_item(self, item, spider):
href = item['href']
title = item['title']
self.fp.write(title + ":" + href + 'n')
return item #传递给下一个即将被执行的管道类
# 重写父类的一个方法,当爬虫关闭时会被调用
def close_spider(self,spider):
print('结束爬虫!')
self.fp.close()
#还可以再定义其他的管道类,要再settings.py中开启
class MysqlPipeline:
conn = None
cursor = None
def open_spider(self,spider):
self.conn = pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='123456',db='test',charset='utf8')
def process_item(self,item,spider):
self.cursor = self.conn.cursor()
try:
self.cursor.execute('insert into test values("%s","%s")'%(item["title"],item["href"]))
self.conn.commit() #没有异常则提交数据
except Exception as e:
print(e)
self.conn.rollback() #有异常则回滚
return item #传递给下一个即将被执行的管道类
def close_spider(self,spider):
self.cursor.close()
self.conn.close()
优点:通用性强
缺点:较为繁琐
不推荐将所有的url添加到start_urls列表中来进行发送请求,建议是手动发送请求:
scrapy.Request(url, callback=None, method='GET', headers=None, body=None,cookies=None, meta=None, encoding='utf-8', priority=0, dont_filter=False, errback=None, flags=None, cb_kwargs=None)
参数解释:
- callback:在下载其下载完响应的数据后执行的回调函数
- headers:请求头,对于一些固定的设置,放在settings.py中指定就可以了,对于那些非固定的,可以在发送请求时指定,其中不包括cookies
- meta:共享数据,用于在不同的请求之间传递数据的比如item,为字典形式。
- dot_filter:scrapy默认(False)会对Request类对象去重,不想去重得改成True,表示不由调度器过滤,在执行多次重复的请求的时候用的比较多
- prority:Request对象执行优先级,默认是0,数值越大,就越被优先调度并执行
- errback:在发生错误的时候执行的函数
- cookies: 携带的cookie,可以是字典形式
- request.meta['proxy']:可以用来设置请求的代理
- request.meta['max_retry_times']:可以用来设置请求的最大重试次数
使用场景:爬取解析的数据不在同一张页面中,需要进行深度爬取。
# 将parse中的item传递到parse_detail中,便是请求传参
class DemoSpider(scrapy.Spider):
name = 'demo'
start_urls = ['www.demo.com']
def parse_detail(self,response):
item = response.meta['item']
yyy = response.xpath('......')
item['yyy'] = yyy
yield item
def parse(self, response, **kwargs):
li_list = response.xpath('.......')
for li in list:
item = DemoItem()
xxx = li.xpath('......')
detail_url = 'www.demo.com' + li.xpath('......')
item['xxx'] = xxx
yield scrapy.Request(detail_url,callback=self.parse_detail,meta = {'item':item})
九. 中间件Middlewares
下载器中间件
下载器中间件在引擎和下载器之间。在这个中间件中我们可以设置代理、更换请求头等来达到反反爬虫的目的;要写下载器中间件,可以在下载器中实现两个方法:
- process_request(self, request, spider),这个方法是在请求发送之前执行
- process_response(self,request,response,spider),这个方法是数据下载到引擎之前执行
数字越小越靠近引擎(process_request先调用),数字越大越靠近下载器(process_response先调用)。
主要是三个方法:
class [项目名称]DownloaderMiddleware:
# 这个方法是用来拦截请求的
def process_request(self, request, spider):
request.headers['User-Agent'] = 'xxxx'
request.meta['proxy'] = 'xxxx'
return None
# 这个方法是用来拦截所有响应的
def process_response(self, request, response, spider):
return response
# 拦截发生异常的请求
def process_exception(self, request, exception, spider):
#对于发生异常的请求,此处可以对传入的request进行修正,如设置request.meta['proxy'],修正完毕后return request
return request
十. CrawlSpider的使用
- 适用于数据量大的网站
- Spider的一个子类
scrapy startproject [项目名称] cd [项目名称路径] scrapy genspider -t crawl [爬虫文件名] [爬虫URL域名](2) 链接提取器与规则解析器
链接提取器:根据指定规则(allow = “正则”)进行指定链接的提取
LinkExtractor(allow=r'正则表达式')
规则解析器:将链接提取器提取到的链接进行指定规则(callback)的解析
- follow=True可以将链接提取器继续作用到链接提取器提取到的链接所对应的页面中
rules = (
Rule(LinkExtractor(allow=r'正则表达式'), callback='parse_item', follow=True),
)
需注意在rules中的callback解析方法间不能实现请求传参,如果想要将两个解析方法解析的数据存储到同一个item中,可以以此存储到两个item。
十一. 分布式爬虫 (1) 简介- 概念:搭建一个分布式的机群,让其对一组资源进行分布联合爬取。
- 作用:提升爬取数据的效率
- 如何实现分布式:
- pip install scrapy-redis
- 原生的scrapy无法实现分布式爬虫,必须让scrapy结合scrapy-redis组件一起实现分布式爬虫(可以给原生的scrapy框架提供可以被共享的管道和调度器)
- 为什么原生的scrapy无法实现分布式:
- 调度器不可以被分布式机群共享
- 管道不可以被分布式机群共享
- 创建一个工程
- 创建一个基于CrawlSpider的爬虫文件
- 导包:from scrapy_redis.spiders import RedisCrawlSpider
- 添加一个新属性:redis_key,表示可以被共享的调度器队列的名称
- 编写数据解析相关的操作
- 将当前爬虫类的父类修改成RedisCrawlSpider
- 修改配置文件settings.py,指定使用可以被共享的管道:
ITEM_PIPELINES = {'scrapy_redis.pipelines.RedisPipeline':400} - 指定调度器:
#增加了一个去重容器类的配置,作用是使用Redis的set集合来存储请求的指纹数据,从而实现去重的持久化 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" #使用scrapy-redis组件自己的调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" #配置调度器是否要持久化,也就是当爬虫结束了,是否要清空redis中请求队列和去重指纹的set,True表示实现增量式爬虫,爬取过的数据则不再进行爬取 SCHEDULER_PERSIST = True
- 配置Redis的配置文件:
①. Linux或Mac:redis.conf
②. Windows:redis.windows.conf
③. 将bind 127.0.0.1删除
④. 关闭保护模式:protected-mode yes改为no(保护模式下其他主机访问redis服务端只能读数据而无法写入数据)
⑤. 结合配置文件开启redis服务(redis-server配置文件)
⑥. 启动客户端(redis-cli) - 执行工程:cd到spiders下,scrapy runspider xxx.py
- 向调度器队列中放入一个起始url,调度器的队列在redis客户端中:lpush [定义的redis_key的名称] [起始url]
- 如果不存本地,则需在settings.py中指定redis服务:
REDIS_HOST = 'ip地址,本机是127.0.0.1'
REDIS_PORT = 6379
REDIS_ENCODING = 'utf-8'
REDIS_PARAMS = {'password':'123456'}
参考
Python超强爬虫8天速成
Python_Scrapy项目开发代码学习
Scrapy爬虫入门教程十三 Settings(设置)
Python爬虫:scrapy框架请求参数meta、headers、cookies一探究竟
Scrapy详解之Request
Scrapy详解之Response
Scrapy-redis和Scrapyd用法详解
Scrapy详解之中间件(Middleware)



