数据提取

从人写的文章中提取数据

这是一篇文章

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
1 
证券代码: 000001 证券简称:平安银行 公告编号: 2023-014
优先股代码: 140002 优先股简称:平银优 01

平安银行股份 有限公司 拟续聘会计师事务所的公告

本公司及董事会全体成员保证信息披露的内容真实、准确、完整,没有虚假记载、误导
性陈述或重大遗漏。

特别提示:
1、拟续聘的会计师事务所名称:安永华明会计师事务所 (特殊普通合伙 )。
2、本公司董事会及董事会审计委员会、独立董事对拟续聘会计师事务所不
存在异议。
3、本次续聘会计师事务所事项 须提交本公司股东大会审议。

平安银行股份有限公司(以下简称 “本行”)于 2023年3月8日召开第 十
二届董事会第 六次会议,审议并通过了《 平安银行股份有限公司关于 聘请2023
年度会计师事务所的议案》 ,拟续聘安永华明会计师事务所 (特殊普通合伙 )(以
下简称“安永华明 ”)担任本行 2023年度中国会计准则审计师 。上述议案 须提
交本行股东大会审议 。现将有关情况公告如下:
一、拟续聘会计师事务所的基本情况
(一)机构信息
1、基本信息
安永华明会计师事务所(特殊普通合伙)于 1992年9月成立, 2012年8月
完成本土化转制, 从一家中外合作的有限责任制事务所转制为特殊普通合伙制事
务所。安永华明总部设在北京,注册地址为北京市东城区东长安街 1号东方广场
安永大楼 17层01-12室。截至 2022年末,拥有合伙人 229人,首席合伙人为毛
鞍宁先生。
截至 2022年末,安永华明拥有执业注册会计师 1,818人,其中拥有证券相
关业务服务经验的执业注册会计师超过 1,500人,注册会计师中签署过证券服务
业务审计报告的注册会计师超过 400人。 2
安永华明 2021年度业务总收入人民币 54.90亿元,其中审计业务收入人民
币52.82亿元,与证券业务 相关的收入为人民币 22.7亿元。 2021年度 A股上市
公司年报审计客户共计 116家,收费总额人民币 7.63亿元。安永华明 所提供服
务的上市公司主要 涉及制造业、金融业、批发和零售业、信息传输、软件和信息
技术服务业、房地产业等 行业,其中金融业 上市公司审计客户 20家。
2、投资者保护能力
安永华明具有良好的投资者保护能力, 已按照相关法律法规要求计提职业风
险基金和购买职业保险,保险涵盖北京总所和全部分所。已计提的职业风险基金
和已购买的职业保险累计赔偿限额之和超过人民币 2亿元。 安永华明近三年不存
在任何因与执业行为相关的民事诉讼而需承担民事责任的情况。
3、诚信记录
安永华明及从业人员近三年没有因执业行为受到任何刑事处罚、行政处罚,
以及证券交易所、行业协会等自律组织的自律监管措施和纪律处分。曾两次收到
证券监督管理机构出具警示函措施的决定,涉及从业人员十 三人。前述出具警示
函的决定属监督管理措施,并非行政处罚。根据相关法律法规的规定,该监督管
理措施不影响安永华明继续承接或执行证券服务业务和其他业务。
(二)项目信息
1、基本信息
项目合伙人及签字注册会计师: 昌华女士, 于2006年成为注册会计师, 2001
年开始从事上市公司审计, 2001年开始在安永华明执业, 2021年开始为本 行提
供审计服务;近三年签署 或复核 5家上市公司审计报告 。
项目质量控制复核合伙人: 田志勇先生,于 2013年成为注册会计师, 2007
年开始从事上市公司审计, 2015年开始在安永华明执业, 2022年开始为本行提
供审计服务;近三年签署或复核 4家上市公司审计报告。
签字注册会计师:王阳燕女士, 于2017年成为注册会计师, 2012年开始从
事上市公司审计, 2012年开始在安永华明执业, 2021年开始为本 行提供审计服
务;近三年签署 或复核 1家上市公司审计报告 。
3
2、诚信记录
项目合伙人、签字注册会计师、项目质量控制复核人近三年不存在因执业行
为受到刑事处罚,或受到证监会及其派出机构、行业主管部门的行政处罚、监督
管理措施,或受到证券交易场所、行业协会等自律组织的自律监管措施、纪律处
分的情况。
3、独立性
安永华明及上述项目合伙人、签字注册会计师、项目质量控制复核人等不存
在违反《中国注册会计师职业道德守则》对独立性要求的情形。
4、审计收费
平安银行股份有限公司 2023年度的审计费用拟定为 合计人民币 1,020万元,
较上年审计费用相比变化不大, 其中财务审计费用拟定为 人民币 870万元(包含
增值税及代垫费用) ,内部控制审计费用拟定为 人民币 150万元(包含增值税及
代垫费用) 。本次审计费用按照市场公允合理的定价原则,综合考虑业务规模、
审计工作量等因素后与安永华明协商确定。
二、拟续聘会计师事务所履行的程序
(一)审计委员会履职情况
本行董事会审计委员会委员查阅了安永华明关于专业胜任能力、 投资者保护
能力、诚信状况和独立性 等相关资料,认为其能够满足为本行提供审计服务的要
求。本行第十二届董事会审计委员会第 四次会议审议通过了《平安银行股份有限
公司关于 聘请2023年度会计师事务所的议案》 ,并同意将该议案提交董事会审
议。
(二)独立董事的事前认可情况和独立意见
本行独立董事对 续聘会计师事务所的相关事项进行了事前认可并发表独立
意见:安永华明在专业胜任能 力、投资者保护能力、 诚信状况和独立性 等方面符
合监管规定。 续聘安永华明担任 本行 2023年度中国会计准则审计师的相关决策
程序符合 《公司法》 、 《证券法》 、 《深圳证券交易所股票上市规则》 及 《公司章程》
等有关规定。 独立董事同意本议案,并同意将本议案提交 本行股东大会审议。
4
(三)董事会对议案审议和表决情况
本行于 2023年3月8日召开第 十二届董事会第 六次会议,审议通过了《 平
安银行股份有限公司关于 聘请2023年度会计师事务所的议案》 。 本议案 须提交本
行股东大会审议。
(四)生效日期
本次续聘会计师事务所事项 须提交本行股东大会审议, 并自 本行股东大会审
议通过之日起生效。
三、备查文件
1、董事会决议;
2、审计委员会履职证明文件;
3、独立董事事前认可和独立意见;
4、安永华明 基本情况说明 。
特此公告。


平安银行股份有限公司董事会
2023年3月9日

要在这篇公告里提取出有关注册会计师的信息。有人会说这不是很简单吗,但是这样的文档有10000篇

那自然要用程序提取了

浅浅吐槽一下,这用常规方法(正则表达式)几乎不可能做到,这是人能做到的事吗?但看看日历,今夕是何年?

如今有了各种各样的大模型,这样的任务还是能办到的

代码说明

准备工作,需要安装这些库

1
2
3
pandas
google.generativeai
PyPDF2

代码主要分为两个部分,分为爬取文件和提取数据两大模块,先后执行

爬取完文件之后,进行数据提取

由于文件内容复杂,这里让AI来填充预先设定好的格式,格式为json的二维数组,为之后转化为excel格式做好准备

提示词

提示词至关重要,不同的提示词虽然在单个请求中表现几乎相同,但在大量请求中就会表现出差异

举个例子”提取到的项目合伙人开始从事上市公司审计的年份“,可能大家觉得就是开始审计的那一年,但在大量请求中,AI会理解为审计了多少年。因此不妨改成”提取到的项目合伙人开始从事上市公司审计的那一年“,这样就会更精确。

以下是AI提示词中的json数组格式部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

{
"证券代码":"提取到的证券代码",
"证券简称":"提取到的证券简称",
"日期":"提取到的日期按照yyyy-mm-dd的格式",
"审计承做的会计师事务所":"提取到的审计承做的会计师事务所全称加合伙类型",
"项目合伙人":"提取到的项目合伙人姓名",
"项目合伙人成为中国注册会计师的年份":"提取到的项目合伙人成为中国注册会计师的年份",
"项目合伙人开始从事上市公司审计的年份":"提取到的项目合伙人开始从事上市公司审计的年份",
"项目合伙人在本所工作的年份":"提取到的项目合伙人在本所工作的年份",
"项目合伙人最近 3 年签署上市公司审计报告家数":"提取到的项目合伙人最近 3 年签署上市公司审计报告家数",
"项目合伙人近 3 年是否受到刑事处罚、行政处罚、行政监管措施和自律处分":"提取到的项目合伙人近 3 年是否受到刑事处罚、行政处罚、行政监管措施和自律处分(回答是否)",
"签字注册会计师":"提取到的不同于项目合伙人的签字注册会计师姓名",
"签字注册会计师成为中国注册会计师的年份":"提取到的签字注册会计师成为中国注册会计师的年份",
"签字注册会计师开始从事上市公司审计的年份":"提取到的签字注册会计师开始从事上市公司审计的年份",
"签字注册会计师在本所工作的年份":"提取到的签字注册会计师在本所工作的年份",
"签字注册会计师最近 3 年签署上市公司审计报告家数":"提取到的签字注册会计师最近 3 年签署上市公司审计报告家数",
"签字注册会计师近 3 年是否受到刑事处罚、行政处罚、行政监管措施和自律处分":"根据提取到的签字注册会计师近 3 年是否受到刑事处罚、行政处罚、行政监管措施和自律处分回答是或者否"
}


先运行Main,再执行error_retry

最后会得到一个data.txt

更改为json格式后使用vscode对json进行检查

确认json格式正确后即可转化为excel工作表。

AI接口

这里使用的是Gemini,需要特殊的网络条件。不同的模型会用不同的请求方式,参考官方的说明文档

在实际使用中,Gemini表现并不能让人满意,若要更换模型,重写api_1方法即可。

以下是代码部分

爬取文件的代码

这里爬取文件网上有现成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274

## Step1:自定义参数

# 1、定义股票代码列表 和 爬取结果输出地址(最好指定一个空的文件夹)———必选项

Codelist_path = r""
Output_path = r""


# 此路径是Step4中的stock_list文件输出地址,若不执行Step4则无需修改
Stocklist_path = r""

# 2、定义要爬取的报表类型(若同时爬取多项中间用;连接)———必选项

# 公司年度报告:category_ndbg_szsh
# 公司治理报告:category_gszl_szsh

Category_All = "category_ndbg_szsh;category_sjdbg_szsh;category_dshgg_szsh;category_rcjy_szsh;category_sf_szsh;category_pg_szsh;category_kzzq_szsh;category_bcgz_szsh;category_tbclts_szsh;category_tszlq_szsh;category_cqdq_szsh;category_qtrz_szsh;category_jj_szsh;category_zf_szsh;category_gszl_szsh;category_jshgg_szsh;category_yjygjxz_szsh;category_bndbg_szsh;category_yjdbg_szsh;category_qyfpxzcs_szsh;category_gddh_szsh;category_zj_szsh;category_gqjl_szsh;category_gszq_szsh;category_gqbd_szsh;category_fxts_szsh;"

# Category_A = Category_All

# Category_A = "category_ndbg_szsh;"

Category_A = "category_rcjy_szsh"


# **Category参数含义说明:**

# | category | 含义 | category | 含义 | category | 含义 | category | 含义 | category | 含义 |
# | --------------------- | ---------- | ---------------------- | -------- | ------------------ | -------- | ------------------ | -------- | -------------------- | -------------- |
# | category_ndbg_szsh | 年报 | category_qyfpxzcs_szsh | 权益分派 | category_gszl_szsh | 公司治理 | category_pg_szsh | 配股 | category_gqbd_szsh | 股权变动 |
# | category_bndbg_szsh | 半年报 | category_dshgg_szsh | 董事会 | category_zj_szsh | 中介报告 | category_jj_szsh | 解禁 | category_bcgz_szsh | 补充更正 |
# | category_yjdbg_szsh | 一季报 | category_jshgg_szsh | 监事会 | category_sf_szsh | 首发 | category_gszq_szsh | 公司债 | category_cqdq_szsh | 澄清致歉 |
# | category_sjdbg_szsh | 三季报 | category_gddh_szsh | 股东大会 | category_zf_szsh | 增发 | category_kzzq_szsh | 可转债 | category_fxts_szsh | 风险提示 |
# | category_yjygjxz_szsh | 业绩预告 | category_rcjy_szsh | 日常经营 | category_gqjl_szsh | 股权激励 | category_qtrz_szsh | 其他融资 | category_tbclts_szsh | 特别处理和退市 |
# | category_tszlq_szsh | 退市整理期 | | | | | | | | |


# 3、定义要爬取的时间段———必选项

SeDate_A = '2019-01-01~2023-01-01'

# 4、定义爬取指定报告的关键词(若不指定则保持为空即可)———可选项

Search_key = '会计师事务所'

# ---------------------------------------------------使用说明:以下代码非必要请勿修改!-------------------------------------------------------

## Step2:导入工具包

import requests
# 用于获取网页内容
from urllib.request import urlretrieve
# 用于下载网络文件到本地
import re
# 用于正则匹配
import math
# 用于调用数学函数
import json
# 用于解析json
import xlwt
# 用于写入excel
import xlrd
# 用于读取excel
import os
# 用于实现系统功能
import pandas as pd
# 导入pandas工具库


## Step3:获取巨潮资讯的数据源格式信息

# 从以下url中提取所需要的上市企业数据源信息(该url包括了巨潮资讯网上目前所有上市企业的stockList信息,我们的目的是从中获取到不同股票的orgId,以便接下来通过orgId去爬取不同企业的指定报告)
url = "http://www.cninfo.com.cn/new/data/szse_stock.json"
ret = requests.get(url=url)
ret = ret.content
stock_list = json.loads(ret)["stockList"]

# 查看巨潮资讯网一共收录了多少家企业数据
len(stock_list)

# 输出stock_list中的前两项,查看一下列表中的具体数据形式
print(stock_list[:2])

## Step4:用pandas把stock_list信息导出为本地Excel文件以便日后备用(可选)

def export_excel(export):
# 将字典列表转换为DataFrame
pf = pd.DataFrame(export)
# 指定字段顺序
order = ['orgId', 'category', 'code', 'pinyin', 'zwjc']
pf = pf[order]
# 将列名替换为中文
columns_map = {
'orgId': 'orgId(原始ID)',
'category': 'category(股市类型)',
'code': 'code(代码)',
'pinyin': 'pinyin(拼音)',
'zwjc': 'zwjc()'
}
pf.rename(columns=columns_map, inplace=True)
# 指定stock_list的存储路径(此处可自行更改)
file_path = pd.ExcelWriter(Stocklist_path)
# 替换空单元格
pf.fillna(' ', inplace=True)
# 输出
pf.to_excel(file_path, encoding='utf-8', index=False)
# 保存表格
file_path.save()


if __name__ == '__main__':
# 将分析完成的列表导出为excel表格
export_excel(stock_list)

## Step5:从stock_list中提取code与orgId建立“code-orgId”匹配清单

# 提取stock_list中的code与orgId,遍历生成独立字典
code_dic = {(it['code']): it['orgId'] for it in stock_list}

print("\n正在进行服务器数据库检索,共找到 {} 家上市公司!\n\n --------------------------------服务器数据比对工作完成,即将开始载入数据进行匹配!--------------------------------\n".format(len(code_dic)))

# 若要输出查看具体“code-orgId”匹配内容请取消注释此行
# print(code_dic)

print("\n --------------------------数据匹配完成,共生成 {} 家上市公司的“code-orgId”匹配数据!--------------------------\n".format(len(code_dic)))



## Step6:从Excel文件读取待爬取的股票清单并生成列表code_list

# 定义一个读取xls文件数据并转为列表的类

class excel_read:
def __init__(self, excel_path=Codelist_path, encoding='utf-8', index=0): # 待爬取企业清单路径
# 获取文本对象
self.data = xlrd.open_workbook(excel_path)
# 根据index获取某个sheet
self.table = self.data.sheets()[index]
# 获取当前sheet页面的总行数,把每一行数据作为list放到 list
self.rows = self.table.nrows

def get_data(self):
result = []
for i in range(self.rows):
# 获取每一行的数据
col = self.table.row_values(i)
# print(col)
result.append(col)
print("待爬取企业已从xls文件中加载完毕,结果如下:\n {}\n".format(result))
return result


# 运用函数生成待爬取企业的code_list
code_list = []
code_list.extend(excel_read().get_data())

# Part2:正式工作

## Step1:定义爬取函数

# 1、对单个页面进行请求,并返回数据信息——通过data自定义特定企业
def get_and_download_pdf_flie(pageNum, stock, searchkey='', category='', seDate=''):
url = 'http://www.cninfo.com.cn/new/hisAnnouncement/query'
pageNum = int(pageNum)
# 定义表单数据
data = {'pageNum': pageNum,
'pageSize': 30,
'column': '',
'tabName': 'fulltext',
'plate': '',
'stock': stock,
'searchkey': searchkey,
'secid': '',
'category': category,
'trade': '',
'seDate': seDate,
'sortName': '',
'sortType': '',
'isHLtitle': 'true'}

# 定义请求头
headers = {'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Connection': 'keep-alive',
'Content-Length': '181',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Host': 'www.cninfo.com.cn',
'Origin': 'http://www.cninfo.com.cn',
'Referer': 'http://www.cninfo.com.cn/new/commonUrl/pageOfSearch?url=disclosure/list/search',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36',
'X-Requested-With': 'XMLHttpRequest'}

# 提交请求
r = requests.post(url, data=data, headers=headers)

# 获取单页年报的数据,数据格式为json,解析并获取json中的年报信息
result = r.json()['announcements']


# 2.对数据信息进行提取
for i in result:
# 避免下载一些年报摘要等不需要的文件
if re.search('摘要', i['announcementTitle']):
pass
else:
title = i['announcementTitle']

# 获取公告文件名
secName = i['secName']

# 获取公司股票代码
secCode = i['secCode']

# 获取adjunctUrl,并组合生成pdf文件下载地址(分析得知巨潮资讯数据库pdf下载地址格式:http://static.cninfo.com.cn/+adjunctUrl)
adjunctUrl = i['adjunctUrl']
down_url = 'http://static.cninfo.com.cn/'+adjunctUrl

# 定义下载之后需要保存到本地的文件名
filename = f'{secCode}{secName}{title}.pdf'

# 用正则表达式将公告文件名中的特殊符号去掉,因为保存文件时命名规则不能带有某些特殊符号(比如*号),否则会导致程序报错
filename = re.sub(r'[(*)(<)(#)(\\)(|)(\")(?)(:)(/)(<em#)(</em#)]', '', filename)

# 定义文件存放地址
filepath = saving_path+'\\'+filename

# 提交下载请求
r = requests.get(down_url)

# 用response.content来写入文件信息
with open(filepath, 'wb') as f:
f.write(r.content)

# 设置进度条
print(f'{filename}下载完毕')

## Step2:自定义保存路径及确定待爬企业数量

# 设置存储年报文件的具体路劲
saving_path = Output_path

# 根据code_list计算待爬企业数量
Num = len(code_list)

print("待爬取企业总数量为:{}\n\n ------------------------------------已加载待爬取企业匹配信息,即将开始爬取数据!------------------------------------\n".format(Num))

## Step3:设定参数进行遍历爬取

# 从code_list中根据待爬企业数量遍历提取code与orgIdS
for i in range(0, Num):
code = code_list[i][0]
orgId = code_dic[code]
# 定义stock
stock = '{},{}'.format(code, orgId)
print("\n即将爬取 {} :".format(stock))
# 定义searchkey
searchkey_A = Search_key
# 定义category
category = Category_A
# 定义seDate
seDate = SeDate_A

# 定义pageNum(需要通过观测,确保pageNum能够涵盖每一次遍历请求的结果页数,此处定为2页即可)
for pageNum in range(1, 3):
try:
get_and_download_pdf_flie(
pageNum, stock, searchkey_A, category, seDate,)
except:
# 超出页数后会报错,需要跳过异常以便于继续遍历执行
pass

print("-------------------------------------------------程序执行完成!-------------------------------------------------")

从PDF提取文字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import PyPDF2 
import os
from PyPDF2 import PdfReader


def extract(file_path) -> str:
reader = PdfReader(file_path)

with open("info.txxt",mode="w",encoding="utf-8") as f:


#逐页把提取出的数据保存到文件中
for i in range(0,len(reader.pages)):
page = reader.pages[i]
text = page.extract_text()

f.write(text)
#print(text)

with open("info.txxt",mode="r",encoding="utf-8") as g:

content = g.read()
return content



if __name__ == "__main__":
extract()

AI api

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import google.generativeai as genai

# 这里存放着Gemini的密钥
genai.configure(api_key="密钥",transport='rest')
model = genai.GenerativeModel(model_name = "gemini-pro")

def api_1(request) -> str:

request_RawContent = str(request)

prompt = "提示词部分"
response = model.generate_content(prompt+request_RawContent)

return response.text

if __name__ == "__main__":
api_1()

主程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
from PdfExtract import *
from GeminiApi1 import *
import os
import time

def get_info_1(file_path) -> str:

content = extract(file_path)

info = api_1(content)

print(info)

return info


if __name__ == "__main__":
path = "./全部文档/"
file_list = os.listdir(path)
num = len(file_list)

for i in range(0,num):
file_path = path + file_list[i]
print(file_path + f"{i}/{num}")

with open("data.txt",mode="a",encoding="utf-8") as data:
try:
content = get_info_1(file_path)
data.writelines(content + "," + "\n")
except:
with open("error.txt",mode="a",encoding="utf-8") as error:
error.writelines(file_path + "\n" )
time.sleep(30)
continue

with open("error.txt",mode="r",encoding="utf-8") as error :

lines = error.read().splitlines()
for line in lines:

file_path = line
print(file_path)
try:
with open("data.txt",mode='a',encoding="utf-8") as data:
content = get_info_1(file_path)
data.writelines(content + "," + "\n")
except:
print("error")
time.sleep(5)
continue


数据提取
https://silenzio111.github.io/2024/01/05/pdfextract/
作者
silenzio
发布于
2024年1月5日
许可协议