使用LINQ + XML来抓取网站

时间:2017-10-31 11:41:43

标签: c# html xml linq

我正在尝试通过创建一个简单的刮刀来学习.NET技术和C#,它应该从维基百科中检索一些地理数据。

首先,我创建了这个方法,从this维基百科网站返回表格中所有国家/地区的所有链接:

public IEnumerable<string> GetLinkToAllCountries()
{

    return from node in XElement.Load(_URL.AbsoluteUri).Elements("body").Descendants()
           where node.Name.LocalName == "a"
                            && node.Parent.Name.LocalName == "td"
                            && node.Parent.Parent.Name.LocalName == "tr"
                            && node.Attribute("href") != null
                            && node.Attribute("title") != null
                                select _URL.Scheme + "//" + _URL.Host + node.Attribute("href").Value;

}

这很好用并返回每个国家Wiki页面的链接列表。其次,我想访问每个国家的页面并检索首都。

我写了类似的东西,但我无法让它起作用:

public IEnumerable<string> ListOfCapitals() {

    var links = GetLinkToAllCountries();

    return from link in links
           from node in XElement.Load(link).Elements("body").Descendants()
           where node.Name.LocalName == "a"
                && node.Parent.Name == "td"
                && node.Attribute("title") != null
           select node.Attribute("title").Value;
}

它没有列出大写字母,Visual Studio似乎制作了一些指向System.Threading的程序集指针。我是否应该因为工作负荷而写异步(访问~200个网站并从每个网站检索数据)?如果没有违反任何规则,我也会喜欢这种方法的一般反馈。谢谢!

1 个答案:

答案 0 :(得分:0)

我认为最好的方法是通过TPL:

https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-parallel-library-tpl

重构 - 给定以下类:

public class WebScrapper
{
    public IEnumerable<string> GetLinkToAllCountries(Uri uri)
    {
        return from node in XElement.Load(uri.AbsoluteUri).Elements("body").Descendants()
            where node.Name.LocalName == "a"
                  && node.Parent.Name.LocalName == "td"
                  && node.Parent.Parent.Name.LocalName == "tr"
                  && node.Attribute("href") != null
                  && node.Attribute("title") != null
            select uri.Scheme + "://" + uri.Host + node.Attribute("href").Value;

    }

    public IEnumerable<string> ListOfCapitals(string link)
    {
        return from node in XElement.Load(link).Elements("body").Descendants()
            where node.Name.LocalName == "a"
                  && node.Parent.Name == "td"
                  && node.Attribute("title") != null
            select node.Attribute("title").Value;
    }
}

然后您可以使用以下内容:

        var webScrapper = new WebScrapper();

        var countryLinks =
            webScrapper.GetLinkToAllCountries(
                new Uri("https://en.wikipedia.org/wiki/List_of_countries_and_dependencies_by_population"));

        var capitals = countryLinks
            .AsParallel()
            .WithDegreeOfParallelism(Convert.ToInt32(Math.Ceiling(Environment.ProcessorCount * 0.75)))
            .SelectMany(s => webScrapper.ListOfCapitals(s))
            .ToList();

WithDegreeOfParallelism - 将用于处理查询的并发执行任务的数量。

还需要考虑的是XElement.Load(uri.AbsoluteUri)中的GetLinkToAllCountriesXElement.Load(link)ListOfCapitals的依赖关系。我重构了这个以便有一层抽象 - 这样linq查询可以用模拟进行单元测试。