HtmlAgilityPack을 사용하여 일부 데이터를 스크랩하고 있습니다.
HTML은 다음과 같습니다.
<div id="id-here">
<dl>
<dt> Field Name </dt>
<dd> Value for above field name </dd>
<dt> Field Name </dt>
<dd> Value for above field name </dd>
<dt> Field Name </dt>
<dd> Value for above field name </dd>
</dl>
</div>
이제 내가 가진 문제는 필드 수를 항상 설정할 수있는 것은 아니기 때문에 각 필드에 안정적으로 액세스 할 수 없습니다.
//*[@id="id-here"]/dl[1]/dd[1]
dd [1]은 한 페이지의 이름 일 수도 있고 사용자가 이름을 기입하지 못한 다른 필드의 전화 일 수도 있으므로 필드가 숨겨집니다.
그래서 나는 모든 DT와 DD 노드를 다음과 같이 얻는다 :
//*[@id="id-here"]/dl[1]/dt | //*[@id="id-here"]/dl[1]/dd
이제 각 노드를 점검하여 원하는 필드와 일치하는지 확인하고 NextSibling 값을 다음과 같이 설정합니다.
foreach (HtmlNode node in details)
{
if (node.InnerText.Contains("Tel:")) telephone = node.NextSibling.InnerText;
if (node.InnerText.Contains("Email:")) email = node.NextSibling.InnerText;
}
이것은 전화기에서 잘 작동하지만 "Email :"노드가 나타나면 어떤 이유에서든 다음 형제에게는 확실히 데이터가 있지만 NextSibling.InnerHTML
과 NextSibling.InnerText
는 비어 있습니다. 실제로 node
에 details
하고 InnerHTML
전체 서식있는 링크로, InnerText
를 전자 메일 주소로 사용하면됩니다.
NextSibling.InnerText
는 A 태그가 자식 또는 무언가로 만들고 있기 때문에 작동하지 않습니까? 디버거에서 한 번 보았고 NextSibling
필요한 정보를 찾지 NextSibling
.
나는 대답이 터무니없이 간단하다라고 확신한다, 나는 단지 그것을 이해할 수 없다. 누구든지 나를 비참하게 만들었지?
이것이 일어나고있는 이유는 node
가 공백으로 dd
요소에서 분리 된 dt
요소 인 경우 node.NextSibling
은 공백 문자 노드 ( </dt>
와 <dd>
). 디버거에서 보면, node.NextSibling
의 NodeType
은 HtmlNodeType.Text
이지 HtmlNodeType.Element
.
dt
노드의 해당 dd
텍스트를 가져 오는 편리한 메소드를 만드는 것이 좋습니다.
internal static string GetMatchingDdValue(HtmlNode dtNode)
{
var found = dtNode.SelectSingleNode("following-sibling::*[1][self::dd]");
return found == null ? "" : found.InnerText;
}
그러면 다음과 같이 사용할 수 있습니다.
if (node.InnerText.Contains("Tel:")) { telephone = GetMatchingDdValue(node); }
위의 메서드에서 사용한 다소 까다로운 XPath의 분석은 다음과 같습니다.
(a) following-sibling::*
^ 현재 노드와 동일한 부모를 공유하고 그 뒤에 발생하는 모든 요소를 선택하십시오.
(b) following-sibling::*[1]
^ set (a)의 첫 번째 노드를 선택하십시오 (있는 경우)
(c) following-sibling::*[1][self::dd]
^ 이름이 "dd"인 요소 인 set (b)의 모든 노드를 선택합니다.
SelectSingleNode()
는 set (c)의 첫 번째 노드를 선택합니다. 항상 1 또는 0 노드 여야합니다.
당신은 following-sibling::dd
또는 following-sibling::*
얻을 가능성이 가장 높지만 위 경로에는 안전 장치가 포함되어 있습니다. 예를 들어 어떤 이유로 든 다음 XML을 가지고 현재 노드가 Tel:
요소 인 경우 :
<dl>
<dt>Tel:</dt>
<dt>Address:</dt>
<dd>50 Fake St.</dd>
</dl>
following-sibling::dd
는 결과를 "50 Fake St."으로, following-sibling::*
는 결과 "Address :"를 제공합니다. 대신, following-sibling::*[1][self::dd]
는이 경우 빈 노드 집합을 선택하므로 메서드는 빈 문자열을 결과로 올바르게 생성합니다.
var html = @"
<div id='id-here'>
<dl>
<dt> Field Name </dt>
<dd> Value for above field name </dd>
<dt> Field Name </dt>
<dd> Value for above field name </dd>
<dt> Field Name </dt>
<dd> Value for above field name </dd>
</dl>
</div>";
html = new Regex(">\r\n\\s*<").Replace(html,"><");
var doc = new HtmlAgilityPack.HtmlDocument();
doc.LoadHtml(html);
Console.Write(doc.DocumentNode.SelectNodes("//dt")[0].NextSibling.OuterHtml);
<dd> Value for above field name </dd>