Подтвердить что ты не робот

Разбор 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

4b9b3361

Ответ 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 (это был намек, Кейт), дает разумную работу набор функций для трамплина.