Как получить определенную информацию HTML с данного сайта - PullRequest
0 голосов
/ 13 июня 2019

Я пытаюсь запрограммировать API для разногласий, и мне нужно извлечь две части информации из HTML-кода веб-страницы https://myanimelist.net/character/214 (и других аналогичных страниц с URL-адресами формы https://myanimelist.net/character/Nдля целых чисел N), а именно URL-адрес изображения персонажа (в данном случае https://cdn.myanimelist.net/images/characters/14/54554.jpg) и имя персонажа (в данном случае Youji Kudou ).После этого мне нужно сохранить эти две части информации в JSON.

Я использую для этого HTMLAgilityPack, но пока не совсем понимаю.Следующая моя первая попытка:

public static void Main()
{ 
    var html = "https://myanimelist.net/character/214";
    HtmlWeb web = new HtmlWeb();
    var htmlDoc = web.Load(html);
    var htmlNodes = htmlDoc.DocumentNode.SelectNodes("//body");

    foreach (var node in htmlNodes.Descendants("tr/td/div/a/img"))
    {
        Console.WriteLine(node.InnerHtml);
    }
}

К сожалению, это не дает никакого вывода.Если я правильно пошел по пути (что, вероятно, является первой ошибкой), это должно быть "tr / td / div / a / img".Я не получаю ошибок, он работает, но я не получаю вывод.

Моя вторая попытка:

public static void Main()
{
    var html = "https://myanimelist.net/character/214";
    HtmlWeb web = new HtmlWeb();
    var htmlDoc = web.Load(html);
    var htmlNodes = htmlDoc.DocumentNode.SelectNodes("//body");
    HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument();
    var script = htmlDoc.DocumentNode.Descendants()
                     .Where(n => n.Name == "tr/td/a/img")
                     .First().InnerText;

    // Return the data of spect and stringify it into a proper JSON object
    var engine = new Jurassic.ScriptEngine();
    var result = engine.Evaluate("(function() { " + script + " return src; })()");
    var json = JSONObject.Stringify(engine, result);

    Console.WriteLine(json);
    Console.ReadKey();
}

Но это также не работает.

Как извлечь необходимую информацию?

РЕДАКТИРОВАТЬ:

Итак, я пошел намного дальше и нашел решение для поиска ссылки.Это было довольно просто.Но сейчас я застрял в поиске имени персонажа.Веб-сайт структурирован так же, как и все остальные ссылки, которые есть (смена последнего номера), поэтому я хочу найти много разных ссылок через цикл for.Вот как я пытался это сделать:

for (int i = 1; i <= 1000; i++)
            {
                HtmlWeb web = new HtmlWeb();
                var html = "https://myanimelist.net/character/" + i;
                var htmlDoc = web.Load(html);
                foreach (var item in htmlDoc.DocumentNode.SelectNodes("//*[@]"))
                {
                    string n;
                    n = item.GetAttributeValue("src", "");
                    foreach (var item2 in htmlDoc.DocumentNode.SelectNodes("//*[@src and @alt='" + n + "']"))
                    {
                        Console.WriteLine(item2.GetAttributeValue("src", ""));
                    }
                }


            }

в первом foreach я бы попытался найти имя, которое всегда находится в одной и той же позиции (например, http://prntscr.com/o1uo3c и http://prntscr.com/o1uo91, а точнее: http://prntscr.com/o1xzbk), но я еще не выяснил, как это происходит. Поскольку структура в HTML не имеет какого-либо типа телосложения, с которым я могу следовать. Второй цикл foreach - это поискдля URL, который работает на данный момент, и n должен дать мне имя, чтобы я мог понять его для каждого другого символа.

Ответы [ 2 ]

1 голос
/ 15 июня 2019

Мне удалось извлечь имя и изображение персонажа из https://myanimelist.net/character/214, используя следующий метод:

public static CharacterData ExtractCharacterNameAndImage(string url)
{
    //Use the following if you are OK with hardcoding the structure of <div> elements.
    //var tableXpath             = "/html/body/div[1]/div[3]/div[3]/div[2]/table"; 
    //Use the following if you are OK with hardcoding the fact that the relevant table comes first.
    var tableXpath             = "/html/body//table"; 
    var nameXpath              = "tr/td[2]/div[4]";
    var imageXpath             = "tr/td[1]/div[1]/a/img";

    var htmlDoc = new HtmlWeb().Load(url);

    var table = htmlDoc.DocumentNode.SelectNodes(tableXpath).First();

    var name = table.SelectNodes(nameXpath).Select(n => n.GetDirectInnerText().Trim()).SingleOrDefault();
    var imageUrl = table.SelectNodes(imageXpath).Select(n => n.GetAttributeValue("src", "")).SingleOrDefault();

    return new CharacterData { Name = name, ImageUrl = imageUrl, Url = url };
}

Где CharacterData определяется следующим образом:

public class CharacterData
{
    public string Name { get; set; }
    public string ImageUrl { get; set; }
    public string Url { get; set; }
}

После этого символьные данные можно сериализовать в JSON с помощью любого из инструментов Как записать файл JSON на C #? , например, :

var url = "https://myanimelist.net/character/214";

var data = ExtractCharacterNameAndImage(url);
var json = JsonConvert.SerializeObject(data, Formatting.Indented);

Console.WriteLine(json);

Какие выходные данные

{
  "Name": "Youji Kudou",
  "ImageUrl": "https://cdn.myanimelist.net/images/characters/14/54554.jpg",
  "Url": "https://myanimelist.net/character/214"
}

Если вы хотите, чтобы Name включал японский в скобках, замените GetDirectInnerText() просто InnerText, что приведет к:

{
  "Name": "Youji Kudou (工藤耀爾)",
  "ImageUrl": "https://cdn.myanimelist.net/images/characters/14/54554.jpg",
  "Url": "https://myanimelist.net/character/214"
}

В качестве альтернативы, если вы предпочитаете, вы можете извлечь имя символа из заголовка документа:

var title = string.Concat(htmlDoc.DocumentNode.SelectNodes("/html/head/title").Select(n => n.InnerText.Trim()));
var index = title.IndexOf("- MyAnimeList.net");
if (index >= 0)
    title = title.Substring(0, index).Trim();

Как определить правильные строки XPath?

Во-первых, используя Firefox 66, я открыл отладчик и загрузил https://myanimelist.net/character/214 в окне с видимыми инструментами отладки.

Далее, следуя инструкциям Как найти путь xpath элемента в инспекторе Firefox , я выбрал Ёдзи Куду (工藤 耀 爾) узел и скопировал его XPath, который оказался:

/html/body/div[1]/div[3]/div[3]/div[2]/table/tbody/tr/td[2]/div[4]

Затем я попытался выбрать этот узел, используя SelectNodes() ... и получил нулевой результат.Но почему?Чтобы определить это, я создал процедуру отладки, которая разбивала бы путь на более длинные части и определяла, где происходит сбой:

static void TestSelect(HtmlDocument htmlDoc, string xpath)
{
    Console.WriteLine("\nInput path: " + xpath);
    var splitPath = xpath.Split('/');
    for (int i = 2; i <= splitPath.Length; i++)
    {
        if (splitPath[i-1] == "")
            continue;
        var thisPath = string.Join("/", splitPath, 0, i);
        Console.Write("Testing \"{0}\": ", thisPath);
        var result = htmlDoc.DocumentNode.SelectNodes(thisPath);
        Console.WriteLine("result count = {0}", result == null ? "null" : result.Count.ToString());
    }
}

Это выводит следующее:

Input path: /html/body/div[1]/div[3]/div[3]/div[2]/table/tbody/tr/td[2]/div[4]
Testing "/html": result count = 1
Testing "/html/body": result count = 1
Testing "/html/body/div[1]": result count = 1
Testing "/html/body/div[1]/div[3]": result count = 1
Testing "/html/body/div[1]/div[3]/div[3]": result count = 1
Testing "/html/body/div[1]/div[3]/div[3]/div[2]": result count = 1
Testing "/html/body/div[1]/div[3]/div[3]/div[2]/table": result count = 1
Testing "/html/body/div[1]/div[3]/div[3]/div[2]/table/tbody": result count = null
Testing "/html/body/div[1]/div[3]/div[3]/div[2]/table/tbody/tr": result count = null
Testing "/html/body/div[1]/div[3]/div[3]/div[2]/table/tbody/tr/td[2]": result count = null
Testing "/html/body/div[1]/div[3]/div[3]/div[2]/table/tbody/tr/td[2]/div[4]": result count = null

Как вы можетевидите, что-то идет не так, выбирая элемент пути <tbody>.Ручная проверка InnerHtml, возвращенного путем выбора /html/body/div[1]/div[3]/div[3]/div[2]/table, показала, что по какой-то причине сервер не включает тег <tbody> при возврате HTML-объекта HtmlWeb - возможно, из-за некоторой разницы в заголовке запроса (s) предоставлено Firefox против HtmlWeb.После того как я пропустил элемент пути tbody, я смог успешно запросить имя персонажа, используя:

/html/body/div[1]/div[3]/div[3]/div[2]/table/tr/td[2]/div[4]

Аналогичный процесс предоставил следующий рабочий путь для изображения:

/html/body/div[1]/div[3]/div[3]/div[2]/table/tr/td[1]/div[1]/a/img

Поскольку оба запроса находят содержимое в одном и том же <table>, в своем окончательном коде я выбрал таблицу только один раз на отдельном шаге и удалил некоторые жесткие коды, относящиеся к конкретному вложению элементов <div>.

Демонстрационная скрипка здесь .

0 голосов
/ 15 июня 2019

Хорошо, чтобы закончить это, я округлил Кодекс, с благодарностью поддержанный dbc, и почти полностью внедрил в проект.Просто если у кого-то в более поздние дни, возможно, возникнут идентичные вопросы, вот они.Это выводит из определенного числа все имена персонажей, ссылки и изображения и записывает их в файл JSON и может быть адаптировано для других веб-сайтов.

using System;
using System.Linq;
using Newtonsoft.Json;
using HtmlAgilityPack;
using System.IO;

namespace SearchingHTML
{
    public class CharacterData
    {
        public string Name { get; set; }
        public string ImageUrl { get; set; }
        public string Url { get; set; }
    }
    public class Program
    {
        public static CharacterData ExtractCharacterNameAndImage(string url)
        {
            var tableXpath = "/html/body//table";
            var nameXpath = "tr/td[2]/div[4]";
            var imageXpath = "tr/td[1]/div[1]/a/img";

            var htmlDoc = new HtmlWeb().Load(url);
            var table = htmlDoc.DocumentNode.SelectNodes(tableXpath).First();
            var name = table.SelectNodes(nameXpath).Select(n => n.GetDirectInnerText().Trim()).SingleOrDefault();
            var imageUrl = table.SelectNodes(imageXpath).Select(n => n.GetAttributeValue("src", "")).SingleOrDefault();

            return new CharacterData { Name = name, ImageUrl = imageUrl, Url = url };
        }
        public static void Main()
        {
            int max = 10000;
            string fileName = @"C:\Users\path of your file.json";

            Console.WriteLine("Environment version: " + Environment.Version);
            Console.WriteLine("Json.NET version: " + typeof(JsonSerializer).Assembly.FullName);
            Console.WriteLine("HtmlAgilityPack version: " + typeof(HtmlDocument).Assembly.FullName);
            Console.WriteLine();

            for (int i = 6; i <= max; i++)
            {
                try
                {
                    var url = "https://myanimelist.net/character/" + i;
                    var htmlDoc = new HtmlWeb().Load(url);
                    var data = ExtractCharacterNameAndImage(url);
                    var json = JsonConvert.SerializeObject(data, Formatting.Indented);
                    Console.WriteLine(json);
                    TextWriter tsw = new StreamWriter(fileName, true);
                    tsw.WriteLine(json);
                    tsw.Close();
                } catch (Exception ex) { }
            }

        }
    }
}

/*******************************************************************************************************************************
 ****************************************************IF TESTING IS REQUIERED****************************************************
 *******************************************************************************************************************************
 * 
 * static void TestSelect(HtmlDocument htmlDoc, string xpath)

        Console.WriteLine("\nInput path: " + xpath);
        var splitPath = xpath.Split('/');
        for (int i = 2; i <= splitPath.Length; i++)
        {
            if (splitPath[i - 1] == "")
                continue;
            var thisPath = string.Join("/", splitPath, 0, i);
            Console.Write("Testing \"{0}\": ", thisPath);
            var result = htmlDoc.DocumentNode.SelectNodes(thisPath);
            Console.WriteLine("result count = {0}", result == null ? "null" : result.Count.ToString());
        }
    }

  *******************************************************************************************************************************
  *********************************************FOR TESTING ENTER THIS INTO MAIN CLASS********************************************
  *******************************************************************************************************************************
  * 
  *     var url2 = "https://myanimelist.net/character/256";
        var data2 = ExtractCharacterNameAndImage(url2);
        var json2 = JsonConvert.SerializeObject(data2, Formatting.Indented);

        Console.WriteLine(json2);



        var nameXpathFromFirefox = "/html/body/div[1]/div[3]/div[3]/div[2]/table/tbody/tr/td[2]/div[4]";
        var imageXpathFromFirefox = "/html/body/div[1]/div[3]/div[3]/div[2]/table/tbody/tr/td[1]/div[1]/a/img";
        TestSelect(htmlDoc, nameXpathFromFirefox);
        TestSelect(htmlDoc, imageXpathFromFirefox);
        var nameXpathFromFirefoxFixed = "/html/body/div[1]/div[3]/div[3]/div[2]/table/tr/td[2]/div[4]";
        var imageXpathFromFirefoxFixed = "/html/body/div[1]/div[3]/div[3]/div[2]/table/tr/td[1]/div[1]/a/img";
        TestSelect(htmlDoc, nameXpathFromFirefoxFixed);
        TestSelect(htmlDoc, imageXpathFromFirefoxFixed);

  *******************************************************************************************************************************
  *******************************************************************************************************************************
  *******************************************************************************************************************************
  */
...