今日は、カレンダー Web サイトを検索してイベントのタグを見つける Web スクレイパーを作成して、タグを検索して、そのイベントに誰が従事したかに関する情報を取得できるようにしたいと考えました。
問題は、検索したい div が js によって追加されるのですが、htmlagilitypack を使用してそれを取得するにはどうすればよいでしょうか?
カレンダー Web サイト: https://esel.at/termine
私のコード:
using System;
using HtmlAgilityPack;
using System.Linq;
using System.Diagnostics;
using System.Threading;
namespace ESEL_Scraper
{
class Program
{
static void Main(string[] args)
{
string Url = $"https://esel.at/termine";
HtmlWeb web = new HtmlWeb();
HtmlDocument doc = web.Load(Url);
HtmlNode[] nodes = doc.DocumentNode.SelectNodes("//div[@class='content']").ToArray();
for(int i = 0; i < nodes.Length; i++) {
Console.WriteLine(nodes[i].InnerText);
}
}
}
}
SelectNodes は、探しているものが見つからない場合は null を返します。そのため、null 例外が発生します。 「div」はありません。 class = "content" の要素。クラスに変更すると、そのページの div 要素によって使用されているため、結果が得られます。
HtmlAgility パック「SelectNodes」を使用すると、結果を使用する前に、何らかの方法で null チェックを行う必要があります。
2
しかし、Web サイトの HTML をチェックインすると、クラス「content」がありました。
– af21112020 年 9 月 5 日 9:17
わかりました。うまくいかない理由がわかった気がします。クラス「content」を持つ div は、 jsによって追加されます。 div の取得方法を知っている人はいますか?
– af21112020 年 9 月 5 日 10:26
短い答え: できません。 Web ページの読み込み時に追加されるデータを解析することは、HtmlAgilityPack を使用することはできません。ページの最初のソース コードにはデータがありません。 長い答え: おそらく、イベントのデータを取得する API 呼び出しがあり、JavaScript 経由でページにプッシュされます。 URLを調べてみるが使用されているので、それを解析してみてください。それは次のとおりです: https://esel.at/api/termine/data?date=05.09.2020&selection=false
記載されているように、JavaScript でコンテンツを追加します。基本的なネットワーク検査を使用すると、別のネットワーク要求があることがわかります。
ここで取得するのは、JavaScript を使用して HTML に追加される JSON 形式のデータです。 HtmlAgility パックを使用する代わりに、JSON を解析する必要があります。以下の例では、これを行うために Newtonsoft.Json パッケージを使用しています。
コードは次のとおりです。
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
namespace ESEL_Scraper
{
internal class Program
{
private static void Main(string[] args)
{
//Simply create request to the API and deserialize JSON using the Root class
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
CookieContainer cookies = new CookieContainer();
// Set the date you want in the link, in this example it's 06.09.2020
var request = (HttpWebRequest)WebRequest.Create("https://esel.at/api/termine/data?date=06.09.2020&selection=false");
request.CookieContainer = cookies;
request.Method = "GET";
request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36";
request.ContentType = "application/json";
request.Headers.Add("accept-language", "en,hr;q=0.9");
request.Headers.Add("accept-encoding", "");
request.Headers.Add("Upgrade-Insecure-Requests", "1");
WebResponse response = request.GetResponse();
StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8);
string responseFromServer = reader.ReadToEnd();
reader.Close();
response.Close();
//Deserialize Json
Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(responseFromServer);
foreach (var el in myDeserializedClass.termine)
{
//Get any field you need
Console.WriteLine(el.title);
Console.WriteLine(el.location);
}
Console.ReadLine();
}
}
// Based on the JSON response https://pastebin.com/Xa5gSp50 I have generated classes using this website: https://json2csharp.com/
public class Termine
{
public int id { get; set; }
public string title { get; set; }
public string category { get; set; }
public string startdate { get; set; }
public string startdatetime { get; set; }
public string starttime { get; set; }
public string enddate { get; set; }
public List<object> runtime { get; set; }
public string thumbnail { get; set; }
public string thumbnail_credits { get; set; }
public string type { get; set; }
public string recommended { get; set; }
public bool online_event { get; set; }
public object feed_urls { get; set; }
public string status { get; set; }
public string tags { get; set; }
public string url { get; set; }
public string sort_date { get; set; }
public string sort_category { get; set; }
public string location_url { get; set; }
public string location { get; set; }
}
public class Meta
{
public List<string> next { get; set; }
public DateTime now { get; set; }
public List<string> date { get; set; }
public DateTime end { get; set; }
public DateTime runtime { get; set; }
public int upcoming { get; set; }
public int running { get; set; }
public int termine { get; set; }
}
public class Root
{
public List<Termine> termine { get; set; }
public Meta meta { get; set; }
}
}