一、工作背景

放弃之前的打算
通过开会讨论,仅爬取心理学畛域的常识来构建心理沙盘的常识图谱是不可取的(事实上,我的项目的指标是依据用户设计的沙盘场景推理出用户的心理状态,而不是做心理学百科知识的科普)。
这一常识图谱构建方向上的扭转归功于咱们小组的探讨和 《常识图谱-概念与技术》 这本书对我的启发,一些常识要点如下(能够跳过间接查看我的项目细节):

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 timefrom scrapy.downloadermiddlewares.retry import RetryMiddlewarefrom scrapy.utils.response import response_status_messageclass 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.5RANDOMIZE_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()