Разбор html с пакетом Agility Pack и Linq
У меня есть следующий HTML
(..)
<tbody>
<tr>
<td class="name"> Test1 </td>
<td class="data"> Data </td>
<td class="data2"> Data 2 </td>
</tr>
<tr>
<td class="name"> Test2 </td>
<td class="data"> Data2 </td>
<td class="data2"> Data 2 </td>
</tr>
</tbody>
(..)
У меня есть имя = > so "Test1" и "Test2". Я хочу знать, как я могу получить данные в "данных" и "данных2" на основе имени, которое у меня есть.
В настоящее время я использую:
var data =
from
tr in doc.DocumentNode.Descendants("tr")
from
td in tr.ChildNodes.Where(x => x.Attributes["class"].Value == "name")
where
td.InnerText == "Test1"
select tr;
Но я получаю {"Object reference not set to an instance of an object."}
, когда пытаюсь посмотреть data
Ответы
Ответ 1
Что касается вашей попытки, у вас есть две проблемы с кодом:
-
ChildNodes
является странным - он также возвращает белые текстовые узлы, которые не имеют атрибутов class
(не могут иметь атрибуты, конечно).
- Как заметил Джеймс Уолфорд, пространство вокруг текста значимо, вы, вероятно, захотите обрезать их.
С этими двумя исправлениями выполняются следующие работы:
var data =
from tr in doc.DocumentNode.Descendants("tr")
from td in tr.Descendants("td").Where(x => x.Attributes["class"].Value == "name")
where td.InnerText.Trim() == "Test1"
select tr;
Ответ 2
Вот путь XPATH - хм... все, кажется, забыли о силе XPATH и сосредоточились исключительно на С# XLinq, в наши дни: -)
Эта функция получает все значения данных, связанные с именем:
public static IEnumerable<string> GetData(HtmlDocument document, string name)
{
return from HtmlNode node in
document.DocumentNode.SelectNodes("//td[@class='name' and contains(text(), '" + name + "')]/following-sibling::td")
select node.InnerText.Trim();
}
Например, этот код сбрасывает все данные Test2:
HtmlDocument doc = new HtmlDocument();
doc.Load(yourHtml);
foreach (string data in GetData(doc, "Test2"))
{
Console.WriteLine(data);
}
Ответ 3
Здесь один подход - сначала проанализировать все данные в структуре данных, а затем прочитать их. Это немного грязно и, безусловно, нуждается в большей проверке, но здесь говорится:
HtmlWeb hw = new HtmlWeb();
HtmlDocument doc = hw.Load("http://jsbin.com/ezuge4");
HtmlNodeCollection nodes = doc.DocumentNode
.SelectNodes("//table[@id='MyTable']//tr");
var data = nodes.Select(
node => node.Descendants("td")
.ToDictionary(descendant => descendant.Attributes["class"].Value,
descendant => descendant.InnerText.Trim())
).ToDictionary(dict => dict["name"]);
string test1Data = data["Test1"]["data"];
Здесь я превращаю каждый <tr>
в словарь, где класс <td>
- это ключ, а текст - значение. Затем я перехожу список словарей в словарь словарей (tip - abstract that away), где ключ name
каждого <tr>
- это ключ.
Ответ 4
вместо
td.InnerText == "Test1"
попробовать
td.InnerText == " Test1 "
или
d.InnerText.Trim() == "Test1"
Ответ 5
Я могу порекомендовать один из двух способов:
http://htmlagilitypack.codeplex.com/, который преобразует html в действительный xml, который затем может быть запрошен с помощью OOTB Linq.
Или
Linq to HTML (http://www.superstarcoders.com/linq-to-html.aspx), который, хотя и не поддерживается на CodePlex (это был намек, Кейт), дает разумную работу набор функций для трамплина.