【背景】css
以前使用HtmlAgilityPack期間,遇到了2個bug:html
1. InnerText沒有包含對應字符串(可是用NextSibling.InnerText卻能夠獲得)node
對於html:session
1
|
<option value="search-alias=instant-video">Amazon Instant Video</option>
|
用以下的代碼:ide
1
2
3
4
|
//<option value="search-alias=instant-video">Amazon Instant Video</option>
string searchValue = singleOptionNode.Attributes["value"].Value; //search-alias=instant-video
//instant-video
string generalCategory = singleOptionNode.InnerText; //CAN NOT get: Amazon Instant Video
|
是不工做的。post
後來通過調試,改成:this
1
2
3
4
|
//<option value="search-alias=instant-video">Amazon Instant Video</option>
string searchValue = singleOptionNode.Attributes["value"].Value; //search-alias=instant-video
//instant-video
string generalCategory = singleOptionNode.NextSibling.InnerText; //can get: Amazon Instant Video
|
倒是能夠的。google
非常尼瑪的詭異。spa
很明顯是一個bug。調試
和:
2.丟失了form節點的input子節點
訪問:
http://www.amazon.com/gp/offer-listing/B0083PWAPW/ref=dp_olp_all_mbc?ie=UTF8&condition=all
獲得的html中,對應的部分是:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<form method=
"POST"
action=
"/gp/item-dispatch/ref=olp_atc_fm_1"
>
<input type=
"hidden"
name=
"session-id"
value=
"182-0726239-4848949"
>
<input type=
"hidden"
name=
"qid"
value=
""
>
<input type=
"hidden"
name=
"sr"
value=
""
>
<input id=
"signInToHUC"
type=
"hidden"
value=
"0"
name=
"signInToHUC"
>
<input type=
"hidden"
name=
"metric-asin.B0083PWAPW"
value=
"1"
>
<input type=
"hidden"
name=
"registryItemID.1"
value=
""
>
<input type=
"hidden"
name=
"registryID.1"
value=
""
>
<input type=
"hidden"
name=
"itemCount"
value=
"1"
>
<input type=
"hidden"
name=
"offeringID.1"
value=
"%2F%2FeHHmpktM3oPoQj%2FOWhDI%2FpHyvwwFCwEfNIBEgFcfAHzKHAzVK%2BZfhkmBFO%2BPbow9JfdOmrE6eKME4ydhLTTK1Dgaf8O3N7SyOR%2F136TvVh0lfJypEt4Q%3D%3D"
>
<input type=
"hidden"
name=
"isAddon"
value=
"0"
>
<input type=
"image"
src=
"http://g-ecx.images-amazon.com/images/G/01/x-locale/nav2/images/add-to-cart-md-p._V192250398_.gif"
align=
"absmiddle"
alt=
"Add to cart"
border=
"0"
height=
"21"
name=
"submit.addToCart"
width=
"112"
/>
</form>
|
能夠經過:
1
2
|
htmlDoc = crl.htmlToHtmlDoc(respHtml);
HtmlNodeCollection postItemNodeList = htmlDoc.DocumentNode.SelectNodes(
"//form[starts-with(@action, '/gp/item-dispatch/ref=') and @method='POST']"
);
|
搜索到form節點,可是結果其下,再去搜input節點:
1
|
HtmlNodeCollection inputTypeNodeList = postItemNode.SelectNodes(
".//input[@type='hidden' and @name and @value]"
);
|
居然獲得的inputTypeNodeList是null:
即form下面,沒有找到任何的child,即,全部的input節點,都丟失了!
再回去查看postItemNode,結果其下就是沒有child的:
因此,應該是對應的HtmlAgilityPack的bug。
【折騰過程】
1. 後來看到:
No child nodes for FORM object
中提到了,說是:
In Html specification form tag can overlap, so Htmlagilitypack handle this node a little different. 。。。
After adding this call all form elements are added as children. |
而後就去看看,結果果真是從child變成了sibling了,並且此處仍是很變態的,NextSibling的NextSibling纔是咱們要的input節點:
因此,此處,看來只能是說動的,相似於上面那個問題同樣的,寫成NextSibling的NextSibling
不過,真是這樣寫的話,那也夠變態的。。。。
2.而後也看到別人也遇到一樣問題:
Problem parsing children of a node with HtmlAgilityPack
並且某人也是放棄了HtmlAgilityPack而轉到了SGMLReader了。
不過,另外有人說,不是bug,而是能夠配置的。
其相關的討論見:
http://htmlagilitypack.codeplex.com/workitem/21782
再參考:
HtmlAgilityPack — Does <form> close itself for some reason?
去,在將html轉爲htmlDoc以前,添加:
1
|
HtmlNode.ElementsFlags.Remove(
"form"
);
|
變爲:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
HtmlNode.ElementsFlags.Remove(
"form"
);
htmlDoc = crl.htmlToHtmlDoc(respHtml);
HtmlNodeCollection postItemNodeList = htmlDoc.DocumentNode.SelectNodes(
"//form[starts-with(@action, '/gp/item-dispatch/ref=') and @method='POST']"
);
if
(postItemNodeList ==
null
)
{
//something error
}
else
{
foreach
(HtmlNode postItemNode
in
postItemNodeList)
{
string
itemDispatchUrl = postItemNode.Attributes[
"action"
].Value;
///gp/item-dispatch/ref=olp_atc_used_1
itemDispatchUrl = constAmazonDomainUrl + itemDispatchUrl;
//http://www.amazon.com/gp/item-dispatch/ref=olp_atc_used_1
Dictionary<
string
,
string
> postDict =
new
Dictionary<
string
,
string
>();
HtmlNodeCollection inputTypeNodeList = postItemNode.SelectNodes(
".//input[@type='hidden' and @name and @value]"
);
|
而後獲得的inputTypeNodeList,的確不是null了,也有了child了:
【總結】
以前還誇獎HtmlAgilityPack好用呢,結果還沒用多久,就出現這麼多的bug。看來真的無法繼續使用了。
每次都要很當心,不知道啥時候就會出錯,真鬱悶。。。
即便不是bug,其自己把form下面的節點,都弄成其sibling這個策略,仍是很變態的。至少讓更多人的,都容易誤解。
【後記】
後來的後來,通過參考別人的解釋:
發現,
其實上述兩個,所謂的bug,就是同一個問題:
對於HtmlAgilityPack,實際上,對於option,form等tag,其默認的處理的結果是:其下的子節點,會變成sibling
因此,上面的:
對於option須要經過NextSibling才能得到對應的InnerText;
對於form子節點爲空,也是須要經過NextSibling(的NextSibling)才能得到對應的input子節點;
其本質都是:
HtmlAgilityPack是針對HTML 3.2的規範去實現的,而HTML 3.2就是這樣規定的。
其不是bug,而是feature
可是很明顯,是屬於讓人蛋疼的feature。
解決辦法有兩種:
1.改源碼
把HtmlNode.cs中的下面這行註釋掉:
1
|
ElementsFlags.Add(
"form"
, HtmlElementFlag.CanOverlap | HtmlElementFlag.Empty);
|
2.不改源碼
在HtmlDocument類型的變量執行LoadHtml以前,加上:
1
|
HtmlNode.ElementsFlags.Remove(
"tagName"
);
|
即,對於我以前的crifanlib.cs中的:
1
2
3
4
5
6
7
8
|
public
HtmlAgilityPack.HtmlDocument htmlToHtmlDoc(
string
html)
{
HtmlAgilityPack.HtmlDocument htmlDoc =
new
HtmlAgilityPack.HtmlDocument();
htmlDoc.LoadHtml(html);
return
htmlDoc;
}
|
換成:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public
HtmlAgilityPack.HtmlDocument htmlToHtmlDoc(
string
html)
{
HtmlAgilityPack.HtmlDocument htmlDoc =
new
HtmlAgilityPack.HtmlDocument();
//make some html tag: form/option, has child
HtmlNode.ElementsFlags.Remove(
"form"
);
HtmlNode.ElementsFlags.Remove(
"option"
);
htmlDoc.LoadHtml(html);
return
htmlDoc;
}
|
便可。
如此,後續解析html獲得的form,option等tag,其child就是咱們所但願的內容了。
另外:
1.對因而否還有其餘特殊的html的tag,也是默認被處理爲子節點變爲sibling的,就不知道了。
2.等有空再去深究背後的那個HTML 3.2規範是怎麼定義的。