|
|
|
|
using HtmlAgilityPack;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Net;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using System.Web;
|
|
|
|
|
using PhilExampleCrawler.Common.Models;
|
|
|
|
|
|
|
|
|
|
namespace PhilExampleCrawler.Core
|
|
|
|
|
{
|
|
|
|
|
internal class BaseCrawler_HAP
|
|
|
|
|
{
|
|
|
|
|
#region HTML Tags & Selectors
|
|
|
|
|
const string SELECTOR_INSERTION = "#srchrslt-adtable > .ad-listitem > article"; //reflects found insertions on the search result page
|
|
|
|
|
|
|
|
|
|
internal const string SELECTOR_TOPAD = ".aditem-main .aditem-main--top .aditem-main--top--right i.icon-feature-topad";
|
|
|
|
|
internal const string SELECTOR_HIGHLIGHT = ".aditem-main .aditem-main--top .aditem-main--top--right i.icon-feature-highlight";
|
|
|
|
|
internal const string SELECTOR_BOTTOM_TAG = ".aditem-main .aditem-main--bottom p .simpletag.tag-small";
|
|
|
|
|
|
|
|
|
|
internal const string SELECTOR_LOCATION = ".aditem-main .aditem-main--top .aditem-main--top--left";
|
|
|
|
|
|
|
|
|
|
internal const string ATTR_DATA_HREF = "data-href";
|
|
|
|
|
|
|
|
|
|
internal const string BOTTOM_TAG_GESUCH = "GESUCH";
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
readonly HtmlWeb _web = new();
|
|
|
|
|
List<string> _cachedHrefs = new();
|
|
|
|
|
bool _firstCrawl = true;
|
|
|
|
|
bool _crawling = false;
|
|
|
|
|
|
|
|
|
|
internal event EventHandler<Insertion> OnNewInsertionFound;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//TODO: crawl categories
|
|
|
|
|
//TODO: crawl locations
|
|
|
|
|
|
|
|
|
|
internal void StartCrawling(CrawlSearchParams searchParams, int intervalSec = 10)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal void Crawl(CrawlSearchParams searchParams, int timeout_ms)
|
|
|
|
|
{
|
|
|
|
|
var searchUrl = AsUrl(searchParams);
|
|
|
|
|
var searchResultDoc = _web.Load(searchUrl, timeout_ms);
|
|
|
|
|
|
|
|
|
|
if (searchResultDoc == null)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var insNodes = GetInsertionNodes(searchResultDoc);
|
|
|
|
|
CompareToFoundNodes(insNodes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//TODO: Implement CancellationToken
|
|
|
|
|
internal void StopCrawling()
|
|
|
|
|
{
|
|
|
|
|
_crawling = false;
|
|
|
|
|
_firstCrawl = true;
|
|
|
|
|
_cachedHrefs = new List<string>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void CompareToFoundNodes(IList<HtmlNode> insertionNodes)
|
|
|
|
|
{
|
|
|
|
|
List<(string href, HtmlNode srcNode)> insHrefNodes = GetInsertionHrefs(insertionNodes);
|
|
|
|
|
|
|
|
|
|
if (_firstCrawl)
|
|
|
|
|
{
|
|
|
|
|
_cachedHrefs.AddRange(insHrefNodes.Select(x => x.href));
|
|
|
|
|
_firstCrawl = false;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
foreach ((string href, HtmlNode srcNode) in insHrefNodes)
|
|
|
|
|
{
|
|
|
|
|
if (!_cachedHrefs.Any(x => x == href))
|
|
|
|
|
{
|
|
|
|
|
_cachedHrefs.Add(href);
|
|
|
|
|
OnNewInsertionFound?.Invoke(this, GetInsertion(srcNode));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#region Node Methods
|
|
|
|
|
private static IList<HtmlNode> GetInsertionNodes(HtmlDocument doc)
|
|
|
|
|
{
|
|
|
|
|
IList<HtmlNode> insNodes = doc.QuerySelectorAll(SELECTOR_INSERTION);
|
|
|
|
|
return insNodes.Where(n => n != null
|
|
|
|
|
&& n.HasAttributes
|
|
|
|
|
&& n.Attributes.Any(a => a.Name == ATTR_DATA_HREF)).ToList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static List<(string href, HtmlNode srcNode)> GetInsertionHrefs(IList<HtmlNode> insertionNodes)
|
|
|
|
|
=> insertionNodes.Select(x => (x.Attributes["data-href"].Value, x)).ToList();
|
|
|
|
|
|
|
|
|
|
private static Insertion GetInsertion(HtmlNode insertionNode)
|
|
|
|
|
{
|
|
|
|
|
//var i = new Insertion
|
|
|
|
|
//{
|
|
|
|
|
// Href = insertionNode.Attributes["data-href"].Value,
|
|
|
|
|
// IsTopAd = insertionNode.QuerySelector(SELECTOR_TOPAD) != null,
|
|
|
|
|
// IsHighlight = insertionNode.QuerySelector(SELECTOR_HIGHLIGHT) != null,
|
|
|
|
|
//};
|
|
|
|
|
|
|
|
|
|
//var plzLoc = GetInsertionLocation(insertionNode);
|
|
|
|
|
//Console.WriteLine("Plz: " + plzLoc.plz + " --- " + "Loc: " + plzLoc.loc);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//var reqNode = insertionNode.QuerySelector(SELECTOR_BOTTOM_TAG);
|
|
|
|
|
//if (reqNode != null)
|
|
|
|
|
// i.IsRequest = reqNode.InnerHtml?.ToUpperInvariant() == BOTTOM_TAG_GESUCH;
|
|
|
|
|
|
|
|
|
|
//return i;
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static (int plz, string loc) GetInsertionLocation(HtmlNode insertionNode)
|
|
|
|
|
{
|
|
|
|
|
var locNode = insertionNode.QuerySelector(SELECTOR_LOCATION);
|
|
|
|
|
string inner = locNode.InnerHtml;
|
|
|
|
|
|
|
|
|
|
if (locNode != null && !string.IsNullOrEmpty(inner))
|
|
|
|
|
{
|
|
|
|
|
if (inner.IndexOf(">") > -1)
|
|
|
|
|
inner = inner.Substring(inner.LastIndexOf(">") + 1).Trim();
|
|
|
|
|
|
|
|
|
|
if(inner.Length >= 5 && int.TryParse(inner.Substring(0, 5), out int plz))
|
|
|
|
|
{
|
|
|
|
|
return (plz, inner.Substring(5).Trim());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return (-1, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const string nums = "0123456789";
|
|
|
|
|
|
|
|
|
|
private static List<Insertion> GetInsertions(IList<HtmlNode> insertionNodes)
|
|
|
|
|
=> insertionNodes.Select(x => GetInsertion(x)).ToList();
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private string AsUrl(CrawlSearchParams searchParams)
|
|
|
|
|
{
|
|
|
|
|
return string.Format(Config.EXAMPLE_SEARCH_URL, HttpUtility.UrlEncode(searchParams.KeyWords),
|
|
|
|
|
HttpUtility.UrlEncode(searchParams.LocationStr),
|
|
|
|
|
searchParams.CategoryID,
|
|
|
|
|
searchParams.Radius);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|