c# - 使用 HtmlAgilityPack 获取同一域上的所有链接

标签 c# dom web-crawler html-agility-pack

我正在编写代码来抓取给定网页上的链接。我正在尝试HtmlAgilityPack读取 html 内容(在我的示例中使用 www.google.co.uk)。我使用的代码如下:

class Program
{
    static async Task Main(string[] args)
    {
        var links = GetLinks(new Uri("https://www.google.co.uk"));
        foreach (var link in links)
        {
            Console.WriteLine(link);
        }
    }

    private static List<string> GetLinks(Uri uri)
    {
        var doc = new HtmlWeb().Load(uri);
        return doc.DocumentNode.Descendants("a")
            .Select(a => a.GetAttributeValue("href", null))
            .Distinct()
            .Where(u => !string.IsNullOrEmpty(u)).ToList();
    }
}

我正在删除空链接和重复链接。这给出了以下结果:

 - https://www.google.co.uk/imghp?hl=en&tab=wi
 - https://maps.google.co.uk/maps?hl=en&tab=wl
 - https://play.google.com/?hl=en&tab=w8
 - https://www.youtube.com/?gl=GB&tab=w1 
 - https://news.google.com/?tab=wn
 - https://mail.google.com/mail/?tab=wm 
 - https://drive.google.com/?tab=wo
 - https://www.google.co.uk/intl/en/about/products?tab=wh
 - http://www.google.co.uk/history/optout?hl=en 
 - /preferences?hl=en
 - https://accounts.google.com/ServiceLogin?hl=en&passive=true&continue=https://www.google.co.uk/&ec=GAZAAQ
 - /advanced_search?hl=en-GB&amp;authuser=0 
 - /intl/en/ads/ 
 - /services/
 - /intl/en/about.html
 - https://www.google.co.uk/setprefdomain?prefdom=US&amp;sig=K_eDMDym3RsPb7-MzvJkS4b2Eg4ns%3D
 - /intl/en/policies/privacy/ 
 - /intl/en/policies/terms/

我想进一步缩小链接范围,以选择仅与同一子域“www.google.co.uk”匹配的链接,包括相对网址。结果列表将缩小为:

 - https://www.google.co.uk/imghp?hl=en&tab=wi
 - https://www.google.co.uk/intl/en/about/products?tab=wh
 - http://www.google.co.uk/history/optout?hl=en 
 - /preferences?hl=en
 - /advanced_search?hl=en-GB&amp;authuser=0 
 - /intl/en/ads/ 
 - /services/
 - /intl/en/about.html
 - https://www.google.co.uk/setprefdomain?prefdom=US&amp;sig=K_eDMDym3RsPb7-MzvJkS4b2Eg4ns%3D
 - /intl/en/policies/privacy/ 
 - /intl/en/policies/terms/

我希望以最有效的方式修改上面的代码来实现此目的,但不确定使用 HtmlAgilityPack 实现它的最佳方法。我已经找到了这个解决方案:

private static List<string> GetLinks(Uri uri)
{
    var doc = new HtmlWeb().Load(uri);
    return doc.DocumentNode.Descendants("a")
        .Select(a => 
        { 
            var val = a.GetAttributeValue("href", null);

            if (val.StartsWith("/"))
                val = $"{uri.Scheme}://{uri.Host}{val}";

            return val;
        })
        .Distinct()
        .Where(u =>
        {
            return !string.IsNullOrEmpty(u) 
                   && u.Contains(uri.Host); // using contains here is a problem
        }).ToList();
}

我非常清楚这里涉及的字符串操作量,将相对 URL 更改为完全限定并匹配“Contains”,这可能会留下不正确的结果。有没有人有一个不那么浪费(字符串比较和操作)的解决方案?

非常感谢任何建议!

最佳答案

除非您的 HTML 很大并且有大量链接,否则我不会担心此处的性能优化。

听起来主要问题只是在 Where 子句中找到 Contains 的替代品。我会使用 Regex 来实现这一点。您可以在方法的开头构建一次匹配模式,然后在 Where 中使用 IsMatch,如下所示:

private static List<string> GetLinks(Uri uri)
{
    var regex = new Regex("^http(s)?://" + uri.Host, RegexOptions.IgnoreCase);
    var doc = new HtmlWeb().Load(uri);

    return doc.DocumentNode
        .Descendants("a")
        .Select(a =>
        {
            var val = a.GetAttributeValue("href", string.Empty);
            return val.StartsWith("/") ? uri.GetLeftPart(UriPartial.Authority) + val : val;
        })
        .Distinct()
        .Where(u => !string.IsNullOrEmpty(u) && regex.IsMatch(u))
        .ToList();
}

fiddle :https://dotnetfiddle.net/BVdv1Y

关于c# - 使用 HtmlAgilityPack 获取同一域上的所有链接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67013087/

相关文章:

web-crawler - 获取站点的 URL 列表

c# - Entity Framework 返回空集

c# - 套接字发送null的长字符串

math - 给定元素的当前边界矩形及其变换矩阵,计算未变换元素的边界矩形

ruby - 如何在 Ruby 中使用 Mechanize 选择单选按钮?

python - 使用 Xpath 获取更多相同类型的元素

c# - DisplayFor 的 IEnumerable<T> 类型 View 模型的 Razor 语法

c# - 此请求的授权已被拒绝 - 新的 Web API 项目

javascript - 如何创建一个具有html标签所有属性的对象?

javascript - 根据数组 JS 中的值创建按钮