一、工作背景
放弃之前的打算 :
通过开会讨论,仅爬取心理学畛域的常识来构建心理沙盘的常识图谱是不可取的(事实上,我的项目的指标是依据用户设计的沙盘场景 推理出 用户的心理状态,而不是做心理学百科知识的科普)。
这一常识图谱构建方向上的扭转归功于咱们小组的探讨和 《常识图谱 - 概念与技术》 这本书对我的启发,一些常识要点如下(能够跳过间接查看我的项目细节):
1、常识图谱狭义概念
为了讲明确我对我的项目的了解,还要从人工智能说起(想在这里一次性的的捋分明,兴许未来会退出一个链接):
图:常识图谱的学科位置
人工智能有 3 个学派:符号主义 、 连贯主义 和行为主义 ;
常识工程 源于 符号主义 ,为了无效的利用常识,首先要在计算机系统中正当的示意常识;
而常识示意 的一个重要形式就是 常识图谱 ;
留神:常识图谱只是常识示意中的一种,除了语义网络以外,谓词逻辑、产生式规定、本体、框架、决策树、贝叶斯网络、马尔可夫逻辑网都是常识示意的模式;
2、KG 钻研意义
KG 是认知智能的基石
- 机器了解数据的实质是从 数据 到常识图谱中的 常识因素 (实体概念、关系)的 映射;
- 了解过程 能够视作建设从 数据 (文本、图片、语音、视频)到 KG(脑海)中 实体、概念、属性 之间的 映射 过程;
几点 KG 对于认知智能的重要性:
①机器语言认知
人类对语言的了解建设在认知能力根底之上,所以解释了咱们听不懂东方的笑话故事。
语言了解须要背景常识,机器了解 自然语言当然也须要 背景常识 了。
②赋能可解释人工智能
“解释”与符号化常识图谱密切相关,人只能了解符号而无奈了解数值,书中有三个很好的例子:
问鲨鱼为什么可怕?你可能解释因为鲨鱼是肉食动物——用概念在解释;问鸟为什么能翱翔?你可能会解释因为它有翅膀——用属性解释;问前段时间鹿晗和关晓彤为什么会刷屏?你可能会解释因为她是他女朋友——用关系在解释;
③有助于加强机器学习的能力
人类的学习高效、强壮,并不需要机器学习那样宏大的样本量,根本原因在于 人类很少从零开始学习,人类善于联合丰盛的先验常识。
3、常识常识图谱
常识图谱能够依据其所涵盖的常识分为四类:事实常识、概念常识、词汇常识和常识常识
有了 1 和 2 的铺垫,咱们抉择为心理沙盘我的项目建设一个 常识常识图谱 的理由就明确了,每一个沙盘实体都来自事实世界,咱们要做的是 高度还原人脑 中实体与实体、实体与概念、概念与概念这些映射关系,从而可能让 心理测试后果更健壮和有说服力 。
以沙盘形容中的虾为例:
①虾看似慢吞吞的游,遇到对手时它骁勇奔下来舒展双臂与它博斗,打断双臂在所不惜,象征充斥阳刚之气。②虾须晦涩,飘逸,虾尾随移其形而动,象征共性和指标。
咱们能够构建这样一种分割:
二、工作思路
沙盘心理分析师提供了一个 entity 文件,里边有大概 600 个实体,长这样:
1、以这 600 个实体为根节点,从 ConceptNet 上爬取实体的所有关系,再递归关系所牵的另一边实体,提取的内容有:
entity1、relation、entity2、att1、att2、weight、id
此七项内容
2、依据这些内容先构建一个常识图谱
三、scrapy 实操
1、在之前创立的爬虫工程中,新建一个 spider 文件,还是通过命令来实现(工程项目创立的具体操作步骤参考 scrapy(一) 爬取心理学畛域词汇)
scrapy genspider -t basic conceptSpider api.conceptnet.io
2、剖析页面,试探爬取——周而复始直到成型
①方法论:
当有一个新的爬虫工作时,我 必然不能也绝不可能 做到间接写成 spider 文件,解正文 settings.py 中的 pipline,写 items.py 文件,写 pipline.py 文件。因为网页是否容许爬取、网页的格局、咱们想要的内容在哪、是否须要递归等,都须要去 debug,缓缓的磨进去。也就是 先别解正文 settings.py 中的 pipline,只消应用命令$ scrapy crawl conceptSpider --nolog
去一步步的迭代测验 咱们的规定是否写对了。
②页面剖析:
查看 ConceptNet 官网文档,得悉这种 api 类型的网页中,每一个 key 代表什么。
先给 start_urls 中只增加了一个‘http://api.conceptnet.io/c/en/apple’
来专一于剖析这个页面,显然每一页都有共性,页面局部截图如下:
其中’edges‘是一个 list 模式,每一项是一个关系,连贯着与 apple 相干的实体;
在网页底部,有一个键值是‘view’,是管前后页的,也就是说里边有下一页的 url;
网页解析:
# 将 json 格局的 api 页面解析为用字符串示意的字典
js_str=response.xpath("//pre").xpath("string(.)").extract()[0].strip()
# 将字符串转化为字典
js=json.loads(js_str)
规则化提取:
for edge in js['edges']:
str=edge['@id'].replace('[','').replace(']','').replace('/a/','').split(',')
# 只取英文关系
if str[1][3:6]!='en/' or str[2][3:6]!='en/':
continue
# 取两个实体
e1=str[1].replace('/c/en/','').replace('/n','').replace('/wn','')[:-1].split('/',1)
e2=str[2].replace('/c/en/','').replace('/n','').replace('/wn','')[:-1].split('/',1)
# 取关系
r=str[0].replace('/r/','')[:-1]
# 摘除后边的标识,放到属性里
e1_att=''e2_att=''
if len(e1)==2:
e1_att=e1[1]
if len(e2)==2:
e2_att=e2[1]
注:测试规则化提取失常后,就能够在上边的 for 循环里边用 item 传给 pipline 去输入了。
③页面跳转
if 'view' in js and 'nextPage' in js['view']:
relPos = js['view']['nextPage']
print("进入衍生页面执行 parse:")
nexthref = self.url_std+relPos
print(nexthref)
yield scrapy.Request(nexthref, callback=self.parse)
留神:这里有坑,如果只判断 'nextPage' in js['view']
,你就会发现 有的实体页面中没有 view 这个键值,会产生谬误,但不影响 pipline 输入的后果。
*3、解决网页拜访频率限度问题
发现问题:
在只有两个 url 的 start_urls 列表上测试没有问题,然而将 400 多个 url 放入 start_urls 中后,失去的后果每次都只有几百,几千条,最多 1 万条。
细节: 察看输入文件 triplets.txt 和终端显示,发现每次在第 602 个页面,第 1206 个页面呈现卡壳,最终可能进行输入。
波折的解决过程:
认为是输入的时候内存爆了导致 write 文件终止或者是 txt 文件大小有限度导致提前敞开文件。后果换成 csv 格局也不好使,都要思考应用数据库了;
去掉 --nolog
后执行 $ scrapy crawl conceptSpider
,发现问题在于 爬取阶段 而不是 pipline 的数据写出阶段。日志中有一些 url 反馈后果是 [429],也就是页面被拜访频繁导致的回绝拜访。
解决办法的帖子 具体如下:
可应用 429 状态码,同时蕴含一个 Retry-After 响应头用于通知客户端多长时间后能够再次申请服务。
middlewares.py: # 当状态码是 429 的时候 爬虫暂停 60 秒再爬取
import time
from scrapy.downloadermiddlewares.retry import RetryMiddleware
from scrapy.utils.response import response_status_message
class TooManyRequestsRetryMiddleware(RetryMiddleware):
def __init__(self, crawler):
super(TooManyRequestsRetryMiddleware, self).__init__(crawler.settings)
self.crawler = crawler
@classmethod
def from_crawler(cls, crawler):
return cls(crawler)
def process_response(self, request, response, spider):
if request.meta.get('dont_retry', False):
return response
elif response.status == 429:
self.crawler.engine.pause()
print("速度太快 暂停 60 秒")
time.sleep(60) # If the rate limit is renewed in a minute, put 60 seconds, and so on.
self.crawler.engine.unpause()
reason = response_status_message(response.status)
return self._retry(request, reason, spider) or response
elif response.status in self.retry_http_codes:
reason = response_status_message(response.status)
return self._retry(request, reason, spider) or response
return response
settings.py:
DOWNLOADER_MIDDLEWARES = { # 开启暂停中间件
'psySpider.middlewares.PsyspiderDownloaderMiddleware': 543,
}
RETRY_HTTP_CODES = [429, 500, 403] # 这个状态重试
DOWNLOAD_DELAY = 0.5
RANDOMIZE_DOWNLOAD_DELAY = True # 发完一个申请 随机暂停
4、piplines.py 写法
# 间接照着爬虫名字补上就能够
class ConceptspiderPipline(object):
def __init__(self):
self.file = open("./triplets1.csv","w",encoding='utf-8')
self.writer = csv.writer(self.file, dialect="excel")
def process_item(self, item, spider):
theme=item['theme']
entity_1 = item['entity_1']
entity_2 = item['entity_2']
relation = item['relation']
att1=item['e1_att']
att2=item['e2_att']
weight=item['weight']
id=item['id']
self.writer.writerow([entity_1,entity_2,relation,att1,att2,weight,id])
return item
5、items.py
# 间接照着爬虫名字补上就能够
class ConceptspiderItem(scrapy.Item):
theme=scrapy.Field()
entity_1=scrapy.Field()
entity_2=scrapy.Field()
relation=scrapy.Field()
e1_att=scrapy.Field()
e2_att=scrapy.Field()
weight=scrapy.Field()
id=scrapy.Field()