目录
一:scrapy简介
scrapy初识
什么是框架?
所谓的框架简单通用解释就是就是一个具有很强通用性并且集成了很多功能的项目模板,该模板可被应用在不同的项目需求中。也可被视为是一个项目的半成品。
如何学习框架?
对于刚接触编程或者初级程序员来讲,对于一个新的框架,只需要掌握该框架的作用及其各个功能的使用和应用即可,对于框架的底层实现和原理,在逐步进阶的过程中在慢慢深入即可。
什么是scrapy?
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,非常出名,非常强悍。其内部已经被集成了各种功能(高性能异步下载,队列,分布式,解析,持久化等)。对于框架的学习,重点是要学习其框架的特性、各个功能的用法即可。
scrapy基本使用
环境安装
- linux和mac操作系统:
pip install scrapy
- Windows系统:
网上安装教程有很多。
图简单的话直接使用Pycharm傻瓜式安装
scrapy使用流程:
- 创建工程:
scrapy startproject ProName
- 进入工程目录:
cd ProName
- 创建爬虫文件:
scrapy genspider spiderName www.xxx.com
- 编写相关操作代码
- 执行工程:
scrapy crawl spiderName
# 执行工程,不打印日志
scrapy crawl spiderName --nolog
爬虫文件剖析
import scrapy
class FirstSpider(scrapy.Spider):
# 爬虫文件的名称:就是爬虫源文件的唯一标识
name = 'first'
# 允许爬取的域名(如果遇到非该域名的url则爬取不到数据)
# 用来限制start_url中那些域名可以进行请求的发送
allowed_domains = ['http://www.baidu.com/']
# 起始爬取的url列表:该列表中存放的url挥别scrapy自动警醒请求的发送
start_urls = ['http://www.baidu.com/']
# 用于数据解析:
# 访问起始URL并获取结果后的回调函数,
# 该函数的response参数就是向起始的url发送请求后,获取的响应对象.该函数返回值必须为可迭代对象或者NUll
def parse(self, response):
print(response.text) # 获取字符串类型的响应内容
print(response.body) # 获取字节类型的相应内容
scrapy基于xpath数据解析操作
需求:爬取糗事百科的段子数据
import scrapy
class QiubaiSpider(scrapy.Spider):
name = 'qiubai'
#allowed_domains = ['www.xxx.com']
start_urls = ['https://www.qiushibaike.com/text/']
def parse(self, response):
div_list = response.xpath('//*[@id="content"]/div/div[2]/div')
for div in div_list:
# xpath函数返回的为列表,列表中存放的数据为Selector类型的数据。
# 我们解析到的内容被封装在了Selector对象中,
# 需要调用extract()函数将解析的内容从Selecor中取出。
author = div.xpath('./div[1]/a[2]/h2/text()')[0].extract()
content = div.xpath('./a[1]/div[1]/span[1]//text()').extract()
print(author, content)
二:scrapy的数据持久化存储
方式一:基于终端指令的持久化存储
- 要求:只可以将parse方法的返回值存储在本地文本中。
- 注意:持久化存储的文本的文件类型只能是:'json'、‘josnlines’、‘xml’、‘csv’、‘jl’、‘marshal’
- 指令:scrapy crawl spiderName -o filepath
- 例:scrapy crawl qiubai qiubai.csv
- 好处:处理简洁高效
- 缺点:局限性较强(只能存储到指定后缀名的文本文件中)
import scrapy
class QiubaiSpider(scrapy.Spider):
name = 'qiubai'
#allowed_domains = ['www.xxx.com']
start_urls = ['https://www.qiushibaike.com/text/']
def parse(self, response):
div_list = response.xpath('//*[@id="content"]/div/div[2]/div')
# 存储所有的数据
all_data = []
for div in div_list:
# xpath函数返回的为列表,列表中存放的数据为Selector类型的数据。
# 我们解析到的内容被封装在了Selector对象中,
# 需要调用extract()函数将解析的内容从Selecor中取出。
author = div.xpath('./div[1]/a[2]/h2/text()')[0].extract()
content = div.xpath('./a[1]/div[1]/span[1]//text()').extract()
# 将数据封装成字典形式
dic = {
'author': author,
'content': content
}
all_data.append(dic)
# 返回数据
return all_data
方式二:基于管道的持久化存储操作
编码流程
- 数据解析
- 在item类中定义相关的属性。
- 将解析的数据封装存储到item类型的对象中。
- 将item类型的对象提交给管道进行持久化存储操作。
- 在管道文件中的process_item方法中接收爬虫文件提交过来的item对象,然后编写持久化存储的代码将item对象中存储的数据进行持久化存储。
- settings.py配置文件中开启管道。
案例实战
需求:将糗事百科首页中的段子和作者数据爬取下来,然后进行持久化存储。
- 爬虫文件:qiubai.py
import scrapy
from qiubaiPro.items import QiubaiproItem
class QiubaiSpider(scrapy.Spider):
name = 'qiubai'
# allowed_domains = ['www.xxx.com']
start_urls = ['https://www.qiushibaike.com/text/']
def parse(self, response):
div_list = response.xpath('//*[@id="content"]/div/div[2]/div')
for div in div_list:
# xpath函数返回的为列表,列表中存放的数据为Selector类型的数据。
# 我们解析到的内容被封装在了Selector对象中,
# 需要调用extract()函数将解析的内容从Selecor中取出。
author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()
author = author.strip('\n') # 过滤空行
content = div.xpath('./a[1]/div[1]/span[1]//text()').extract()
content = ''.join(content)
# 将解析到的数据封装至items对象中
item = QiubaiproItem()
item['author'] = author
item['content'] = content
# 提交item到管道文件(pipelines.py)
yield item
- items文件:itewms.py
import scrapy
class QiubaiproItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
author = scrapy.Field()
content = scrapy.Field()
- 管道文件:pipelines.py
from itemadapter import ItemAdapter
class QiubaiproPipeline:
# 构造方法
def __init__(self):
self.fp = None
# 重写父类的一个方法,只在开始爬虫时执行一次
def open_spider(self, spider):
print('爬虫开始!')
self.fp = open('./qiubai.txt', 'w', encoding='utf-8')
# 该方法专门用来处理item类型对象
# 该方法可以接受爬虫文件提交过来的item对象
# 因为该方法会被执行调用多次,所以文件的开启和关闭操作写在了另外两个只会各自执行一次的方法中。
def process_item(self, item, spider):
# 将爬虫程序提交的item进行持久化存储
author = item['author']
content = item['content']
self.fp.wirte(author + ':' + content + '\n')
#会传递给下一个即将被执行的管道类
return item
# 结束爬虫时,执行一次
def close_spider(self, spider):
self.fp.close()
print('爬虫结束')
- 配置文件
# 开启管道
ITEM_PIPELINES = {
# 300表示为优先级,值越小优先级越高
'qiubaiPro.pipelines.QiubaiproPipeline': 300,
}
面试题:如果最终需要将爬取到的数据值一份存储到磁盘文件,一份存储到数据库中,则应该如何操作scrapy?
答:管道文件中的代码为
#该类为管道类,该类中的process_item方法是用来实现持久化存储操作的。
class DoublekillPipeline(object):
def process_item(self, item, spider):
#持久化操作代码 (方式1:写入磁盘文件)
return item
#如果想实现另一种形式的持久化操作,则可以再定制一个管道类:
class DoublekillPipeline_db(object):
def process_item(self, item, spider):
#持久化操作代码 (方式1:写入数据库)
return item
在settings.py开启管道操作代码为:
#下列结构为字典,字典中的键值表示的是即将被启用执行的管道文件和其执行的优先级。
ITEM_PIPELINES = {
'doublekill.pipelines.DoublekillPipeline': 300,
'doublekill.pipelines.DoublekillPipeline_db': 200,
}
#上述代码中,字典中的两组键值分别表示会执行管道文件中对应的两个管道类中的process_item方法,实现两种不同形式的持久化操作。
三:scrapy基于Spider类的全站数据爬取
大部分的网站展示的数据都进行了分页操作,那么将所有页码对应的页面数据进行爬取就是爬虫中的全站数据爬取。
基于scrapy如何进行全站数据爬取呢?
方式一:将每一个页码对应的url存放到爬虫文件的起始url列表(start_urls)中。(不推荐)
方式二:使用Request方法手动发起请求。(推荐)
- 手动请求发送:scrapy.Request(url, callback)
案例实战:爬取校花网全站数据
import scrapy
class XiaohuaSpider(scrapy.Spider):
name = 'xiaohua'
# allowed_domains = ['www.xxx.com']
start_urls = ['http://www.xiaohuar.com/daxue/']
# 生成一个url模板
url = 'http://www.xiaohuar.com/daxue/index_%d.html'
pageNum = 2
def parse(self, response):
div_list = response.xpath('//*[@id="wrap"]/div/div/div')
for div in div_list:
img_name = div.xpath('./div/div[1]/a/text()').extract_first()
print(img_name)
# 爬取所有页码数据
if self.pageNum <= 3: # 一共爬取10页
self.pageNum += 1
new_url = format(self.url % self.pageNum)
# 递归爬取数据:callback参数的值为回调函数(将url请求后,得到的相应数据继续进行parse解析),递归调用parse函数
yield scrapy.Request(url=new_url, callback=self.parse)
四:scrapy五大核心组件简介
- 引擎(Scrapy)
用来处理整个系统的数据流处理, 触发事务(框架核心) - 调度器(Scheduler)
用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址 - 下载器(Downloader)
用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的) - 爬虫(Spiders)
爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面 - 项目管道(Pipeline)
负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。
五:请求传参
- 使用场景:如果爬取解析的数据不再同一张页面中(深度爬取)
import scrapy
from bossPro.items import BossproItem
class BossSpider(scrapy.Spider):
name = 'boss'
# allowed_domains = ['www.xxx.com']
start_urls = ['https://www.zhipin.com/job_detail/?query=python&city=101010100&industry=&position=']
url = 'https://www.zhipin.com/c101010100/?query=python&page=%d'
page_num = 2
#回调函数接受item
def parse_detail(self,response):
item = response.meta['item']
job_desc = response.xpath('//*[@id="main"]/div[3]/div/div[2]/div[2]/div[1]/div//text()').extract()
job_desc = ''.join(job_desc)
# print(job_desc)
item['job_desc'] = job_desc
yield item
#解析首页中的岗位名称
def parse(self, response):
li_list = response.xpath('//*[@id="main"]/div/div[3]/ul/li')
for li in li_list:
item = BossproItem()
job_name = li.xpath('.//div[@class="info-primary"]/h3/a/div[1]/text()').extract_first()
item['job_name'] = job_name
# print(job_name)
detail_url = 'https://www.zhipin.com'+li.xpath('.//div[@class="info-primary"]/h3/a/@href').extract_first()
#对详情页发请求获取详情页的页面源码数据
#手动请求的发送
#请求传参:meta={},可以将meta字典传递给请求对应的回调函数
yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item})
#分页操作
if self.page_num <= 3:
new_url = format(self.url%self.page_num)
self.page_num += 1
yield scrapy.Request(new_url,callback=self.parse)
六:scrapy图片数据爬取之ImagesPipeline
基于scrapy爬取字符串类型数据和爬取图片类型数据的区别?
字符串:只需要通过xpath进行解析并提交管道进行持久化存储
图片:xpath解析出图片src属性值,单独对图片地址进行请求获取二进制数据
ImagesPipeline
只需将img的src属性值进行解析,提交给管道,管道就会对图片的src进行请求发送获取图片的二进制类型数据,并进行持久化存储。
使用流程
- 数据解析(图片的地址)
- 将存储图片地址的item提交到定制的管道类中
- 在管道文件中自定义一个基于ImagesPipeline的一个管道类
- get_media_requests()
- file_path()
- item_completed()
- 在配置文件中:
- 指定文件存储的目录:IMAGES_STORE = './imgs'
- 开启管道:自定制的管道类
- ITEM_PIPELINES = {
'imgPro.pipelines.ImgPipeLine': 300,
}
七:中间件
下载中间件(Downloader Middlewares)
- 位置:引擎和下载器之间
- 作用:批量拦截到整个工程所有的请求和相应
- 拦截请求
- UA伪装:process_request()
- 代理IP:process_exception()
- 拦截响应
- 篡改相应数据、相应对象
-
拦截请求
middlewares类:
import random
class MiddleproDownloaderMiddleware:
# Not all methods need to be defined. If a method is not defined,
# scrapy acts as if the downloader middleware does not modify the
# passed objects.
user_agent_list = [
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
"(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
"Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 "
"(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 "
"(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 "
"(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 "
"(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 "
"(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 "
"(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 "
"(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 "
"(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
]
# 可被选用的代理IP
PROXY_http = [
'153.180.102.104:80',
'195.208.131.189:56055',
]
PROXY_https = [
'120.83.49.90:9000',
'95.189.112.214:35508',
]
#拦截请求
def process_request(self, request, spider):
# UA伪装
request.headers['User-Agent'] = random.choice(self.user_agent_list)
return None
#拦截所有响应
def process_response(self, request, response, spider):
# Called with the response returned from the downloader.
return response
#拦截发生异常的请求
def process_exception(self, request, exception, spider):
#代理IP
# 对拦截到请求的url进行判断(协议头到底是http还是https)
# request.url返回值:http://www.xxx.com
h = request.url.split(':')[0] # 请求的协议头
if h == 'https':
ip = random.choice(self.PROXY_https)
request.meta['proxy'] = 'https://' + ip
else:
ip = random.choice(self.PROXY_http)
request.meta['proxy'] = 'http://' + ip
# 将修正后的请求对象进行重新发送
return request
def spider_opened(self, spider):
spider.logger.info('Spider opened: %s' % spider.name)