所谓的框架简单通用解释就是就是一个具有很强通用性并且集成了很多功能的项目模板,该模板可被应用在不同的项目需求中。也可被视为是一个项目的半成品。
对于刚接触编程或者初级程序员来讲,对于一个新的框架,只需要掌握该框架的作用及其各个功能的使用和应用即可,对于框架的底层实现和原理,在逐步进阶的过程中在慢慢深入即可。
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,非常出名,非常强悍。其内部已经被集成了各种功能(高性能异步下载,队列,分布式,解析,持久化等)。对于框架的学习,重点是要学习其框架的特性、各个功能的用法即可。
pip install 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) # 获取字节类型的相应内容
需求:爬取糗事百科的段子数据
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)
- 要求:只可以将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配置文件中开启管道。
需求:将糗事百科首页中的段子和作者数据爬取下来,然后进行持久化存储。
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
import scrapy
class QiubaiproItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
author = scrapy.Field()
content = scrapy.Field()
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,
}
答:管道文件中的代码为
#该类为管道类,该类中的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方法,实现两种不同形式的持久化操作。
大部分的网站展示的数据都进行了分页操作,那么将所有页码对应的页面数据进行爬取就是爬虫中的全站数据爬取。
方式一:将每一个页码对应的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)
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)
字符串:只需要通过xpath进行解析并提交管道进行持久化存储
图片:xpath解析出图片src属性值,单独对图片地址进行请求获取二进制数据
只需将img的src属性值进行解析,提交给管道,管道就会对图片的src进行请求发送获取图片的二进制类型数据,并进行持久化存储。
- 数据解析(图片的地址)
- 将存储图片地址的item提交到定制的管道类中
- 在管道文件中自定义一个基于ImagesPipeline的一个管道类
- get_media_requests()
- file_path()
- item_completed()
- 在配置文件中:
- 指定文件存储的目录:IMAGES_STORE = './imgs'
- 开启管道:自定制的管道类
- ITEM_PIPELINES = {
'imgPro.pipelines.ImgPipeLine': 300,
}
- 位置:引擎和下载器之间
- 作用:批量拦截到整个工程所有的请求和相应
- 拦截请求
- 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)