首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >发送Http请求

发送Http请求

作者头像
solenovex
发布于 2022-11-24 08:21:18
发布于 2022-11-24 08:21:18
4.6K00
代码可运行
举报
文章被收录于专栏:草根专栏草根专栏
运行总次数:0
代码可运行

有这么一本Python的书: <<Python 网络数据采集>>

我准备用.NET Core及第三方库实现里面所有的例子. 

这是第一部分, 主要使用的是AngleSharp: https://anglesharp.github.io/

(文章的章节书与该书是对应的)

第1章 初见网络爬虫

发送Http请求

 在python里面这样发送http请求, 它使用的是python的标准库urllib:

在.NET Core里面, 你可以使用HttpClient, 相应的C#代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
            var client = new HttpClient();
            HttpResponseMessage response = await client.GetAsync("http://pythonscraping.com/pages/page1.html");
            response.EnsureSuccessStatusCode();
            var responseBody = await response.Content.ReadAsStringAsync();
            Console.WriteLine(responseBody);
            return responseBody;

或者可以简写为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
                var client = new HttpClient();
                var responseBody = await client.GetStringAsync("http://pythonscraping.com/pages/page1.html");
                Console.WriteLine(responseBody);

其结果如下:

使用AngleSharp解析html源码

python里面可以使用BeautifulSoup或者MechanicalSoup等库对html源码进行解析.

而.NET Core可以使用AngleSharpHtml Agility PackDotnetSpider(国产, 也支持元素抽取).等库来操作Html文档.

这里我先使用的是AngleSharp, AngleSharp的解析库可以使用标准的W3C规范来解析HTML, MathML, XML, SVG和CSS. 它支持.NET Standard 1.0.

安装AngleSharp

通过Nuget即可: https://www.nuget.org/packages/AngleSharp/

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Install-Package AngleSharp 

或者dotnet-cli:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
dotnet add package AngleSharp

AngleSharp的一个简单例子

下面这个例子(1.2.2)是把页面中h1元素的内容显示出来.

书中Python的代码:

下面是.NET Core的C#代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        public static async Task ReadWithAngleSharpAsync()
        {
            var htmlSourceCode = await SendRequestWithHttpClientAsync();
            var parser = new HtmlParser();
            var document = await parser.ParseAsync(htmlSourceCode);

            Console.WriteLine($"Serializing the (original) document: {document.QuerySelector("h1").OuterHtml}");
            Console.WriteLine($"Serializing the (original) document: {document.QuerySelector("html > body > h1").OuterHtml}");
        }

在这里AngleSharp首先需要创建一个可以循环使用的HtmlParser(Html解析器), 然后使用解析器解析html源码即可: parser.Parse() 或者异步版本 parser.ParseAsync().

解析返回对象的类型是IHtmlDocument, 里面是解析好的DOM. 其中DOM是和AngleSharp里的类这样对应的:

这个图其实是老一点的版本, 新版本的DOM模型是稍微有点不同的, 不过你只要理解这个意思就行...

AngleSharp有很多特点, 但是最重要的特点就是它支持querySelector()querySelectorAll()方法, 就像DOM的方法一样.

上面这个例子里, 其html的结构大致如下:

所以针对返回的IHtmlDocument对象document, 我们使用document.QuerySelector("h1").OuterHtml, 就可以返回h1的OuterHtml. 而使用document.QuerySelector("html > body > h1").OuterHtml 也是同样的效果, 因为标准的CSS选择器是都支持的.

QuerySelector()返回的是一个/0个元素, 相当于Linq的FirstOrDefault().

其运行结果如下:

异常情况处理

发送Http请求之后, 可能会发生错误, 例如网页不存在(或者请求时出错), 服务器不存在等等.

针对这些情况, .NET Core程序会返回HTTP错误, 可能是404也可能是500等. 但是所有的类型HttpClient都会抛出HttpRequestException, 我们可以这样处理这种异常:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        public static async Task ResponseWithErrorsAsync()
        {
            try
            {
                var client = new HttpClient();
                var responseBody = await client.GetStringAsync("http://notexistwebsite");
                Console.WriteLine(responseBody);
            }
            catch (HttpRequestException e)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("\nException Caught!");
                Console.WriteLine("Message :{0} ", e.Message);
            }
        }

但是即使网页获取成功了, 网页上的内容也并非完全是我们所期待的, 仍可能会抛出异常. 比如说你想要找的标签不存在, 那么就会返回null, 然后再调用改标签的属性, 就会发生NullReferenceException.

所以这种情况可以捕获NullReferenceException, 也可以使用代码判断:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        public static async Task ReadNonExistTagAsync()
        {
            var htmlSourceCode = await SendRequestWithHttpClientAsync();
            var parser = new HtmlParser();
            var document = await parser.ParseAsync(htmlSourceCode);

            var nonExistTag = document.QuerySelector("h8");
            Console.WriteLine(nonExistTag);
            Console.WriteLine($"nonExistTag is null: {nonExistTag is null}");

            try
            {
                Console.WriteLine(nonExistTag.QuerySelector("p").OuterHtml);
            }
            catch (NullReferenceException)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("Tag was not found");
            }
        }

完整的例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        public static async Task RunAllAsync()
        {
            Console.ForegroundColor = ConsoleColor.Red;
            async Task<string> GetTileAsync(string uri)
            {
                var httpClient = new HttpClient();
                try
                {
                    var responseHtml = await httpClient.GetStringAsync(uri);
                    var parser = new HtmlParser();
                    var document = await parser.ParseAsync(responseHtml);
                    var tagContent = document.QuerySelector("body > h8").TextContent;
                    return tagContent;
                }
                catch (HttpRequestException e)
                {
                    Console.WriteLine($"{nameof(HttpRequestException)}:");
                    Console.WriteLine("Message :{0} ", e.Message);
                    return null;
                }
                catch (NullReferenceException)
                {
                    Console.WriteLine($"{nameof(NullReferenceException)}:");
                    Console.WriteLine("Tag was not found");
                    return null;
                }
            }

            var title = await GetTileAsync("http://www.pythonscraping.com/pages/page1.html");
            if (string.IsNullOrWhiteSpace(title))
            {
                Console.WriteLine("Title was not found");
            }
            else
            {
                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine(title);
            }
        }

第2章 复杂HTML解析

首先我把请求Http返回HTML代码的那部分封装成了一个方法以便复用:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        public static async Task<string> GetHtmlSourceCodeAsync(string uri)
        {
            var httpClient = new HttpClient();
            try
            {
                var htmlSource = await httpClient.GetStringAsync(uri);
                return htmlSource;
            }
            catch (HttpRequestException e)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine($"{nameof(HttpRequestException)}: {e.Message}");
                return null;
            }
        }

CSS是网络爬虫的福音, 下面这两个元素在页面中可能会出现很多次:

我们可以使用AngleSharp里面的QuerySelectorAll()方法把所有符合条件的元素都找出来, 返回到一个结果集合里.

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        public static async Task FindGreenClassAsync()
        {
            const string url = "http://www.pythonscraping.com/pages/warandpeace.html";
            var html = await GetHtmlSourceCodeAsync(url);
            if (!string.IsNullOrWhiteSpace(html))
            {
                var parser = new HtmlParser();
                var document = await parser.ParseAsync(html);
                var nameList = document.QuerySelectorAll("span > .green");

                Console.WriteLine("Green names are:");
                Console.ForegroundColor = ConsoleColor.Green;
                foreach (var item in nameList)
                {
                    Console.WriteLine(item.TextContent);
                }
            }
            else
            {
                Console.WriteLine("No html source code returned.");
            }
        }

非常简单, 和DOM的标准操作是一样的.

如果只需要元素的文字部分, 那么就是用其TextContent属性即可.

再看个例子

1. 找出页面中所有的h1, h2, h3, h4, h5, h6元素

2. 找出class为green或red的span元素.

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        public static async Task FindByAttributeAsync()
        {
            const string url = "http://www.pythonscraping.com/pages/warandpeace.html";
            var html = await GetHtmlSourceCodeAsync(url);
            if (!string.IsNullOrWhiteSpace(html))
            {
                var parser = new HtmlParser();
                var document = await parser.ParseAsync(html);

                var headers = document.QuerySelectorAll("*")
                    .Where(x => new[] { "h1", "h2", "h3", "h4", "h5", "h6" }.Contains(x.TagName.ToLower()));
                Console.WriteLine("Headers are:");
                PrintItemsText(headers);

                var greenAndRed = document.All
                    .Where(x => x.TagName == "span" && (x.ClassList.Contains("green") || x.ClassList.Contains("red")));
                Console.WriteLine("Green and Red spans are:");
                PrintItemsText(greenAndRed);

                var thePrinces = document.QuerySelectorAll("*").Where(x => x.TextContent == "the prince");
                Console.WriteLine(thePrinces.Count());
            }
            else
            {
                Console.WriteLine("No html source code returned.");
            }

            void PrintItemsText(IEnumerable<IElement> elements)
            {
                foreach (var item in elements)
                {
                    Console.WriteLine(item.TextContent);
                }
            }
        }

这里我们可以看到QuerySelectorAll()的返回结果可以使用Linq的Where方法进行过滤, 这样就很强大了.

TagName属性就是元素的标签名.

此外, 还有一个document.All, All属性是该Document所有元素的集合, 它同样也支持Linq.

(该方法中使用了一个本地方法).

由于同时支持CSS选择器和Linq, 所以抽取元素的工作简单多了.

导航树

一个页面, 它的结构可以是这样的:

这里面有几个概念:

子标签和后代标签.

子标签是父标签的下一级, 而后代标签则是指父标签下面所有级别的标签.

tr是table的子标签, tr, th, td, img都是table的后代标签.

使用AngleSharp, 找出子标签可以使用.Children属性. 而找出后代标签, 可以使用CSS选择器.

兄弟标签

找到前一个兄弟标签使用.PreviousElementSibling属性, 后一个兄弟标签是.NextElementSibling属性.

父标签

.ParentElement属性就是父标签.

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        public static async Task FindDescendantAsync()
        {
            const string url = "http://www.pythonscraping.com/pages/page3.html";
            var html = await GetHtmlSourceCodeAsync(url);
            if (!string.IsNullOrWhiteSpace(html))
            {
                var parser = new HtmlParser();
                var document = await parser.ParseAsync(html);

                var tableChildren = document.QuerySelector("table#giftList > tbody").Children;
                Console.WriteLine("Table's children are:");
                foreach (var child in tableChildren)
                {
                    System.Console.WriteLine(child.LocalName);
                }

                var descendants = document.QuerySelectorAll("table#giftList > tbody *");
                Console.WriteLine("Table's descendants are:");
                foreach (var item in descendants)
                {
                    Console.WriteLine(item.LocalName);
                }

                var siblings = document.QuerySelectorAll("table#giftList > tbody > tr").Select(x => x.NextElementSibling);
                Console.WriteLine("Table's descendants are:");
                foreach (var item in siblings)
                {
                    Console.WriteLine(item?.LocalName);
                }

                var parentSibling = document.All.SingleOrDefault(x => x.HasAttribute("src") && x.GetAttribute("src") == "../img/gifts/img1.jpg")
                    ?.ParentElement.PreviousElementSibling;
                if (parentSibling != null)
                {
                    Console.WriteLine($"Parent's previous sibling is: {parentSibling.TextContent}");
                }
            }
            else
            {
                Console.WriteLine("No html source code returned.");
            }
        }

结果:

使用正则表达式

"如果你有一个问题打算使用正则表达式来解决, 那么现在你有两个问题了".

这里有一个测试正则表达式的网站: https://www.regexpal.com/

目前, AngleSharp支持通过CSS选择器来查找元素, 也可以使用Linq来过滤元素, 当然也可以通过多种方式使用正则表达式进行更复杂的查找动作.

关于正则表达式我就不介绍了. 直接看例子.

我想找到页面中所有的满足下列要求的图片, 其src的值以../img/gifts/img开头并且随后跟着数字, 然后格式为.jpg的图标.

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        public static async Task FindByRegexAsync()
        {
            const string url = "http://www.pythonscraping.com/pages/page3.html";
            var html = await GetHtmlSourceCodeAsync(url);
            if (!string.IsNullOrWhiteSpace(html))
            {
                var parser = new HtmlParser();
                var document = await parser.ParseAsync(html);

                var images = document.QuerySelectorAll("img")
                    .Where(x => x.HasAttribute("src") && Regex.Match(x.Attributes["src"].Value, @"\.\.\/img\/gifts/img.*\.jpg").Success);
                foreach (var item in images)
                {
                    Console.WriteLine(item.Attributes["src"].Value);
                }

                var elementsWith2Attributes = document.All.Where(x => x.Attributes.Length == 2);
                foreach (var item in elementsWith2Attributes)
                {
                    Console.WriteLine(item.LocalName);
                    foreach (var attr in item.Attributes)
                    {
                        Console.WriteLine($"\t{attr.Name} - {attr.Value}");
                    }
                }
            }
            else
            {
                Console.WriteLine("No html source code returned.");
            }
        }

这个其实没有任何难度.

但从本例可以看到, 判断元素有没有一个属性可以使用HasAttribute("xxx")方法, 可以通过.Attributes索引来获取属性, 其属性值就是.Attributes["xxx"].Value.

如果不会正则表达式, 我相信多写的Linq的过滤代码也差不多能达到要求.

第3章 开始采集

遍历单个域名

就是几个应用的例子, 直接贴代码吧.

打印出一个页面内所有的超链接地址:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        public static async Task TraversingASingleDomainAsync()
        {
            var httpClient = new HttpClient();
            var htmlSource = await httpClient.GetStringAsync("http://en.wikipedia.org/wiki/Kevin_Bacon");

            var parser = new HtmlParser();
            var document = await parser.ParseAsync(htmlSource);
            var links = document.QuerySelectorAll("a");
            foreach (var link in links)
            {
                Console.WriteLine(link.Attributes["href"]?.Value);
            }
        }

找出满足下列条件的超链接:

  • 在id为bodyContent的div里
  • url不包括分号
  • url以/wiki开头
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        public static async Task FindSpecificLinksAsync()
        {
            var httpClient = new HttpClient();
            var htmlSource = await httpClient.GetStringAsync("http://en.wikipedia.org/wiki/Kevin_Bacon");

            var parser = new HtmlParser();
            var document = await parser.ParseAsync(htmlSource);
            var links = document.QuerySelector("div#bodyContent").QuerySelectorAll("a")
                .Where(x => x.HasAttribute("href") && Regex.Match(x.Attributes["href"].Value, @"^(/wiki/)((?!:).)*$").Success);
            foreach (var link in links)
            {
                Console.WriteLine(link.Attributes["href"]?.Value);
            }
        }

随机找到页面里面一个连接, 然后递归调用自己的方法, 直到主动停止:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        private static async Task<IEnumerable<IElement>> GetLinksAsync(string uri)
        {
            var httpClient = new HttpClient();
            var htmlSource = await httpClient.GetStringAsync($"http://en.wikipedia.org{uri}");
            var parser = new HtmlParser();
            var document = await parser.ParseAsync(htmlSource);

            var links = document.QuerySelector("div#bodyContent").QuerySelectorAll("a")
                .Where(x => x.HasAttribute("href") && Regex.Match(x.Attributes["href"].Value, @"^(/wiki/)((?!:).)*$").Success);
            return links;
        }

        public static async Task GetRandomNestedLinksAsync()
        {
            var random = new Random();
            var links = (await GetLinksAsync("/wiki/Kevin_Bacon")).ToList();
            while (links.Any())
            {
                var newArticle = links[random.Next(0, links.Count)].Attributes["href"].Value;
                Console.WriteLine(newArticle);
                links = (await GetLinksAsync(newArticle)).ToList();
            }
        }

采集整个网站

首先要了解几个概念:

浅网 surface web: 是互联网上搜索引擎可以直接抓取到的那部分网络.

与浅网对立的就是深网 deep web: 互联网中90%都是深网.

暗网Darknet / dark web / dark internet: 它完全是另外一种怪兽. 它们也建立在已有的网络基础上, 但是使用Tor客户端, 带有运行在HTTP之上的新协议, 提供了一个信息交换的安全隧道. 这类网也可以采集, 但是超出了本书的范围.....

深网相对暗网还是比较容易采集的.

采集整个网站的两个好处:

  • 生成网站地图
  • 收集数据

由于网站的规模和深度, 所以采集到的超链接很多可能是重复的, 这时我们就需要链接去重, 可以使用Set类型的集合:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        private static readonly HashSet<string> LinkSet = new HashSet<string>();
        private static readonly HttpClient HttpClient = new HttpClient();
        private static readonly HtmlParser Parser = new HtmlParser();

        public static async Task GetUniqueLinksAsync(string uri = "")
        {
            var htmlSource = await HttpClient.GetStringAsync($"http://en.wikipedia.org{uri}");
            var document = await Parser.ParseAsync(htmlSource);

            var links = document.QuerySelectorAll("a")
                .Where(x => x.HasAttribute("href") && Regex.Match(x.Attributes["href"].Value, @"^(/wiki/)").Success);

            foreach (var link in links)
            {
                if (!LinkSet.Contains(link.Attributes["href"].Value))
                {
                    var newPage = link.Attributes["href"].Value;
                    Console.WriteLine(newPage);
                    LinkSet.Add(newPage);
                    await GetUniqueLinksAsync(newPage);
                }
            }
        }

(递归调用的深度需要注意一下, 不然有时候能崩溃).

收集整个网站数据

这个例子相对网站, 包括收集相关文字和异常处理等:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        private static readonly HashSet<string> LinkSet = new HashSet<string>();
        private static readonly HttpClient HttpClient = new HttpClient();
        private static readonly HtmlParser Parser = new HtmlParser();

       public static async Task GetLinksWithInfoAsync(string uri = "")
        {
            var htmlSource = await HttpClient.GetStringAsync($"http://en.wikipedia.org{uri}");
            var document = await Parser.ParseAsync(htmlSource);

            try
            {
                var title = document.QuerySelector("h1").TextContent;
                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine(title);

                var contentElement = document.QuerySelector("#mw-content-text").QuerySelectorAll("p").FirstOrDefault();
                if (contentElement != null)
                {
                    Console.WriteLine(contentElement.TextContent);
                }

                var alink = document.QuerySelector("#ca-edit").QuerySelectorAll("span a").SingleOrDefault(x => x.HasAttribute("href"))?.Attributes["href"].Value;
                Console.WriteLine(alink);
            }
            catch (NullReferenceException)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("Cannot find the tag!");
            }

            var links = document.QuerySelectorAll("a")
                .Where(x => x.HasAttribute("href") && Regex.Match(x.Attributes["href"].Value, @"^(/wiki/)").Success).ToList();
            foreach (var link in links)
            {
                if (!LinkSet.Contains(link.Attributes["href"].Value))
                {
                    var newPage = link.Attributes["href"].Value;
                    Console.WriteLine(newPage);
                    LinkSet.Add(newPage);
                    await GetLinksWithInfoAsync(newPage);
                }
            }
        }

不知前方水深的例子

第一个例子, 寻找随机外链:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using AngleSharp.Parser.Html;

namespace WebScrapingWithDotNetCore.Chapter03
{
    public class CrawlingAcrossInternet
    {
        private static readonly Random Random = new Random();
        private static readonly HttpClient HttpClient = new HttpClient();
        private static readonly HashSet<string> InternalLinks = new HashSet<string>();
        private static readonly HashSet<string> ExternalLinks = new HashSet<string>();
        private static readonly HtmlParser Parser = new HtmlParser();

        public static async Task FollowExternalOnlyAsync(string startingSite)
        {
            var externalLink = await GetRandomExternalLinkAsync(startingSite);
            if (externalLink != null)
            {
                Console.WriteLine($"External Links is: {externalLink}");
                await FollowExternalOnlyAsync(externalLink);
            }
            else
            {
                Console.WriteLine("Random External link is null, Crawling terminated.");
            }
        }

        private static async Task<string> GetRandomExternalLinkAsync(string startingPage)
        {
            try
            {
                var htmlSource = await HttpClient.GetStringAsync(startingPage);
                var externalLinks = (await GetExternalLinksAsync(htmlSource, SplitAddress(startingPage)[0])).ToList();
                if (externalLinks.Any())
                {
                    return externalLinks[Random.Next(0, externalLinks.Count)];
                }

                var internalLinks = (await GetInternalLinksAsync(htmlSource, startingPage)).ToList();
                if (internalLinks.Any())
                {
                    return await GetRandomExternalLinkAsync(internalLinks[Random.Next(0, internalLinks.Count)]);
                }

                return null;
            }
            catch (HttpRequestException e)
            {
                Console.WriteLine($"Error requesting: {e.Message}");
                return null;
            }
        }

        private static string[] SplitAddress(string address)
        {
            var addressParts = address.Replace("http://", "").Replace("https://", "").Split("/");
            return addressParts;
        }

        private static async Task<IEnumerable<string>> GetInternalLinksAsync(string htmlSource, string includeUrl)
        {
            var document = await Parser.ParseAsync(htmlSource);
            var links = document.QuerySelectorAll("a")
                .Where(x => x.HasAttribute("href") && Regex.Match(x.Attributes["href"].Value, $@"^(/|.*{includeUrl})").Success)
                .Select(x => x.Attributes["href"].Value);
            foreach (var link in links)
            {
                if (!string.IsNullOrEmpty(link) && !InternalLinks.Contains(link))
                {
                    InternalLinks.Add(link);
                }
            }
            return InternalLinks;
        }

        private static async Task<IEnumerable<string>> GetExternalLinksAsync(string htmlSource, string excludeUrl)
        {
            var document = await Parser.ParseAsync(htmlSource);

            var links = document.QuerySelectorAll("a")
                .Where(x => x.HasAttribute("href") && Regex.Match(x.Attributes["href"].Value, $@"^(http|www)((?!{excludeUrl}).)*$").Success)
                .Select(x => x.Attributes["href"].Value);
            foreach (var link in links)
            {
                if (!string.IsNullOrEmpty(link) && !ExternalLinks.Contains(link))
                {
                    ExternalLinks.Add(link);
                }
            }
            return ExternalLinks;
        }

        private static readonly HashSet<string> AllExternalLinks = new HashSet<string>();
        private static readonly HashSet<string> AllInternalLinks = new HashSet<string>();

        public static async Task GetAllExternalLinksAsync(string siteUrl)
        {
            try
            {
                var htmlSource = await HttpClient.GetStringAsync(siteUrl);
                var internalLinks = await GetInternalLinksAsync(htmlSource, SplitAddress(siteUrl)[0]);
                var externalLinks = await GetExternalLinksAsync(htmlSource, SplitAddress(siteUrl)[0]);
                foreach (var link in externalLinks)
                {
                    if (!AllExternalLinks.Contains(link))
                    {
                        AllExternalLinks.Add(link);
                        Console.WriteLine(link);
                    }
                }

                foreach (var link in internalLinks)
                {
                    if (!AllInternalLinks.Contains(link))
                    {
                        Console.WriteLine($"The link is: {link}");
                        AllInternalLinks.Add(link);
                        await GetAllExternalLinksAsync(link);
                    }
                }
            }
            catch (HttpRequestException e)
            {
                Console.WriteLine(e);
                Console.WriteLine($"Request error: {e.Message}");
            }
        }
    }
}

程序有Bug, 您可以给解决下......

第一部分先到这....主要用的是AngleSharp. AngleSharp不止这些功能, 很强大的, 具体请看文档.

由于该书下一部分使用的是Python的Scrapy, 所以下篇文章我也许应该使用DotNetSpider了, 这是一个国产的库....

项目的代码在: https://github.com/solenovex/Web-Scraping-With-.NET-Core

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-04-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
AngleSharp :在 C# 中轻松解析和操作 HTML/XML 文档
AngleSharp 是一个 C# 库,主要用于解析和操作 HTML 和 XML 文档,类似于浏览器的 DOM 操作。允许你在 C# 中使用类似浏览器的方式处理网页数据,进行网页抓取、数据提取和处理等任务。
郑子铭
2025/04/10
2560
AngleSharp :在 C# 中轻松解析和操作 HTML/XML 文档
.NET轻松写博客园爬虫
爬虫,是一种按照一定的规则,自动地抓取网站的程序或者脚本。`.NET`写爬虫非常简单,并能轻松优化性能。今天我将分享一段简短的代码,爬出博客园前200页精华内容,然后通过微小的改动,将代码升级为多线程爬虫,让爬虫速度提升数倍;最后将对爬到了内容进行一些有趣的分析。
心莱科技雪雁
2019/09/05
9590
.NET轻松写博客园爬虫
C#的HTTP 客户端和服务
在网络编程中,HTTP(超文本传输协议)是应用最为广泛的协议之一。C#提供了强大的类库来支持HTTP通信,包括HttpClient用于客户端请求,以及HttpListener用于服务端监听。本文将详细介绍如何在C#中使用这些工具进行HTTP通信。
Michel_Rolle
2024/10/09
3.1K0
委托与线程在C#编程中的应用及选择
委托是一种表示对具有特定参数列表和返回类型的方法的引用的类型。可以使用委托将方法作为参数传递给其他方法,或者异步地调用方法。
jackcode
2023/04/26
1.4K0
委托与线程在C#编程中的应用及选择
异步编程
1) 异步方法的返回值一般是Task<T>,T是真正的返回值类型,Task<int>。惯例:异步方法名字以Async结尾。
睡神
2023/01/13
8120
如何实现Http请求报头的自动转发[应用篇]
如今的应用部署逐渐向微服务化发展,导致一个完整的事务往往会跨越很多的应用或服务,出于分布式链路跟踪的需要,我们往往将从上游服务获得的跟踪请求报头无脑地向下游服务进行转发。本文介绍的这个名为HeaderForwarder的组件可以帮助我们完成针对指定HTTP请求报头的自动转发。本篇文章分为上下两篇,上篇通过三个例子介绍HeaderForwarder的应用场景,下篇则介绍该组件的设计与实现。[源代码从这里下载]
蒋金楠
2020/11/24
1.3K0
如何实现Http请求报头的自动转发[应用篇]
C# HttpClient 请求认证、数据传输笔记
客户端请求服务器时,需要通过授权认证许可,方能获取服务器资源,目前比较常见的认证方式有 Basic 、JWT、Cookie。
痴者工良
2021/04/26
2.7K0
.Net Core使用HttpClient请求Web API注意事项
使用HttpClient可以很方便的请求Web API,但在使用时有一些需要注意的地方,不然会给你的程序带来毁灭性的问题。
Mr. Wei
2018/07/18
1.4K0
.Net Core使用HttpClient请求Web API注意事项
四种为HttpClient添加默认请求报头的解决方案
HttpClient在Web调用中具有广泛的应用,而为它添加默认请求头是我们经常遇到的需求,本文介绍4种为HttpClient添加默认请求头的方式。
蒋金楠
2019/09/29
1K0
使用C#和HtmlAgilityPack打造强大的Snapchat视频爬虫
Snapchat作为一款备受欢迎的社交媒体应用,允许用户分享照片和视频。然而,由于其特有的内容自动消失特性,爬虫开发面临一些挑战。本文将详细介绍如何巧妙运用C#和HtmlAgilityPack库,构建一个高效的Snapchat视频爬虫。该爬虫能够从Snapchat网页版中提取视频链接,并将其下载保存到本地。为了提升爬虫的效率和可靠性,我们将使用代理IP技术和多线程技术,以规避Snapchat的反爬机制。
jackcode
2023/11/27
4260
使用C#和HtmlAgilityPack打造强大的Snapchat视频爬虫
手把手教你用.NET Core写爬虫
自从上一个项目58HouseSearch从.NET迁移到.NET core之后,磕磕碰碰磨蹭了一个月才正式上线到新版本。
李国宝
2018/05/23
2.2K0
手把手教你用.NET Core写爬虫
.NET 异步编程(异步方法、异步委托、CancellationToken、WhenAll、yield)
如果同样的功能,既有同步方法,又有异步方法,那么 首先使用异步方法。.NET5中,很多框架中的方法也都支持异步:Main、WinForm事件处理函数。 对于不支持的异步方法怎么办?Wait()(无返回值);Result(有返回值)。风险:死锁,尽量不用。
鱼找水需要时间
2024/03/20
2460
.NET 异步编程(异步方法、异步委托、CancellationToken、WhenAll、yield)
C# NET 异步,你也许不知道的5种用法
async/await异步操作,是C#中非常惊艳的“语法糖”,让异步编程变得优美且傻瓜化到了不可思议的程度。就连JavaScript都借鉴了async/await语法,让回调泛滥的JavaScript代码变得很优美。
苏州程序大白
2021/08/13
1.4K0
C#语言async, await 简单介绍与实例(入门级)
本文介绍异步编程的基本思想和语法。在程序处理里,程序基本上有两种处理方式:同步和异步。对于有些新手,甚至认为“同步”是同时进行的意思,这显然是错误的。
郑子铭
2023/08/30
6750
C#语言async, await 简单介绍与实例(入门级)
ASP.NET Core 单元测试:如何 Mock HttpClient.GetStringAsync()
在 ASP.NET Core 单元测试中模拟HttpClient.GetStringAsync() 的技巧。
Edi Wang
2021/05/27
1.4K0
ASP.NET Core 6框架揭秘实例演示[17]:利用IHttpClientFactory工厂来创建HttpClient
在一个采用依赖注入框架的应用中,我们一般不太推荐利用手工创建的HttpClient对象来进行HTTP调用,使用的HttpClient对象最好利用注入的IHttpClientFactory工厂来创建。前者引起的问题,以及后者带来的好处,将通过如下这几个演示程序展现出来。IHttpClientFactory类型由“Microsoft.Extensions.Http”这个NuGet包提供,“Microsoft.NET.Sdk.Web”SDK具有该包的默认引用。如果采用“Microsoft.NET.Sdk”这个SDK,需要添加该包的引用。(本篇提供的实例已经汇总到《ASP.NET Core 6框架揭秘-实例演示版》)
蒋金楠
2022/05/09
9270
ASP.NET Core 6框架揭秘实例演示[17]:利用IHttpClientFactory工厂来创建HttpClient
使用 System.Net.Http.Json 高效处理Json数据
在这篇文章,我将介绍一个名为 System.Net.Http.Json 的扩展库,它最近添加到了 .NET 中,我们看一下这个库能够给我们解决什么问题,今天会介绍下如何在代码中使用。
全球技术精选
2021/01/09
2.4K0
使用 System.Net.Http.Json 高效处理Json数据
http请求超时,底层发生了什么?
业务方反应调用接口超时,但是在服务端监控并没有看到5xx异常, 于是我们模拟一下请求超时时发生了什么?
有态度的马甲
2025/04/07
2330
http请求超时,底层发生了什么?
掌握25个C#实战技巧:从代码优化到高效开发
C#持续进化,每个版本都引入新特性,助你编写更高效、更优雅的代码。掌握以下技巧,不仅能提升代码质量,还能让开发过程更愉悦。
郑子铭
2025/04/18
2550
掌握25个C#实战技巧:从代码优化到高效开发
世俱杯直播数据源怎么用 C# 爬取
在体育赛事直播日益普及的今天,许多开发者希望从网络中抓取实时的直播数据源,例如“世俱杯”等大型赛事的直播链接。本文将介绍如何使用 C# 编写一个简单的网页爬虫程序,用于抓取多个指定网站上的 HTML 内容,并提取其中可能包含的直播数据源信息。
用户2695996
2025/07/11
870
世俱杯直播数据源怎么用 C# 爬取
推荐阅读
相关推荐
AngleSharp :在 C# 中轻松解析和操作 HTML/XML 文档
更多 >
交个朋友
加入腾讯云官网粉丝站
蹲全网底价单品 享第一手活动信息
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档