python 知识图谱demo_古诗词知识图谱Demo
早前調(diào)研了知識(shí)圖譜的基礎(chǔ)概念和技術(shù)框架,最近這兩個(gè)月倒騰了一個(gè)古詩(shī)詞的圖譜demo,僅以此文記錄一下實(shí)驗(yàn)過(guò)程。從零開(kāi)始做這個(gè)Demo,整個(gè)過(guò)程大致分為三大步驟:數(shù)據(jù)采集,數(shù)據(jù)存儲(chǔ)以及圖譜應(yīng)用,全文將按這三步進(jìn)行記錄。
一、數(shù)據(jù)采集
既然是從零開(kāi)始,那第一步就是要爬取數(shù)據(jù)。搜了幾個(gè)詩(shī)詞網(wǎng)站,對(duì)比網(wǎng)頁(yè)排版結(jié)構(gòu)和內(nèi)容豐富程度,個(gè)人覺(jué)得古詩(shī)詞網(wǎng)是個(gè)不錯(cuò)的選擇,在這里感謝站長(zhǎng)為經(jīng)典文化傳承作出的貢獻(xiàn)。
1. 網(wǎng)頁(yè)分析
F12打開(kāi)詩(shī)詞列表頁(yè)的源碼,查看頭部信息如下圖:
請(qǐng)求的url格式固定,只有頁(yè)碼改變;請(qǐng)求的類型為get。
多看幾個(gè)頁(yè)面,可以發(fā)現(xiàn)請(qǐng)求頭中Cookie的hm_lvt和hm_lpvt為兩個(gè)時(shí)間戳,不同頁(yè)面只有hml_pvt發(fā)生改變;old_url取值為當(dāng)前頁(yè)碼;Referer也是隨頁(yè)碼改變的固定格式url。
請(qǐng)求列表頁(yè)面的返回結(jié)果為json列表,可以非常方便地提取需要的信息,而不用去html中定位并解析目標(biāo)元素,省去了爬蟲中的一半工作量:
每一個(gè)json對(duì)應(yīng)一首詩(shī)詞,包含標(biāo)題、正文、作者、朝代、標(biāo)簽、體裁、作者介紹、譯注、賞析等信息,這種結(jié)構(gòu)化的數(shù)據(jù),也免去了數(shù)據(jù)抽取和整理的很多工作。
2. 爬蟲代碼
這一類網(wǎng)站廣告很少,也沒(méi)有收費(fèi)業(yè)務(wù),帶有公益性質(zhì),網(wǎng)站服務(wù)器一般也扛不住爬蟲的壓力,常常會(huì)采取一些反爬措施,比如封禁IP。為了爬取這些網(wǎng)站,一方面要降低爬取速度;另一方面要維護(hù)代理池,在被封的時(shí)候更換IP。爬取過(guò)程中及時(shí)保存爬蟲結(jié)果,并記錄爬取失敗的頁(yè)面,方便以后再重爬。
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ù)存儲(chǔ)
知識(shí)圖譜的數(shù)據(jù)層有多重存儲(chǔ)方式,本文選擇采用Neo4j搭建。Noe4j是一個(gè)高性能的輕量級(jí)圖形數(shù)據(jù)庫(kù),應(yīng)對(duì)小型知識(shí)圖譜綽綽有余。雖然關(guān)系型數(shù)據(jù)庫(kù)通過(guò)多重join也可以實(shí)現(xiàn)數(shù)據(jù)間的復(fù)雜關(guān)系查詢,但是多表數(shù)據(jù)join然后過(guò)濾篩選導(dǎo)致性能會(huì)非常差,而圖數(shù)據(jù)庫(kù)很好地解決這樣的問(wèn)題,它只用遍歷相關(guān)節(jié)點(diǎn),不用操作全量數(shù)據(jù),性能會(huì)大大提升。
1. Neo4j安裝
從Neo4j官網(wǎng)可以下載開(kāi)源的Noe4j社區(qū)版,解壓到D盤,然后配置環(huán)境變量。注意:Neo4j依賴于Java運(yùn)行環(huán)境,安裝Neo4j前請(qǐng)檢查本機(jī)是否安裝Java并配置Java的環(huán)境變量。
打開(kāi)cmd命令行窗口,進(jìn)入安裝目錄下的bin文件夾,執(zhí)行“neo4j install-service”命令,安裝Neo4j服務(wù)。然后執(zhí)行“neo4j start”命令,啟動(dòng)Neo4j的服務(wù)。
2. Neo4j操作
Neo4j是一種圖數(shù)據(jù)庫(kù),其中并沒(méi)有數(shù)據(jù)表的概念,只包含節(jié)點(diǎn)和邊,節(jié)點(diǎn)表示實(shí)體,邊表示實(shí)體間的關(guān)系(分為有向關(guān)系和無(wú)向關(guān)系),節(jié)點(diǎn)和邊可以包含鍵值對(duì)表示的屬性。
用慣了關(guān)系型數(shù)據(jù)庫(kù),初次接觸圖數(shù)據(jù)庫(kù)感覺(jué)有點(diǎn)別扭。為了便于自己理解,我是這樣來(lái)類比的:
節(jié)點(diǎn)和關(guān)系是圖數(shù)據(jù)庫(kù)中定義的最原始的兩個(gè)基類,節(jié)點(diǎn)(關(guān)系)的標(biāo)簽表示由節(jié)點(diǎn)(關(guān)系)基類派生出來(lái)的一類節(jié)點(diǎn)(關(guān)系)。當(dāng)導(dǎo)入數(shù)據(jù)之后,具有屬性值的某一個(gè)節(jié)點(diǎn)(關(guān)系),就是該標(biāo)簽對(duì)應(yīng)的節(jié)點(diǎn)(關(guān)系)派生類所生成的實(shí)例。
(1)導(dǎo)入數(shù)據(jù)
Neo4j提供import命令,可以批量導(dǎo)入csv格式的數(shù)據(jù)。針對(duì)json格式的數(shù)據(jù),可以轉(zhuǎn)為csv格式,然后用import導(dǎo)入,注意json中的雙引號(hào)需要進(jìn)行轉(zhuǎn)義。為避免格式轉(zhuǎn)換過(guò)程中的錯(cuò)誤,可以調(diào)用apoc函數(shù)庫(kù)中的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的查詢語(yǔ)言為Cypher(第一眼看成了Cython,然而這兩個(gè)半點(diǎn)不沾邊)。官網(wǎng)有完整版的Cypher手冊(cè),本文只挑選最基礎(chǔ)的幾個(gè)語(yǔ)句簡(jiǎn)要介紹。
A.增:
新建節(jié)點(diǎn): CREATE (node: NodeType {AttributeKey : AttributeValue})
// 創(chuàng)建一個(gè)姓名為Jack的Person類的節(jié)點(diǎn),并返回該節(jié)點(diǎn)
CREATE (a:Person {name:"Jack"})
RETURN a
新建關(guān)系:不能單獨(dú)創(chuàng)建關(guān)系,必須指明關(guān)系的起始節(jié)點(diǎn)和終止節(jié)點(diǎn)。--表示無(wú)向關(guān)系,->和 EndNode
// 創(chuàng)建兩個(gè)Person之間Knows的關(guān)系,并返回節(jié)點(diǎn)和關(guān)系
CREATE (a:Person)-[k:KNOWS]-(b:Person)
RETURN a, k, b
B.查:
查詢節(jié)點(diǎn):MATCH (node: NodeType {AttributeKey : AttributeValue}) WHERE node.AttributeKey = AttributeValue
// 查詢1970年后出生的Person節(jié)點(diǎn),并返回節(jié)點(diǎn)
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é)點(diǎn)和關(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é)點(diǎn),并將名字改為Michel,年齡改為23
MATCH (p:Person)
WHERE p.name = "Jack"
SET p = {name: "Michel", age: 23}
D.刪:
刪除節(jié)點(diǎn):與該節(jié)點(diǎn)相關(guān)的關(guān)系也需要?jiǎng)h除。MATCH (node) - [relationship] - () DELETE node, relationship
// 刪除名為Jack的Person節(jié)點(diǎn)及關(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é)點(diǎn)的年齡屬性
MATCH (p:Person)
WHERE p.name = "Michel"
REMOVE p.age
3. Neo4j實(shí)踐
本文設(shè)計(jì)的知識(shí)圖譜包含三類節(jié)點(diǎn):詩(shī)詞(Poem)、作者(Author)、標(biāo)簽(Tag)。作者與詩(shī)詞是寫作(WRITE)的關(guān)系,詩(shī)詞、作者與標(biāo)簽是標(biāo)識(shí)(LABEL)關(guān)系。
// 在三類節(jié)點(diǎn)上創(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é)點(diǎn)
merge (a:Author{name: poem.poet, dynasty: poem.dynasty})
// 作者到詩(shī)詞的關(guān)系
merge (a)-[r1:WRITE]->(p)',
{batchSize:100000, iterateList:true, parallel:true});
// 建立詩(shī)詞、作者與標(biāo)簽之間的關(guān)系
match (a:Author)-[:WRITE]->(p:Poem)
where p.tag <> ''
unwind split(trim(p.tag), ",") as tag
// 標(biāo)簽節(jié)點(diǎn)
merge (t:Tag{tag: tag})
// 詩(shī)詞到標(biāo)簽的關(guān)系
merge (p)-[r1:LABEL]->(t)
// 作者到標(biāo)簽的關(guān)系
merge (a)-[r2:LABEL]->(t);
圖數(shù)據(jù)庫(kù)創(chuàng)建成功之后,可以查詢看看效果,Neo4j的可視化做的還是挺好看的。
三、圖譜應(yīng)用
知識(shí)問(wèn)答是基于知識(shí)圖譜的一項(xiàng)應(yīng)用,前沿的問(wèn)答系統(tǒng)多采用深度學(xué)習(xí)、自然語(yǔ)言處理等技術(shù)。本文采用最簡(jiǎn)單的正則匹配( ̄▽ ̄)~*
1.首先,定義可以回答的問(wèn)題類型:
查找詩(shī)詞的正則:
source_list = [
'[\"\'“‘《]?(?:是|出自|來(lái)[自|源])(?:哪[首|篇|個(gè)|里|兒|]?|什么)的?(?:[詩(shī)詞][文句]?|文章|句子)?',
'[\"\'“‘《]?的(?:來(lái)源|出處|(?:整[首|篇]|完整|全)[詩(shī)詞文])',
'(?:含有?|包[含括])[\"\'“‘《]?的(?:[詩(shī)詞][文句]?|文章|句子)'
]
source_list = list(map(re.compile, source_list))
查找作者的正則:
author_list = [
'[\"\'“‘《]?的(?:作者|[詩(shī)詞]人)',
'[\"\'“‘《]?是(?:誰(shuí)|哪[位個(gè)])(?:作者|[詩(shī)詞]人)?',
]
author_list = list(map(re.compile, author_list))
查找標(biāo)簽的正則:
tag_list = [
'(?:寫|描[寫繪述]|表達(dá))(\S+?)的?[詩(shī)詞]',
]
tag_list = list(map(re.compile, tag_list))
整合問(wèn)題正則:
rules = {
'source': source_list,
'author': author_list,
'tag': tag_list,
}
2.其次,根據(jù)正則判斷問(wèn)題類型,并提取問(wèn)題中的要素
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ù)問(wèn)題要素查找并返回問(wèn)題答案
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
后記:
本文只是搭建了一個(gè)非常小的圖譜demo,后續(xù)還有很多地方需要完善,如有遺漏或錯(cuò)誤,請(qǐng)大家不吝指出,歡迎交流。
總結(jié)
以上是生活随笔為你收集整理的python 知识图谱demo_古诗词知识图谱Demo的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: libcurl库使用详情、libcurl
- 下一篇: websocket python爬虫_p