python 知识图谱demo_古诗词知识图谱Demo
早前調(diào)研了知識圖譜的基礎(chǔ)概念和技術(shù)框架,最近這兩個月倒騰了一個古詩詞的圖譜demo,僅以此文記錄一下實驗過程。從零開始做這個Demo,整個過程大致分為三大步驟:數(shù)據(jù)采集,數(shù)據(jù)存儲以及圖譜應(yīng)用,全文將按這三步進(jìn)行記錄。
一、數(shù)據(jù)采集
既然是從零開始,那第一步就是要爬取數(shù)據(jù)。搜了幾個詩詞網(wǎng)站,對比網(wǎng)頁排版結(jié)構(gòu)和內(nèi)容豐富程度,個人覺得古詩詞網(wǎng)是個不錯的選擇,在這里感謝站長為經(jīng)典文化傳承作出的貢獻(xiàn)。
1. 網(wǎng)頁分析
F12打開詩詞列表頁的源碼,查看頭部信息如下圖:
請求的url格式固定,只有頁碼改變;請求的類型為get。
多看幾個頁面,可以發(fā)現(xiàn)請求頭中Cookie的hm_lvt和hm_lpvt為兩個時間戳,不同頁面只有hml_pvt發(fā)生改變;old_url取值為當(dāng)前頁碼;Referer也是隨頁碼改變的固定格式url。
請求列表頁面的返回結(jié)果為json列表,可以非常方便地提取需要的信息,而不用去html中定位并解析目標(biāo)元素,省去了爬蟲中的一半工作量:
每一個json對應(yīng)一首詩詞,包含標(biāo)題、正文、作者、朝代、標(biāo)簽、體裁、作者介紹、譯注、賞析等信息,這種結(jié)構(gòu)化的數(shù)據(jù),也免去了數(shù)據(jù)抽取和整理的很多工作。
2. 爬蟲代碼
這一類網(wǎng)站廣告很少,也沒有收費業(yè)務(wù),帶有公益性質(zhì),網(wǎng)站服務(wù)器一般也扛不住爬蟲的壓力,常常會采取一些反爬措施,比如封禁IP。為了爬取這些網(wǎng)站,一方面要降低爬取速度;另一方面要維護代理池,在被封的時候更換IP。爬取過程中及時保存爬蟲結(jié)果,并記錄爬取失敗的頁面,方便以后再重爬。
def crawl_pages(page_list, save_path, ip_pool, retry_times=5):
fail_list = list()
lvt_code = int(time.time())
ip = random.choice(ip_pool)
for page in page_list:
time.sleep(3 * random.random())
lpvt_code = int(time.time())
page_url = 'https://www.gushici.com/poetry_list?page={0}'.format(page)
referer = 'https://www.gushici.com/p_{0}'.format(page)
headers = {'Host': 'www.gushici.com',
'Connection': 'keep-alive',
'Accept': '*/*',
'X-Requested-With': 'XMLHttpRequest',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36',
'Referer': referer,
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Cookie': 'JSESSIONID=48304F9E8D55A9F2F8ACC14B7EC5A02D; Hm_lvt_98209c07e81fcbdd5f79bd9e94c617eb={0}; Hm_lpvt_98209c07e81fcbdd5f79bd9e94c617eb={1};\old_url=/p_{2}'.format(lvt_code, lpvt_code, page)}
times = 0
flag = False
while times <= retry_times:
times += 1
try:
response = requests.get(page_url, headers=headers, proxies={'https': ip}, verify=False, timeout=10)
flag = True
with open(os.path.join(save_path, page), 'w', encoding='utf-8') as f:
json.dump(response.text, f, ensure_ascii=False)
break
except:
ip = random.choice(ip_pool)
if not flag:
fail_list.append(page)
with open(os.path.join(save_path, 'fail'), 'w', encoding='utf-8') as f:
json.dump(fail_list, f, ensure_ascii=False)
二、數(shù)據(jù)存儲
知識圖譜的數(shù)據(jù)層有多重存儲方式,本文選擇采用Neo4j搭建。Noe4j是一個高性能的輕量級圖形數(shù)據(jù)庫,應(yīng)對小型知識圖譜綽綽有余。雖然關(guān)系型數(shù)據(jù)庫通過多重join也可以實現(xiàn)數(shù)據(jù)間的復(fù)雜關(guān)系查詢,但是多表數(shù)據(jù)join然后過濾篩選導(dǎo)致性能會非常差,而圖數(shù)據(jù)庫很好地解決這樣的問題,它只用遍歷相關(guān)節(jié)點,不用操作全量數(shù)據(jù),性能會大大提升。
1. Neo4j安裝
從Neo4j官網(wǎng)可以下載開源的Noe4j社區(qū)版,解壓到D盤,然后配置環(huán)境變量。注意:Neo4j依賴于Java運行環(huán)境,安裝Neo4j前請檢查本機是否安裝Java并配置Java的環(huán)境變量。
打開cmd命令行窗口,進(jìn)入安裝目錄下的bin文件夾,執(zhí)行“neo4j install-service”命令,安裝Neo4j服務(wù)。然后執(zhí)行“neo4j start”命令,啟動Neo4j的服務(wù)。
2. Neo4j操作
Neo4j是一種圖數(shù)據(jù)庫,其中并沒有數(shù)據(jù)表的概念,只包含節(jié)點和邊,節(jié)點表示實體,邊表示實體間的關(guān)系(分為有向關(guān)系和無向關(guān)系),節(jié)點和邊可以包含鍵值對表示的屬性。
用慣了關(guān)系型數(shù)據(jù)庫,初次接觸圖數(shù)據(jù)庫感覺有點別扭。為了便于自己理解,我是這樣來類比的:
節(jié)點和關(guān)系是圖數(shù)據(jù)庫中定義的最原始的兩個基類,節(jié)點(關(guān)系)的標(biāo)簽表示由節(jié)點(關(guān)系)基類派生出來的一類節(jié)點(關(guān)系)。當(dāng)導(dǎo)入數(shù)據(jù)之后,具有屬性值的某一個節(jié)點(關(guān)系),就是該標(biāo)簽對應(yīng)的節(jié)點(關(guān)系)派生類所生成的實例。
(1)導(dǎo)入數(shù)據(jù)
Neo4j提供import命令,可以批量導(dǎo)入csv格式的數(shù)據(jù)。針對json格式的數(shù)據(jù),可以轉(zhuǎn)為csv格式,然后用import導(dǎo)入,注意json中的雙引號需要進(jìn)行轉(zhuǎn)義。為避免格式轉(zhuǎn)換過程中的錯誤,可以調(diào)用apoc函數(shù)庫中的json導(dǎo)入工具。
apoc的安裝方式為:從github中下載apoc的jar包,將jar包復(fù)制到Neo4j安裝目錄的plugins路徑下,在neo4j.conf中配置apoc.import.file.enabled=true,表示允許apoc導(dǎo)入文件。重啟Neo4j服務(wù),調(diào)用apoc.load.json即可導(dǎo)入json數(shù)據(jù)。
(2)查詢數(shù)據(jù)
Neo4j的查詢語言為Cypher(第一眼看成了Cython,然而這兩個半點不沾邊)。官網(wǎng)有完整版的Cypher手冊,本文只挑選最基礎(chǔ)的幾個語句簡要介紹。
A.增:
新建節(jié)點: CREATE (node: NodeType {AttributeKey : AttributeValue})
// 創(chuàng)建一個姓名為Jack的Person類的節(jié)點,并返回該節(jié)點
CREATE (a:Person {name:"Jack"})
RETURN a
新建關(guān)系:不能單獨創(chuàng)建關(guān)系,必須指明關(guān)系的起始節(jié)點和終止節(jié)點。--表示無向關(guān)系,->和 EndNode
// 創(chuàng)建兩個Person之間Knows的關(guān)系,并返回節(jié)點和關(guān)系
CREATE (a:Person)-[k:KNOWS]-(b:Person)
RETURN a, k, b
B.查:
查詢節(jié)點:MATCH (node: NodeType {AttributeKey : AttributeValue}) WHERE node.AttributeKey = AttributeValue
// 查詢1970年后出生的Person節(jié)點,并返回節(jié)點
MATCH (n)
WHERE n.born > 1970
RETURN n;
查詢關(guān)系:MATCH StartNode - (relationship: RelationshipType {AttributeKey : AttributeValue}) -> EndNode WHERE relationship.AttributeKey = AttributeValue
// 查詢自從2015年起居住(LIVES_IN)在NewYork城市(City)的名叫Mike的人(Person),并返回節(jié)點和關(guān)系
MATCH (p:Person {name:"Michel"})-[s:LIVES_IN]->(c:City {name:"NewYork"})
WHERE s.since = 2015
RETURN p,s,c
C.改:
修改屬性:MATCH (variable : NodeType|RelationshipType) SET variable = {AttributeKey : AttributeValue}
// 查詢名為Jack的Person類節(jié)點,并將名字改為Michel,年齡改為23
MATCH (p:Person)
WHERE p.name = "Jack"
SET p = {name: "Michel", age: 23}
D.刪:
刪除節(jié)點:與該節(jié)點相關(guān)的關(guān)系也需要刪除。MATCH (node) - [relationship] - () DELETE node, relationship
// 刪除名為Jack的Person節(jié)點及關(guān)聯(lián)關(guān)系
MATCH (p:Person)-[relationship]-()
WHERE p.name = "Jack"
DELETE relationship, p
刪除屬性:MATCH (node) - [relationship] - () REMOVE node.AttributeKey, relationship.AttributeKey
// 刪除名為Michel的Person節(jié)點的年齡屬性
MATCH (p:Person)
WHERE p.name = "Michel"
REMOVE p.age
3. Neo4j實踐
本文設(shè)計的知識圖譜包含三類節(jié)點:詩詞(Poem)、作者(Author)、標(biāo)簽(Tag)。作者與詩詞是寫作(WRITE)的關(guān)系,詩詞、作者與標(biāo)簽是標(biāo)識(LABEL)關(guān)系。
// 在三類節(jié)點上創(chuàng)建索引
create index on :Poem(uuid);
create index on :Author(name);
create index on :Tag(tag);
// 將數(shù)據(jù)導(dǎo)入Neo4j
call apoc.periodic.iterate('call apoc.load.json("web_file_poetry.json") yield value as poem',
'merge (p:Poem{uuid: poem.poem_id})
set p.title = poem.title, p.content=poem.poem, p.tag=poem.tag, p.appreciation=poem.appreciation, p.background=poem.background
// 作者節(jié)點
merge (a:Author{name: poem.poet, dynasty: poem.dynasty})
// 作者到詩詞的關(guān)系
merge (a)-[r1:WRITE]->(p)',
{batchSize:100000, iterateList:true, parallel:true});
// 建立詩詞、作者與標(biāo)簽之間的關(guān)系
match (a:Author)-[:WRITE]->(p:Poem)
where p.tag <> ''
unwind split(trim(p.tag), ",") as tag
// 標(biāo)簽節(jié)點
merge (t:Tag{tag: tag})
// 詩詞到標(biāo)簽的關(guān)系
merge (p)-[r1:LABEL]->(t)
// 作者到標(biāo)簽的關(guān)系
merge (a)-[r2:LABEL]->(t);
圖數(shù)據(jù)庫創(chuàng)建成功之后,可以查詢看看效果,Neo4j的可視化做的還是挺好看的。
三、圖譜應(yīng)用
知識問答是基于知識圖譜的一項應(yīng)用,前沿的問答系統(tǒng)多采用深度學(xué)習(xí)、自然語言處理等技術(shù)。本文采用最簡單的正則匹配( ̄▽ ̄)~*
1.首先,定義可以回答的問題類型:
查找詩詞的正則:
source_list = [
'[\"\'“‘《]?(?:是|出自|來[自|源])(?:哪[首|篇|個|里|兒|]?|什么)的?(?:[詩詞][文句]?|文章|句子)?',
'[\"\'“‘《]?的(?:來源|出處|(?:整[首|篇]|完整|全)[詩詞文])',
'(?:含有?|包[含括])[\"\'“‘《]?的(?:[詩詞][文句]?|文章|句子)'
]
source_list = list(map(re.compile, source_list))
查找作者的正則:
author_list = [
'[\"\'“‘《]?的(?:作者|[詩詞]人)',
'[\"\'“‘《]?是(?:誰|哪[位個])(?:作者|[詩詞]人)?',
]
author_list = list(map(re.compile, author_list))
查找標(biāo)簽的正則:
tag_list = [
'(?:寫|描[寫繪述]|表達(dá))(\S+?)的?[詩詞]',
]
tag_list = list(map(re.compile, tag_list))
整合問題正則:
rules = {
'source': source_list,
'author': author_list,
'tag': tag_list,
}
2.其次,根據(jù)正則判斷問題類型,并提取問題中的要素
def match(question):
match_result = None
for mode, temp_list in rules.items():
for temp in temp_list:
text = temp.findall(question)
if text:
match_result = (mode, text[0])
break
return match_result
3.最后,從Neo4j中根據(jù)問題要素查找并返回問題答案
def parse(question):
match_result = match(question)
if match_result is None:
return None
mode, text = match_result
res = None
cql = None
if mode == 'source':
cql = 'match (p:Poem) where p.content contains "{0}" return p.title, p.content'.format(text)
elif mode == 'author':
cql = 'match (a:Author)-[:WRITE]->(p:Poem) where p.content contains "{0}" return a.name'.format(text)
elif mode == 'tag':
tag_list = jieba.lcut(text)
cql = 'match (p:Poem)-[:LABEL]->(t:Tag) where t.tag in {0} return p.content, t.tag'.format(tag_list)
else:
pass
if cql:
res = neo4j_graph.run(cql).to_data_frame()
return res
后記:
本文只是搭建了一個非常小的圖譜demo,后續(xù)還有很多地方需要完善,如有遺漏或錯誤,請大家不吝指出,歡迎交流。
總結(jié)
以上是生活随笔為你收集整理的python 知识图谱demo_古诗词知识图谱Demo的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: libcurl库使用详情、libcurl
- 下一篇: python 计算器 casio_CAS