(手稿PFC020512)
用慣了xpath、scrapy或jsoup這類HTML 文檔解析器,今天決定試試實現其tag selector標籤選擇器。
HTML是一個規範的文檔。一般來說,HTML文檔是對稱閉合的,跟XML文檔很相似,但不是嚴格的。HTML tag標籤一般由“<”、“>”包圍,標籤開始符基本要素爲左右尖括號界定符、tag標籤名、tag標籤屬性和tag標籤屬性值,表達式爲:<[tag name] [tag attribute name]=”[tag attribute value]”>,標籤結束符表達式爲:</[tag name]>。其中,tag標籤屬性和值爲非必要。標籤屬性和值的賦值表達式爲:[屬性]=”[值]”或[屬性]=[值]兩種,一般推薦前者的使用方法。屬性賦值表達式之間使用空格分隔。
HTML標籤具有規範的屬性,也可以自定義屬性。自定義屬性可以保存一定的數據,不能被標準的HTML解釋器識別。一般情況下,優先考慮標準HTML的實現。
HTML文檔解析器本質上是語法樹的一種實現,簡單來說,根據特定的標籤詞彙和語法規則實現對HTML文檔的解析處理。底層的語法樹一般是使用類似於遞歸的處理方法。像函數遞歸和快速排序都是很有意思的語法規則設計。都是使用簡短的語句實現人的智慧精華。說了這麼多,下面來試試從高級語言層面實現標籤選擇器。這裏筆者主要是通過正則表達式來完成其實現。
標籤選擇器最基本的是標籤的識別。前面提到,標籤開始符和結束符的表達式分別爲:<[tag]>、</[tag]>,[tag]爲由英文字母組成的標籤名。對應正則表達式:
<python>
tag_start_pattern=’<tag>’
tag_end_pattern=’</tag>’
</python>
標籤屬性賦值表達式爲:[ ][attribute]=”[value]”,對應正則表達式:
<python>
tag_attribute_pattern=’[ ][attribute][=][“][value][“]’
</python>
應用模式除包含上述表達式外,還需進行標籤最小配對或標籤對稱等優化。
以下是實現代碼的草稿:
<python>
# -*- coding: UTF-8 -*-
# !/usr/bin/env python
'''
author: MRN6
blog: [email protected]
'''
import re
#定義path規則
def qpath(path=None, html=None):
if path is None or html is None:
return []
rules=path.split("//")
matches=""
for rule in rules:
if len(rule.strip())<1:
continue
ruledatas=rule.split(':')
tag=ruledatas[0]
attributedatas=ruledatas[1].split('=')
attribute=attributedatas[0]
value=attributedatas[1]
print('<'+tag+' '+attribute+'="'+value+'"')
rules=len(ruledatas)
if rules==2:
matches=re.findall('(<'+tag+'[^<>]*'+attribute+'="'+value+'[^"]*"[^<>]*>((?!<'+tag+'[^<>]*'+attribute+'="'+value+'"[^<>]*>).)*</'+tag+'>$)', html, re.M|re.S|re.I)
elif rules==3 and ruledatas[2]=='END':
matches=re.findall('(<'+tag+'[^<>]*'+attribute+'="'+value+'[^"]*"[^<>]*>((?!</'+tag+'>).)*</'+tag+'>$)', html, re.M|re.S|re.I)
#注:參考https://blog.csdn.net/iteye_13785/article/details/82638686
#檢查標籤對稱問題
if len(matches)>0:
for match in matches:
smatches=re.findall('<'+tag, match[0], re.M|re.S|re.I)
ematches=re.findall('</'+tag+'>', match[0], re.M|re.S|re.I)
slen=len(smatches)
elen=len(ematches)
if slen!=elen:
print(match[0]+' greedy match: '+str(slen)+'-'+str(elen))
return matches
html='''
<!DOCTYPE html>
<html><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1">
<title>標題</title>
</head>
<body>
<div id="root">
<div class="content-item first">
<div class="content-title">title1</div>
<div class="content-body">content1</div>
</div>
<div class="content-item">
<div class="content-title">title_2</div>
<div class="content-body">content2</div>
</div>
<div> </div>
<div class="content-item">
<div class="content-title">title3_</div>
<div class="content-body">content3</div>
</div>
</div>
<div> </div>
</body>
</html>
'''
mypath="//div:id=root//div:class=content-item"
mypath2="//div:id=root//div:class=content-item//div:class=content-title:END"
results=qpath(mypath, html)
print(len(results))
for result in results:
print(result)
results2=qpath(mypath2, html)
print(len(results2))
for result in results2:
print(result)
</python>