Мне удалось извлечь имя и изображение персонажа из 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 #? , например, json.net :
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>
.
Демонстрационная скрипка здесь .